Add code mirror editors to the dev-tools data fields (#3981)

* Add yaml code mirror editor to the dev-tools yaml fields

* Add jinja2 editor on dev template

* Migrate to UpdatingElement, review comments

* update cm, add types

* types

* dev tools mqtt
This commit is contained in:
Bram Kragten 2019-10-12 21:33:51 +02:00 committed by Paulus Schoutsen
parent 4728c12225
commit 12840231be
15 changed files with 387 additions and 265 deletions

View File

@ -74,7 +74,7 @@
"@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "~2.8.0",
"chartjs-chart-timeline": "^0.3.0",
"codemirror": "^5.45.0",
"codemirror": "^5.49.0",
"cpx": "^1.5.0",
"deep-clone-simple": "^1.1.1",
"es6-object-assign": "^1.1.0",
@ -117,6 +117,7 @@
"@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^3.0.12",
"@types/chromecast-caf-sender": "^1.0.1",
"@types/codemirror": "^0.0.78",
"@types/hls.js": "^0.12.3",
"@types/leaflet": "^1.4.3",
"@types/memoize-one": "4.1.0",

View File

@ -0,0 +1,160 @@
import { loadCodeMirror } from "../resources/codemirror.ondemand";
import { fireEvent } from "../common/dom/fire_event";
import {
UpdatingElement,
property,
customElement,
PropertyValues,
} from "lit-element";
import { Editor } from "codemirror";
declare global {
interface HASSDomEvents {
"editor-save": undefined;
}
}
@customElement("ha-code-editor")
export class HaCodeEditor extends UpdatingElement {
public codemirror?: Editor;
@property() public mode?: string;
@property() public autofocus = false;
@property() public rtl = false;
@property() public error = false;
@property() private _value = "";
public set value(value: string) {
this._value = value;
}
public get value(): string {
return this.codemirror ? this.codemirror.getValue() : this._value;
}
public get hasComments(): boolean {
return this.shadowRoot!.querySelector("span.cm-comment") ? true : false;
}
public connectedCallback() {
super.connectedCallback();
if (!this.codemirror) {
return;
}
this.codemirror.refresh();
if (this.autofocus !== false) {
this.codemirror.focus();
}
}
protected update(changedProps: PropertyValues): void {
super.update(changedProps);
if (!this.codemirror) {
return;
}
if (changedProps.has("mode")) {
this.codemirror.setOption("mode", this.mode);
}
if (changedProps.has("autofocus")) {
this.codemirror.setOption("autofocus", this.autofocus !== false);
}
if (changedProps.has("_value") && this._value !== this.value) {
this.codemirror.setValue(this._value);
}
if (changedProps.has("rtl")) {
this.codemirror.setOption("gutters", this._calcGutters());
this._setScrollBarDirection();
}
if (changedProps.has("error")) {
this.classList.toggle("error-state", this.error);
}
}
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this._load();
}
private async _load(): Promise<void> {
const loaded = await loadCodeMirror();
const codeMirror = loaded.codeMirror;
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot!.innerHTML = `
<style>
${loaded.codeMirrorCss}
.CodeMirror {
height: var(--code-mirror-height, auto);
direction: var(--code-mirror-direction, ltr);
}
.CodeMirror-scroll {
max-height: var(--code-mirror-max-height, --code-mirror-height);
}
.CodeMirror-gutters {
border-right: 1px solid var(--paper-input-container-color, var(--secondary-text-color));
background-color: var(--paper-dialog-background-color, var(--primary-background-color));
transition: 0.2s ease border-right;
}
:host(.error-state) .CodeMirror-gutters {
border-color: var(--error-state-color, red);
}
.CodeMirror-focused .CodeMirror-gutters {
border-right: 2px solid var(--paper-input-container-focus-color, var(--primary-color));
}
.CodeMirror-linenumber {
color: var(--paper-dialog-color, var(--primary-text-color));
}
.rtl .CodeMirror-vscrollbar {
right: auto;
left: 0px;
}
.rtl-gutter {
width: 20px;
}
</style>`;
this.codemirror = codeMirror(shadowRoot, {
value: this._value,
lineNumbers: true,
tabSize: 2,
mode: this.mode,
autofocus: this.autofocus !== false,
viewportMargin: Infinity,
extraKeys: {
Tab: "indentMore",
"Shift-Tab": "indentLess",
},
gutters: this._calcGutters(),
});
this._setScrollBarDirection();
this.codemirror!.on("changes", () => this._onChange());
}
private _onChange(): void {
const newValue = this.value;
if (newValue === this._value) {
return;
}
this._value = newValue;
fireEvent(this, "value-changed", { value: this._value });
}
private _calcGutters(): string[] {
return this.rtl ? ["rtl-gutter", "CodeMirror-linenumbers"] : [];
}
private _setScrollBarDirection(): void {
if (this.codemirror) {
this.codemirror.getWrapperElement().classList.toggle("rtl", this.rtl);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-code-editor": HaCodeEditor;
}
}

View File

@ -1,139 +0,0 @@
// @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";
import { customElement } from "lit-element";
declare global {
interface HASSDomEvents {
"yaml-changed": {
value: string;
};
"yaml-save": undefined;
}
}
@customElement("ha-yaml-editor")
export class HaYamlEditor extends HTMLElement {
public codemirror?: any;
private _autofocus = false;
private _rtl = false;
private _value: string;
public constructor() {
super();
CodeMirror.commands.save = (cm: CodeMirror) => {
fireEvent(cm.getWrapperElement(), "yaml-save");
};
this._value = "";
const shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `
<style>
${codeMirrorCSS}
.CodeMirror {
height: var(--code-mirror-height, auto);
direction: var(--code-mirror-direction, ltr);
}
.CodeMirror-scroll {
max-height: var(--code-mirror-max-height, --code-mirror-height);
}
.CodeMirror-gutters {
border-right: 1px solid var(--paper-input-container-color, var(--secondary-text-color));
background-color: var(--paper-dialog-background-color, var(--primary-background-color));
transition: 0.2s ease border-right;
}
:host(.error-state) .CodeMirror-gutters {
border-color: var(--error-state-color, red);
}
.CodeMirror-focused .CodeMirror-gutters {
border-right: 2px solid var(--paper-input-container-focus-color, var(--primary-color));
}
.CodeMirror-linenumber {
color: var(--paper-dialog-color, var(--primary-text-color));
}
.rtl .CodeMirror-vscrollbar {
right: auto;
left: 0px;
}
.rtl-gutter {
width: 20px;
}
</style>`;
}
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();
}
set rtl(rtl: boolean) {
this._rtl = rtl;
this.setScrollBarDirection();
}
set autofocus(autofocus: boolean) {
this._autofocus = autofocus;
if (this.codemirror) {
this.codemirror.focus();
}
}
set error(error: boolean) {
this.classList.toggle("error-state", error);
}
get hasComments(): boolean {
return this.shadowRoot!.querySelector("span.cm-comment") ? true : false;
}
public connectedCallback(): void {
if (!this.codemirror) {
this.codemirror = CodeMirror(
(this.shadowRoot as unknown) as HTMLElement,
{
value: this._value,
lineNumbers: true,
mode: "yaml",
tabSize: 2,
autofocus: this._autofocus,
viewportMargin: Infinity,
extraKeys: {
Tab: "indentMore",
"Shift-Tab": "indentLess",
},
gutters: this._rtl ? ["rtl-gutter", "CodeMirror-linenumbers"] : [],
}
);
this.setScrollBarDirection();
this.codemirror.on("changes", () => this._onChange());
} else {
this.codemirror.refresh();
}
}
private _onChange(): void {
fireEvent(this, "yaml-changed", { value: this.codemirror.getValue() });
}
private setScrollBarDirection(): void {
if (this.codemirror) {
this.codemirror.getWrapperElement().classList.toggle("rtl", this._rtl);
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-yaml-editor": HaYamlEditor;
}
}

View File

@ -20,7 +20,7 @@ declare global {
"ha-device-picker": any;
"ha-device-condition-picker": any;
"ha-textarea": any;
"ha-yaml-editor": any;
"ha-code-editor": any;
"ha-service-picker": any;
"mwc-button": any;
"ha-device-trigger-picker": any;

View File

@ -1,8 +1,6 @@
import { h, Component } from "preact";
import yaml from "js-yaml";
import "../../../components/ha-yaml-editor";
// tslint:disable-next-line
import { HaYamlEditor } from "../../../components/ha-yaml-editor";
import "../../../components/ha-code-editor";
const isEmpty = (obj: object) => {
for (const key in obj) {
@ -14,8 +12,6 @@ const isEmpty = (obj: object) => {
};
export default class YAMLTextArea extends Component<any, any> {
private _yamlEditor!: HaYamlEditor;
constructor(props) {
super(props);
@ -63,12 +59,6 @@ export default class YAMLTextArea extends Component<any, any> {
}
}
public componentDidMount() {
setTimeout(() => {
this._yamlEditor.codemirror.refresh();
}, 1);
}
public render({ label }, { value, isValid }) {
const style: any = {
minWidth: 300,
@ -77,16 +67,14 @@ export default class YAMLTextArea extends Component<any, any> {
return (
<div>
<p>{label}</p>
<ha-yaml-editor
ref={this._storeYamlEditorRef}
<ha-code-editor
mode="yaml"
style={style}
value={value}
error={isValid === false}
onyaml-changed={this.onChange}
onvalue-changed={this.onChange}
/>
</div>
);
}
private _storeYamlEditorRef = (yamlEditor) => (this._yamlEditor = yamlEditor);
}

View File

@ -1,17 +1,18 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import yaml from "js-yaml";
import "../../../components/ha-code-editor";
import "../../../resources/ha-style";
import "./events-list";
import "./event-subscribe-card";
import { EventsMixin } from "../../../mixins/events-mixin";
const ERROR_SENTINEL = {};
/*
* @appliesMixin EventsMixin
*/
@ -32,6 +33,11 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
.ha-form {
margin-right: 16px;
max-width: 400px;
}
mwc-button {
margin-top: 8px;
}
.header {
@ -62,11 +68,16 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
required
value="{{eventType}}"
></paper-input>
<paper-textarea
label="Event Data (YAML, optional)"
value="{{eventData}}"
></paper-textarea>
<mwc-button on-click="fireEvent" raised>Fire Event</mwc-button>
<p>Event Data (YAML, optional)</p>
<ha-code-editor
mode="yaml"
value="[[eventData]]"
error="[[!validJSON]]"
on-value-changed="_yamlChanged"
></ha-code-editor>
<mwc-button on-click="fireEvent" raised disabled="[[!validJSON]]"
>Fire Event</mwc-button
>
</div>
</div>
@ -97,6 +108,16 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
type: String,
value: "",
},
parsedJSON: {
type: Object,
computed: "_computeParsedEventData(eventData)",
},
validJSON: {
type: Boolean,
computed: "_computeValidJSON(parsedJSON)",
},
};
}
@ -104,19 +125,28 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
this.eventType = ev.detail.eventType;
}
fireEvent() {
var eventData;
_computeParsedEventData(eventData) {
try {
eventData = this.eventData ? yaml.safeLoad(this.eventData) : {};
return eventData.trim() ? yaml.safeLoad(eventData) : {};
} catch (err) {
/* eslint-disable no-alert */
alert("Error parsing YAML: " + err);
/* eslint-enable no-alert */
return ERROR_SENTINEL;
}
}
_computeValidJSON(parsedJSON) {
return parsedJSON !== ERROR_SENTINEL;
}
_yamlChanged(ev) {
this.eventData = ev.detail.value;
}
fireEvent() {
if (!this.eventType) {
alert("Event type is a mandatory field");
return;
}
this.hass.callApi("POST", "events/" + this.eventType, eventData).then(
this.hass.callApi("POST", "events/" + this.eventType, this.parsedJSON).then(
function() {
this.fire("hass-notification", {
message: "Event " + this.eventType + " successful fired!",

View File

@ -9,12 +9,12 @@ import {
} from "lit-element";
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea";
import { HomeAssistant } from "../../../types";
import { haStyle } from "../../../resources/styles";
import "../../../components/ha-card";
import "../../../components/ha-code-editor";
import "./mqtt-subscribe-card";
@customElement("developer-tools-mqtt")
@ -48,12 +48,12 @@ class HaPanelDevMqtt extends LitElement {
@value-changed=${this._handleTopic}
></paper-input>
<paper-textarea
always-float-label
label="Payload (template allowed)"
<p>Payload (template allowed)</p>
<ha-code-editor
mode="jinja2"
.value="${this.payload}"
@value-changed=${this._handlePayload}
></paper-textarea>
></ha-code-editor>
</div>
<div class="card-actions">
<mwc-button @click=${this._publish}>Publish</mwc-button>

View File

@ -1,5 +1,4 @@
import "@material/mwc-button";
import "@polymer/paper-input/paper-textarea";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -7,6 +6,7 @@ import yaml from "js-yaml";
import { ENTITY_COMPONENT_DOMAINS } from "../../../data/entity";
import "../../../components/entity/ha-entity-picker";
import "../../../components/ha-code-editor";
import "../../../components/ha-service-picker";
import "../../../resources/ha-style";
import "../../../util/app-localstorage-document";
@ -30,6 +30,10 @@ class HaPanelDevService extends PolymerElement {
max-width: 400px;
}
mwc-button {
margin-top: 8px;
}
.description {
margin-top: 24px;
white-space: pre-wrap;
@ -109,20 +113,16 @@ class HaPanelDevService extends PolymerElement {
allow-custom-entity
></ha-entity-picker>
</template>
<paper-textarea
always-float-label
label="Service Data (YAML, optional)"
value="{{serviceData}}"
autocapitalize="none"
autocomplete="off"
spellcheck="false"
></paper-textarea>
<p>Service Data (YAML, optional)</p>
<ha-code-editor
mode="yaml"
value="[[serviceData]]"
error="[[!validJSON]]"
on-value-changed="_yamlChanged"
></ha-code-editor>
<mwc-button on-click="_callService" raised disabled="[[!validJSON]]">
Call Service
</mwc-button>
<template is="dom-if" if="[[!validJSON]]">
<span class="error">Invalid YAML</span>
</template>
</div>
<template is="dom-if" if="[[!domainService]]">
@ -305,6 +305,10 @@ class HaPanelDevService extends PolymerElement {
entity_id: ev.target.value,
});
}
_yamlChanged(ev) {
this.serviceData = ev.detail.value;
}
}
customElements.define("developer-tools-service", HaPanelDevService);

View File

@ -1,16 +1,17 @@
import "@material/mwc-button";
import "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import yaml from "js-yaml";
import "../../../components/entity/ha-entity-picker";
import "../../../components/ha-code-editor";
import "../../../resources/ha-style";
import { EventsMixin } from "../../../mixins/events-mixin";
const ERROR_SENTINEL = {};
/*
* @appliesMixin EventsMixin
*/
@ -27,13 +28,14 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
direction: ltr;
}
ha-entity-picker,
.state-input,
paper-textarea {
display: block;
.inputs {
max-width: 400px;
}
mwc-button {
margin-top: 8px;
}
.entities th {
text-align: left;
}
@ -66,7 +68,7 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
}
</style>
<div>
<div class="inputs">
<p>
Set the representation of a device within Home Assistant.<br />
This will not communicate with the actual device.
@ -89,14 +91,16 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
value="{{_state}}"
class="state-input"
></paper-input>
<paper-textarea
label="State attributes (YAML, optional)"
autocapitalize="none"
autocomplete="off"
spellcheck="false"
value="{{_stateAttributes}}"
></paper-textarea>
<mwc-button on-click="handleSetState" raised>Set State</mwc-button>
<p>State attributes (YAML, optional)</p>
<ha-code-editor
mode="yaml"
value="[[_stateAttributes]]"
error="[[!validJSON]]"
on-value-changed="_yamlChanged"
></ha-code-editor>
<mwc-button on-click="handleSetState" disabled="[[!validJSON]]" raised
>Set State</mwc-button
>
</div>
<h1>Current entities</h1>
@ -166,6 +170,16 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
type: Object,
},
parsedJSON: {
type: Object,
computed: "_computeParsedStateAttributes(_stateAttributes)",
},
validJSON: {
type: Boolean,
computed: "_computeValidJSON(parsedJSON)",
},
_entityId: {
type: String,
value: "",
@ -229,20 +243,13 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
}
handleSetState() {
var attr;
try {
attr = this._stateAttributes ? yaml.safeLoad(this._stateAttributes) : {};
} catch (err) {
/* eslint-disable no-alert */
alert("Error parsing YAML: " + err);
/* eslint-enable no-alert */
if (!this._entityId) {
alert("Entity is a mandatory field");
return;
}
this.hass.callApi("POST", "states/" + this._entityId, {
state: this._state,
attributes: attr,
attributes: this.parsedJSON,
});
}
@ -341,6 +348,22 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
return output;
}
_computeParsedStateAttributes(stateAttributes) {
try {
return stateAttributes.trim() ? yaml.safeLoad(stateAttributes) : {};
} catch (err) {
return ERROR_SENTINEL;
}
}
_computeValidJSON(parsedJSON) {
return parsedJSON !== ERROR_SENTINEL;
}
_yamlChanged(ev) {
this._stateAttributes = ev.detail.value;
}
}
customElements.define("developer-tools-state", HaPanelDevState);

View File

@ -1,9 +1,9 @@
import "@polymer/paper-input/paper-textarea";
import "@polymer/paper-spinner/paper-spinner";
import { timeOut } from "@polymer/polymer/lib/utils/async";
import { Debouncer } from "@polymer/polymer/lib/utils/debounce";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-code-editor";
import "../../../resources/ha-style";
@ -46,12 +46,6 @@ class HaPanelDevTemplate extends PolymerElement {
right: 8px;
}
paper-textarea {
--paper-input-container-input: {
@apply --paper-font-code1;
}
}
.rendered {
@apply --paper-font-code1;
clear: both;
@ -85,11 +79,14 @@ class HaPanelDevTemplate extends PolymerElement {
>
</li>
</ul>
<paper-textarea
label="Template editor"
value="{{template}}"
<p>Template editor</p>
<ha-code-editor
mode="jinja2"
value="[[template]]"
error="[[error]]"
autofocus
></paper-textarea>
on-value-changed="templateChanged"
></ha-code-editor>
</div>
<div class="render-pane">
@ -144,7 +141,6 @@ For loop example:
{{ state.name | lower }} is {{state.state_with_unit}}
{%- endfor %}.`,
/* eslint-enable max-len */
observer: "templateChanged",
},
processed: {
@ -154,6 +150,11 @@ For loop example:
};
}
ready() {
super.ready();
this.renderTemplate();
}
computeFormClasses(narrow) {
return narrow ? "content fit" : "content fit layout horizontal";
}
@ -162,7 +163,8 @@ For loop example:
return error ? "error rendered" : "rendered";
}
templateChanged() {
templateChanged(ev) {
this.template = ev.detail.value;
if (this.error) {
this.error = false;
}

View File

@ -17,10 +17,10 @@ import { LovelaceCardEditor } from "../../types";
import { getCardElementTag } from "../../common/get-card-element-tag";
import { computeRTL } from "../../../../common/util/compute_rtl";
import "../../../../components/ha-yaml-editor";
import "../../../../components/ha-code-editor";
// This is not a duplicate import, one is for types, one is for element.
// tslint:disable-next-line
import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import { HaCodeEditor } from "../../../../components/ha-code-editor";
import { fireEvent } from "../../../../common/dom/fire_event";
import { EntityConfig } from "../../entity-rows/types";
@ -65,12 +65,6 @@ export class HuiCardEditor extends LitElement {
try {
this._config = yaml.safeLoad(this.yaml);
this._updateConfigElement();
setTimeout(() => {
if (this._yamlEditor) {
this._yamlEditor.codemirror.refresh();
}
fireEvent(this as HTMLElement, "iron-resize");
}, 1);
this._error = undefined;
} catch (err) {
this._error = err.message;
@ -94,14 +88,19 @@ export class HuiCardEditor extends LitElement {
return this._error !== undefined;
}
private get _yamlEditor(): HaYamlEditor {
return this.shadowRoot!.querySelector("ha-yaml-editor")!;
private get _yamlEditor(): HaCodeEditor {
return this.shadowRoot!.querySelector("ha-code-editor")! as HaCodeEditor;
}
public toggleMode() {
this._GUImode = !this._GUImode;
}
public connectedCallback() {
super.connectedCallback();
this._refreshYamlEditor();
}
protected render(): TemplateResult {
return html`
<div class="wrapper">
@ -121,12 +120,14 @@ export class HuiCardEditor extends LitElement {
`
: html`
<div class="yaml-editor">
<ha-yaml-editor
.autofocus=${true}
.rtl=${computeRTL(this.hass)}
<ha-code-editor
mode="yaml"
autofocus
.value=${this.yaml}
@yaml-changed=${this._handleYAMLChanged}
></ha-yaml-editor>
.error=${this._error}
.rtl=${computeRTL(this.hass)}
@value-changed=${this._handleYAMLChanged}
></ha-code-editor>
</div>
`}
${this._error
@ -165,13 +166,25 @@ export class HuiCardEditor extends LitElement {
if (changedProperties.has("_GUImode")) {
if (this._GUImode === false) {
// Refresh code editor when switching to yaml mode
this._yamlEditor.codemirror.refresh();
this._yamlEditor.codemirror.focus();
this._refreshYamlEditor(true);
}
fireEvent(this as HTMLElement, "iron-resize");
}
}
private _refreshYamlEditor(focus = false) {
// wait on render
setTimeout(() => {
if (this._yamlEditor && this._yamlEditor.codemirror) {
this._yamlEditor.codemirror.refresh();
if (focus) {
this._yamlEditor.codemirror.focus();
}
}
fireEvent(this as HTMLElement, "iron-resize");
}, 1);
}
private _handleUIConfigChanged(ev: UIConfigChangedEvent) {
ev.stopPropagation();
const config = ev.detail.config;

View File

@ -14,10 +14,10 @@ import { Lovelace } from "./types";
import "../../components/ha-icon";
import { haStyle } from "../../resources/styles";
import "../../components/ha-yaml-editor";
import "../../components/ha-code-editor";
// This is not a duplicate import, one is for types, one is for element.
// tslint:disable-next-line
import { HaYamlEditor } from "../../components/ha-yaml-editor";
import { HaCodeEditor } from "../../components/ha-code-editor";
import { HomeAssistant } from "../../types";
import { computeRTL } from "../../common/util/compute_rtl";
@ -33,7 +33,7 @@ class LovelaceFullConfigEditor extends LitElement {
public closeEditor?: () => void;
private _saving?: boolean;
private _changed?: boolean;
private _generation?: number;
private _generation = 1;
static get properties() {
return {
@ -81,14 +81,15 @@ class LovelaceFullConfigEditor extends LitElement {
</app-toolbar>
</app-header>
<div class="content">
<ha-yaml-editor
.autofocus=${true}
<ha-code-editor
mode="yaml"
autofocus
.rtl=${computeRTL(this.hass)}
.hass="${this.hass}"
@yaml-changed="${this._yamlChanged}"
@yaml-save="${this._handleSave}"
@value-changed="${this._yamlChanged}"
@editor-save="${this._handleSave}"
>
</ha-yaml-editor>
</ha-code-editor>
</div>
</app-header-layout>
`;
@ -96,8 +97,6 @@ class LovelaceFullConfigEditor extends LitElement {
protected firstUpdated() {
this.yamlEditor.value = yaml.safeDump(this.lovelace!.config);
this.yamlEditor.codemirror.clearHistory();
this._generation = this.yamlEditor.codemirror.changeGeneration(true);
}
static get styles(): CSSResult[] {
@ -143,10 +142,9 @@ class LovelaceFullConfigEditor extends LitElement {
}
private _yamlChanged() {
if (!this._generation) {
return;
}
this._changed = !this.yamlEditor.codemirror.isClean(this._generation);
this._changed = !this.yamlEditor
.codemirror!.getDoc()
.isClean(this._generation);
if (this._changed && !window.onbeforeunload) {
window.onbeforeunload = () => {
return true;
@ -202,14 +200,16 @@ class LovelaceFullConfigEditor extends LitElement {
} catch (err) {
alert(`Unable to save YAML: ${err}`);
}
this._generation = this.yamlEditor.codemirror.changeGeneration(true);
this._generation = this.yamlEditor
.codemirror!.getDoc()
.changeGeneration(true);
window.onbeforeunload = null;
this._saving = false;
this._changed = false;
}
private get yamlEditor(): HaYamlEditor {
return this.shadowRoot!.querySelector("ha-yaml-editor")!;
private get yamlEditor(): HaCodeEditor {
return this.shadowRoot!.querySelector("ha-code-editor")! as HaCodeEditor;
}
}

View File

@ -0,0 +1,13 @@
interface LoadedCodeMirror {
codeMirror: any;
codeMirrorCss: any;
}
let loaded: Promise<LoadedCodeMirror>;
export const loadCodeMirror = async (): Promise<LoadedCodeMirror> => {
if (!loaded) {
loaded = import(/* webpackChunkName: "codemirror" */ "./codemirror");
}
return loaded;
};

View File

@ -0,0 +1,13 @@
// @ts-ignore
import _CodeMirror, { Editor } from "codemirror";
// @ts-ignore
import _codeMirrorCss from "codemirror/lib/codemirror.css";
import "codemirror/mode/yaml/yaml";
import "codemirror/mode/jinja2/jinja2";
import { fireEvent } from "../common/dom/fire_event";
_CodeMirror.commands.save = (cm: Editor) => {
fireEvent(cm.getWrapperElement(), "editor-save");
};
export const codeMirror: any = _CodeMirror;
export const codeMirrorCss: any = _codeMirrorCss;

View File

@ -1652,6 +1652,13 @@
resolved "https://registry.yarnpkg.com/@types/clone/-/clone-0.1.30.tgz#e7365648c1b42136a59c7d5040637b3b5c83b614"
integrity sha1-5zZWSMG0ITalnH1QQGN7O1yDthQ=
"@types/codemirror@^0.0.78":
version "0.0.78"
resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-0.0.78.tgz#75a8eabda268c8e734855fb24e8c86192e2e18ad"
integrity sha512-QpMQUpEL+ZNcpEhjvYM/H6jqDx9nNcJqymA2kbkNthFS2I7ekL7ofEZ7+MoQAFTBuJers91K0FGCMpL7MwC9TQ==
dependencies:
"@types/tern" "*"
"@types/compression@^0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/compression/-/compression-0.0.33.tgz#95dc733a2339aa846381d7f1377792d2553dc27d"
@ -1686,7 +1693,7 @@
resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-0.0.20.tgz#cae698714dd61ebee5ab3f2aeb9a34ba1011735a"
integrity sha512-6dhZJLbA7aOwkYB2GDGdIqJ20wmHnkDzaxV9PJXe7O02I2dSFTERzRB6JrX6cWKaS+VqhhY7cQUMCbO5kloFUw==
"@types/estree@0.0.39":
"@types/estree@*", "@types/estree@0.0.39":
version "0.0.39"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
@ -1894,6 +1901,13 @@
dependencies:
"@types/node" "*"
"@types/tern@*":
version "0.23.3"
resolved "https://registry.yarnpkg.com/@types/tern/-/tern-0.23.3.tgz#4b54538f04a88c9ff79de1f6f94f575a7f339460"
integrity sha512-imDtS4TAoTcXk0g7u4kkWqedB3E4qpjXzCpD2LU5M5NAXHzCDsypyvXSaG7mM8DKYkCRa7tFp4tS/lp/Wo7Q3w==
dependencies:
"@types/estree" "*"
"@types/ua-parser-js@^0.7.31":
version "0.7.32"
resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.32.tgz#8827d451d6702307248073b5d98aa9293d02b5e5"
@ -4041,10 +4055,10 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
codemirror@^5.45.0:
version "5.45.0"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.45.0.tgz#db5ebbb3bf44028c684053f3954d011efcec27ad"
integrity sha512-c19j644usCE8gQaXa0jqn2B/HN9MnB2u6qPIrrhrMkB+QAP42y8G4QnTwuwbVSoUS1jEl7JU9HZMGhCDL0nsAw==
codemirror@^5.49.0:
version "5.49.0"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.49.0.tgz#adedbffcc81091e4a0334bcb96b1ae3b7ada5e3f"
integrity sha512-Hyzr0HToBdZpLBN9dYFO/KlJAsKH37/cXVHPAqa+imml0R92tb9AkmsvjnXL+SluEvjjdfkDgRjc65NG5jnMYA==
collection-map@^1.0.0:
version "1.0.0"