diff --git a/package.json b/package.json
index 6baea313b5..e42662bcc7 100644
--- a/package.json
+++ b/package.json
@@ -87,6 +87,7 @@
"react-big-calendar": "^0.19.2",
"regenerator-runtime": "^0.12.1",
"round-slider": "^1.3.2",
+ "superstruct": "^0.6.0",
"unfetch": "^4.0.1",
"web-animations-js": "^2.3.1",
"xss": "^1.0.3"
diff --git a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts
index eebdd38494..e51dd1b526 100644
--- a/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-entities-card-editor.ts
@@ -1,11 +1,13 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
+import { struct } from "superstruct";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-toggle-button/paper-toggle-button";
import { processEditorEntities } from "../process-editor-entities";
+
import { EntitiesEditorEvent, EditorTarget } from "../types";
import { hassLocalizeLitMixin } from "../../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../../types";
@@ -20,6 +22,24 @@ import "../../components/hui-entity-editor";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
+const entitiesConfigStruct = struct.union([
+ {
+ entity: "string",
+ name: "string?",
+ icon: "string?",
+ },
+ "string",
+]);
+
+const cardConfigStruct = struct({
+ type: "string",
+ id: "string|number",
+ title: "string|number?",
+ theme: "string?",
+ show_header_toggle: "boolean?",
+ entities: [entitiesConfigStruct],
+});
+
export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement)
implements LovelaceCardEditor {
public hass?: HomeAssistant;
@@ -39,6 +59,8 @@ export class HuiEntitiesCardEditor extends hassLocalizeLitMixin(LitElement)
}
public setConfig(config: Config): void {
+ config = cardConfigStruct(config);
+
this._config = { type: "entities", ...config };
this._configEntities = processEditorEntities(config.entities);
}
diff --git a/src/panels/lovelace/editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/hui-dialog-edit-card.ts
index a686e91b82..306fe59f18 100644
--- a/src/panels/lovelace/editor/hui-dialog-edit-card.ts
+++ b/src/panels/lovelace/editor/hui-dialog-edit-card.ts
@@ -20,7 +20,7 @@ declare global {
}
const dialogShowEvent = "show-edit-card";
-const dialogTag = "hui-dialog-edit-config";
+const dialogTag = "hui-dialog-edit-card";
export interface EditCardDialogParams {
cardConfig: LovelaceCardConfig;
@@ -81,7 +81,7 @@ export class HuiDialogEditCard extends LitElement {
declare global {
interface HTMLElementTagNameMap {
- "hui-dialog-edit-config": HuiDialogEditCard;
+ "hui-dialog-edit-card": HuiDialogEditCard;
}
}
diff --git a/src/panels/lovelace/editor/hui-edit-card.ts b/src/panels/lovelace/editor/hui-edit-card.ts
index 5e69084d92..92e9a23962 100644
--- a/src/panels/lovelace/editor/hui-edit-card.ts
+++ b/src/panels/lovelace/editor/hui-edit-card.ts
@@ -53,6 +53,8 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
private _loading?: boolean;
private _isToggleAvailable?: boolean;
private _saving: boolean;
+ private _errorMsg?: TemplateResult;
+ private _cardType?: string;
static get properties(): PropertyDeclarations {
return {
@@ -62,6 +64,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
_configElement: {},
_configValue: {},
_configState: {},
+ _errorMsg: {},
_uiEditor: {},
_saving: {},
_loading: {},
@@ -77,14 +80,11 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
set cardConfig(cardConfig: LovelaceCardConfig) {
this._originalConfig = cardConfig;
if (String(cardConfig.id) !== this._cardId) {
- this._loading = true;
- this._uiEditor = true;
- this._configElement = undefined;
this._configValue = { format: "yaml", value: undefined };
this._configState = "OK";
- this._isToggleAvailable = false;
+ this._uiEditor = true;
this._cardId = String(cardConfig.id);
- this._loadConfigElement();
+ this._loadConfigElement(cardConfig);
}
}
@@ -105,6 +105,24 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
}
protected render(): TemplateResult {
+ let content;
+ if (!this._configElement !== undefined) {
+ if (this._uiEditor) {
+ content = html`
+
${this._configElement}
+ `;
+ } else {
+ content = html`
+
+ `;
+ }
+ }
+
return html`
${this.renderStyle()}
@@ -118,19 +136,13 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
class="${classMap({ hidden: this._loading! })}"
>
${
- this._uiEditor && this._configElement !== null
+ this._errorMsg
? html`
- ${this._configElement}
- `
- : html`
-
+ ${this._errorMsg}
`
+ : ""
}
+ ${content}
@@ -164,7 +176,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
>
`
- : html``
+ : ""
}
`;
@@ -200,6 +212,10 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
.element-editor {
margin-bottom: 8px;
}
+ .error {
+ color: #ef5350;
+ border-bottom: 1px solid #ef5350;
+ }
hr {
color: #000;
opacity: 0.12;
@@ -213,7 +229,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
`;
}
- private _toggleEditor(): void {
+ private async _toggleEditor(): Promise {
if (!this._isToggleAvailable) {
alert("You can't switch editor.");
return;
@@ -235,9 +251,13 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
schema: extYamlSchema,
}),
};
- this._configElement.setConfig(this._configValue!
- .value as LovelaceCardConfig);
this._uiEditor = !this._uiEditor;
+ const cardConfig = this._configValue!.value! as LovelaceCardConfig;
+ if (cardConfig.type !== this._cardType) {
+ await this._loadConfigElement(cardConfig);
+ this._cardType = cardConfig.type;
+ }
+ this._configElement.setConfig(cardConfig);
}
this._resizeDialog();
}
@@ -263,6 +283,7 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
}
private _closeDialog(): void {
+ this._cardId = undefined;
this._dialog.close();
}
@@ -363,11 +384,16 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
return JSON.stringify(configValue) !== JSON.stringify(this._originalConfig);
}
- private async _loadConfigElement(): Promise {
- if (!this._originalConfig) {
+ private async _loadConfigElement(conf: LovelaceCardConfig): Promise {
+ if (!conf) {
return;
}
- const conf = this._originalConfig;
+
+ this._errorMsg = undefined;
+ this._loading = true;
+ this._configElement = undefined;
+ this._isToggleAvailable = false;
+
const tag = conf.type.startsWith(CUSTOM_TYPE_PREFIX)
? conf!.type.substr(CUSTOM_TYPE_PREFIX.length)
: `hui-${conf!.type}-card`;
@@ -378,20 +404,30 @@ export class HuiEditCard extends hassLocalizeLitMixin(LitElement) {
try {
configElement = await elClass.getConfigElement();
} catch (err) {
- this._configElement = null;
this._uiEditor = false;
+ this._configElement = null;
return;
}
- this._isToggleAvailable = true;
+ try {
+ configElement.setConfig(conf);
+ } catch (err) {
+ this._errorMsg = html`
+ Your config is not supported by the UI editor:
${err.message}
Falling back to YAML editor.
+ `;
+ this._uiEditor = false;
+ this._configElement = null;
+ return;
+ }
- configElement.setConfig(conf);
configElement.hass = this.hass;
configElement.addEventListener("config-changed", (ev) =>
this._handleUIConfigChanged(ev.detail.config)
);
this._configValue = { format: "json", value: conf };
this._configElement = configElement;
+ this._isToggleAvailable = true;
this._updatePreview(conf);
}
}
diff --git a/yarn.lock b/yarn.lock
index c2efe9bb1e..645bdfbaf0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4226,6 +4226,16 @@ clone-buffer@^1.0.0:
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
+clone-deep@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
+ integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==
+ dependencies:
+ for-own "^1.0.0"
+ is-plain-object "^2.0.4"
+ kind-of "^6.0.0"
+ shallow-clone "^1.0.0"
+
clone-stats@^0.0.1, clone-stats@~0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
@@ -6452,6 +6462,11 @@ follow-redirects@^1.0.0:
dependencies:
debug "=3.1.0"
+for-in@^0.1.3:
+ version "0.1.8"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
+ integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=
+
for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -8556,7 +8571,7 @@ kind-of@^5.0.0:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
-kind-of@^6.0.0, kind-of@^6.0.2:
+kind-of@^6.0.0, kind-of@^6.0.1, kind-of@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
@@ -9716,6 +9731,14 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
+mixin-object@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
+ integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=
+ dependencies:
+ for-in "^0.1.3"
+ is-extendable "^0.1.1"
+
mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@@ -12267,6 +12290,15 @@ shady-css-parser@^0.1.0:
resolved "https://registry.yarnpkg.com/shady-css-parser/-/shady-css-parser-0.1.0.tgz#534dc79c8ca5884c5ed92a4e5a13d6d863bca428"
integrity sha512-irfJUUkEuDlNHKZNAp2r7zOyMlmbfVJ+kWSfjlCYYUx/7dJnANLCyTzQZsuxy5NJkvtNwSxY5Gj8MOlqXUQPyA==
+shallow-clone@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571"
+ integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==
+ dependencies:
+ is-extendable "^0.1.1"
+ kind-of "^5.0.0"
+ mixin-object "^2.0.1"
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -12903,6 +12935,14 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
+superstruct@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.6.0.tgz#20d2073526cf683a57f258695e009c4a19134ad0"
+ integrity sha512-6Y+bh5oFXCMUmGGzcdwd8M2qXMWn9aH3Qu2wV8Cg/Lxu+3fTxJ0dTx54nKd/Sm3lSz3i901xVatzev7c/xN8Lg==
+ dependencies:
+ clone-deep "^2.0.1"
+ kind-of "^6.0.1"
+
supports-color@3.1.2, supports-color@5.4.0, supports-color@^0.2.0, supports-color@^2.0.0, supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"