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:
parent
4728c12225
commit
12840231be
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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!",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -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;
|
24
yarn.lock
24
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue