Skip to content

Commit 503dec7

Browse files
authored
GUI editor for conditional card (#5051)
* GUI editor for conditional card * Typing * Fix typos. Remove quotes * Add lovelace to card picker * Address review comments
1 parent 5a2649a commit 503dec7

File tree

3 files changed

+302
-3
lines changed

3 files changed

+302
-3
lines changed

src/panels/lovelace/cards/hui-conditional-card.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,26 @@ import { customElement } from "lit-element";
22

33
import { HuiConditionalBase } from "../components/hui-conditional-base";
44
import { createCardElement } from "../create-element/create-card-element";
5-
import { LovelaceCard } from "../types";
5+
import { LovelaceCard, LovelaceCardEditor } from "../types";
66
import { computeCardSize } from "../common/compute-card-size";
77
import { ConditionalCardConfig } from "./types";
88

99
@customElement("hui-conditional-card")
1010
class HuiConditionalCard extends HuiConditionalBase implements LovelaceCard {
11+
public static async getConfigElement(): Promise<LovelaceCardEditor> {
12+
await import(
13+
/* webpackChunkName: "hui-conditional-card-editor" */ "../editor/config-elements/hui-conditional-card-editor"
14+
);
15+
return document.createElement("hui-conditional-card-editor");
16+
}
17+
18+
public static getStubConfig(): object {
19+
return {
20+
conditions: [],
21+
card: {},
22+
};
23+
}
24+
1125
public setConfig(config: ConditionalCardConfig): void {
1226
this.validateConfig(config);
1327

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
import {
2+
html,
3+
LitElement,
4+
TemplateResult,
5+
customElement,
6+
property,
7+
CSSResult,
8+
css,
9+
} from "lit-element";
10+
import "@polymer/paper-tabs";
11+
12+
import { struct } from "../../common/structs/struct";
13+
import { HomeAssistant } from "../../../../types";
14+
import { LovelaceCardEditor } from "../../types";
15+
import { StackCardConfig } from "../../cards/types";
16+
import { fireEvent } from "../../../../common/dom/fire_event";
17+
import { LovelaceConfig } from "../../../../data/lovelace";
18+
19+
import "../../../../components/entity/ha-entity-picker";
20+
import "../../../../components/ha-switch";
21+
22+
const conditionStruct = struct({
23+
entity: "string",
24+
state: "string?",
25+
state_not: "string?",
26+
});
27+
const cardConfigStruct = struct({
28+
type: "string",
29+
card: "any",
30+
conditions: struct.optional([conditionStruct]),
31+
});
32+
33+
@customElement("hui-conditional-card-editor")
34+
export class HuiConditionalCardEditor extends LitElement
35+
implements LovelaceCardEditor {
36+
@property() public hass?: HomeAssistant;
37+
@property() public lovelace?: LovelaceConfig;
38+
@property() private _config?: StackCardConfig;
39+
@property() private _cardTab: boolean = false;
40+
41+
public setConfig(config: StackCardConfig): void {
42+
this._config = cardConfigStruct(config);
43+
}
44+
45+
protected render(): TemplateResult {
46+
if (!this.hass || !this._config) {
47+
return html``;
48+
}
49+
50+
return html`
51+
<paper-tabs
52+
.selected=${this._cardTab ? "1" : "0"}
53+
@iron-select=${this._selectTab}
54+
>
55+
<paper-tab
56+
>${this.hass!.localize(
57+
"ui.panel.lovelace.editor.card.conditional.conditions"
58+
)}</paper-tab
59+
>
60+
<paper-tab
61+
>${this.hass!.localize(
62+
"ui.panel.lovelace.editor.card.conditional.card"
63+
)}</paper-tab
64+
>
65+
</paper-tabs>
66+
${this._cardTab
67+
? html`
68+
<div class="card">
69+
${this._config.card.type
70+
? html`
71+
<div class="card-options">
72+
<mwc-button @click=${this._handleReplaceCard}
73+
>${this.hass!.localize(
74+
"ui.panel.lovelace.editor.card.conditional.change_type"
75+
)}</mwc-button
76+
>
77+
</div>
78+
<hui-card-editor
79+
.hass=${this.hass}
80+
.value=${this._config.card}
81+
@config-changed=${this._handleCardChanged}
82+
></hui-card-editor>
83+
`
84+
: html`
85+
<hui-card-picker
86+
.hass=${this.hass}
87+
.lovelace=${this.lovelace}
88+
@config-changed=${this._handleCardChanged}
89+
></hui-card-picker>
90+
`}
91+
</div>
92+
`
93+
: html`
94+
<div class="conditions">
95+
${this.hass!.localize(
96+
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
97+
)}
98+
${this._config.conditions.map((cond, idx) => {
99+
return html`
100+
<div class="condition">
101+
<div class="entity">
102+
<ha-entity-picker
103+
.hass=${this.hass}
104+
.value=${cond.entity}
105+
.index=${idx}
106+
.configValue=${"entity"}
107+
@change=${this._changeCondition}
108+
allow-custom-entity
109+
></ha-entity-picker>
110+
</div>
111+
<div class="state">
112+
<paper-dropdown-menu>
113+
<paper-listbox
114+
.selected=${cond.state_not !== undefined ? 1 : 0}
115+
slot="dropdown-content"
116+
.index=${idx}
117+
.configValue=${"invert"}
118+
@selected-item-changed=${this._changeCondition}
119+
>
120+
<paper-item
121+
>${this.hass!.localize(
122+
"ui.panel.lovelace.editor.card.conditional.state_equal"
123+
)}</paper-item
124+
>
125+
<paper-item
126+
>${this.hass!.localize(
127+
"ui.panel.lovelace.editor.card.conditional.state_not_equal"
128+
)}</paper-item
129+
>
130+
</paper-listbox>
131+
</paper-dropdown-menu>
132+
<paper-input
133+
.label="${this.hass!.localize(
134+
"ui.panel.lovelace.editor.card.generic.state"
135+
)} (${this.hass!.localize(
136+
"ui.panel.lovelace.editor.card.conditional.current_state"
137+
)}: '${this.hass?.states[cond.entity].state}')"
138+
.value=${cond.state_not !== undefined
139+
? cond.state_not
140+
: cond.state}
141+
.index=${idx}
142+
.configValue=${"state"}
143+
@value-changed=${this._changeCondition}
144+
></paper-input>
145+
</div>
146+
</div>
147+
`;
148+
})}
149+
<div class="condition">
150+
<ha-entity-picker
151+
.hass=${this.hass}
152+
@change=${this._addCondition}
153+
></ha-entity-picker>
154+
</div>
155+
</div>
156+
`}
157+
`;
158+
}
159+
160+
private _selectTab(ev: Event): void {
161+
this._cardTab = parseInt((ev.target! as any).selected!, 10) === 1;
162+
}
163+
164+
private _handleCardChanged(ev: CustomEvent): void {
165+
ev.stopPropagation();
166+
if (!this._config) {
167+
return;
168+
}
169+
this._config.card = ev.detail.config;
170+
fireEvent(this, "config-changed", { config: this._config });
171+
}
172+
private _handleReplaceCard(): void {
173+
if (!this._config) {
174+
return;
175+
}
176+
this._config.card = {};
177+
fireEvent(this, "config-changed", { config: this._config });
178+
}
179+
180+
private _addCondition(ev: Event): void {
181+
const target = ev.target! as any;
182+
if (target.value === "" || !this._config) {
183+
return;
184+
}
185+
this._config.conditions.push({
186+
entity: target.value,
187+
state: "",
188+
});
189+
target.value = "";
190+
fireEvent(this, "config-changed", { config: this._config });
191+
}
192+
private _changeCondition(ev: Event): void {
193+
const target = ev.target as any;
194+
if (!this._config || !target) {
195+
return;
196+
}
197+
if (target.configValue === "entity" && target.value === "") {
198+
this._config.conditions.splice(target.index, 1);
199+
} else {
200+
const condition = this._config.conditions[target.index];
201+
if (target.configValue === "entity") {
202+
condition.entity = target.value;
203+
} else if (target.configValue === "state") {
204+
if (condition.state_not !== undefined) {
205+
condition.state_not = target.value;
206+
} else {
207+
condition.state = target.value;
208+
}
209+
} else if (target.configValue === "invert") {
210+
if (target.selected === 1) {
211+
if (condition.state) {
212+
condition.state_not = condition.state;
213+
delete condition.state;
214+
}
215+
} else {
216+
if (condition.state_not) {
217+
condition.state = condition.state_not;
218+
delete condition.state_not;
219+
}
220+
}
221+
}
222+
this._config.conditions[target.index] = condition;
223+
}
224+
fireEvent(this, "config-changed", { config: this._config });
225+
}
226+
227+
static get styles(): CSSResult {
228+
return css`
229+
paper-tabs {
230+
--paper-tabs-selection-bar-color: var(--primary-color);
231+
--paper-tab-ink: var(--primary-color);
232+
border-bottom: 1px solid var(--divider-color);
233+
}
234+
.conditions {
235+
margin-top: 8px;
236+
}
237+
.condition {
238+
margin-top: 8px;
239+
border: 1px solid var(--divider-color);
240+
padding: 12px;
241+
}
242+
.condition .state {
243+
display: flex;
244+
align-items: flex-end;
245+
}
246+
.condition .state paper-dropdown-menu {
247+
margin-right: 16px;
248+
}
249+
.condition .state paper-input {
250+
flex-grow: 1;
251+
}
252+
253+
.card {
254+
margin-top: 8px;
255+
border: 1px solid var(--divider-color);
256+
padding: 12px;
257+
}
258+
@media (max-width: 450px) {
259+
.card,
260+
.condition {
261+
margin: 8px -12px 0;
262+
}
263+
}
264+
.card .card-options {
265+
display: flex;
266+
justify-content: flex-end;
267+
width: 100%;
268+
}
269+
`;
270+
}
271+
}
272+
273+
declare global {
274+
interface HTMLElementTagNameMap {
275+
"hui-conditional-card-editor": HuiConditionalCardEditor;
276+
}
277+
}

src/translations/en.json

+10-2
Original file line numberDiff line numberDiff line change
@@ -1966,7 +1966,14 @@
19661966
},
19671967
"conditional": {
19681968
"name": "Conditional",
1969-
"description": "The Conditional card displays another card based on entity states."
1969+
"description": "The Conditional card displays another card based on entity states.",
1970+
"conditions": "Conditions",
1971+
"card": "Card",
1972+
"state_equal": "State is equal to",
1973+
"state_not_equal": "State is not equal to",
1974+
"current_state": "current",
1975+
"condition_explanation": "The card will be shown when ALL conditions below are fulfilled.",
1976+
"change_type": "Change type"
19701977
},
19711978
"config": {
19721979
"required": "Required",
@@ -2041,7 +2048,8 @@
20412048
"title": "Title",
20422049
"theme": "Theme",
20432050
"unit": "Unit",
2044-
"url": "Url"
2051+
"url": "Url",
2052+
"state": "State"
20452053
},
20462054
"map": {
20472055
"name": "Map",

0 commit comments

Comments
 (0)