From 38d2a2cc04b3e6bca683a5c281cba9e930e975bc Mon Sep 17 00:00:00 2001 From: lovelylain Date: Wed, 11 Dec 2024 09:26:00 +0800 Subject: [PATCH] support custom module --- src/ha-interfaces.ts | 7 +-- src/ha-panel-ingress.ts | 104 +++++++++++++++++++++++++++------------- 2 files changed, 76 insertions(+), 35 deletions(-) diff --git a/src/ha-interfaces.ts b/src/ha-interfaces.ts index ff78aa5..e22ec90 100644 --- a/src/ha-interfaces.ts +++ b/src/ha-interfaces.ts @@ -18,6 +18,7 @@ export interface HaPanelLovelace extends HTMLElement { export interface PanelInfo | null> { component_name: string; config: T; + icon: string | null; title: string | null; url_path: string; } @@ -46,9 +47,9 @@ export const navigate = (path: string, options?: NavigateOptions) => { (document.createElement("ha-panel-custom") as any).navigate(path, options); }; -export const ensureHaPanel = async (name: string) => { - if (!customElements.get(`ha-panel-${name}`)) { - const panels = [{ url_path: "tmp", component_name: "iframe" }]; +export const ensureHaElem = async (name: string, panel: string) => { + if (!customElements.get(name)) { + const panels = [{ url_path: "tmp", component_name: panel }]; const ppr = document.createElement("partial-panel-resolver") as any; await ppr.getRoutes(panels).routes.tmp.load(); } diff --git a/src/ha-panel-ingress.ts b/src/ha-panel-ingress.ts index a213523..ebc1b75 100644 --- a/src/ha-panel-ingress.ts +++ b/src/ha-panel-ingress.ts @@ -3,7 +3,7 @@ import { CustomPanelProperties, PanelInfo, navigate, - ensureHaPanel, + ensureHaElem, } from "./ha-interfaces"; import { fetchHassioAddonInfo, ingressSession } from "./hassio-ingress"; import { enableSidebarSwipe } from "./hass-sidebar-swipe"; @@ -13,10 +13,11 @@ import { mdiCoffeeOutline } from "@mdi/js"; const getHassioAddonUrl = async ( elem: HTMLElement, hass: HomeAssistant, - addonSlug: string + addonSlug: string, + dryRun?: boolean ): Promise => { const showError = (msg: string): undefined => { - (elem.shadowRoot || elem).innerHTML = `
${msg}
`; + if (!dryRun) (elem.shadowRoot || elem).innerHTML = `
${msg}
`; }; const addon = await fetchHassioAddonInfo(hass, addonSlug); @@ -28,7 +29,7 @@ const getHassioAddonUrl = async ( } const targetUrl = addon.ingress_url.replace(/\/+$/, ""); - if (!(await ingressSession.init(hass))) { + if (!dryRun && !(await ingressSession.init(hass))) { return showError(`Unable to create an Ingress session`); } return targetUrl; @@ -44,6 +45,7 @@ interface IngressPanelUrlInfo { interface IngressPanelConfig { children?: Record; title?: string; + icon?: string; url?: string | IngressPanelUrlInfo; index?: string; addon?: string; @@ -103,25 +105,30 @@ class HaPanelIngress extends HTMLElement { } const panel = props.panel as PanelInfo; - let { config, title, url_path: panelPath } = panel; + let { config, url_path: panelPath } = panel; + config.title = panel.title!; + config.icon = panel.icon!; if (config.children) { - const page = (props.route?.path || "").split("/")[1]; + const page = (props.route?.path || "").split("/", 2)[1]; if (page && page in config.children) { config = config.children[page]; panelPath = `${panelPath}/${page}`; } - } else { - config.title = title || ""; } if (this._panelPath === panelPath) { return; } this._panelPath = panelPath; + delete this._setProperties; return config; } - private async _getTargetUrl(config: IngressPanelConfig, props: CustomPanelProperties) { + private async _getTargetUrl( + config: IngressPanelConfig, + props: CustomPanelProperties, + dryRun?: boolean + ) { let targetUrl = ""; const urlParams = new URLSearchParams(window.location.search); const { url: urlInfo, addon: addonSlug } = config; @@ -139,7 +146,7 @@ class HaPanelIngress extends HTMLElement { } else if (isIngress) { targetUrl = `/api/ingress/${config.token!.value}`; if (addonSlug) { - const url = await getHassioAddonUrl(this, props.hass!, addonSlug); + const url = await getHassioAddonUrl(this, props.hass!, addonSlug, dryRun); if (!url) { return; } @@ -149,21 +156,23 @@ class HaPanelIngress extends HTMLElement { targetUrl = urlInfo; } - if (this._isHassio) { - ingressSession.fini(); - delete this._isHassio; - } - if (addonSlug) { - this._isHassio = props.hass; - if (this._disconnected) { + if (!dryRun) { + if (this._isHassio) { ingressSession.fini(); + delete this._isHassio; + } + if (addonSlug) { + this._isHassio = props.hass; + if (this._disconnected) { + ingressSession.fini(); + } } } - let { index } = config; + let { index, ui_mode: uiMode } = config; if (index !== undefined) { const path = urlParams.get("index"); - if (path && !/(^|\/)\.\.\//.test(path)) { + if (!dryRun && path && !/(^|\/)\.\.\//.test(path)) { index = path.replace(/^\/+/, ""); } targetUrl = `${targetUrl}/${index}`; @@ -174,12 +183,14 @@ class HaPanelIngress extends HTMLElement { targetUrl = url.href; } - if (urlParams.has("replace")) { + if (dryRun || uiMode === "module") { + return targetUrl; + } else if (urlParams.has("replace")) { if (addonSlug) { targetUrl = `/files/ingress/iframe.html?ingress=${encodeURIComponent(targetUrl)}`; } window.location.href = targetUrl; - } else if (config.ui_mode === "replace") { + } else if (uiMode === "replace") { if (targetUrl.indexOf("://") !== -1) { window.location.href = targetUrl; } @@ -194,7 +205,43 @@ class HaPanelIngress extends HTMLElement { config: IngressPanelConfig, props: CustomPanelProperties ) { - const { title } = config; + const forwardProps = (elem: any, keys: string[]) => { + if ("setProperties" in elem) { + return elem.setProperties as (props: CustomPanelProperties) => void; + } else { + return (props: CustomPanelProperties) => { + for (const k of keys) { + if (k in props) elem[k] = props[k as keyof typeof props]; + } + }; + } + }; + + const root = this.shadowRoot as ShadowRoot; + const { ui_mode: uiMode, title, icon } = config; + if (uiMode === "module") { + const children: Record = {}; + for (const [k, c] of Object.entries(config.children || {})) { + if (!c.title) continue; + const url = await this._getTargetUrl(c, props, true); + if (url) children[k] = { url, title: c.title, ui_mode: c.ui_mode }; + } + try { + const { default: create } = await import(targetUrl); + const elem = await create({ children, title, icon, ensureHaElem, ingressSession }); + if (!(elem instanceof HTMLElement)) { + throw new Error("module should export default async(config)=>HTMLElement"); + } + root.innerHTML = ""; + root.appendChild(elem); + this._setProperties = forwardProps(elem, ["hass", "narrow", "route"]); + this._setProperties(props); + } catch (e) { + root.innerHTML = `
module url[${targetUrl}] is invalid:\n${e}
`; + } + return; + } + let html = ` `; @@ -208,7 +255,7 @@ class HaPanelIngress extends HTMLElement { } `; - const showToolbar = config.ui_mode === "toolbar"; + const showToolbar = uiMode === "toolbar"; if (/*!showToolbar &&*/ enableSidebarSwipe()) { html += '
'; css += ` @@ -222,25 +269,18 @@ class HaPanelIngress extends HTMLElement { } if (showToolbar) { - await ensureHaPanel("iframe"); + await ensureHaElem("hass-subpage", "iframe"); html = `${html} `; } - const root = this.shadowRoot as ShadowRoot; root.innerHTML = `${html}`; if (showToolbar) { const subpage = root.querySelector("hass-subpage") as any; this._setButtons(subpage); subpage.header = title; - this._setProperties = (props) => { - for (const k of ["hass", "narrow"]) { - if (k in props) { - subpage[k] = props[k as keyof typeof props]; - } - } - }; + this._setProperties = forwardProps(subpage, ["hass", "narrow"]); this._setProperties(props); } }