From 6dea7b005c26689e953def87bb1537d37148785c Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Sun, 17 Jan 2021 08:48:20 +0100 Subject: [PATCH 1/5] Convert customize to LitElement + option to directly jump to entity --- .../config/customize/ha-config-customize.js | 111 --------- .../config/customize/ha-config-customize.ts | 95 ++++++++ .../config/customize/ha-form-customize.js | 40 ++-- src/panels/config/ha-entity-config.js | 222 ------------------ src/panels/config/ha-entity-config.ts | 134 +++++++++++ src/translations/en.json | 3 +- 6 files changed, 251 insertions(+), 354 deletions(-) delete mode 100644 src/panels/config/customize/ha-config-customize.js create mode 100644 src/panels/config/customize/ha-config-customize.ts delete mode 100644 src/panels/config/ha-entity-config.js create mode 100644 src/panels/config/ha-entity-config.ts diff --git a/src/panels/config/customize/ha-config-customize.js b/src/panels/config/customize/ha-config-customize.js deleted file mode 100644 index d9fa1d6e4356..000000000000 --- a/src/panels/config/customize/ha-config-customize.js +++ /dev/null @@ -1,111 +0,0 @@ -import { html } from "@polymer/polymer/lib/utils/html-tag"; -/* eslint-plugin-disable lit */ -import { PolymerElement } from "@polymer/polymer/polymer-element"; -import { computeStateDomain } from "../../../common/entity/compute_state_domain"; -import { computeStateName } from "../../../common/entity/compute_state_name"; -import { sortStatesByName } from "../../../common/entity/states_sort_by_name"; -import "../../../layouts/hass-tabs-subpage"; -import LocalizeMixin from "../../../mixins/localize-mixin"; -import "../../../styles/polymer-ha-style"; -import { documentationUrl } from "../../../util/documentation-url"; -import "../ha-config-section"; -import "../ha-entity-config"; -import { configSections } from "../ha-panel-config"; -import "./ha-form-customize"; - -/* - * @appliesMixin LocalizeMixin - */ -class HaConfigCustomize extends LocalizeMixin(PolymerElement) { - static get template() { - return html` - - -
- - - [[localize('ui.panel.config.customize.picker.header')]] - - - [[localize('ui.panel.config.customize.picker.introduction')]] -
- - [[localize("ui.panel.config.customize.picker.documentation")]] - -
- - -
-
-
- `; - } - - static get properties() { - return { - hass: Object, - isWide: Boolean, - narrow: Boolean, - route: Object, - showAdvanced: Boolean, - entities: { - type: Array, - computed: "computeEntities(hass)", - }, - - entityConfig: { - type: Object, - value: { - component: "ha-form-customize", - computeSelectCaption: (stateObj) => - computeStateName(stateObj) + - " (" + - computeStateDomain(stateObj) + - ")", - }, - }, - }; - } - - computeClasses(isWide) { - return isWide ? "content" : "content narrow"; - } - - _backTapped() { - history.back(); - } - - _computeTabs() { - return configSections.advanced; - } - - computeEntities(hass) { - return Object.keys(hass.states) - .map((key) => hass.states[key]) - .sort(sortStatesByName); - } - - _computeDocumentationUrl(hass) { - return documentationUrl( - hass, - "/docs/configuration/customizing-devices/#customization-using-the-ui" - ); - } -} -customElements.define("ha-config-customize", HaConfigCustomize); diff --git a/src/panels/config/customize/ha-config-customize.ts b/src/panels/config/customize/ha-config-customize.ts new file mode 100644 index 000000000000..2d0e91823203 --- /dev/null +++ b/src/panels/config/customize/ha-config-customize.ts @@ -0,0 +1,95 @@ +import { + css, + CSSResult, + html, + LitElement, + property, + TemplateResult, +} from "lit-element"; +import "../../../components/ha-card"; +import "../../../components/ha-fab"; +import "../../../components/ha-svg-icon"; +import "../../../layouts/hass-loading-screen"; +import "../../../layouts/hass-tabs-subpage"; +import "../../../styles/polymer-ha-style"; +import { HomeAssistant, Route } from "../../../types"; +import { documentationUrl } from "../../../util/documentation-url"; +import "../ha-config-section"; +import "../ha-entity-config"; +import { configSections } from "../ha-panel-config"; +import "./ha-form-customize"; + +class HaConfigCustomize extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public isWide?: boolean; + + @property() public narrow?: boolean; + + @property() public route!: Route; + + private _selectedEntityId = ""; + + protected render(): TemplateResult { + return html` + + + + + ${this.hass.localize("ui.panel.config.customize.picker.header")} + + + ${this.hass.localize( + "ui.panel.config.customize.picker.introduction" + )} +
+ + ${this.hass.localize( + "ui.panel.config.customize.picker.documentation" + )} + +
+ + +
+ +
+ `; + } + + protected firstUpdated(changedProps) { + super.firstUpdated(changedProps); + + if (!this.route.path.includes("/edit/")) { + return; + } + const routeSegments = this.route.path.split("/edit/"); + this._selectedEntityId = + routeSegments.length > 1 ? routeSegments[1] : undefined; + } + + static get styles(): CSSResult { + return css` + a { + color: var(--primary-color); + } + `; + } +} +customElements.define("ha-config-customize", HaConfigCustomize); diff --git a/src/panels/config/customize/ha-form-customize.js b/src/panels/config/customize/ha-form-customize.js index 4df81153df1d..5e22d6b5d9ca 100644 --- a/src/panels/config/customize/ha-form-customize.js +++ b/src/panels/config/customize/ha-form-customize.js @@ -12,7 +12,7 @@ import hassAttributeUtil from "../../../util/hass-attributes-util"; import "../ha-form-style"; import "./ha-form-customize-attributes"; -class HaFormCustomize extends LocalizeMixin(PolymerElement) { +export class HaFormCustomize extends LocalizeMixin(PolymerElement) { static get template() { return html` - -
-
- - - - - -
- -
- - -
-
-
-
- SAVE - -
-
- `; - } - - static get properties() { - return { - hass: { - type: Object, - observer: "hassChanged", - }, - - label: { - type: String, - value: "Device", - }, - - entities: { - type: Array, - observer: "entitiesChanged", - }, - - allowDelete: { - type: Boolean, - value: false, - }, - - selectedEntity: { - type: Number, - value: -1, - observer: "entityChanged", - }, - - formState: { - type: String, - // no-devices, loading, saving, editing - value: "no-devices", - }, - - config: { - type: Object, - }, - }; - } - - connectedCallback() { - super.connectedCallback(); - this.formEl = document.createElement(this.config.component); - this.formEl.hass = this.hass; - this.$.form.appendChild(this.formEl); - this.entityChanged(this.selectedEntity); - } - - computeSelectCaption(stateObj) { - return this.config.computeSelectCaption - ? this.config.computeSelectCaption(stateObj) - : computeStateName(stateObj); - } - - computeShowNoDevices(formState) { - return formState === "no-devices"; - } - - computeShowSpinner(formState) { - return formState === "loading" || formState === "saving"; - } - - computeShowPlaceholder(formState) { - return formState !== "editing"; - } - - computeShowForm(formState) { - return formState === "editing"; - } - - hassChanged(hass) { - if (this.formEl) { - this.formEl.hass = hass; - } - } - - entitiesChanged(entities, oldEntities) { - if (entities.length === 0) { - this.formState = "no-devices"; - return; - } - if (!oldEntities) { - this.selectedEntity = 0; - return; - } - - const oldEntityId = oldEntities[this.selectedEntity].entity_id; - - const newIndex = entities.findIndex(function (ent) { - return ent.entity_id === oldEntityId; - }); - - if (newIndex === -1) { - this.selectedEntity = 0; - } else if (newIndex !== this.selectedEntity) { - // Entity moved index - this.selectedEntity = newIndex; - } - } - - entityChanged(index) { - if (!this.entities || !this.formEl) return; - const entity = this.entities[index]; - if (!entity) return; - - this.formState = "loading"; - // eslint-disable-next-line @typescript-eslint/no-this-alias - const el = this; - this.formEl.loadEntity(entity).then(function () { - el.formState = "editing"; - }); - } - - saveEntity() { - this.formState = "saving"; - // eslint-disable-next-line @typescript-eslint/no-this-alias - const el = this; - this.formEl.saveEntity().then(function () { - el.formState = "editing"; - }); - } -} - -customElements.define("ha-entity-config", HaEntityConfig); diff --git a/src/panels/config/ha-entity-config.ts b/src/panels/config/ha-entity-config.ts new file mode 100644 index 000000000000..3bf8683a1515 --- /dev/null +++ b/src/panels/config/ha-entity-config.ts @@ -0,0 +1,134 @@ +import "@material/mwc-button"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; +import { dynamicElement } from "../../common/dom/dynamic-element-directive"; +import "../../components/entity/ha-entity-picker"; +import "../../components/ha-card"; +import "../../components/ha-circular-progress"; +import { haStyle } from "../../resources/styles"; +import "../../styles/polymer-ha-style"; +import type { HomeAssistant } from "../../types"; +import { HaFormCustomize } from "./customize/ha-form-customize"; +import "../../components/buttons/ha-progress-button"; + +@customElement("ha-entity-config") +export class HaEntityConfig extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public selectedEntityId?: string; + + @property() private _formState: "initial" | "loading" | "saving" | "editing" = + "initial"; + + @query("#form") private _form!: HaFormCustomize; + + protected render(): TemplateResult { + return html` + +
+ + + +
+ ${dynamicElement("ha-form-customize", { + hass: this.hass, + id: "form", + })} +
+
+
+ + ${this.hass.localize("ui.common.save")} + +
+
+ `; + } + + protected updated(changedProps: PropertyValues) { + super.updated(changedProps); + if ( + changedProps.has("selectedEntityId") && + changedProps.get("selectedEntityId") !== this.selectedEntityId + ) { + this._selectEntity(this.selectedEntityId); + this.requestUpdate(); + } + } + + private _selectedEntityChanged(ev) { + this._selectEntity(ev.target.value); + } + + private _selectEntity(entityId?: string) { + if (!this._form || !entityId) return; + const entity = this.hass.states[entityId]; + if (!entity) return; + + this._formState = "loading"; + // eslint-disable-next-line @typescript-eslint/no-this-alias + const el = this; + this._form.loadEntity(entity).then(function () { + el._formState = "editing"; + }); + } + + private _saveEntity(ev) { + if (this._formState !== "editing") return; + this._formState = "saving"; + const button = ev.target; + button.progress = true; + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const el = this; + this._form + .saveEntity() + .then(function () { + el._formState = "editing"; + button.actionSuccess(); + button.progress = false; + }) + .catch(() => { + button.actionError(); + button.progress = false; + }); + } + + static get styles(): CSSResult[] { + return [ + haStyle, + css` + ha-card { + direction: ltr; + } + + .form-placeholder { + height: 96px; + } + + .hidden { + display: none; + } + `, + ]; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index d4b29bdeb9b4..238acd7218fd 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1096,8 +1096,7 @@ "picker": { "header": "Customizations", "introduction": "Tweak per-entity attributes. Added/edited customizations will take effect immediately. Removed customizations will take effect when the entity is updated.", - "documentation": "Customization documentation", - "entity": "Entity" + "documentation": "Customization documentation" }, "warning": { "include_sentence": "It seems that your configuration.yaml doesn't properly", From a00ed535fb5d74507b763f24607d413df25fec6b Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Sun, 17 Jan 2021 09:05:35 +0100 Subject: [PATCH 2/5] Cleanup --- src/panels/config/customize/ha-config-customize.ts | 8 ++------ src/panels/config/ha-entity-config.ts | 9 ++------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/panels/config/customize/ha-config-customize.ts b/src/panels/config/customize/ha-config-customize.ts index 2d0e91823203..c1802cc6aaa9 100644 --- a/src/panels/config/customize/ha-config-customize.ts +++ b/src/panels/config/customize/ha-config-customize.ts @@ -7,11 +7,8 @@ import { TemplateResult, } from "lit-element"; import "../../../components/ha-card"; -import "../../../components/ha-fab"; -import "../../../components/ha-svg-icon"; import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-tabs-subpage"; -import "../../../styles/polymer-ha-style"; import { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import "../ha-config-section"; @@ -28,7 +25,7 @@ class HaConfigCustomize extends LitElement { @property() public route!: Route; - private _selectedEntityId = ""; + @property() private _selectedEntityId = ""; protected render(): TemplateResult { return html` @@ -80,8 +77,7 @@ class HaConfigCustomize extends LitElement { return; } const routeSegments = this.route.path.split("/edit/"); - this._selectedEntityId = - routeSegments.length > 1 ? routeSegments[1] : undefined; + this._selectedEntityId = routeSegments.length > 1 ? routeSegments[1] : ""; } static get styles(): CSSResult { diff --git a/src/panels/config/ha-entity-config.ts b/src/panels/config/ha-entity-config.ts index 3bf8683a1515..61c4c34fe80c 100644 --- a/src/panels/config/ha-entity-config.ts +++ b/src/panels/config/ha-entity-config.ts @@ -11,6 +11,7 @@ import { TemplateResult, } from "lit-element"; import { dynamicElement } from "../../common/dom/dynamic-element-directive"; +import "../../components/buttons/ha-progress-button"; import "../../components/entity/ha-entity-picker"; import "../../components/ha-card"; import "../../components/ha-circular-progress"; @@ -18,13 +19,12 @@ import { haStyle } from "../../resources/styles"; import "../../styles/polymer-ha-style"; import type { HomeAssistant } from "../../types"; import { HaFormCustomize } from "./customize/ha-form-customize"; -import "../../components/buttons/ha-progress-button"; @customElement("ha-entity-config") export class HaEntityConfig extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property() public selectedEntityId?: string; + @property() public selectedEntityId!: string; @property() private _formState: "initial" | "loading" | "saving" | "editing" = "initial"; @@ -39,7 +39,6 @@ export class HaEntityConfig extends LitElement { .hass=${this.hass} .value=${this.selectedEntityId} .configValue=${"entity"} - @change=${this._selectedEntityChanged} allow-custom-entity hideClearIcon > @@ -75,10 +74,6 @@ export class HaEntityConfig extends LitElement { } } - private _selectedEntityChanged(ev) { - this._selectEntity(ev.target.value); - } - private _selectEntity(entityId?: string) { if (!this._form || !entityId) return; const entity = this.hass.states[entityId]; From 8d91c18365d936102d5c3597f255b1dbbeee7982 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 27 Jan 2021 00:12:18 +0100 Subject: [PATCH 3/5] Changes from review --- src/panels/config/ha-entity-config.ts | 41 +++++++++++++-------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/panels/config/ha-entity-config.ts b/src/panels/config/ha-entity-config.ts index 61c4c34fe80c..3943b5d9256c 100644 --- a/src/panels/config/ha-entity-config.ts +++ b/src/panels/config/ha-entity-config.ts @@ -10,7 +10,6 @@ import { query, TemplateResult, } from "lit-element"; -import { dynamicElement } from "../../common/dom/dynamic-element-directive"; import "../../components/buttons/ha-progress-button"; import "../../components/entity/ha-entity-picker"; import "../../components/ha-card"; @@ -39,16 +38,15 @@ export class HaEntityConfig extends LitElement { .hass=${this.hass} .value=${this.selectedEntityId} .configValue=${"entity"} + @change=${this._selectedEntityChanged} allow-custom-entity hideClearIcon >
- ${dynamicElement("ha-form-customize", { - hass: this.hass, - id: "form", - })} + +
@@ -74,7 +72,11 @@ export class HaEntityConfig extends LitElement { } } - private _selectEntity(entityId?: string) { + private _selectedEntityChanged(ev) { + this._selectEntity(ev.target.value); + } + + private async _selectEntity(entityId?: string) { if (!this._form || !entityId) return; const entity = this.hass.states[entityId]; if (!entity) return; @@ -82,12 +84,11 @@ export class HaEntityConfig extends LitElement { this._formState = "loading"; // eslint-disable-next-line @typescript-eslint/no-this-alias const el = this; - this._form.loadEntity(entity).then(function () { - el._formState = "editing"; - }); + await this._form.loadEntity(entity); + el._formState = "editing"; } - private _saveEntity(ev) { + private async _saveEntity(ev) { if (this._formState !== "editing") return; this._formState = "saving"; const button = ev.target; @@ -95,17 +96,15 @@ export class HaEntityConfig extends LitElement { // eslint-disable-next-line @typescript-eslint/no-this-alias const el = this; - this._form - .saveEntity() - .then(function () { - el._formState = "editing"; - button.actionSuccess(); - button.progress = false; - }) - .catch(() => { - button.actionError(); - button.progress = false; - }); + try { + await this._form.saveEntity(); + el._formState = "editing"; + button.actionSuccess(); + } catch { + button.actionError(); + } finally { + button.progress = false; + } } static get styles(): CSSResult[] { From 3c090b9a8ef196a0c81e56fdc8c17ec40023f302 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 27 Jan 2021 00:23:36 +0100 Subject: [PATCH 4/5] Simplify state handling --- src/panels/config/ha-entity-config.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/panels/config/ha-entity-config.ts b/src/panels/config/ha-entity-config.ts index 3943b5d9256c..80a1309c8fb3 100644 --- a/src/panels/config/ha-entity-config.ts +++ b/src/panels/config/ha-entity-config.ts @@ -25,8 +25,8 @@ export class HaEntityConfig extends LitElement { @property() public selectedEntityId!: string; - @property() private _formState: "initial" | "loading" | "saving" | "editing" = - "initial"; + // False if no entity is selected or currently saving or loading + @property() private _formEditState = false; @query("#form") private _form!: HaFormCustomize; @@ -52,7 +52,7 @@ export class HaEntityConfig extends LitElement {
${this.hass.localize("ui.common.save")} @@ -81,16 +81,16 @@ export class HaEntityConfig extends LitElement { const entity = this.hass.states[entityId]; if (!entity) return; - this._formState = "loading"; + this._formEditState = false; // eslint-disable-next-line @typescript-eslint/no-this-alias const el = this; await this._form.loadEntity(entity); - el._formState = "editing"; + el._formEditState = true; } private async _saveEntity(ev) { - if (this._formState !== "editing") return; - this._formState = "saving"; + if (!this._formEditState) return; + this._formEditState = false; const button = ev.target; button.progress = true; @@ -98,7 +98,7 @@ export class HaEntityConfig extends LitElement { const el = this; try { await this._form.saveEntity(); - el._formState = "editing"; + el._formEditState = true; button.actionSuccess(); } catch { button.actionError(); From 9a6f03a2b52c1ac97015a958b37704f8ed375404 Mon Sep 17 00:00:00 2001 From: Philip Allgaier Date: Wed, 27 Jan 2021 11:00:00 +0100 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Bram Kragten --- src/panels/config/ha-entity-config.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/panels/config/ha-entity-config.ts b/src/panels/config/ha-entity-config.ts index 80a1309c8fb3..9fb1d804ddb6 100644 --- a/src/panels/config/ha-entity-config.ts +++ b/src/panels/config/ha-entity-config.ts @@ -82,10 +82,8 @@ export class HaEntityConfig extends LitElement { if (!entity) return; this._formEditState = false; - // eslint-disable-next-line @typescript-eslint/no-this-alias - const el = this; await this._form.loadEntity(entity); - el._formEditState = true; + this._formEditState = true; } private async _saveEntity(ev) { @@ -94,11 +92,9 @@ export class HaEntityConfig extends LitElement { const button = ev.target; button.progress = true; - // eslint-disable-next-line @typescript-eslint/no-this-alias - const el = this; try { await this._form.saveEntity(); - el._formEditState = true; + this._formEditState = true; button.actionSuccess(); } catch { button.actionError();