From a2ec878ef0b02076004cac7a22cc3585f287cfac Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Wed, 30 Sep 2020 09:20:10 -0500 Subject: [PATCH] Entities Card: Entity Row Editor (#7134) Co-authored-by: Bram Kragten --- src/components/entity/ha-entity-picker.ts | 4 +- .../lovelace/cards/hui-entities-card.ts | 38 ++-- src/panels/lovelace/cards/types.ts | 8 +- src/panels/lovelace/common/has-changed.ts | 1 + .../common/process-config-entities.ts | 14 +- .../create-element/create-element-base.ts | 3 +- .../create-element/create-row-element.ts | 18 +- .../card-editor/hui-dialog-edit-card.ts | 55 +++--- .../config-elements/config-elements-style.ts | 32 ++-- .../hui-alarm-panel-card-editor.ts | 42 +++-- .../config-elements/hui-button-card-editor.ts | 6 +- .../hui-calendar-card-editor.ts | 6 +- .../hui-conditional-card-editor.ts | 122 ++++++------ .../hui-entities-card-editor.ts | 123 ++++++++++++- .../config-elements/hui-entity-card-editor.ts | 6 +- .../config-elements/hui-gauge-card-editor.ts | 40 ++-- .../hui-generic-entity-row-editor.ts | 173 ++++++++++++++++++ .../config-elements/hui-glance-card-editor.ts | 32 ++-- .../hui-history-graph-card-editor.ts | 28 +-- .../hui-humidifier-card-editor.ts | 10 +- .../config-elements/hui-iframe-card-editor.ts | 10 +- .../config-elements/hui-light-card-editor.ts | 6 +- .../config-elements/hui-map-card-editor.ts | 44 ++--- .../hui-markdown-card-editor.ts | 10 +- .../hui-picture-card-editor.ts | 6 +- .../hui-picture-entity-card-editor.ts | 6 +- .../hui-picture-glance-card-editor.ts | 6 +- .../hui-plant-status-card-editor.ts | 10 +- .../config-elements/hui-sensor-card-editor.ts | 6 +- .../config-elements/hui-stack-card-editor.ts | 25 +-- .../hui-thermostat-card-editor.ts | 10 +- .../hui-weather-forecast-card-editor.ts | 14 +- .../lovelace/editor/hui-detail-editor-base.ts | 86 +++++++++ ...i-card-editor.ts => hui-element-editor.ts} | 112 ++++++++---- .../editor/hui-entities-card-row-editor.ts | 53 ++++-- .../lovelace-editor/hui-lovelace-editor.ts | 6 +- src/panels/lovelace/editor/types.ts | 18 +- .../editor/view-editor/hui-view-editor.ts | 26 +-- src/panels/lovelace/types.ts | 11 ++ src/translations/en.json | 10 + 40 files changed, 902 insertions(+), 334 deletions(-) create mode 100644 src/panels/lovelace/editor/config-elements/hui-generic-entity-row-editor.ts create mode 100644 src/panels/lovelace/editor/hui-detail-editor-base.ts rename src/panels/lovelace/editor/{card-editor/hui-card-editor.ts => hui-element-editor.ts} (70%) diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 3cafa0a88b..94ddcf4517 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -93,6 +93,8 @@ export class HaEntityPicker extends LitElement { @property() public entityFilter?: HaEntityPickerEntityFilterFunc; + @property({ type: Boolean }) public hideClearIcon = false; + @property({ type: Boolean }) private _opened = false; @query("vaadin-combo-box-light") private _comboBox!: HTMLElement; @@ -204,7 +206,7 @@ export class HaEntityPicker extends LitElement { autocorrect="off" spellcheck="false" > - ${this.value + ${this.value && !this.hideClearIcon ? html` ` : ""} @@ -198,10 +202,10 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { ? html`` : html` "type" in conf + ) as EntityConfig[]).map((conf) => conf.entity)} > `} @@ -285,20 +289,20 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard { `; } - private renderEntity(entityConf: EntitiesCardEntityConfig): TemplateResult { + private renderEntity(entityConf: LovelaceRowConfig): TemplateResult { const element = createRowElement( - this._config!.state_color - ? { + !("type" in entityConf) && this._config!.state_color + ? ({ state_color: true, - ...entityConf, - } + ...(entityConf as EntityConfig), + } as EntityConfig) : entityConf ); if (this._hass) { element.hass = this._hass; } - return html`
${element}
`; + return html`
${element}
`; } } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 8c2923967d..78997f8b31 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -3,7 +3,11 @@ import { FullCalendarView } from "../../../types"; import { Condition } from "../common/validate-condition"; import { HuiImage } from "../components/hui-image"; import { LovelaceElementConfig } from "../elements/types"; -import { EntityConfig, EntityFilterEntityConfig } from "../entity-rows/types"; +import { + EntityConfig, + EntityFilterEntityConfig, + LovelaceRowConfig, +} from "../entity-rows/types"; import { LovelaceHeaderFooterConfig } from "../header-footer/types"; export interface AlarmPanelCardConfig extends LovelaceCardConfig { @@ -60,7 +64,7 @@ export interface EntitiesCardConfig extends LovelaceCardConfig { type: "entities"; show_header_toggle?: boolean; title?: string; - entities: Array; + entities: Array; theme?: string; icon?: string; header?: LovelaceHeaderFooterConfig; diff --git a/src/panels/lovelace/common/has-changed.ts b/src/panels/lovelace/common/has-changed.ts index d4b3320410..db4b9de518 100644 --- a/src/panels/lovelace/common/has-changed.ts +++ b/src/panels/lovelace/common/has-changed.ts @@ -56,6 +56,7 @@ export function hasConfigOrEntitiesChanged( return entities.some( (entity) => + "entity" in entity && oldHass.states[entity.entity] !== element.hass!.states[entity.entity] ); } diff --git a/src/panels/lovelace/common/process-config-entities.ts b/src/panels/lovelace/common/process-config-entities.ts index e0b9045432..7d9a858227 100644 --- a/src/panels/lovelace/common/process-config-entities.ts +++ b/src/panels/lovelace/common/process-config-entities.ts @@ -1,8 +1,10 @@ // Parse array of entity objects from config import { isValidEntityId } from "../../../common/entity/valid_entity_id"; -import { EntityConfig } from "../entity-rows/types"; +import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types"; -export const processConfigEntities = ( +export const processConfigEntities = < + T extends EntityConfig | LovelaceRowConfig +>( entities: Array ): T[] => { if (!entities || !Array.isArray(entities)) { @@ -24,7 +26,7 @@ export const processConfigEntities = ( if (typeof entityConf === "string") { config = { entity: entityConf } as T; } else if (typeof entityConf === "object" && !Array.isArray(entityConf)) { - if (!entityConf.entity) { + if (!("entity" in entityConf)) { throw new Error( `Entity object at position ${index} is missing entity field.` ); @@ -34,9 +36,11 @@ export const processConfigEntities = ( throw new Error(`Invalid entity specified at position ${index}.`); } - if (!isValidEntityId(config.entity)) { + if (!isValidEntityId((config as EntityConfig).entity!)) { throw new Error( - `Invalid entity ID at position ${index}: ${config.entity}` + `Invalid entity ID at position ${index}: ${ + (config as EntityConfig).entity + }` ); } diff --git a/src/panels/lovelace/create-element/create-element-base.ts b/src/panels/lovelace/create-element/create-element-base.ts index 23b2083e1e..a857921f8e 100644 --- a/src/panels/lovelace/create-element/create-element-base.ts +++ b/src/panels/lovelace/create-element/create-element-base.ts @@ -16,6 +16,7 @@ import { LovelaceCard, LovelaceCardConstructor, LovelaceHeaderFooter, + LovelaceRowConstructor, } from "../types"; const TIMEOUT = 2000; @@ -39,7 +40,7 @@ interface CreateElementConfigTypes { row: { config: LovelaceRowConfig; element: LovelaceRow; - constructor: unknown; + constructor: LovelaceRowConstructor; }; "header-footer": { config: LovelaceHeaderFooterConfig; diff --git a/src/panels/lovelace/create-element/create-row-element.ts b/src/panels/lovelace/create-element/create-row-element.ts index 433b50e0a5..f697b89adf 100644 --- a/src/panels/lovelace/create-element/create-row-element.ts +++ b/src/panels/lovelace/create-element/create-row-element.ts @@ -4,11 +4,14 @@ import "../entity-rows/hui-script-entity-row"; import "../entity-rows/hui-sensor-entity-row"; import "../entity-rows/hui-text-entity-row"; import "../entity-rows/hui-toggle-entity-row"; -import { EntityConfig } from "../entity-rows/types"; +import { LovelaceRowConfig } from "../entity-rows/types"; import "../special-rows/hui-attribute-row"; import "../special-rows/hui-button-row"; import "../special-rows/hui-call-service-row"; -import { createLovelaceElement } from "./create-element-base"; +import { + createLovelaceElement, + getLovelaceElementClass, +} from "./create-element-base"; const ALWAYS_LOADED_TYPES = new Set([ "media-player-entity", @@ -74,7 +77,7 @@ const DOMAIN_TO_ELEMENT_TYPE = { weather: "weather", }; -export const createRowElement = (config: EntityConfig) => +export const createRowElement = (config: LovelaceRowConfig) => createLovelaceElement( "row", config, @@ -83,3 +86,12 @@ export const createRowElement = (config: EntityConfig) => DOMAIN_TO_ELEMENT_TYPE, undefined ); + +export const getRowElementClass = (type: string) => { + return getLovelaceElementClass( + type, + "row", + ALWAYS_LOADED_TYPES, + LAZY_LOAD_TYPES + ); +}; diff --git a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts index e700cd097b..aa45460df6 100755 --- a/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts +++ b/src/panels/lovelace/editor/card-editor/hui-dialog-edit-card.ts @@ -1,42 +1,42 @@ +import { mdiHelpCircle } from "@mdi/js"; import deepFreeze from "deep-freeze"; import { css, CSSResultArray, customElement, html, + internalProperty, LitElement, property, - internalProperty, + PropertyValues, query, TemplateResult, - PropertyValues, } from "lit-element"; -import { mdiHelpCircle } from "@mdi/js"; - -import { fireEvent } from "../../../../common/dom/fire_event"; -import { haStyleDialog } from "../../../../resources/styles"; -import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; -import { addCard, replaceCard } from "../config-util"; -import { getCardDocumentationURL } from "../get-card-documentation-url"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; -import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; - -import type { HomeAssistant } from "../../../../types"; -import type { GUIModeChangedEvent } from "../types"; -import type { ConfigChangedEvent, HuiCardEditor } from "./hui-card-editor"; -import type { EditCardDialogParams } from "./show-edit-card-dialog"; -import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; import type { HASSDomEvent } from "../../../../common/dom/fire_event"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { computeRTLDirection } from "../../../../common/util/compute_rtl"; +import "../../../../components/ha-circular-progress"; +import "../../../../components/ha-dialog"; +import "../../../../components/ha-header-bar"; import type { LovelaceCardConfig, LovelaceViewConfig, } from "../../../../data/lovelace"; - -import "./hui-card-editor"; +import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; +import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; +import { haStyleDialog } from "../../../../resources/styles"; +import type { HomeAssistant } from "../../../../types"; +import { showSaveSuccessToast } from "../../../../util/toast-saved-success"; +import { addCard, replaceCard } from "../config-util"; +import { getCardDocumentationURL } from "../get-card-documentation-url"; +import "../hui-element-editor"; +import type { + ConfigChangedEvent, + HuiElementEditor, +} from "../hui-element-editor"; +import type { GUIModeChangedEvent } from "../types"; import "./hui-card-preview"; -import "../../../../components/ha-dialog"; -import "../../../../components/ha-header-bar"; -import "../../../../components/ha-circular-progress"; +import type { EditCardDialogParams } from "./show-edit-card-dialog"; declare global { // for fire event @@ -65,7 +65,7 @@ export class HuiDialogEditCard extends LitElement implements HassDialog { @internalProperty() private _guiModeAvailable? = true; - @query("hui-card-editor") private _cardEditorEl?: HuiCardEditor; + @query("hui-element-editor") private _cardEditorEl?: HuiElementEditor; @internalProperty() private _GUImode = true; @@ -183,14 +183,14 @@ export class HuiDialogEditCard extends LitElement implements HassDialog {
- + >
- ha-switch { - padding: 16px 0; - } - .side-by-side { - display: flex; - } - .side-by-side > * { - flex: 1; - padding-right: 4px; - } - .suffix { - margin: 0 8px; - } - +export const configElementStyle = css` + ha-switch { + padding: 16px 0; + } + .side-by-side { + display: flex; + } + .side-by-side > * { + flex: 1; + padding-right: 4px; + } + .suffix { + margin: 0 8px; + } `; diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts index bf9c963199..f21af05f04 100644 --- a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts @@ -3,14 +3,15 @@ import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import { css, - CSSResult, + CSSResultArray, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; +import { array, assert, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/entity/ha-entity-picker"; import "../../../../components/ha-icon"; @@ -20,7 +21,6 @@ import "../../components/hui-theme-select-editor"; import { LovelaceCardEditor } from "../../types"; import { EditorTarget, EntitiesEditorEvent } from "../types"; import { configElementStyle } from "./config-elements-style"; -import { assert, object, string, optional, array } from "superstruct"; const cardConfigStruct = object({ type: string(), @@ -68,7 +68,6 @@ export class HuiAlarmPanelCardEditor extends LitElement const states = ["arm_home", "arm_away", "arm_night", "arm_custom_bypass"]; return html` - ${configElementStyle}
- + > ` : html` + ${this.hass.localize( + "ui.panel.lovelace.editor.card.entities.entity_row_editor" + )} + + + `; + } + return html` - ${configElementStyle}
): void { + this._editRowIndex = ev.detail.index; + this._editRowConfig = this._configEntities![this._editRowIndex]; + } + + private _goBack(): void { + this._editRowIndex = undefined; + this._editRowConfig = undefined; + this._editRowGuiModeAvailable = true; + this._editRowGuiMode = true; + } + + private _toggleMode(): void { + this._cardEditorEl?.toggleMode(); + } + + private _handleEntityRowConfigChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const value = ev.detail.config as LovelaceRowConfig; + this._editRowGuiModeAvailable = ev.detail.guiModeAvailable; + + const newConfigEntities = this._configEntities!.concat(); + + if (!value) { + newConfigEntities.splice(this._editRowIndex!, 1); + this._goBack(); + } else { + newConfigEntities[this._editRowIndex!] = value; + } + + this._editRowConfig = value; + + this._config = { ...this._config!, entities: newConfigEntities }; + + this._configEntities = processEditorEntities(this._config!.entities); + + fireEvent(this, "config-changed", { config: this._config! }); + } + + private _handleGUIModeChanged(ev: HASSDomEvent): void { + ev.stopPropagation(); + this._editRowGuiMode = ev.detail.guiMode; + this._editRowGuiModeAvailable = ev.detail.guiModeAvailable; + } + + static get styles(): CSSResultArray { + return [ + configElementStyle, + css` + .edit-entity-row-header { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 18px; + } + `, + ]; + } } declare global { diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts index 49a9db601c..f4d8f54921 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts @@ -1,5 +1,6 @@ import "@polymer/paper-input/paper-input"; import { + CSSResult, customElement, html, internalProperty, @@ -75,7 +76,6 @@ export class HuiEntityCardEditor extends LitElement } return html` - ${configElementStyle}
+ +
+ + +
+ + + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.entities.secondary_info_values.none" + )} + ${Object.keys(SecondaryInfoValues).map((info) => { + if ( + !("domains" in SecondaryInfoValues[info]) || + ("domains" in SecondaryInfoValues[info] && + SecondaryInfoValues[info].domains!.includes(domain)) + ) { + return html` + ${this.hass!.localize( + `ui.panel.lovelace.editor.card.entities.secondary_info_values.${info}` + )} + `; + } + return ""; + })} + + +
+ `; + } + + private _valueChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target! as EditorTarget; + const value = target.value || ev.detail?.item?.value; + + if (this[`_${target.configValue}`] === value) { + return; + } + + if (target.configValue) { + if (value === "" || !value) { + this._config = { ...this._config }; + delete this._config[target.configValue!]; + } else { + this._config = { + ...this._config, + [target.configValue!]: value, + }; + } + } + + fireEvent(this, "config-changed", { config: this._config }); + } + + static get styles(): CSSResult { + return configElementStyle; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-generic-entity-row-editor": HuiGenericEntityRowEditor; + } +} diff --git a/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts index 7d3de02655..9372cba30b 100644 --- a/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-glance-card-editor.ts @@ -2,19 +2,31 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; import { + CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; +import { + array, + assert, + boolean, + number, + object, + optional, + string, + union, +} from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { computeRTLDirection } from "../../../../common/util/compute_rtl"; import "../../../../components/entity/state-badge"; import "../../../../components/ha-card"; +import "../../../../components/ha-formfield"; import "../../../../components/ha-icon"; import "../../../../components/ha-switch"; -import "../../../../components/ha-formfield"; import { HomeAssistant } from "../../../../types"; import { ConfigEntity, GlanceCardConfig } from "../../cards/types"; import "../../components/hui-entity-editor"; @@ -27,17 +39,6 @@ import { EntitiesEditorEvent, } from "../types"; import { configElementStyle } from "./config-elements-style"; -import { computeRTLDirection } from "../../../../common/util/compute_rtl"; -import { - string, - union, - object, - optional, - number, - boolean, - assert, - array, -} from "superstruct"; const cardConfigStruct = object({ type: string(), @@ -102,7 +103,6 @@ export class HuiGlanceCardEditor extends LitElement const dir = computeRTLDirection(this.hass!); return html` - ${configElementStyle}
): void { assert(config, cardConfigStruct); @@ -128,13 +129,13 @@ export class HuiStackCardEditor extends LitElement >
- + > ` : html` +
+ + + + +
+ + ${this.hass.localize( + this.guiMode + ? "ui.panel.lovelace.editor.edit_card.show_code_editor" + : "ui.panel.lovelace.editor.edit_card.show_visual_editor" + )} + +
+ + `; + } + + private _goBack(): void { + fireEvent(this, "go-back"); + } + + private _toggleMode(): void { + fireEvent(this, "toggle-gui-mode"); + } + + static get styles(): CSSResult { + return css` + .header { + display: flex; + justify-content: space-between; + align-items: center; + } + .back-title { + display: flex; + align-items: center; + font-size: 18px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-detail-editor-base": HuiDetailEditorBase; + } +} diff --git a/src/panels/lovelace/editor/card-editor/hui-card-editor.ts b/src/panels/lovelace/editor/hui-element-editor.ts similarity index 70% rename from src/panels/lovelace/editor/card-editor/hui-card-editor.ts rename to src/panels/lovelace/editor/hui-element-editor.ts index ff4613ed0e..c8f6078096 100644 --- a/src/panels/lovelace/editor/card-editor/hui-card-editor.ts +++ b/src/panels/lovelace/editor/hui-element-editor.ts @@ -11,26 +11,33 @@ import { query, TemplateResult, } from "lit-element"; -import { fireEvent } from "../../../../common/dom/fire_event"; -import { computeRTL } from "../../../../common/util/compute_rtl"; -import { deepEqual } from "../../../../common/util/deep-equal"; -import "../../../../components/ha-circular-progress"; -import "../../../../components/ha-code-editor"; -import type { HaCodeEditor } from "../../../../components/ha-code-editor"; +import { fireEvent } from "../../../common/dom/fire_event"; +import { computeRTL } from "../../../common/util/compute_rtl"; +import { deepEqual } from "../../../common/util/deep-equal"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-code-editor"; +import type { HaCodeEditor } from "../../../components/ha-code-editor"; import type { LovelaceCardConfig, LovelaceConfig, -} from "../../../../data/lovelace"; -import type { HomeAssistant } from "../../../../types"; -import { handleStructError } from "../../common/structs/handle-errors"; -import { getCardElementClass } from "../../create-element/create-card-element"; -import type { LovelaceRowConfig } from "../../entity-rows/types"; -import type { LovelaceCardEditor } from "../../types"; -import { GUISupportError } from "../gui-support-error"; -import type { GUIModeChangedEvent } from "../types"; +} from "../../../data/lovelace"; +import type { HomeAssistant } from "../../../types"; +import { handleStructError } from "../common/structs/handle-errors"; +import { getCardElementClass } from "../create-element/create-card-element"; +import { getRowElementClass } from "../create-element/create-row-element"; +import type { LovelaceRowConfig } from "../entity-rows/types"; +import type { + LovelaceCardConstructor, + LovelaceCardEditor, + LovelaceRowConstructor, + LovelaceRowEditor, +} from "../types"; +import "./config-elements/hui-generic-entity-row-editor"; +import { GUISupportError } from "./gui-support-error"; +import { GUIModeChangedEvent } from "./types"; export interface ConfigChangedEvent { - config: LovelaceCardConfig; + config: LovelaceCardConfig | LovelaceRowConfig; error?: string; guiModeAvailable?: boolean; } @@ -47,21 +54,27 @@ declare global { export interface UIConfigChangedEvent extends Event { detail: { - config: LovelaceCardConfig; + config: LovelaceCardConfig | LovelaceRowConfig; }; } -@customElement("hui-card-editor") -export class HuiCardEditor extends LitElement { +const GENERIC_ROW_TYPE = "generic-row"; + +@customElement("hui-element-editor") +export class HuiElementEditor extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public lovelace?: LovelaceConfig; + @property() public elementType: "row" | "card" = "card"; + @internalProperty() private _yaml?: string; - @internalProperty() private _config?: LovelaceCardConfig; + @internalProperty() private _config?: LovelaceCardConfig | LovelaceRowConfig; - @internalProperty() private _configElement?: LovelaceCardEditor; + @internalProperty() private _configElement?: + | LovelaceCardEditor + | LovelaceRowEditor; @internalProperty() private _configElType?: string; @@ -95,11 +108,11 @@ export class HuiCardEditor extends LitElement { this._setConfig(); } - public get value(): LovelaceCardConfig | undefined { + public get value(): LovelaceCardConfig | LovelaceRowConfig | undefined { return this._config; } - public set value(config: LovelaceCardConfig | undefined) { + public set value(config: LovelaceCardConfig | LovelaceRowConfig | undefined) { if (this._config && deepEqual(config, this._config)) { return; } @@ -220,7 +233,11 @@ export class HuiCardEditor extends LitElement { if (this._configElement && changedProperties.has("hass")) { this._configElement.hass = this.hass; } - if (this._configElement && changedProperties.has("lovelace")) { + if ( + this._configElement && + "lovelace" in this._configElement && + changedProperties.has("lovelace") + ) { this._configElement.lovelace = this.lovelace; } } @@ -244,37 +261,61 @@ export class HuiCardEditor extends LitElement { return; } - const cardType = this.value.type; + let type: string; + + if ( + this.elementType === "row" && + !this.value.type && + "entity" in this.value + ) { + type = GENERIC_ROW_TYPE; + } else { + type = this.value.type!; + } + let configElement = this._configElement; try { this._error = undefined; this._warnings = undefined; - if (this._configElType !== cardType) { - // If the card type has changed, we need to load a new GUI editor - if (!this.value.type) { - throw new Error("No card type defined"); + if (this._configElType !== type) { + // If the type has changed, we need to load a new GUI editor + if (!type) { + throw new Error(`No ${this.elementType} type defined`); } - const elClass = await getCardElementClass(cardType); + let elClass: + | LovelaceCardConstructor + | LovelaceRowConstructor + | undefined; + + if (this.elementType === "card") { + elClass = await getCardElementClass(type); + } else if (this.elementType === "row" && type !== GENERIC_ROW_TYPE) { + elClass = await getRowElementClass(type); + } this._loading = true; // Check if a GUI editor exists if (elClass && elClass.getConfigElement) { configElement = await elClass.getConfigElement(); + } else if (this.elementType === "row" && type === GENERIC_ROW_TYPE) { + configElement = document.createElement( + "hui-generic-entity-row-editor" + ); } else { configElement = undefined; - throw new GUISupportError( - `No visual editor available for: ${cardType}` - ); + throw new GUISupportError(`No visual editor available for: ${type}`); } this._configElement = configElement; - this._configElType = cardType; + this._configElType = type; // Perform final setup this._configElement.hass = this.hass; - this._configElement.lovelace = this.lovelace; + if ("lovelace" in this._configElement) { + this._configElement.lovelace = this.lovelace; + } this._configElement.addEventListener("config-changed", (ev) => this._handleUIConfigChanged(ev as UIConfigChangedEvent) ); @@ -282,6 +323,7 @@ export class HuiCardEditor extends LitElement { // Setup GUI editor and check that it can handle the current config try { + // @ts-ignore this._configElement!.setConfig(this.value); } catch (err) { throw new GUISupportError( @@ -340,6 +382,6 @@ export class HuiCardEditor extends LitElement { declare global { interface HTMLElementTagNameMap { - "hui-card-editor": HuiCardEditor; + "hui-element-editor": HuiElementEditor; } } diff --git a/src/panels/lovelace/editor/hui-entities-card-row-editor.ts b/src/panels/lovelace/editor/hui-entities-card-row-editor.ts index 760d285f9a..0e6a42bdb8 100644 --- a/src/panels/lovelace/editor/hui-entities-card-row-editor.ts +++ b/src/panels/lovelace/editor/hui-entities-card-row-editor.ts @@ -1,4 +1,5 @@ -import { mdiClose, mdiDrag } from "@mdi/js"; +import "@material/mwc-icon-button"; +import { mdiClose, mdiDrag, mdiPencil } from "@mdi/js"; import { css, CSSResult, @@ -19,7 +20,7 @@ import Sortable, { import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/entity/ha-entity-picker"; import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker"; -import "../../../components/ha-icon-button"; +import "../../../components/ha-svg-icon"; import { sortableStyles } from "../../../resources/ha-sortable-style"; import { HomeAssistant } from "../../../types"; import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types"; @@ -85,26 +86,38 @@ export class HuiEntitiesCardRowEditor extends LitElement { )}
- - -
` : html` `} + + + + + +
`; }) @@ -164,7 +177,7 @@ export class HuiEntitiesCardRowEditor extends LitElement { animation: 150, fallbackClass: "sortable-fallback", handle: ".handle", - onEnd: async (evt: SortableEvent) => this._entityMoved(evt), + onEnd: async (evt: SortableEvent) => this._rowMoved(evt), }); } @@ -180,7 +193,7 @@ export class HuiEntitiesCardRowEditor extends LitElement { fireEvent(this, "entities-changed", { entities: newConfigEntities }); } - private _entityMoved(ev: SortableEvent): void { + private _rowMoved(ev: SortableEvent): void { if (ev.oldIndex === ev.newIndex) { return; } @@ -192,7 +205,7 @@ export class HuiEntitiesCardRowEditor extends LitElement { fireEvent(this, "entities-changed", { entities: newEntities }); } - private _removeSpecialRow(ev: CustomEvent): void { + private _removeRow(ev: CustomEvent): void { const index = (ev.currentTarget as any).index; const newConfigEntities = this.entities!.concat(); @@ -218,6 +231,12 @@ export class HuiEntitiesCardRowEditor extends LitElement { fireEvent(this, "entities-changed", { entities: newConfigEntities }); } + private _editRow(ev: CustomEvent): void { + fireEvent(this, "edit-row", { + index: (ev.currentTarget as any).index, + }); + } + static get styles(): CSSResult[] { return [ sortableStyles, @@ -226,13 +245,16 @@ export class HuiEntitiesCardRowEditor extends LitElement { display: flex; align-items: center; } + .entity .handle { padding-right: 8px; cursor: move; } + .entity ha-entity-picker { flex-grow: 1; } + .special-row { height: 60px; font-size: 16px; @@ -247,7 +269,8 @@ export class HuiEntitiesCardRowEditor extends LitElement { flex-direction: column; } - .special-row mwc-icon-button { + .remove-icon, + .edit-icon { --mdc-icon-button-size: 36px; color: var(--secondary-text-color); } diff --git a/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts b/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts index 0d806692a3..dff6c618a4 100644 --- a/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts +++ b/src/panels/lovelace/editor/lovelace-editor/hui-lovelace-editor.ts @@ -1,5 +1,6 @@ import "@polymer/paper-input/paper-input"; import { + CSSResult, customElement, html, LitElement, @@ -35,7 +36,6 @@ export class HuiLovelaceEditor extends LitElement { protected render(): TemplateResult { return html` - ${configElementStyle}
{ getConfigElement?: () => LovelaceCardEditor; } +export interface LovelaceRowConstructor extends Constructor { + getConfigElement?: () => LovelaceRowEditor; +} + export interface LovelaceHeaderFooter extends HTMLElement { hass?: HomeAssistant; getCardSize(): number | Promise; @@ -60,3 +65,9 @@ export interface LovelaceCardEditor extends HTMLElement { setConfig(config: LovelaceCardConfig): void; refreshYamlEditor?: (focus: boolean) => void; } + +export interface LovelaceRowEditor extends HTMLElement { + hass?: HomeAssistant; + setConfig(config: LovelaceRowConfig): void; + refreshYamlEditor?: (focus: boolean) => void; +} diff --git a/src/translations/en.json b/src/translations/en.json index 7a28805113..11b2e3fb1c 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2331,6 +2331,16 @@ "description": "The Entities card is the most common type of card. It groups items together into lists.", "special_row": "special row", "edit_special_row": "Edit row using the code editor", + "entity_row_editor": "Entity Row Editor", + "secondary_info_values": { + "none": "No Secondary Info", + "entity-id": "Entity ID", + "last-changed": "Last Changed", + "last-triggered": "Last Triggered", + "position": "Position", + "tilt-position": "Tilt Position", + "brightness": "Brightness" + }, "entity_row": { "divider": "Divider", "call-service": "Call Service",