Add rebuild support to editor preview (#4932)

* Add rebuild support to editor preview

* getLovelaceCardClass function added

* Use error class

* Tiny cleanup

* Misplaced comment
This commit is contained in:
Paulus Schoutsen 2020-02-20 00:55:42 -08:00 committed by GitHub
parent 6d54496187
commit 52609dded9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 34 deletions

View File

@ -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`;
}

View File

@ -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);

View File

@ -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 = <T extends keyof CreateElementConfigTypes>(
return element;
};
const _getCustomTag = (type: string) =>
type.startsWith(CUSTOM_TYPE_PREFIX)
? type.substr(CUSTOM_TYPE_PREFIX.length)
: undefined;
export const createLovelaceElement = <T extends keyof CreateElementConfigTypes>(
tagSuffix: T,
config: CreateElementConfigTypes[T]["config"],
alwaysLoadTypes?: Set<string>,
lazyLoadTypes?: { [domain: string]: () => unknown },
lazyLoadTypes?: { [domain: string]: () => Promise<unknown> },
// 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 = <T extends keyof CreateElementConfigTypes>(
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 = <T extends keyof CreateElementConfigTypes>(
return _createErrorElement(`Unknown type encountered: ${type}.`, config);
};
export const getLovelaceElementClass = async <
T extends keyof CreateElementConfigTypes
>(
type: string,
tagSuffix: T,
alwaysLoadTypes?: Set<string>,
lazyLoadTypes?: { [domain: string]: () => Promise<unknown> }
): Promise<CreateElementConfigTypes[T]["constructor"]> => {
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}`);
};

View File

@ -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

View File

@ -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<void> {
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 };
}

View File

@ -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) {

View File

@ -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<LovelaceCard> {
getStubConfig?: (hass: HomeAssistant) => LovelaceCardConfig;
getConfigElement?: () => LovelaceCardEditor;
}
export interface LovelaceHeaderFooter extends HTMLElement {
hass?: HomeAssistant;
setConfig(config: LovelaceHeaderFooterConfig): void;