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-vertical-stack-card";
import "../cards/hui-weather-forecast-card"; import "../cards/hui-weather-forecast-card";
import { LovelaceCardConfig } from "../../../data/lovelace"; import { LovelaceCardConfig } from "../../../data/lovelace";
import { createLovelaceElement } from "./create-element-base"; import {
createLovelaceElement,
getLovelaceElementClass,
} from "./create-element-base";
const ALWAYS_LOADED_TYPES = new Set([ const ALWAYS_LOADED_TYPES = new Set([
"entities", "entities",
@ -56,3 +59,6 @@ export const createCardElement = (config: LovelaceCardConfig) =>
undefined, undefined,
undefined undefined
); );
export const getCardElementClass = (type: string) =>
getLovelaceElementClass(type, "card", ALWAYS_LOADED_TYPES, LAZY_LOAD_TYPES);

View File

@ -7,7 +7,12 @@ import {
createErrorCardElement, createErrorCardElement,
createErrorCardConfig, createErrorCardConfig,
} from "../cards/hui-error-card"; } 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 { fireEvent } from "../../../common/dom/fire_event";
import { LovelaceElementConfig, LovelaceElement } from "../elements/types"; import { LovelaceElementConfig, LovelaceElement } from "../elements/types";
import { LovelaceRow, LovelaceRowConfig } from "../entity-rows/types"; import { LovelaceRow, LovelaceRowConfig } from "../entity-rows/types";
@ -17,13 +22,30 @@ const CUSTOM_TYPE_PREFIX = "custom:";
const TIMEOUT = 2000; const TIMEOUT = 2000;
interface CreateElementConfigTypes { interface CreateElementConfigTypes {
card: { config: LovelaceCardConfig; element: LovelaceCard }; card: {
badge: { config: LovelaceBadgeConfig; element: LovelaceBadge }; config: LovelaceCardConfig;
element: { config: LovelaceElementConfig; element: LovelaceElement }; element: LovelaceCard;
row: { config: LovelaceRowConfig; element: LovelaceRow }; 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": { "header-footer": {
config: LovelaceHeaderFooterConfig; config: LovelaceHeaderFooterConfig;
element: LovelaceHeaderFooter; element: LovelaceHeaderFooter;
constructor: unknown;
}; };
} }
@ -75,11 +97,16 @@ const _maybeCreate = <T extends keyof CreateElementConfigTypes>(
return element; 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>( export const createLovelaceElement = <T extends keyof CreateElementConfigTypes>(
tagSuffix: T, tagSuffix: T,
config: CreateElementConfigTypes[T]["config"], config: CreateElementConfigTypes[T]["config"],
alwaysLoadTypes?: Set<string>, alwaysLoadTypes?: Set<string>,
lazyLoadTypes?: { [domain: string]: () => unknown }, lazyLoadTypes?: { [domain: string]: () => Promise<unknown> },
// Allow looking at "entity" in config and mapping that to a type // Allow looking at "entity" in config and mapping that to a type
domainTypes?: { _domain_not_found: string; [domain: string]: string }, domainTypes?: { _domain_not_found: string; [domain: string]: string },
// Default type if no type given. If given, entity types will not work. // 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); return _createErrorElement("No card type configured.", config);
} }
if (config.type && config.type.startsWith(CUSTOM_TYPE_PREFIX)) { const customTag = config.type ? _getCustomTag(config.type) : undefined;
return _maybeCreate(config.type.substr(CUSTOM_TYPE_PREFIX.length), config);
if (customTag) {
return _maybeCreate(customTag, config);
} }
let type: string | undefined; let type: string | undefined;
@ -131,3 +160,44 @@ export const createLovelaceElement = <T extends keyof CreateElementConfigTypes>(
return _createErrorElement(`Unknown type encountered: ${type}.`, config); 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 { HomeAssistant } from "../../../../types";
import { LovelaceCardConfig } from "../../../../data/lovelace"; import { LovelaceCardConfig } from "../../../../data/lovelace";
import { LovelaceCardEditor } from "../../types"; import { LovelaceCardEditor } from "../../types";
import { getCardElementTag } from "../../common/get-card-element-tag";
import { computeRTL } from "../../../../common/util/compute_rtl"; import { computeRTL } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-code-editor"; import "../../../../components/ha-code-editor";
@ -23,6 +22,7 @@ import "../../../../components/ha-code-editor";
import { HaCodeEditor } from "../../../../components/ha-code-editor"; import { HaCodeEditor } from "../../../../components/ha-code-editor";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { EntityConfig } from "../../entity-rows/types"; import { EntityConfig } from "../../entity-rows/types";
import { getCardElementClass } from "../../create-element/create-card-element";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@ -215,13 +215,7 @@ export class HuiCardEditor extends LitElement {
throw new Error("No card type defined"); throw new Error("No card type defined");
} }
const tag = getCardElementTag(cardType); const elClass = await getCardElementClass(cardType);
// Check if the card type exists
const elClass = customElements.get(tag);
if (!elClass) {
throw new Error(`Unknown card type encountered: ${cardType}.`);
}
this._loading = true; this._loading = true;
// Check if a GUI editor exists // Check if a GUI editor exists

View File

@ -10,9 +10,9 @@ import "@material/mwc-button";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { LovelaceCardConfig } from "../../../../data/lovelace"; import { LovelaceCardConfig } from "../../../../data/lovelace";
import { getCardElementTag } from "../../common/get-card-element-tag";
import { CardPickTarget } from "../types"; import { CardPickTarget } from "../types";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { getCardElementClass } from "../../create-element/create-card-element";
const cards: string[] = [ const cards: string[] = [
"alarm-panel", "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 type = (ev.currentTarget! as CardPickTarget).type;
const tag = getCardElementTag(type);
const elClass = customElements.get(tag); const elClass = await getCardElementClass(type);
let config: LovelaceCardConfig = { type }; let config: LovelaceCardConfig = { type };
if (elClass && elClass.getStubConfig) { if (elClass && elClass.getStubConfig) {
const cardConfig = elClass.getStubConfig(this.hass); const cardConfig = elClass.getStubConfig(this.hass!);
config = { ...config, ...cardConfig }; config = { ...config, ...cardConfig };
} }

View File

@ -7,13 +7,23 @@ import { HomeAssistant } from "../../../../types";
import { LovelaceCardConfig } from "../../../../data/lovelace"; import { LovelaceCardConfig } from "../../../../data/lovelace";
import { LovelaceCard } from "../../types"; import { LovelaceCard } from "../../types";
import { ConfigError } from "../types"; import { ConfigError } from "../types";
import { getCardElementTag } from "../../common/get-card-element-tag";
import { createErrorCardConfig } from "../../cards/hui-error-card"; import { createErrorCardConfig } from "../../cards/hui-error-card";
import { computeRTL } from "../../../../common/util/compute_rtl"; import { computeRTL } from "../../../../common/util/compute_rtl";
export class HuiCardPreview extends HTMLElement { export class HuiCardPreview extends HTMLElement {
private _hass?: HomeAssistant; private _hass?: HomeAssistant;
private _element?: LovelaceCard; 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) { set hass(hass: HomeAssistant) {
if (!this._hass || this._hass.language !== hass.language) { if (!this._hass || this._hass.language !== hass.language) {
@ -36,6 +46,9 @@ export class HuiCardPreview extends HTMLElement {
} }
set config(configValue: LovelaceCardConfig) { set config(configValue: LovelaceCardConfig) {
const curConfig = this._config;
this._config = configValue;
if (!configValue) { if (!configValue) {
this._cleanup(); this._cleanup();
return; return;
@ -53,9 +66,7 @@ export class HuiCardPreview extends HTMLElement {
return; return;
} }
const tag = getCardElementTag(configValue.type); if (curConfig && configValue.type === curConfig.type) {
if (tag.toUpperCase() === this._element.tagName) {
try { try {
this._element.setConfig(deepClone(configValue)); this._element.setConfig(deepClone(configValue));
} catch (err) { } catch (err) {

View File

@ -1,4 +1,4 @@
import { HomeAssistant } from "../../types"; import { HomeAssistant, Constructor } from "../../types";
import { import {
LovelaceCardConfig, LovelaceCardConfig,
LovelaceConfig, LovelaceConfig,
@ -37,6 +37,11 @@ export interface LovelaceCard extends HTMLElement {
setConfig(config: LovelaceCardConfig): void; setConfig(config: LovelaceCardConfig): void;
} }
export interface LovelaceCardConstructor extends Constructor<LovelaceCard> {
getStubConfig?: (hass: HomeAssistant) => LovelaceCardConfig;
getConfigElement?: () => LovelaceCardEditor;
}
export interface LovelaceHeaderFooter extends HTMLElement { export interface LovelaceHeaderFooter extends HTMLElement {
hass?: HomeAssistant; hass?: HomeAssistant;
setConfig(config: LovelaceHeaderFooterConfig): void; setConfig(config: LovelaceHeaderFooterConfig): void;