mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-01 05:27:46 +00:00
commit
7063ced7fd
2
setup.py
2
setup.py
@ -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",
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
128
src/panels/lovelace/hui-editor.ts
Normal file
128
src/panels/lovelace/hui-editor.ts
Normal 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);
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user