Move condition editor into its own file (#18340)

This commit is contained in:
Paul Bottein 2023-10-23 15:31:49 +02:00 committed by GitHub
parent aeaf091b50
commit 4354ad3807
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 204 additions and 155 deletions

View File

@ -6,6 +6,7 @@ import { customElement, property, state } from "lit/decorators";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation"; import { stopPropagation } from "../../../../common/dom/stop_propagation";
import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-button-menu"; import "../../../../components/ha-button-menu";
import "../../../../components/ha-icon-button"; import "../../../../components/ha-icon-button";
import "../../../../components/ha-list-item"; import "../../../../components/ha-list-item";
@ -14,15 +15,14 @@ import "../../../../components/ha-yaml-editor";
import { haStyle } from "../../../../resources/styles"; import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { ICON_CONDITION } from "../../common/icon-condition"; import { ICON_CONDITION } from "../../common/icon-condition";
import { Condition } from "../../common/validate-condition"; import { Condition, LegacyCondition } from "../../common/validate-condition";
import type { LovelaceConditionEditorConstructor } from "./types"; import type { LovelaceConditionEditorConstructor } from "./types";
import { handleStructError } from "../../../../common/structs/handle-errors";
@customElement("ha-card-condition-editor") @customElement("ha-card-condition-editor")
export default class HaCardConditionEditor extends LitElement { export default class HaCardConditionEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) condition!: Condition; @property({ attribute: false }) condition!: Condition | LegacyCondition;
@state() public _yamlMode = false; @state() public _yamlMode = false;
@ -30,20 +30,25 @@ export default class HaCardConditionEditor extends LitElement {
@state() public _uiWarnings: string[] = []; @state() public _uiWarnings: string[] = [];
private get _editor() { @state() _condition?: Condition;
const element = customElements.get(
`ha-card-condition-${this.condition.condition}`
) as LovelaceConditionEditorConstructor | undefined;
return element; private get _editor() {
if (!this._condition) return undefined;
return customElements.get(
`ha-card-condition-${this._condition.condition}`
) as LovelaceConditionEditorConstructor | undefined;
} }
protected willUpdate(changedProperties: PropertyValues): void { protected willUpdate(changedProperties: PropertyValues): void {
if (changedProperties.has("condition")) { if (changedProperties.has("condition")) {
this._condition = {
condition: "state",
...this.condition,
};
const validator = this._editor?.validateUIConfig; const validator = this._editor?.validateUIConfig;
if (validator) { if (validator) {
try { try {
validator(this.condition, this.hass); validator(this._condition, this.hass);
this._uiAvailable = true; this._uiAvailable = true;
this._uiWarnings = []; this._uiWarnings = [];
} catch (err) { } catch (err) {
@ -65,7 +70,9 @@ export default class HaCardConditionEditor extends LitElement {
} }
protected render() { protected render() {
const condition = this.condition; const condition = this._condition;
if (!condition) return nothing;
return html` return html`
<div class="header"> <div class="header">
@ -75,7 +82,7 @@ export default class HaCardConditionEditor extends LitElement {
></ha-svg-icon> ></ha-svg-icon>
<span class="title"> <span class="title">
${this.hass.localize( ${this.hass.localize(
`ui.panel.lovelace.editor.card.conditional.condition.${condition.condition}.label` `ui.panel.lovelace.editor.condition-editor.condition.${condition.condition}.label`
) || condition.condition} ) || condition.condition}
</span> </span>
<ha-button-menu <ha-button-menu

View File

@ -0,0 +1,144 @@
import { mdiPlus } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/ha-button";
import "../../../../components/ha-list-item";
import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-svg-icon";
import type { HomeAssistant } from "../../../../types";
import { ICON_CONDITION } from "../../common/icon-condition";
import { Condition, LegacyCondition } from "../../common/validate-condition";
import "./ha-card-condition-editor";
import { LovelaceConditionEditorConstructor } from "./types";
import "./types/ha-card-condition-screen";
import "./types/ha-card-condition-state";
const UI_CONDITION = [
"state",
"screen",
] as const satisfies readonly Condition["condition"][];
@customElement("ha-card-conditions-editor")
export class HaCardConditionsEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public conditions!: (
| Condition
| LegacyCondition
)[];
protected render() {
return html`
<div class="conditions">
${this.hass!.localize(
"ui.panel.lovelace.editor.condition-editor.explanation"
)}
${this.conditions.map(
(cond, idx) => html`
<div class="condition">
<ha-card-condition-editor
.index=${idx}
@value-changed=${this._conditionChanged}
.hass=${this.hass}
.condition=${cond}
></ha-card-condition-editor>
</div>
`
)}
<div>
<ha-button-menu
@action=${this._addCondition}
fixed
@closed=${stopPropagation}
>
<ha-button
slot="trigger"
outlined
.label=${this.hass.localize(
"ui.panel.lovelace.editor.condition-editor.add"
)}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
${UI_CONDITION.map(
(condition) => html`
<ha-list-item .value=${condition} graphic="icon">
${this.hass!.localize(
`ui.panel.lovelace.editor.condition-editor.condition.${condition}.label`
) || condition}
<ha-svg-icon
slot="graphic"
.path=${ICON_CONDITION[condition]}
></ha-svg-icon>
</ha-list-item>
`
)}
</ha-button-menu>
</div>
</div>
`;
}
private _addCondition(ev: CustomEvent): void {
const condition = (ev.currentTarget as HaSelect).items[ev.detail.index]
.value as Condition["condition"];
const conditions = [...this.conditions];
const elClass = customElements.get(`ha-card-condition-${condition}`) as
| LovelaceConditionEditorConstructor
| undefined;
conditions.push(
elClass?.defaultConfig
? { ...elClass.defaultConfig }
: { condition: condition }
);
fireEvent(this, "value-changed", { value: conditions });
}
private _conditionChanged(ev: CustomEvent) {
ev.stopPropagation();
const conditions = [...this.conditions];
const newValue = ev.detail.value;
const index = (ev.target as any).index;
if (newValue === null) {
conditions.splice(index, 1);
} else {
conditions[index] = newValue;
}
fireEvent(this, "value-changed", { value: conditions });
}
static get styles(): CSSResultGroup {
return [
css`
mwc-tab-bar {
border-bottom: 1px solid var(--divider-color);
}
.conditions {
margin-top: 8px;
}
.condition {
margin-top: 8px;
border: 1px solid var(--divider-color);
}
.condition .content {
padding: 12px;
}
ha-button-menu {
margin-top: 12px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-card-conditions-editor": HaCardConditionsEditor;
}
}

View File

@ -129,11 +129,11 @@ export class HaCardConditionScreen extends LitElement {
return { return {
value: b, value: b,
label: `${localize( label: `${localize(
`ui.panel.lovelace.editor.card.conditional.condition.screen.breakpoints_list.${b}` `ui.panel.lovelace.editor.condition-editor.condition.screen.breakpoints_list.${b}`
)}${ )}${
value value
? ` (${localize( ? ` (${localize(
`ui.panel.lovelace.editor.card.conditional.condition.screen.min`, `ui.panel.lovelace.editor.condition-editor.condition.screen.min`,
{ size: value } { size: value }
)})` )})`
: "" : ""
@ -188,7 +188,7 @@ export class HaCardConditionScreen extends LitElement {
switch (schema.name) { switch (schema.name) {
case "breakpoints": case "breakpoints":
return this.hass.localize( return this.hass.localize(
`ui.panel.lovelace.editor.card.conditional.condition.screen.${schema.name}` `ui.panel.lovelace.editor.condition-editor.condition.screen.${schema.name}`
); );
default: default:
return ""; return "";

View File

@ -61,19 +61,20 @@ export class HaCardConditionState extends LitElement {
schema: [ schema: [
{ {
name: "invert", name: "invert",
required: true,
selector: { selector: {
select: { select: {
mode: "dropdown", mode: "dropdown",
options: [ options: [
{ {
label: localize( label: localize(
"ui.panel.lovelace.editor.card.conditional.state_equal" "ui.panel.lovelace.editor.condition-editor.condition.state.state_equal"
), ),
value: "false", value: "false",
}, },
{ {
label: localize( label: localize(
"ui.panel.lovelace.editor.card.conditional.state_not_equal" "ui.panel.lovelace.editor.condition-editor.condition.state.state_not_equal"
), ),
value: "true", value: "true",
}, },
@ -148,7 +149,7 @@ export class HaCardConditionState extends LitElement {
return `${this.hass.localize( return `${this.hass.localize(
"ui.components.entity.entity-state-picker.state" "ui.components.entity.entity-state-picker.state"
)} (${this.hass.localize( )} (${this.hass.localize(
"ui.panel.lovelace.editor.card.conditional.current_state" "ui.panel.lovelace.editor.condition-editor.condition.state.current_state"
)}: ${this.hass.formatEntityState(entity)})`; )}: ${this.hass.formatEntityState(entity)})`;
} }
return `${this.hass.localize( return `${this.hass.localize(

View File

@ -1,23 +1,15 @@
import "@material/mwc-tab-bar/mwc-tab-bar"; import "@material/mwc-tab-bar/mwc-tab-bar";
import "@material/mwc-tab/mwc-tab"; import "@material/mwc-tab/mwc-tab";
import type { MDCTabBarActivatedEvent } from "@material/tab-bar"; import type { MDCTabBarActivatedEvent } from "@material/tab-bar";
import { import { mdiCodeBraces, mdiContentCopy, mdiListBoxOutline } from "@mdi/js";
mdiCodeBraces,
mdiContentCopy,
mdiListBoxOutline,
mdiPlus,
} from "@mdi/js";
import deepClone from "deep-clone-simple"; import deepClone from "deep-clone-simple";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { any, array, assert, assign, object, optional } from "superstruct"; import { any, array, assert, assign, object, optional } from "superstruct";
import { storage } from "../../../../common/decorators/storage"; import { storage } from "../../../../common/decorators/storage";
import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event"; import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/ha-button"; import "../../../../components/ha-button";
import "../../../../components/ha-list-item"; import "../../../../components/ha-list-item";
import "../../../../components/ha-menu-button";
import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-svg-icon"; import "../../../../components/ha-svg-icon";
import type { import type {
LovelaceCardConfig, LovelaceCardConfig,
@ -25,27 +17,17 @@ import type {
} from "../../../../data/lovelace"; } from "../../../../data/lovelace";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import type { ConditionalCardConfig } from "../../cards/types"; import type { ConditionalCardConfig } from "../../cards/types";
import { ICON_CONDITION } from "../../common/icon-condition";
import { Condition } from "../../common/validate-condition";
import type { LovelaceCardEditor } from "../../types"; import type { LovelaceCardEditor } from "../../types";
import "../card-editor/hui-card-element-editor"; import "../card-editor/hui-card-element-editor";
import type { HuiCardElementEditor } from "../card-editor/hui-card-element-editor"; import type { HuiCardElementEditor } from "../card-editor/hui-card-element-editor";
import "../card-editor/hui-card-picker"; import "../card-editor/hui-card-picker";
import "../conditions/ha-card-condition-editor"; import "../conditions/ha-card-conditions-editor";
import { LovelaceConditionEditorConstructor } from "../conditions/types";
import "../conditions/types/ha-card-condition-screen";
import "../conditions/types/ha-card-condition-state";
import "../hui-element-editor"; import "../hui-element-editor";
import type { ConfigChangedEvent } from "../hui-element-editor"; import type { ConfigChangedEvent } from "../hui-element-editor";
import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import type { GUIModeChangedEvent } from "../types"; import type { GUIModeChangedEvent } from "../types";
import { configElementStyle } from "./config-elements-style"; import { configElementStyle } from "./config-elements-style";
const UI_CONDITION = [
"state",
"screen",
] as const satisfies readonly Condition["condition"][];
const cardConfigStruct = assign( const cardConfigStruct = assign(
baseLovelaceCardConfig, baseLovelaceCardConfig,
object({ object({
@ -162,59 +144,12 @@ export class HuiConditionalCardEditor
</div> </div>
` `
: html` : html`
<div class="conditions"> <ha-card-conditions-editor
<ha-alert alert-type="info">
${this.hass!.localize(
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
)}
</ha-alert>
${this._config.conditions.map((cond, idx) => {
const condition: Condition = {
condition: "state",
...cond,
};
return html`
<div class="condition">
<ha-card-condition-editor
.index=${idx}
@value-changed=${this._conditionChanged}
.hass=${this.hass} .hass=${this.hass}
.condition=${condition} .conditions=${this._config.conditions}
></ha-card-condition-editor> @value-changed=${this._conditionChanged}
</div>
`;
})}
<div>
<ha-button-menu
@action=${this._addCondition}
fixed
@closed=${stopPropagation}
> >
<ha-button </ha-card-conditions-editor>
slot="trigger"
outlined
.label=${this.hass.localize(
"ui.panel.lovelace.editor.card.conditional.add_condition"
)}
>
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
</ha-button>
${UI_CONDITION.map(
(condition) => html`
<ha-list-item .value=${condition} graphic="icon">
${this.hass!.localize(
`ui.panel.lovelace.editor.card.conditional.condition.${condition}.label`
) || condition}
<ha-svg-icon
slot="graphic"
.path=${ICON_CONDITION[condition]}
></ha-svg-icon>
</ha-list-item>
`
)}
</ha-button-menu>
</div>
</div>
`} `}
`; `;
} }
@ -281,43 +216,16 @@ export class HuiConditionalCardEditor
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
private _addCondition(ev: CustomEvent): void { private _conditionChanged(ev: CustomEvent) {
const condition = (ev.currentTarget as HaSelect).items[ev.detail.index] ev.stopPropagation();
.value as Condition["condition"];
if (!this._config) { if (!this._config) {
return; return;
} }
const conditions = [...this._config.conditions]; const conditions = ev.detail.value;
const elClass = customElements.get(`ha-card-condition-${condition}`) as
| LovelaceConditionEditorConstructor
| undefined;
conditions.push(
elClass?.defaultConfig
? { ...elClass.defaultConfig }
: { condition: condition }
);
this._config = { ...this._config, conditions }; this._config = { ...this._config, conditions };
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
private _conditionChanged(ev: CustomEvent) {
ev.stopPropagation();
const conditions = [...this._config!.conditions];
const newValue = ev.detail.value;
const index = (ev.target as any).index;
if (newValue === null) {
conditions.splice(index, 1);
} else {
conditions[index] = newValue;
}
this._config = { ...this._config!, conditions };
fireEvent(this, "config-changed", { config: this._config });
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
configElementStyle, configElementStyle,
@ -325,19 +233,6 @@ export class HuiConditionalCardEditor
mwc-tab-bar { mwc-tab-bar {
border-bottom: 1px solid var(--divider-color); border-bottom: 1px solid var(--divider-color);
} }
.conditions {
margin-top: 8px;
}
.condition {
margin-top: 8px;
border: 1px solid var(--divider-color);
}
.condition .content {
padding: 12px;
}
ha-button-menu {
margin-top: 12px;
}
.card { .card {
margin-top: 8px; margin-top: 8px;
border: 1px solid var(--divider-color); border: 1px solid var(--divider-color);

View File

@ -4754,6 +4754,29 @@
"none": "No action" "none": "No action"
} }
}, },
"condition-editor": {
"explanation": "The card will be shown when ALL conditions below are fulfilled.",
"add": "Add condition",
"condition": {
"screen": {
"label": "Screen",
"breakpoints": "Screen sizes",
"breakpoints_list": {
"mobile": "Mobile",
"tablet": "Tablet",
"desktop": "Desktop",
"wide": "Wide"
},
"min": "min: {size}px"
},
"state": {
"label": "Entity state",
"state_equal": "State is equal to",
"state_not_equal": "State is not equal to",
"current_state": "current"
}
}
},
"card": { "card": {
"alarm-panel": { "alarm-panel": {
"name": "Alarm panel", "name": "Alarm panel",
@ -4782,28 +4805,7 @@
"description": "The Conditional card displays another card based on entity states.", "description": "The Conditional card displays another card based on entity states.",
"conditions": "Conditions", "conditions": "Conditions",
"card": "Card", "card": "Card",
"state_equal": "State is equal to", "change_type": "Change type"
"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",
"add_condition": "Add condition",
"condition": {
"screen": {
"label": "Screen",
"breakpoints": "Screen sizes",
"breakpoints_list": {
"mobile": "Mobile",
"tablet": "Tablet",
"desktop": "Desktop",
"wide": "Wide"
},
"min": "min: {size}px"
},
"state": {
"label": "Entity state"
}
}
}, },
"config": { "config": {
"required": "required", "required": "required",