diff --git a/src/panels/lovelace/cards/hui-entity-filter-card.ts b/src/panels/lovelace/cards/hui-entity-filter-card.ts index ed1db86230..bda8f94c0e 100644 --- a/src/panels/lovelace/cards/hui-entity-filter-card.ts +++ b/src/panels/lovelace/cards/hui-entity-filter-card.ts @@ -12,6 +12,11 @@ import { LovelaceCard } from "../types"; import { EntityFilterCardConfig } from "./types"; class EntityFilterCard extends ReactiveElement implements LovelaceCard { + public static async getConfigElement(): Promise { + await import("../editor/config-elements/hui-entity-filter-card-editor"); + return document.createElement("hui-entity-filter-card-editor"); + } + public static getStubConfig( hass: HomeAssistant, entities: string[], diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-filter-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-filter-card-editor.ts new file mode 100644 index 0000000000..3e332eafc9 --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-entity-filter-card-editor.ts @@ -0,0 +1,391 @@ +import "@material/mwc-tab-bar/mwc-tab-bar"; +import "@material/mwc-tab/mwc-tab"; +import type { MDCTabBarActivatedEvent } from "@material/tab-bar"; +import { mdiClose } from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { + any, + array, + assert, + assign, + boolean, + object, + optional, + string, +} from "superstruct"; +import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/entity/ha-entity-picker"; +import { LovelaceCardConfig, LovelaceConfig } from "../../../../data/lovelace"; +import { HomeAssistant } from "../../../../types"; +import { EntityFilterCardConfig } from "../../cards/types"; +import { + EntityFilterEntityConfig, + LovelaceRowConfig, +} from "../../entity-rows/types"; +import { LovelaceCardEditor } from "../../types"; +import "../card-editor/hui-card-element-editor"; +import type { HuiCardElementEditor } from "../card-editor/hui-card-element-editor"; +import "../card-editor/hui-card-picker"; +import "../hui-element-editor"; +import type { ConfigChangedEvent } from "../hui-element-editor"; +import { baseLovelaceCardConfig } from "../structs/base-card-struct"; +import { entitiesConfigStruct } from "../structs/entities-struct"; +import { EntitiesEditorEvent, GUIModeChangedEvent } from "../types"; +import { configElementStyle } from "./config-elements-style"; +import { processEditorEntities } from "../process-editor-entities"; +import { computeRTLDirection } from "../../../../common/util/compute_rtl"; + +const cardConfigStruct = assign( + baseLovelaceCardConfig, + object({ + card: optional(any()), + entities: array(entitiesConfigStruct), + state_filter: array(string()), + show_empty: optional(boolean()), + }) +); + +@customElement("hui-entity-filter-card-editor") +export class HuiEntityFilterCardEditor + extends LitElement + implements LovelaceCardEditor +{ + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public lovelace?: LovelaceConfig; + + @state() protected _config?: EntityFilterCardConfig; + + @state() private _configEntities?: LovelaceRowConfig[]; + + @state() private _GUImode = true; + + @state() private _guiModeAvailable? = true; + + @state() private _cardTab = false; + + @query("hui-card-element-editor") + private _cardEditorEl?: HuiCardElementEditor; + + public setConfig(config: EntityFilterCardConfig): void { + assert(config, cardConfigStruct); + this._config = config; + this._configEntities = processEditorEntities(config.entities); + } + + public focusYamlEditor() { + this._cardEditorEl?.focusYamlEditor(); + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + return html` + + + + + ${this._cardTab ? this._renderCardEditor() : this._renderFilterEditor()} + `; + } + + private _renderFilterEditor(): TemplateResult { + return html` +
+ +
+
+

+ ${this.hass!.localize( + "ui.panel.lovelace.editor.card.entity-filter.display_states" + )} + (${this.hass!.localize( + "ui.panel.lovelace.editor.card.config.required" + )}) +

+ ${this._config!.state_filter.map( + (stte, idx) => html`
+ + + + +
` + )} + +
+ `; + } + + private _renderCardEditor(): TemplateResult { + return html` +
+ + + + ${this._config!.card && this._config!.card.type !== undefined + ? html` +
+ + ${this.hass!.localize( + !this._cardEditorEl || this._GUImode + ? "ui.panel.lovelace.editor.edit_card.show_code_editor" + : "ui.panel.lovelace.editor.edit_card.show_visual_editor" + )} + + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.conditional.change_type" + )} +
+ + ` + : html` + + `} +
+ `; + } + + private _selectTab(ev: MDCTabBarActivatedEvent): void { + this._cardTab = ev.detail.index === 1; + } + + private _toggleMode(): void { + this._cardEditorEl?.toggleMode(); + } + + private _setMode(value: boolean): void { + this._GUImode = value; + if (this._cardEditorEl) { + this._cardEditorEl.GUImode = value; + } + } + + private _showEmptyToggle(): void { + if (!this._config || !this.hass) { + return; + } + this._config = { + ...this._config, + show_empty: this._config.show_empty === false, + }; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _entitiesChanged(ev: EntitiesEditorEvent): void { + if (!this._config || !this.hass) { + return; + } + if (!ev.detail || !ev.detail.entities) { + return; + } + + this._config = { + ...this._config, + entities: ev.detail.entities as EntityFilterEntityConfig[], + }; + this._configEntities = processEditorEntities(this._config.entities); + + fireEvent(this, "config-changed", { config: this._config }); + } + + private _stateDeleted(ev: Event): void { + const target = ev.target! as any; + if (target.value === "" || !this._config) { + return; + } + const state_filter = [...this._config.state_filter]; + state_filter.splice(target.index, 1); + + this._config = { ...this._config, state_filter }; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _stateAdded(ev: Event): void { + const target = ev.target! as any; + if (target.value === "" || !this._config) { + return; + } + const state_filter = [...this._config.state_filter]; + state_filter.push(target.value); + + this._config = { ...this._config, state_filter }; + target.value = ""; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _stateChanged(ev: Event): void { + const target = ev.target! as any; + if (target.value === "" || !this._config) { + return; + } + const state_filter = [...this._config.state_filter]; + state_filter[target.index] = target.value; + + this._config = { ...this._config, state_filter }; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _handleGUIModeChanged(ev: HASSDomEvent): void { + ev.stopPropagation(); + this._GUImode = ev.detail.guiMode; + this._guiModeAvailable = ev.detail.guiModeAvailable; + } + + private _handleCardPicked(ev: CustomEvent): void { + ev.stopPropagation(); + if (!this._config) { + return; + } + const cardConfig = { ...ev.detail.config } as LovelaceCardConfig; + delete cardConfig.entities; + this._setMode(true); + this._guiModeAvailable = true; + this._config = { ...this._config, card: cardConfig }; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _handleCardChanged(ev: HASSDomEvent): void { + ev.stopPropagation(); + if (!this._config) { + return; + } + const cardConfig = { ...ev.detail.config } as LovelaceCardConfig; + delete cardConfig.entities; + this._config = { + ...this._config, + card: cardConfig, + }; + this._guiModeAvailable = ev.detail.guiModeAvailable; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _handleReplaceCard(): void { + if (!this._config) { + return; + } + // @ts-ignore + this._config = { ...this._config, card: {} }; + // @ts-ignore + fireEvent(this, "config-changed", { config: this._config }); + } + + private _getCardConfig(): LovelaceCardConfig { + const cardConfig = { ...this._config!.card } as LovelaceCardConfig; + cardConfig.entities = []; + return cardConfig; + } + + static get styles(): CSSResultGroup { + return [ + configElementStyle, + css` + mwc-tab-bar { + border-bottom: 1px solid var(--divider-color); + } + + .entities, + .states, + .card { + margin-top: 8px; + border: 1px solid var(--divider-color); + padding: 12px; + } + @media (max-width: 450px) { + .entities, + .states, + .card { + margin: 8px -12px 0; + } + } + + .state { + display: flex; + justify-content: flex-end; + width: 100%; + } + .state paper-input { + flex-grow: 1; + } + + .card .card-options { + display: flex; + justify-content: flex-end; + width: 100%; + } + .gui-mode-button { + margin-right: auto; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-entity-filter-card-editor": HuiEntityFilterCardEditor; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index efd586c6e9..61dd1810d2 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3402,7 +3402,13 @@ }, "entity-filter": { "name": "Entity Filter", - "description": "The Entity Filter card allows you to define a list of entities that you want to track only when in a certain state." + "description": "The Entity Filter card allows you to define a list of entities that you want to track only when in a certain state.", + "filters": "Filters", + "card": "Card", + "display_states": "States to show", + "show_empty": "Show when empty", + "state": "state", + "delete_state": "Delete state" }, "gauge": { "name": "Gauge",