From 503dec7345e5931e170b40ef4993d0d84e2594c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Fri, 6 Mar 2020 12:58:41 +0100 Subject: [PATCH] GUI editor for conditional card (#5051) * GUI editor for conditional card * Typing * Fix typos. Remove quotes * Add lovelace to card picker * Address review comments --- .../lovelace/cards/hui-conditional-card.ts | 16 +- .../hui-conditional-card-editor.ts | 277 ++++++++++++++++++ src/translations/en.json | 12 +- 3 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts diff --git a/src/panels/lovelace/cards/hui-conditional-card.ts b/src/panels/lovelace/cards/hui-conditional-card.ts index b395966a17..f5e72e57b1 100644 --- a/src/panels/lovelace/cards/hui-conditional-card.ts +++ b/src/panels/lovelace/cards/hui-conditional-card.ts @@ -2,12 +2,26 @@ import { customElement } from "lit-element"; import { HuiConditionalBase } from "../components/hui-conditional-base"; import { createCardElement } from "../create-element/create-card-element"; -import { LovelaceCard } from "../types"; +import { LovelaceCard, LovelaceCardEditor } from "../types"; import { computeCardSize } from "../common/compute-card-size"; import { ConditionalCardConfig } from "./types"; @customElement("hui-conditional-card") class HuiConditionalCard extends HuiConditionalBase implements LovelaceCard { + public static async getConfigElement(): Promise { + await import( + /* webpackChunkName: "hui-conditional-card-editor" */ "../editor/config-elements/hui-conditional-card-editor" + ); + return document.createElement("hui-conditional-card-editor"); + } + + public static getStubConfig(): object { + return { + conditions: [], + card: {}, + }; + } + public setConfig(config: ConditionalCardConfig): void { this.validateConfig(config); diff --git a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts new file mode 100644 index 0000000000..b1c83867ab --- /dev/null +++ b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts @@ -0,0 +1,277 @@ +import { + html, + LitElement, + TemplateResult, + customElement, + property, + CSSResult, + css, +} from "lit-element"; +import "@polymer/paper-tabs"; + +import { struct } from "../../common/structs/struct"; +import { HomeAssistant } from "../../../../types"; +import { LovelaceCardEditor } from "../../types"; +import { StackCardConfig } from "../../cards/types"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { LovelaceConfig } from "../../../../data/lovelace"; + +import "../../../../components/entity/ha-entity-picker"; +import "../../../../components/ha-switch"; + +const conditionStruct = struct({ + entity: "string", + state: "string?", + state_not: "string?", +}); +const cardConfigStruct = struct({ + type: "string", + card: "any", + conditions: struct.optional([conditionStruct]), +}); + +@customElement("hui-conditional-card-editor") +export class HuiConditionalCardEditor extends LitElement + implements LovelaceCardEditor { + @property() public hass?: HomeAssistant; + @property() public lovelace?: LovelaceConfig; + @property() private _config?: StackCardConfig; + @property() private _cardTab: boolean = false; + + public setConfig(config: StackCardConfig): void { + this._config = cardConfigStruct(config); + } + + protected render(): TemplateResult { + if (!this.hass || !this._config) { + return html``; + } + + return html` + + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.conditional.conditions" + )} + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.conditional.card" + )} + + ${this._cardTab + ? html` +
+ ${this._config.card.type + ? html` +
+ ${this.hass!.localize( + "ui.panel.lovelace.editor.card.conditional.change_type" + )} +
+ + ` + : html` + + `} +
+ ` + : html` +
+ ${this.hass!.localize( + "ui.panel.lovelace.editor.card.conditional.condition_explanation" + )} + ${this._config.conditions.map((cond, idx) => { + return html` +
+
+ +
+
+ + + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.conditional.state_equal" + )} + ${this.hass!.localize( + "ui.panel.lovelace.editor.card.conditional.state_not_equal" + )} + + + +
+
+ `; + })} +
+ +
+
+ `} + `; + } + + private _selectTab(ev: Event): void { + this._cardTab = parseInt((ev.target! as any).selected!, 10) === 1; + } + + private _handleCardChanged(ev: CustomEvent): void { + ev.stopPropagation(); + if (!this._config) { + return; + } + this._config.card = ev.detail.config; + fireEvent(this, "config-changed", { config: this._config }); + } + private _handleReplaceCard(): void { + if (!this._config) { + return; + } + this._config.card = {}; + fireEvent(this, "config-changed", { config: this._config }); + } + + private _addCondition(ev: Event): void { + const target = ev.target! as any; + if (target.value === "" || !this._config) { + return; + } + this._config.conditions.push({ + entity: target.value, + state: "", + }); + target.value = ""; + fireEvent(this, "config-changed", { config: this._config }); + } + private _changeCondition(ev: Event): void { + const target = ev.target as any; + if (!this._config || !target) { + return; + } + if (target.configValue === "entity" && target.value === "") { + this._config.conditions.splice(target.index, 1); + } else { + const condition = this._config.conditions[target.index]; + if (target.configValue === "entity") { + condition.entity = target.value; + } else if (target.configValue === "state") { + if (condition.state_not !== undefined) { + condition.state_not = target.value; + } else { + condition.state = target.value; + } + } else if (target.configValue === "invert") { + if (target.selected === 1) { + if (condition.state) { + condition.state_not = condition.state; + delete condition.state; + } + } else { + if (condition.state_not) { + condition.state = condition.state_not; + delete condition.state_not; + } + } + } + this._config.conditions[target.index] = condition; + } + fireEvent(this, "config-changed", { config: this._config }); + } + + static get styles(): CSSResult { + return css` + paper-tabs { + --paper-tabs-selection-bar-color: var(--primary-color); + --paper-tab-ink: var(--primary-color); + border-bottom: 1px solid var(--divider-color); + } + .conditions { + margin-top: 8px; + } + .condition { + margin-top: 8px; + border: 1px solid var(--divider-color); + padding: 12px; + } + .condition .state { + display: flex; + align-items: flex-end; + } + .condition .state paper-dropdown-menu { + margin-right: 16px; + } + .condition .state paper-input { + flex-grow: 1; + } + + .card { + margin-top: 8px; + border: 1px solid var(--divider-color); + padding: 12px; + } + @media (max-width: 450px) { + .card, + .condition { + margin: 8px -12px 0; + } + } + .card .card-options { + display: flex; + justify-content: flex-end; + width: 100%; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "hui-conditional-card-editor": HuiConditionalCardEditor; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 4370783005..7021bf57fc 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1966,7 +1966,14 @@ }, "conditional": { "name": "Conditional", - "description": "The Conditional card displays another card based on entity states." + "description": "The Conditional card displays another card based on entity states.", + "conditions": "Conditions", + "card": "Card", + "state_equal": "State is equal to", + "state_not_equal": "State is not equal to", + "current_state": "current", + "condition_explanation": "The card will be shown when ALL conditions below are fulfilled.", + "change_type": "Change type" }, "config": { "required": "Required", @@ -2041,7 +2048,8 @@ "title": "Title", "theme": "Theme", "unit": "Unit", - "url": "Url" + "url": "Url", + "state": "State" }, "map": { "name": "Map",