Add getElementConfig to Glance + Add Form UI for updating YAML (#1944)

* Working version

* Working kind of

* Some more changes

* More review changes

* Progress

* Review updates

* Adding new changes

* Remove un-needed code

* Adding Types

* Updating UI Editor a bit

* Updating from missed reviews

* Updates from Reviews

* Yaml is not update each time. Instead stored as LovelaceConfig.

* Update to not pull config from preview but store it each time it changed

* Updating from Reviews

* Try catch fix

* Update hui-dialog-edit-card.ts
This commit is contained in:
Zack Arnett 2018-11-06 04:09:28 -05:00 committed by Paulus Schoutsen
parent cdb2093ea6
commit 935639e5e0
7 changed files with 260 additions and 50 deletions

View File

@ -7,24 +7,23 @@ import {
import { TemplateResult } from "lit-html";
import { classMap } from "lit-html/directives/classMap";
import computeStateDisplay from "../../../common/entity/compute_state_display";
import computeStateName from "../../../common/entity/compute_state_name";
import processConfigEntities from "../common/process-config-entities";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event.js";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../types.js";
import { LovelaceCard, LovelaceConfig, LovelaceCardEditor } from "../types.js";
import { longPress } from "../common/directives/long-press-directive";
import toggleEntity from "../common/entity/toggle-entity";
import computeStateDisplay from "../../../common/entity/compute_state_display.js";
import computeStateName from "../../../common/entity/compute_state_name.js";
import processConfigEntities from "../common/process-config-entities";
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element.js";
import toggleEntity from "../common/entity/toggle-entity.js";
import "../../../components/entity/state-badge";
import "../../../components/ha-card";
import "../../../components/ha-icon";
import { fireEvent } from "../../../common/dom/fire_event";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../types";
import { LovelaceCard, LovelaceConfig } from "../types";
import { longPress } from "../common/directives/long-press-directive";
interface EntityConfig {
export interface EntityConfig {
name: string;
icon: string;
entity: string;
@ -34,7 +33,7 @@ interface EntityConfig {
service_data?: object;
}
interface Config extends LovelaceConfig {
export interface Config extends LovelaceConfig {
show_name?: boolean;
show_state?: boolean;
title?: string;
@ -45,6 +44,11 @@ interface Config extends LovelaceConfig {
export class HuiGlanceCard extends hassLocalizeLitMixin(LitElement)
implements LovelaceCard {
public static async getConfigElement(): Promise<LovelaceCardEditor> {
await import("../editor/hui-glance-card-editor");
return document.createElement("hui-glance-card-editor");
}
public hass?: HomeAssistant;
private _config?: Config;
private _configEntities?: EntityConfig[];

View File

@ -1,5 +1,7 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { fireEvent } from "../../../common/dom/fire_event";
import yaml from "js-yaml";
import { when } from "lit-html/directives/when";
import { TemplateResult } from "lit-html";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-input/paper-textarea";
@ -10,18 +12,26 @@ import "@polymer/paper-dialog/paper-dialog";
import { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
import { HomeAssistant } from "../../../types";
import { getCardConfig, updateCardConfig } from "../common/data";
import { fireEvent } from "../../../common/dom/fire_event";
import "./hui-yaml-editor";
import "./hui-yaml-card-preview";
// This is not a duplicate import, one is for types, one is for element.
// tslint:disable-next-line
import { HuiYAMLCardPreview } from "./hui-yaml-card-preview";
import { LovelaceCardEditor, LovelaceConfig } from "../types";
import { YamlChangedEvent, ConfigValue } from "./types";
const CUSTOM_TYPE_PREFIX = "custom:";
export class HuiDialogEditCard extends LitElement {
protected hass?: HomeAssistant;
private _cardId?: string;
private _cardConfig?: string;
private _originalConfigYaml?: string;
private _configElement?: LovelaceCardEditor | null;
private _reloadLovelace?: () => void;
private _editorToggle?: boolean;
private _configValue?: ConfigValue;
static get properties(): PropertyDeclarations {
return {
@ -29,8 +39,9 @@ export class HuiDialogEditCard extends LitElement {
cardId: {
type: Number,
},
_cardConfig: {},
_dialogClosedCallback: {},
_configElement: {},
_editorToggle: {},
};
}
@ -38,8 +49,10 @@ export class HuiDialogEditCard extends LitElement {
this.hass = hass;
this._cardId = cardId;
this._reloadLovelace = reloadLovelace;
this._cardConfig = "";
this._loadConfig();
this._editorToggle = true;
this._configElement = undefined;
this._configValue = { format: "yaml", value: "" };
this._loadConfig().then(() => this._loadConfigElement());
// Wait till dialog is rendered.
await this.updateComplete;
this._dialog.open();
@ -53,58 +66,147 @@ export class HuiDialogEditCard extends LitElement {
return this.shadowRoot!.querySelector("hui-yaml-card-preview")!;
}
protected render() {
protected render(): TemplateResult {
return html`
<style>
paper-dialog {
width: 650px;
}
.element-editor {
margin-bottom: 16px;
}
</style>
<paper-dialog with-backdrop>
<h2>Card Configuration</h2>
<paper-dialog-scrollable>
<hui-yaml-editor
.yaml="${this._cardConfig}"
@yaml-changed="${this._handleYamlChanged}"
></hui-yaml-editor>
${
this._editorToggle && this._configElement !== null
? html`<div class="element-editor">${when(
this._configElement,
() => this._configElement,
() => html`Loading...`
)}</div>`
: html`
<hui-yaml-editor
.yaml="${this._configValue!.value}"
@yaml-changed="${this._handleYamlChanged}"
></hui-yaml-editor>`
}
<hui-yaml-card-preview
.hass="${this.hass}"
.yaml="${this._cardConfig}"
.value="${this._configValue}"
></hui-yaml-card-preview>
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
<paper-button @click="${this._closeDialog}">Cancel</paper-button>
<paper-button @click="${this._updateConfig}">Save</paper-button>
<paper-button
@click="${this._toggleEditor}"
>Toggle Editor</paper-button>
<paper-button
@click="${this._closeDialog}"
>Cancel</paper-button>
<paper-button
@click="${this._updateConfigInBackend}"'
>Save</paper-button>
</div>
</paper-dialog>
`;
}
private _handleYamlChanged(ev) {
this._previewEl.yaml = ev.detail.yaml;
private _handleYamlChanged(ev: YamlChangedEvent): void {
this._configValue = { format: "yaml", value: ev.detail.yaml };
this._updatePreview(this._configValue);
}
private _closeDialog() {
private _handleJSConfigChanged(value: LovelaceConfig): void {
this._configElement!.setConfig(value);
this._configValue = { format: "js", value };
this._updatePreview(this._configValue);
}
private _updatePreview(value: ConfigValue) {
if (!this._previewEl) {
return;
}
this._previewEl.value = value;
}
private _closeDialog(): void {
this._dialog.close();
}
private async _loadConfig() {
this._cardConfig = await getCardConfig(this.hass!, this._cardId!);
await this.updateComplete;
// This will center the dialog with the updated config
private _toggleEditor(): void {
if (this._editorToggle && this._configValue!.format === "js") {
this._configValue = {
format: "yaml",
value: yaml.safeDump(this._configValue!.value),
};
} else if (this._configElement && this._configValue!.format === "yaml") {
this._configValue = {
format: "js",
value: yaml.safeLoad(this._configValue!.value),
};
this._configElement.setConfig(this._configValue!.value as LovelaceConfig);
}
this._editorToggle = !this._editorToggle;
}
private async _loadConfig(): Promise<void> {
const cardConfig = await getCardConfig(this.hass!, this._cardId!);
this._configValue = {
format: "yaml",
value: cardConfig,
};
this._originalConfigYaml = cardConfig;
}
private async _loadConfigElement(): Promise<void> {
const conf = yaml.safeLoad(this._configValue!.value);
const tag = conf.type.startsWith(CUSTOM_TYPE_PREFIX)
? conf.type.substr(CUSTOM_TYPE_PREFIX.length)
: `hui-${conf.type}-card`;
const elClass = customElements.get(tag);
let configElement;
try {
configElement = await elClass.getConfigElement();
} catch (err) {
this._configElement = null;
return;
}
configElement.setConfig(conf);
configElement.hass = this.hass;
configElement.addEventListener("config-changed", (ev) =>
this._handleJSConfigChanged(ev.detail.config)
);
this._configValue = { format: "js", value: conf };
this._configElement = configElement;
// This will center the dialog with the updated config Element
fireEvent(this._dialog, "iron-resize");
}
private async _updateConfig() {
const newCardConfig = this.shadowRoot!.querySelector("hui-yaml-editor")!
.yaml;
private async _updateConfigInBackend(): Promise<void> {
if (this._configValue!.format === "js") {
this._configValue = {
format: "yaml",
value: yaml.safeDump(this._configValue!.value),
};
}
if (this._cardConfig === newCardConfig) {
if (this._configValue!.value === this._originalConfigYaml) {
this._dialog.close();
return;
}
try {
await updateCardConfig(this.hass!, this._cardId!, newCardConfig);
await updateCardConfig(
this.hass!,
this._cardId!,
this._configValue!.value
);
this._dialog.close();
this._reloadLovelace!();
} catch (err) {

View File

@ -0,0 +1,79 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { TemplateResult } from "lit-html";
import "@polymer/paper-checkbox/paper-checkbox.js";
import { hassLocalizeLitMixin } from "../../../mixins/lit-localize-mixin";
import { HomeAssistant } from "../../../types.js";
import { LovelaceCardEditor } from "../types.js";
import { fireEvent } from "../../../common/dom/fire_event.js";
import { Config } from "../cards/hui-glance-card";
import "../../../components/entity/state-badge.js";
import "../../../components/entity/ha-entity-picker";
import "../../../components/ha-card.js";
import "../../../components/ha-icon.js";
export class HuiGlanceCardEditor extends hassLocalizeLitMixin(LitElement)
implements LovelaceCardEditor {
public hass?: HomeAssistant;
private _config?: Config;
static get properties(): PropertyDeclarations {
return {
hass: {},
_config: {},
};
}
public setConfig(config: Config): void {
this._config = { type: "glance", ...config };
}
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<paper-input
label="Title"
value="${this._config!.title}"
.configValue=${"title"}
@value-changed="${this._valueChanged}"
></paper-input><br>
<paper-checkbox
?checked="${this._config!.show_name !== false}"
.configValue=${"show_name"}
@change="${this._valueChanged}"
>Show Entity's Name?</paper-checkbox><br><br>
<paper-checkbox
?checked="${this._config!.show_state !== false}"
.configValue=${"show_state"}
@change="${this._valueChanged}"
>Show Entity's State Text?</paper-checkbox><br>
`;
}
private _valueChanged(ev: MouseEvent): void {
if (!this._config || !this.hass) {
return;
}
const target = ev.target! as any;
const newValue =
target.checked !== undefined ? target.checked : target.value;
fireEvent(this, "config-changed", {
config: { ...this._config, [target.configValue]: newValue },
});
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-glance-card-editor": HuiGlanceCardEditor;
}
}
customElements.define("hui-glance-card-editor", HuiGlanceCardEditor);

View File

@ -6,6 +6,7 @@ import createCardElement from "../common/create-card-element";
import createErrorCardConfig from "../common/create-error-card-config";
import { HomeAssistant } from "../../../types";
import { LovelaceCard } from "../types";
import { ConfigValue } from "./types";
export class HuiYAMLCardPreview extends HTMLElement {
private _hass?: HomeAssistant;
@ -17,20 +18,24 @@ export class HuiYAMLCardPreview extends HTMLElement {
}
}
set yaml(value: string) {
set value(configValue: ConfigValue) {
if (this.lastChild) {
this.removeChild(this.lastChild);
}
if (value === "") {
if (!configValue.value || configValue.value === "") {
return;
}
let conf;
try {
conf = yaml.safeLoad(value);
} catch (err) {
conf = createErrorCardConfig(`Invalid YAML: ${err.message}`, undefined);
if (configValue.format === "yaml") {
try {
conf = yaml.safeLoad(configValue.value);
} catch (err) {
conf = createErrorCardConfig(`Invalid YAML: ${err.message}`, undefined);
}
} else {
conf = configValue.value;
}
const element = createCardElement(conf);

View File

@ -1,8 +1,9 @@
import { html, LitElement, PropertyDeclarations } from "@polymer/lit-element";
import { fireEvent } from "../../../common/dom/fire_event";
import { TemplateResult } from "lit-html";
import "@polymer/paper-input/paper-textarea";
import { fireEvent } from "../../../common/dom/fire_event";
export class HuiYAMLEditor extends LitElement {
public yaml?: string;
@ -12,7 +13,7 @@ export class HuiYAMLEditor extends LitElement {
};
}
protected render() {
protected render(): TemplateResult {
return html`
<style>
paper-textarea {
@ -20,15 +21,17 @@ export class HuiYAMLEditor extends LitElement {
}
</style>
<paper-textarea
max-rows=10
value="${this.yaml}"
@value-changed="${this._valueChanged}"
></paper-textarea>
`;
}
private _valueChanged(ev) {
this.yaml = ev.target.value;
fireEvent(this, "yaml-changed", { yaml: ev.target.value });
private _valueChanged(ev: MouseEvent): void {
const target = ev.target! as any;
this.yaml = target.value;
fireEvent(this, "yaml-changed", { yaml: target.value });
}
}

View File

@ -0,0 +1,12 @@
import { LovelaceConfig } from "../types";
export interface YamlChangedEvent extends Event {
detail: {
yaml: string;
};
}
export interface ConfigValue {
format: "js" | "yaml";
value: string | LovelaceConfig;
}

View File

@ -9,3 +9,8 @@ export interface LovelaceCard extends HTMLElement {
getCardSize(): number;
setConfig(config: LovelaceConfig): void;
}
export interface LovelaceCardEditor extends HTMLElement {
hass?: HomeAssistant;
setConfig(config: LovelaceConfig): void;
}