Skip to content

Commit

Permalink
support custom module
Browse files Browse the repository at this point in the history
  • Loading branch information
lovelylain committed Dec 11, 2024
1 parent c1bf0b8 commit 38d2a2c
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 35 deletions.
7 changes: 4 additions & 3 deletions src/ha-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface HaPanelLovelace extends HTMLElement {
export interface PanelInfo<T = Record<string, any> | null> {
component_name: string;
config: T;
icon: string | null;
title: string | null;
url_path: string;
}
Expand Down Expand Up @@ -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();
}
Expand Down
104 changes: 72 additions & 32 deletions src/ha-panel-ingress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -13,10 +13,11 @@ import { mdiCoffeeOutline } from "@mdi/js";
const getHassioAddonUrl = async (
elem: HTMLElement,
hass: HomeAssistant,
addonSlug: string
addonSlug: string,
dryRun?: boolean
): Promise<string | undefined> => {
const showError = (msg: string): undefined => {
(elem.shadowRoot || elem).innerHTML = `<pre>${msg}</pre>`;
if (!dryRun) (elem.shadowRoot || elem).innerHTML = `<pre>${msg}</pre>`;
};

const addon = await fetchHassioAddonInfo(hass, addonSlug);
Expand All @@ -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;
Expand All @@ -44,6 +45,7 @@ interface IngressPanelUrlInfo {
interface IngressPanelConfig {
children?: Record<string, IngressPanelConfig>;
title?: string;
icon?: string;
url?: string | IngressPanelUrlInfo;
index?: string;
addon?: string;
Expand Down Expand Up @@ -103,25 +105,30 @@ class HaPanelIngress extends HTMLElement {
}

const panel = props.panel as PanelInfo<IngressPanelConfig>;
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;
Expand All @@ -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;
}
Expand All @@ -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}`;
Expand All @@ -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;
}
Expand All @@ -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<string, IngressPanelConfig> = {};
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 = `<pre>module url[${targetUrl}] is invalid:\n${e}</pre>`;
}
return;
}

let html = `
<iframe ${title ? `title="${title}"` : ""} src="${targetUrl}" allow="fullscreen"></iframe>
`;
Expand All @@ -208,7 +255,7 @@ class HaPanelIngress extends HTMLElement {
}
`;

const showToolbar = config.ui_mode === "toolbar";
const showToolbar = uiMode === "toolbar";
if (/*!showToolbar &&*/ enableSidebarSwipe()) {
html += '<div id="swipebar"></div>';
css += `
Expand All @@ -222,25 +269,18 @@ class HaPanelIngress extends HTMLElement {
}

if (showToolbar) {
await ensureHaPanel("iframe");
await ensureHaElem("hass-subpage", "iframe");
html = `<hass-subpage main-page>${html}
<ha-icon-button slot="toolbar-icon"></ha-icon-button>
</hass-subpage>`;
}

const root = this.shadowRoot as ShadowRoot;
root.innerHTML = `<style>${css}</style>${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);
}
}
Expand Down

0 comments on commit 38d2a2c

Please sign in to comment.