From d9d92c87668805c49d5831a45ecc9f8b7c124664 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Dec 2018 11:38:18 +0100 Subject: [PATCH 1/4] Simplify Lovelace mode (#2243) --- src/panels/lovelace/ha-panel-lovelace.ts | 22 +++++++++------------- src/panels/lovelace/types.ts | 3 +-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index 6bc94a67c0..f6edee5e0b 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -10,6 +10,7 @@ import { LitElement, html, PropertyValues } from "@polymer/lit-element"; import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; import { TemplateResult } from "lit-html"; import { showSaveDialog } from "./editor/show-save-config-dialog"; +import { generateLovelaceConfig } from "./common/generate-lovelace-config"; interface LovelacePanelConfig { mode: "yaml" | "storage"; @@ -121,12 +122,11 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) { } private async _fetchConfig(force) { - let conf; - let gen: boolean; + let conf: LovelaceConfig; + let confMode: Lovelace["mode"] = this.panel!.config.mode; try { conf = await fetchConfig(this.hass!, force); - gen = false; } catch (err) { if (err.code !== "config_not_found") { // tslint:disable-next-line @@ -135,21 +135,17 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) { this._errorMsg = err.message; return; } - const { - generateLovelaceConfig, - } = await import("./common/generate-lovelace-config"); conf = generateLovelaceConfig(this.hass!, this.localize); - gen = true; + confMode = "generated"; } this._state = "loaded"; this.lovelace = { config: conf, - autoGen: gen, editMode: this.lovelace ? this.lovelace.editMode : false, - mode: this.panel!.config.mode, + mode: confMode, setEditMode: (editMode: boolean) => { - if (!editMode || !this.lovelace!.autoGen) { + if (!editMode || this.lovelace!.mode !== "generated") { this._updateLovelace({ editMode }); return; } @@ -158,16 +154,16 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) { }); }, saveConfig: async (newConfig: LovelaceConfig): Promise => { - const { config, autoGen } = this.lovelace!; + const { config, mode } = this.lovelace!; try { // Optimistic update - this._updateLovelace({ config: newConfig, autoGen: false }); + this._updateLovelace({ config: newConfig, mode: "storage" }); await saveConfig(this.hass!, newConfig); } catch (err) { // tslint:disable-next-line console.error(err); // Rollback the optimistic update - this._updateLovelace({ config, autoGen }); + this._updateLovelace({ config, mode }); throw err; } }, diff --git a/src/panels/lovelace/types.ts b/src/panels/lovelace/types.ts index 52a7a6bb31..33d7cfec6d 100644 --- a/src/panels/lovelace/types.ts +++ b/src/panels/lovelace/types.ts @@ -4,8 +4,7 @@ import { LovelaceCardConfig, LovelaceConfig } from "../../data/lovelace"; export interface Lovelace { config: LovelaceConfig; editMode: boolean; - autoGen: boolean; - mode: "yaml" | "storage"; + mode: "generated" | "yaml" | "storage"; setEditMode: (editMode: boolean) => void; saveConfig: (newConfig: LovelaceConfig) => Promise; } From d57bcc27013d4b46d338ba99b9c89874bbe04b5f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 10 Dec 2018 11:40:34 +0100 Subject: [PATCH 2/4] Disable edit in yaml mode (#2244) --- src/panels/lovelace/hui-root.js | 57 +++++++++++++-------------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/src/panels/lovelace/hui-root.js b/src/panels/lovelace/hui-root.js index 614e833de0..537359b4ee 100644 --- a/src/panels/lovelace/hui-root.js +++ b/src/panels/lovelace/hui-root.js @@ -124,7 +124,9 @@ class HUIRoot extends NavigateMixin( > - Refresh + Unused entities [[localize("ui.panel.lovelace.editor.configure_ui")]] (alpha) Help @@ -174,55 +176,32 @@ class HUIRoot extends NavigateMixin( return { narrow: Boolean, showMenu: Boolean, - hass: { - type: Object, - observer: "_hassChanged", - }, + hass: { type: Object, observer: "_hassChanged" }, config: { type: Object, computed: "_computeConfig(lovelace)", observer: "_configChanged", }, - lovelace: { - type: Object, - }, - columns: { - type: Number, - observer: "_columnsChanged", - }, - - _curView: { - type: Number, - value: 0, - }, - - route: { - type: Object, - observer: "_routeChanged", - }, - - notificationsOpen: { - type: Boolean, - value: false, - }, - - _persistentNotifications: { - type: Array, - value: [], - }, - + lovelace: { type: Object }, + columns: { type: Number, observer: "_columnsChanged" }, + _curView: { type: Number, value: 0 }, + route: { type: Object, observer: "_routeChanged" }, + notificationsOpen: { type: Boolean, value: false }, + _persistentNotifications: { type: Array, value: [] }, _notifications: { type: Array, computed: "_updateNotifications(hass.states, _persistentNotifications)", }, - + _yamlMode: { + type: Boolean, + computed: "_computeYamlMode(lovelace)", + }, _editMode: { type: Boolean, value: false, computed: "_computeEditMode(lovelace)", observer: "_editModeChanged", }, - routeData: Object, }; } @@ -309,6 +288,10 @@ class HUIRoot extends NavigateMixin( } _editModeEnable() { + if (this._yamlMode) { + window.alert("The edit UI is not available when in YAML mode."); + return; + } this.lovelace.setEditMode(true); if (this.config.views.length < 2) { this.$.view.classList.remove("tabs-hidden"); @@ -445,6 +428,10 @@ class HUIRoot extends NavigateMixin( return lovelace ? lovelace.config : null; } + _computeYamlMode(lovelace) { + return lovelace ? lovelace.mode === "yaml" : false; + } + _computeEditMode(lovelace) { return lovelace ? lovelace.editMode : false; } From 0899d429674de6c3b7e08586b8e26a0f44d43be5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Dec 2018 12:48:05 +0100 Subject: [PATCH 3/4] Add raw config editor for storage mode (#2246) * Add raw config editor for storage mode * Lint * Allow inserting spaces by pressing tab --- src/panels/lovelace/ha-panel-lovelace.ts | 29 ++++- src/panels/lovelace/hui-editor.ts | 128 +++++++++++++++++++++++ src/panels/lovelace/hui-root.js | 19 +++- src/panels/lovelace/types.ts | 1 + 4 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 src/panels/lovelace/hui-editor.ts diff --git a/src/panels/lovelace/ha-panel-lovelace.ts b/src/panels/lovelace/ha-panel-lovelace.ts index f6edee5e0b..07445a9291 100644 --- a/src/panels/lovelace/ha-panel-lovelace.ts +++ b/src/panels/lovelace/ha-panel-lovelace.ts @@ -16,6 +16,8 @@ interface LovelacePanelConfig { mode: "yaml" | "storage"; } +let editorLoaded = false; + class LovelacePanel extends hassLocalizeLitMixin(LitElement) { public panel?: PanelInfo; public hass?: HomeAssistant; @@ -23,7 +25,7 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) { public showMenu?: boolean; public route?: object; private _columns?: number; - private _state?: "loading" | "loaded" | "error"; + private _state?: "loading" | "loaded" | "error" | "yaml-editor"; private _errorMsg?: string; private lovelace?: Lovelace; private mqls?: MediaQueryList[]; @@ -42,6 +44,11 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) { }; } + constructor() { + super(); + this._closeEditor = this._closeEditor.bind(this); + } + public render(): TemplateResult { const state = this._state!; @@ -80,6 +87,15 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) { `; } + if (state === "yaml-editor") { + return html` + + `; + } + return html` cols + Number(mql.matches), @@ -144,6 +164,13 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) { config: conf, editMode: this.lovelace ? this.lovelace.editMode : false, mode: confMode, + enableFullEditMode: () => { + if (!editorLoaded) { + editorLoaded = true; + import("./hui-editor"); + } + this._state = "yaml-editor"; + }, setEditMode: (editMode: boolean) => { if (!editMode || this.lovelace!.mode !== "generated") { this._updateLovelace({ editMode }); diff --git a/src/panels/lovelace/hui-editor.ts b/src/panels/lovelace/hui-editor.ts new file mode 100644 index 0000000000..b1e7ace684 --- /dev/null +++ b/src/panels/lovelace/hui-editor.ts @@ -0,0 +1,128 @@ +import { LitElement, html } from "@polymer/lit-element"; +import { TemplateResult } from "lit-html"; +import yaml from "js-yaml"; + +import "@polymer/app-layout/app-header-layout/app-header-layout"; +import "@polymer/app-layout/app-header/app-header"; +import "@polymer/app-layout/app-toolbar/app-toolbar"; +import "@polymer/paper-button/paper-button"; +import "@polymer/paper-icon-button/paper-icon-button"; + +import { Lovelace } from "./types"; +import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; + +const TAB_INSERT = " "; + +class LovelaceFullConfigEditor extends hassLocalizeLitMixin(LitElement) { + public lovelace?: Lovelace; + public closeEditor?: () => void; + private _haStyle?: DocumentFragment; + + static get properties() { + return { + lovelace: {}, + }; + } + + public render(): TemplateResult { + return html` + ${this.renderStyle()} + + + + +
Edit Config
+ Save +
+
+
+ +
+
+ `; + } + + protected firstUpdated() { + const textArea = this.textArea; + textArea.value = yaml.safeDump(this.lovelace!.config); + textArea.addEventListener("keydown", (e) => { + if (e.keyCode !== 9) { + return; + } + + e.preventDefault(); + + // tab was pressed, get caret position/selection + const val = textArea.value; + const start = textArea.selectionStart; + const end = textArea.selectionEnd; + + // set textarea value to: text before caret + tab + text after caret + textArea.value = + val.substring(0, start) + TAB_INSERT + val.substring(end); + + // put caret at right position again + textArea.selectionStart = textArea.selectionEnd = + start + TAB_INSERT.length; + }); + } + + protected renderStyle() { + if (!this._haStyle) { + this._haStyle = document.importNode( + (document.getElementById("ha-style")! + .children[0] as HTMLTemplateElement).content, + true + ); + } + + return html` + ${this._haStyle} + + `; + } + + private _handleSave() { + let value; + try { + value = yaml.safeLoad(this.textArea.value); + } catch (err) { + alert(`Unable to parse YAML: ${err}`); + return; + } + + this.lovelace!.saveConfig(value); + } + + private get textArea(): HTMLTextAreaElement { + return this.shadowRoot!.querySelector("textarea")!; + } +} + +customElements.define("hui-editor", LovelaceFullConfigEditor); diff --git a/src/panels/lovelace/hui-root.js b/src/panels/lovelace/hui-root.js index 537359b4ee..415855e555 100644 --- a/src/panels/lovelace/hui-root.js +++ b/src/panels/lovelace/hui-root.js @@ -124,11 +124,14 @@ class HUIRoot extends NavigateMixin( > -