diff --git a/package.json b/package.json
index 231ad11ac4..91464b3481 100644
--- a/package.json
+++ b/package.json
@@ -68,6 +68,7 @@
"@webcomponents/webcomponentsjs": "^2.2.0",
"chart.js": "~2.7.2",
"chartjs-chart-timeline": "^0.2.1",
+ "codemirror": "^5.43.0",
"deep-clone-simple": "^1.1.1",
"es6-object-assign": "^1.1.0",
"eslint-import-resolver-webpack": "^0.10.1",
@@ -102,6 +103,7 @@
"@babel/preset-typescript": "^7.1.0",
"@gfx/zopfli": "^1.0.9",
"@types/chai": "^4.1.7",
+ "@types/codemirror": "^0.0.71",
"@types/mocha": "^5.2.5",
"babel-eslint": "^10",
"babel-loader": "^8.0.4",
diff --git a/src/panels/lovelace/components/hui-yaml-editor.ts b/src/panels/lovelace/components/hui-yaml-editor.ts
new file mode 100644
index 0000000000..f83d392bee
--- /dev/null
+++ b/src/panels/lovelace/components/hui-yaml-editor.ts
@@ -0,0 +1,93 @@
+// @ts-ignore
+import CodeMirror from "codemirror";
+import "codemirror/mode/yaml/yaml";
+// @ts-ignore
+import codeMirrorCSS from "codemirror/lib/codemirror.css";
+import { fireEvent } from "../../../common/dom/fire_event";
+declare global {
+ interface HASSDomEvents {
+ "yaml-changed": {
+ value: string;
+ };
+ }
+}
+
+export class HuiYamlEditor extends HTMLElement {
+ public codemirror: CodeMirror;
+ private _value: string;
+
+ public constructor() {
+ super();
+ this._value = "";
+ const shadowRoot = this.attachShadow({ mode: "open" });
+ shadowRoot.innerHTML = `
+ `;
+ }
+
+ set value(value: string) {
+ if (this.codemirror) {
+ if (value !== this.codemirror.getValue()) {
+ this.codemirror.setValue(value);
+ }
+ }
+ this._value = value;
+ }
+
+ get value(): string {
+ return this.codemirror.getValue();
+ }
+
+ get hasComments(): boolean {
+ return this.shadowRoot!.querySelector("span.cm-comment") ? true : false;
+ }
+
+ public connectedCallback(): void {
+ if (!this.codemirror) {
+ this.codemirror = CodeMirror(this.shadowRoot, {
+ value: this._value,
+ lineNumbers: true,
+ mode: "yaml",
+ tabSize: 2,
+ autofocus: true,
+ extraKeys: {
+ Tab: (cm: CodeMirror) => {
+ const spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
+ cm.replaceSelection(spaces);
+ },
+ },
+ });
+ this.codemirror.on("changes", () => this._onChange());
+ } else {
+ this.codemirror.refresh();
+ }
+ }
+
+ private _onChange(): void {
+ fireEvent(this, "yaml-changed", { value: this.codemirror.getValue() });
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "hui-yaml-editor": HuiYamlEditor;
+ }
+}
+
+window.customElements.define("hui-yaml-editor", HuiYamlEditor);
diff --git a/src/panels/lovelace/editor/card-editor/hui-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-edit-card.ts
index 7eac2fba87..b11007465f 100644
--- a/src/panels/lovelace/editor/card-editor/hui-edit-card.ts
+++ b/src/panels/lovelace/editor/card-editor/hui-edit-card.ts
@@ -23,23 +23,22 @@ import { HomeAssistant } from "../../../../types";
import { LovelaceCardConfig } from "../../../../data/lovelace";
import { fireEvent } from "../../../../common/dom/fire_event";
-import "./hui-yaml-editor";
+import "../../components/hui-yaml-editor";
+// This is not a duplicate import, one is for types, one is for element.
+// tslint:disable-next-line
+import { HuiYamlEditor } from "../../components/hui-yaml-editor";
import "./hui-card-preview";
// This is not a duplicate import, one is for types, one is for element.
// tslint:disable-next-line
import { HuiCardPreview } from "./hui-card-preview";
import { LovelaceCardEditor, Lovelace } from "../../types";
-import { YamlChangedEvent, ConfigValue, ConfigError } from "../types";
-import { extYamlSchema } from "../yaml-ext-schema";
+import { ConfigValue, ConfigError } from "../types";
import { EntityConfig } from "../../entity-rows/types";
import { getCardElementTag } from "../../common/get-card-element-tag";
import { addCard, replaceCard } from "../config-util";
declare global {
interface HASSDomEvents {
- "yaml-changed": {
- yaml: string;
- };
"entities-changed": {
entities: EntityConfig[];
};
@@ -120,8 +119,7 @@ export class HuiEditCard extends LitElement {
? this._configElement
: html`
`}
@@ -195,6 +193,11 @@ export class HuiEditCard extends LitElement {
await this.updateComplete;
this._loading = false;
this._resizeDialog();
+ if (!this._uiEditor) {
+ setTimeout(() => {
+ this.yamlEditor.codemirror.refresh();
+ }, 1);
+ }
}
private async _resizeDialog(): Promise {
@@ -217,9 +220,7 @@ export class HuiEditCard extends LitElement {
const cardConf: LovelaceCardConfig =
this._configValue!.format === "yaml"
- ? yaml.safeLoad(this._configValue!.value!, {
- schema: extYamlSchema,
- })
+ ? yaml.safeLoad(this._configValue!.value!)
: this._configValue!.value!;
try {
@@ -241,12 +242,12 @@ export class HuiEditCard extends LitElement {
}
}
- private _handleYamlChanged(ev: YamlChangedEvent): void {
- this._configValue = { format: "yaml", value: ev.detail.yaml };
+ private _handleYamlChanged(ev: CustomEvent): void {
+ this._configValue = { format: "yaml", value: ev.detail.value };
try {
- const config = yaml.safeLoad(this._configValue.value, {
- schema: extYamlSchema,
- }) as LovelaceCardConfig;
+ const config = yaml.safeLoad(
+ this._configValue.value
+ ) as LovelaceCardConfig;
this._updatePreview(config);
this._configState = "OK";
} catch (err) {
@@ -263,7 +264,9 @@ export class HuiEditCard extends LitElement {
this._updatePreview(value);
}
- private _updatePreview(config: LovelaceCardConfig) {
+ private async _updatePreview(config: LovelaceCardConfig) {
+ await this.updateComplete;
+
if (!this._previewEl) {
return;
}
@@ -295,9 +298,7 @@ export class HuiEditCard extends LitElement {
this._uiEditor = !this._uiEditor;
} else if (this._configElement && this._configValue!.format === "yaml") {
const yamlConfig = this._configValue!.value;
- const cardConfig = yaml.safeLoad(yamlConfig, {
- schema: extYamlSchema,
- }) as LovelaceCardConfig;
+ const cardConfig = yaml.safeLoad(yamlConfig) as LovelaceCardConfig;
this._uiEditor = !this._uiEditor;
if (cardConfig.type !== this._cardType) {
const succes = await this._loadConfigElement(cardConfig);
@@ -356,6 +357,7 @@ export class HuiEditCard extends LitElement {
configElement = await elClass.getConfigElement();
} else {
this._configValue = { format: "yaml", value: yaml.safeDump(conf) };
+ this._updatePreview(conf);
this._uiEditor = false;
this._configElement = null;
return false;
@@ -372,6 +374,7 @@ export class HuiEditCard extends LitElement {
format: "yaml",
value: yaml.safeDump(conf),
};
+ this._updatePreview(conf);
this._uiEditor = false;
this._configElement = null;
return false;
@@ -398,6 +401,10 @@ export class HuiEditCard extends LitElement {
}
}
+ private get yamlEditor(): HuiYamlEditor {
+ return this.shadowRoot!.querySelector("hui-yaml-editor")!;
+ }
+
static get styles(): CSSResult[] {
return [
haStyleDialog,
diff --git a/src/panels/lovelace/editor/card-editor/hui-yaml-editor.ts b/src/panels/lovelace/editor/card-editor/hui-yaml-editor.ts
deleted file mode 100644
index df2767d902..0000000000
--- a/src/panels/lovelace/editor/card-editor/hui-yaml-editor.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import {
- html,
- LitElement,
- PropertyDeclarations,
- TemplateResult,
-} from "lit-element";
-import "@polymer/paper-input/paper-textarea";
-
-import { HomeAssistant } from "../../../../types";
-import { fireEvent } from "../../../../common/dom/fire_event";
-
-export class HuiYAMLEditor extends LitElement {
- protected hass?: HomeAssistant;
- private _yaml?: string;
-
- static get properties(): PropertyDeclarations {
- return { _yaml: {} };
- }
-
- set yaml(yaml: string) {
- if (yaml === undefined) {
- return;
- } else {
- this._yaml = yaml;
- }
- }
-
- protected render(): TemplateResult | void {
- return html`
- ${this.renderStyle()}
-
- `;
- }
-
- private renderStyle(): TemplateResult {
- return html`
-
- `;
- }
-
- private _valueChanged(ev: Event): void {
- const target = ev.target! as any;
- this._yaml = target.value;
- fireEvent(this, "yaml-changed", {
- yaml: target.value,
- });
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- "hui-yaml-editor": HuiYAMLEditor;
- }
-}
-
-customElements.define("hui-yaml-editor", HuiYAMLEditor);
diff --git a/src/panels/lovelace/editor/yaml-ext-schema.ts b/src/panels/lovelace/editor/yaml-ext-schema.ts
deleted file mode 100644
index 357962d911..0000000000
--- a/src/panels/lovelace/editor/yaml-ext-schema.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import yaml from "js-yaml";
-
-const secretYamlType = new yaml.Type("!secret", {
- kind: "scalar",
- construct(data) {
- data = data || "";
- return "!secret " + data;
- },
-});
-
-const includeYamlType = new yaml.Type("!include", {
- kind: "scalar",
- construct(data) {
- data = data || "";
- return "!include " + data;
- },
-});
-
-export const extYamlSchema = yaml.Schema.create([
- secretYamlType,
- includeYamlType,
-]);
diff --git a/src/panels/lovelace/hui-editor.ts b/src/panels/lovelace/hui-editor.ts
index 9e44f7cc32..8d1c201fb9 100644
--- a/src/panels/lovelace/hui-editor.ts
+++ b/src/panels/lovelace/hui-editor.ts
@@ -14,8 +14,10 @@ import { Lovelace } from "./types";
import "../../components/ha-icon";
import { haStyle } from "../../resources/ha-style";
-
-const TAB_INSERT = " ";
+import "./components/hui-yaml-editor";
+// This is not a duplicate import, one is for types, one is for element.
+// tslint:disable-next-line
+import { HuiYamlEditor } from "./components/hui-yaml-editor";
const lovelaceStruct = struct.interface({
title: "string?",
@@ -28,16 +30,13 @@ class LovelaceFullConfigEditor extends LitElement {
public closeEditor?: () => void;
private _saving?: boolean;
private _changed?: boolean;
- private _hashAdded?: boolean;
- private _hash?: boolean;
+ private _generation?: number;
static get properties() {
return {
lovelace: {},
_saving: {},
_changed: {},
- _hashAdded: {},
- _hash: {},
};
}
@@ -51,11 +50,6 @@ class LovelaceFullConfigEditor extends LitElement {
@click="${this._closeEditor}"
>
Edit Config
- ${this._hash
- ? html`
-
- `
- : ""}
Save