Merge pull request #2247 from home-assistant/dev

20181210.1
This commit is contained in:
Paulus Schoutsen 2018-12-10 12:49:26 +01:00 committed by GitHub
commit 7063ced7fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 206 additions and 53 deletions

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name="home-assistant-frontend", name="home-assistant-frontend",
version="20181210.0", version="20181210.1",
description="The Home Assistant frontend", description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer", url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors", author="The Home Assistant Authors",

View File

@ -10,11 +10,14 @@ import { LitElement, html, PropertyValues } from "@polymer/lit-element";
import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin"; import { hassLocalizeLitMixin } from "../../mixins/lit-localize-mixin";
import { TemplateResult } from "lit-html"; import { TemplateResult } from "lit-html";
import { showSaveDialog } from "./editor/show-save-config-dialog"; import { showSaveDialog } from "./editor/show-save-config-dialog";
import { generateLovelaceConfig } from "./common/generate-lovelace-config";
interface LovelacePanelConfig { interface LovelacePanelConfig {
mode: "yaml" | "storage"; mode: "yaml" | "storage";
} }
let editorLoaded = false;
class LovelacePanel extends hassLocalizeLitMixin(LitElement) { class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
public panel?: PanelInfo<LovelacePanelConfig>; public panel?: PanelInfo<LovelacePanelConfig>;
public hass?: HomeAssistant; public hass?: HomeAssistant;
@ -22,7 +25,7 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
public showMenu?: boolean; public showMenu?: boolean;
public route?: object; public route?: object;
private _columns?: number; private _columns?: number;
private _state?: "loading" | "loaded" | "error"; private _state?: "loading" | "loaded" | "error" | "yaml-editor";
private _errorMsg?: string; private _errorMsg?: string;
private lovelace?: Lovelace; private lovelace?: Lovelace;
private mqls?: MediaQueryList[]; private mqls?: MediaQueryList[];
@ -41,6 +44,11 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
}; };
} }
constructor() {
super();
this._closeEditor = this._closeEditor.bind(this);
}
public render(): TemplateResult { public render(): TemplateResult {
const state = this._state!; const state = this._state!;
@ -79,6 +87,15 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
`; `;
} }
if (state === "yaml-editor") {
return html`
<hui-editor
.lovelace="${this.lovelace}"
.closeEditor="${this._closeEditor}"
></hui-editor>
`;
}
return html` return html`
<hass-loading-screen <hass-loading-screen
.narrow="${this.narrow}" .narrow="${this.narrow}"
@ -104,6 +121,10 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
this._updateColumns(); this._updateColumns();
} }
private _closeEditor() {
this._state = "loaded";
}
private _updateColumns() { private _updateColumns() {
const matchColumns = this.mqls!.reduce( const matchColumns = this.mqls!.reduce(
(cols, mql) => cols + Number(mql.matches), (cols, mql) => cols + Number(mql.matches),
@ -121,12 +142,11 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
} }
private async _fetchConfig(force) { private async _fetchConfig(force) {
let conf; let conf: LovelaceConfig;
let gen: boolean; let confMode: Lovelace["mode"] = this.panel!.config.mode;
try { try {
conf = await fetchConfig(this.hass!, force); conf = await fetchConfig(this.hass!, force);
gen = false;
} catch (err) { } catch (err) {
if (err.code !== "config_not_found") { if (err.code !== "config_not_found") {
// tslint:disable-next-line // tslint:disable-next-line
@ -135,21 +155,24 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
this._errorMsg = err.message; this._errorMsg = err.message;
return; return;
} }
const {
generateLovelaceConfig,
} = await import("./common/generate-lovelace-config");
conf = generateLovelaceConfig(this.hass!, this.localize); conf = generateLovelaceConfig(this.hass!, this.localize);
gen = true; confMode = "generated";
} }
this._state = "loaded"; this._state = "loaded";
this.lovelace = { this.lovelace = {
config: conf, config: conf,
autoGen: gen,
editMode: this.lovelace ? this.lovelace.editMode : false, editMode: this.lovelace ? this.lovelace.editMode : false,
mode: this.panel!.config.mode, mode: confMode,
enableFullEditMode: () => {
if (!editorLoaded) {
editorLoaded = true;
import(/* webpackChunkName: "lovelace-yaml-editor" */ "./hui-editor");
}
this._state = "yaml-editor";
},
setEditMode: (editMode: boolean) => { setEditMode: (editMode: boolean) => {
if (!editMode || !this.lovelace!.autoGen) { if (!editMode || this.lovelace!.mode !== "generated") {
this._updateLovelace({ editMode }); this._updateLovelace({ editMode });
return; return;
} }
@ -158,16 +181,16 @@ class LovelacePanel extends hassLocalizeLitMixin(LitElement) {
}); });
}, },
saveConfig: async (newConfig: LovelaceConfig): Promise<void> => { saveConfig: async (newConfig: LovelaceConfig): Promise<void> => {
const { config, autoGen } = this.lovelace!; const { config, mode } = this.lovelace!;
try { try {
// Optimistic update // Optimistic update
this._updateLovelace({ config: newConfig, autoGen: false }); this._updateLovelace({ config: newConfig, mode: "storage" });
await saveConfig(this.hass!, newConfig); await saveConfig(this.hass!, newConfig);
} catch (err) { } catch (err) {
// tslint:disable-next-line // tslint:disable-next-line
console.error(err); console.error(err);
// Rollback the optimistic update // Rollback the optimistic update
this._updateLovelace({ config, autoGen }); this._updateLovelace({ config, mode });
throw err; throw err;
} }
}, },

View File

@ -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()}
<app-header-layout>
<app-header>
<app-toolbar>
<paper-icon-button
icon="hass:close"
@click="${this.closeEditor}"
></paper-icon-button>
<div main-title>Edit Config</div>
<paper-button @click="${this._handleSave}">Save</paper-button>
</app-toolbar>
</app-header>
<div class="content">
<textarea
autocomplete="off"
autocorrect="off"
autocapitalize="off"
spellcheck="false"
></textarea>
</div>
</app-header-layout>
`;
}
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}
<style>
app-header-layout {
height: 100vh;
}
.content {
height: calc(100vh - 64px);
}
textarea {
height: calc(100% - 16px);
width: 100%;
border: 0;
outline: 0;
font-size: 12pt;
font-family: "Courier New", Courier, monospace;
padding: 8px;
}
</style>
`;
}
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);

View File

@ -124,9 +124,14 @@ class HUIRoot extends NavigateMixin(
> >
<paper-icon-button icon="hass:dots-vertical" slot="dropdown-trigger"></paper-icon-button> <paper-icon-button icon="hass:dots-vertical" slot="dropdown-trigger"></paper-icon-button>
<paper-listbox on-iron-select="_deselect" slot="dropdown-content"> <paper-listbox on-iron-select="_deselect" slot="dropdown-content">
<paper-item on-click="_handleRefresh">Refresh</paper-item> <template is='dom-if' if="[[_yamlMode]]">
<paper-item on-click="_handleRefresh">Refresh</paper-item>
</template>
<paper-item on-click="_handleUnusedEntities">Unused entities</paper-item> <paper-item on-click="_handleUnusedEntities">Unused entities</paper-item>
<paper-item on-click="_editModeEnable">[[localize("ui.panel.lovelace.editor.configure_ui")]] (alpha)</paper-item> <paper-item on-click="_editModeEnable">[[localize("ui.panel.lovelace.editor.configure_ui")]]</paper-item>
<template is='dom-if' if="[[_storageMode]]">
<paper-item on-click="_handleFullEditor">Raw config editor</paper-item>
</template>
<paper-item on-click="_handleHelp">Help</paper-item> <paper-item on-click="_handleHelp">Help</paper-item>
</paper-listbox> </paper-listbox>
</paper-menu-button> </paper-menu-button>
@ -174,55 +179,36 @@ class HUIRoot extends NavigateMixin(
return { return {
narrow: Boolean, narrow: Boolean,
showMenu: Boolean, showMenu: Boolean,
hass: { hass: { type: Object, observer: "_hassChanged" },
type: Object,
observer: "_hassChanged",
},
config: { config: {
type: Object, type: Object,
computed: "_computeConfig(lovelace)", computed: "_computeConfig(lovelace)",
observer: "_configChanged", observer: "_configChanged",
}, },
lovelace: { lovelace: { type: Object },
type: Object, columns: { type: Number, observer: "_columnsChanged" },
}, _curView: { type: Number, value: 0 },
columns: { route: { type: Object, observer: "_routeChanged" },
type: Number, notificationsOpen: { type: Boolean, value: false },
observer: "_columnsChanged", _persistentNotifications: { type: Array, value: [] },
},
_curView: {
type: Number,
value: 0,
},
route: {
type: Object,
observer: "_routeChanged",
},
notificationsOpen: {
type: Boolean,
value: false,
},
_persistentNotifications: {
type: Array,
value: [],
},
_notifications: { _notifications: {
type: Array, type: Array,
computed: "_updateNotifications(hass.states, _persistentNotifications)", computed: "_updateNotifications(hass.states, _persistentNotifications)",
}, },
_yamlMode: {
type: Boolean,
computed: "_computeYamlMode(lovelace)",
},
_storageMode: {
type: Boolean,
computed: "_computeStorageMode(lovelace)",
},
_editMode: { _editMode: {
type: Boolean, type: Boolean,
value: false, value: false,
computed: "_computeEditMode(lovelace)", computed: "_computeEditMode(lovelace)",
observer: "_editModeChanged", observer: "_editModeChanged",
}, },
routeData: Object, routeData: Object,
}; };
} }
@ -308,7 +294,15 @@ class HUIRoot extends NavigateMixin(
window.open("https://www.home-assistant.io/lovelace/", "_blank"); window.open("https://www.home-assistant.io/lovelace/", "_blank");
} }
_handleFullEditor() {
this.lovelace.enableFullEditMode();
}
_editModeEnable() { _editModeEnable() {
if (this._yamlMode) {
window.alert("The edit UI is not available when in YAML mode.");
return;
}
this.lovelace.setEditMode(true); this.lovelace.setEditMode(true);
if (this.config.views.length < 2) { if (this.config.views.length < 2) {
this.$.view.classList.remove("tabs-hidden"); this.$.view.classList.remove("tabs-hidden");
@ -445,6 +439,14 @@ class HUIRoot extends NavigateMixin(
return lovelace ? lovelace.config : null; return lovelace ? lovelace.config : null;
} }
_computeYamlMode(lovelace) {
return lovelace ? lovelace.mode === "yaml" : false;
}
_computeStorageMode(lovelace) {
return lovelace ? lovelace.mode === "storage" : false;
}
_computeEditMode(lovelace) { _computeEditMode(lovelace) {
return lovelace ? lovelace.editMode : false; return lovelace ? lovelace.editMode : false;
} }

View File

@ -4,8 +4,8 @@ import { LovelaceCardConfig, LovelaceConfig } from "../../data/lovelace";
export interface Lovelace { export interface Lovelace {
config: LovelaceConfig; config: LovelaceConfig;
editMode: boolean; editMode: boolean;
autoGen: boolean; mode: "generated" | "yaml" | "storage";
mode: "yaml" | "storage"; enableFullEditMode: () => void;
setEditMode: (editMode: boolean) => void; setEditMode: (editMode: boolean) => void;
saveConfig: (newConfig: LovelaceConfig) => Promise<void>; saveConfig: (newConfig: LovelaceConfig) => Promise<void>;
} }