diff --git a/src/panels/lovelace/common/get-card-element-tag.ts b/src/panels/lovelace/common/get-card-element-tag.ts deleted file mode 100644 index ee80d760a1..0000000000 --- a/src/panels/lovelace/common/get-card-element-tag.ts +++ /dev/null @@ -1,7 +0,0 @@ -const CUSTOM_TYPE_PREFIX = "custom:"; - -export function getCardElementTag(type: string): string { - return type.startsWith(CUSTOM_TYPE_PREFIX) - ? type.substr(CUSTOM_TYPE_PREFIX.length) - : `hui-${type}-card`; -} diff --git a/src/panels/lovelace/create-element/create-card-element.ts b/src/panels/lovelace/create-element/create-card-element.ts index d3acc76267..3669bba1f4 100644 --- a/src/panels/lovelace/create-element/create-card-element.ts +++ b/src/panels/lovelace/create-element/create-card-element.ts @@ -11,7 +11,10 @@ import "../cards/hui-thermostat-card"; import "../cards/hui-vertical-stack-card"; import "../cards/hui-weather-forecast-card"; import { LovelaceCardConfig } from "../../../data/lovelace"; -import { createLovelaceElement } from "./create-element-base"; +import { + createLovelaceElement, + getLovelaceElementClass, +} from "./create-element-base"; const ALWAYS_LOADED_TYPES = new Set([ "entities", @@ -56,3 +59,6 @@ export const createCardElement = (config: LovelaceCardConfig) => undefined, undefined ); + +export const getCardElementClass = (type: string) => + getLovelaceElementClass(type, "card", ALWAYS_LOADED_TYPES, LAZY_LOAD_TYPES); diff --git a/src/panels/lovelace/create-element/create-element-base.ts b/src/panels/lovelace/create-element/create-element-base.ts index 61f1b6a432..72c85b9709 100644 --- a/src/panels/lovelace/create-element/create-element-base.ts +++ b/src/panels/lovelace/create-element/create-element-base.ts @@ -7,7 +7,12 @@ import { createErrorCardElement, createErrorCardConfig, } from "../cards/hui-error-card"; -import { LovelaceCard, LovelaceBadge, LovelaceHeaderFooter } from "../types"; +import { + LovelaceCard, + LovelaceBadge, + LovelaceHeaderFooter, + LovelaceCardConstructor, +} from "../types"; import { fireEvent } from "../../../common/dom/fire_event"; import { LovelaceElementConfig, LovelaceElement } from "../elements/types"; import { LovelaceRow, LovelaceRowConfig } from "../entity-rows/types"; @@ -17,13 +22,30 @@ const CUSTOM_TYPE_PREFIX = "custom:"; const TIMEOUT = 2000; interface CreateElementConfigTypes { - card: { config: LovelaceCardConfig; element: LovelaceCard }; - badge: { config: LovelaceBadgeConfig; element: LovelaceBadge }; - element: { config: LovelaceElementConfig; element: LovelaceElement }; - row: { config: LovelaceRowConfig; element: LovelaceRow }; + card: { + config: LovelaceCardConfig; + element: LovelaceCard; + constructor: LovelaceCardConstructor; + }; + badge: { + config: LovelaceBadgeConfig; + element: LovelaceBadge; + constructor: unknown; + }; + element: { + config: LovelaceElementConfig; + element: LovelaceElement; + constructor: unknown; + }; + row: { + config: LovelaceRowConfig; + element: LovelaceRow; + constructor: unknown; + }; "header-footer": { config: LovelaceHeaderFooterConfig; element: LovelaceHeaderFooter; + constructor: unknown; }; } @@ -75,11 +97,16 @@ const _maybeCreate = ( return element; }; +const _getCustomTag = (type: string) => + type.startsWith(CUSTOM_TYPE_PREFIX) + ? type.substr(CUSTOM_TYPE_PREFIX.length) + : undefined; + export const createLovelaceElement = ( tagSuffix: T, config: CreateElementConfigTypes[T]["config"], alwaysLoadTypes?: Set, - lazyLoadTypes?: { [domain: string]: () => unknown }, + lazyLoadTypes?: { [domain: string]: () => Promise }, // Allow looking at "entity" in config and mapping that to a type domainTypes?: { _domain_not_found: string; [domain: string]: string }, // Default type if no type given. If given, entity types will not work. @@ -98,8 +125,10 @@ export const createLovelaceElement = ( return _createErrorElement("No card type configured.", config); } - if (config.type && config.type.startsWith(CUSTOM_TYPE_PREFIX)) { - return _maybeCreate(config.type.substr(CUSTOM_TYPE_PREFIX.length), config); + const customTag = config.type ? _getCustomTag(config.type) : undefined; + + if (customTag) { + return _maybeCreate(customTag, config); } let type: string | undefined; @@ -131,3 +160,44 @@ export const createLovelaceElement = ( return _createErrorElement(`Unknown type encountered: ${type}.`, config); }; + +export const getLovelaceElementClass = async < + T extends keyof CreateElementConfigTypes +>( + type: string, + tagSuffix: T, + alwaysLoadTypes?: Set, + lazyLoadTypes?: { [domain: string]: () => Promise } +): Promise => { + const customTag = _getCustomTag(type); + + if (customTag) { + const customCls = customElements.get(customTag); + return customCls + ? customCls + : new Promise((resolve, reject) => { + // We will give custom components up to TIMEOUT seconds to get defined + setTimeout( + () => reject(new Error(`Custom element not found: ${customTag}`)), + TIMEOUT + ); + + customElements + .whenDefined(customTag) + .then(() => resolve(customElements.get(customTag))); + }); + } + + const tag = `hui-${type}-${tagSuffix}`; + const cls = customElements.get(tag); + + if (alwaysLoadTypes && type in alwaysLoadTypes) { + return cls; + } + + if (lazyLoadTypes && type in lazyLoadTypes) { + return cls || lazyLoadTypes[type]().then(() => customElements.get(tag)); + } + + throw new Error(`Unknown type: ${type}`); +}; diff --git a/src/panels/lovelace/editor/card-editor/hui-card-editor.ts b/src/panels/lovelace/editor/card-editor/hui-card-editor.ts index 0509788ef4..9bd7d38cc5 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-editor.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-editor.ts @@ -14,7 +14,6 @@ import "@material/mwc-button"; import { HomeAssistant } from "../../../../types"; import { LovelaceCardConfig } from "../../../../data/lovelace"; import { LovelaceCardEditor } from "../../types"; -import { getCardElementTag } from "../../common/get-card-element-tag"; import { computeRTL } from "../../../../common/util/compute_rtl"; import "../../../../components/ha-code-editor"; @@ -23,6 +22,7 @@ import "../../../../components/ha-code-editor"; import { HaCodeEditor } from "../../../../components/ha-code-editor"; import { fireEvent } from "../../../../common/dom/fire_event"; import { EntityConfig } from "../../entity-rows/types"; +import { getCardElementClass } from "../../create-element/create-card-element"; declare global { interface HASSDomEvents { @@ -215,13 +215,7 @@ export class HuiCardEditor extends LitElement { throw new Error("No card type defined"); } - const tag = getCardElementTag(cardType); - - // Check if the card type exists - const elClass = customElements.get(tag); - if (!elClass) { - throw new Error(`Unknown card type encountered: ${cardType}.`); - } + const elClass = await getCardElementClass(cardType); this._loading = true; // Check if a GUI editor exists diff --git a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts index 422e24d86b..a035237067 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-picker.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-picker.ts @@ -10,9 +10,9 @@ import "@material/mwc-button"; import { HomeAssistant } from "../../../../types"; import { LovelaceCardConfig } from "../../../../data/lovelace"; -import { getCardElementTag } from "../../common/get-card-element-tag"; import { CardPickTarget } from "../types"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { getCardElementClass } from "../../create-element/create-card-element"; const cards: string[] = [ "alarm-panel", @@ -94,15 +94,14 @@ export class HuiCardPicker extends LitElement { }); } - private _cardPicked(ev: Event): void { + private async _cardPicked(ev: Event): Promise { const type = (ev.currentTarget! as CardPickTarget).type; - const tag = getCardElementTag(type); - const elClass = customElements.get(tag); + const elClass = await getCardElementClass(type); let config: LovelaceCardConfig = { type }; if (elClass && elClass.getStubConfig) { - const cardConfig = elClass.getStubConfig(this.hass); + const cardConfig = elClass.getStubConfig(this.hass!); config = { ...config, ...cardConfig }; } diff --git a/src/panels/lovelace/editor/card-editor/hui-card-preview.ts b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts index 46c03731e5..9bce069854 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-preview.ts +++ b/src/panels/lovelace/editor/card-editor/hui-card-preview.ts @@ -7,13 +7,23 @@ import { HomeAssistant } from "../../../../types"; import { LovelaceCardConfig } from "../../../../data/lovelace"; import { LovelaceCard } from "../../types"; import { ConfigError } from "../types"; -import { getCardElementTag } from "../../common/get-card-element-tag"; import { createErrorCardConfig } from "../../cards/hui-error-card"; import { computeRTL } from "../../../../common/util/compute_rtl"; export class HuiCardPreview extends HTMLElement { private _hass?: HomeAssistant; private _element?: LovelaceCard; + private _config?: LovelaceCardConfig; + + constructor() { + super(); + this.addEventListener("ll-rebuild", () => { + this._cleanup(); + if (this._config) { + this.config = this._config; + } + }); + } set hass(hass: HomeAssistant) { if (!this._hass || this._hass.language !== hass.language) { @@ -36,6 +46,9 @@ export class HuiCardPreview extends HTMLElement { } set config(configValue: LovelaceCardConfig) { + const curConfig = this._config; + this._config = configValue; + if (!configValue) { this._cleanup(); return; @@ -53,9 +66,7 @@ export class HuiCardPreview extends HTMLElement { return; } - const tag = getCardElementTag(configValue.type); - - if (tag.toUpperCase() === this._element.tagName) { + if (curConfig && configValue.type === curConfig.type) { try { this._element.setConfig(deepClone(configValue)); } catch (err) { diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index 2e2cbab398..71b36c01e2 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -1,4 +1,4 @@ -import { HomeAssistant } from "../../types"; +import { HomeAssistant, Constructor } from "../../types"; import { LovelaceCardConfig, LovelaceConfig, @@ -37,6 +37,11 @@ export interface LovelaceCard extends HTMLElement { setConfig(config: LovelaceCardConfig): void; } +export interface LovelaceCardConstructor extends Constructor { + getStubConfig?: (hass: HomeAssistant) => LovelaceCardConfig; + getConfigElement?: () => LovelaceCardEditor; +} + export interface LovelaceHeaderFooter extends HTMLElement { hass?: HomeAssistant; setConfig(config: LovelaceHeaderFooterConfig): void;