Add "Add condition" button in the editor

This commit is contained in:
Paul Bottein 2023-09-28 17:48:57 +02:00
parent 873ce2b405
commit 3a03abbd3a
No known key found for this signature in database
7 changed files with 111 additions and 49 deletions

View File

@ -3,6 +3,12 @@ import { HomeAssistant } from "../../../types";
export type Condition = StateCondition | ResponsiveCondition; export type Condition = StateCondition | ResponsiveCondition;
export type LegacyCondition = {
entity?: string;
state?: string;
state_not?: string;
};
export type StateCondition = { export type StateCondition = {
condition: "state"; condition: "state";
entity?: string; entity?: string;
@ -50,7 +56,7 @@ export function checkConditionsMet(
function valideStateCondition(condition: StateCondition) { function valideStateCondition(condition: StateCondition) {
return ( return (
!!condition.entity && condition.entity != null &&
(condition.state != null || condition.state_not != null) (condition.state != null || condition.state_not != null)
); );
} }

View File

@ -1,34 +1,36 @@
import { mdiCodeBraces, mdiDelete, mdiListBoxOutline } from "@mdi/js"; import { mdiCodeBraces, mdiDelete, mdiListBoxOutline } from "@mdi/js";
import { LitElement, css, html, nothing } from "lit"; import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; 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 "../../../components/ha-icon-button"; import "../../../../components/ha-icon-button";
import "../../../components/ha-yaml-editor"; 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 "./types/ha-card-condition-responsive"; import { Condition, LegacyCondition } from "../../common/validate-condition";
import "./types/ha-card-condition-state"; import type { LovelaceConditionEditorConstructor } from "./types";
import { Condition } from "./validate-condition";
@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;
protected render() { protected render() {
const condition = this.condition; const condition: Condition = {
condition: "state",
...this.condition,
};
const element = customElements.get( const element = customElements.get(
`ha-card-condition-${condition.condition}` `ha-card-condition-${condition.condition}`
) as any | undefined; ) as LovelaceConditionEditorConstructor | undefined;
const supported = element !== undefined; const supported = element !== undefined;
const valid = const valid =
element && element &&
(!element.validateUIConfig || element.validateUIConfig(this.condition)); (!element.validateUIConfig || element.validateUIConfig(condition));
const yamlMode = this._yamlMode || !supported || !valid; const yamlMode = this._yamlMode || !supported || !valid;

View File

@ -0,0 +1,6 @@
import { Condition } from "../../common/validate-condition";
export interface LovelaceConditionEditorConstructor {
defaultConfig?: Condition;
validateUIConfig?: (condition: Condition) => boolean;
}

View File

@ -1,14 +1,14 @@
import { html, LitElement } from "lit"; import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { getAllCombinations } from "../../../../common/array/combinations"; import { getAllCombinations } from "../../../../../common/array/combinations";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { LocalizeFunc } from "../../../../common/translations/localize"; import { LocalizeFunc } from "../../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form"; import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../../components/ha-form/types";
import { HaFormSchema } from "../../../../components/ha-form/types"; import { HaFormSchema } from "../../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../../types";
import { ResponsiveCondition } from "../validate-condition"; import { ResponsiveCondition } from "../../../common/validate-condition";
const BREAKPOINT_VALUES = [0, 768, 1024, 1280, Infinity]; const BREAKPOINT_VALUES = [0, 768, 1024, 1280, Infinity];
const BREAKPOINTS = ["mobile", "tablet", "desktop", "wide"] as const; const BREAKPOINTS = ["mobile", "tablet", "desktop", "wide"] as const;

View File

@ -2,13 +2,13 @@ import { html, LitElement, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { assert, literal, object, optional, string } from "superstruct"; import { assert, literal, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";
import { LocalizeFunc } from "../../../../common/translations/localize"; import { LocalizeFunc } from "../../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form"; import "../../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { SchemaUnion } from "../../../../../components/ha-form/types";
import { HaFormSchema } from "../../../../components/ha-form/types"; import { HaFormSchema } from "../../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../../types";
import { StateCondition } from "../validate-condition"; import { StateCondition } from "../../../common/validate-condition";
const stateConditionStruct = object({ const stateConditionStruct = object({
condition: literal("state"), condition: literal("state"),
@ -33,7 +33,7 @@ export class HaCardConditionState extends LitElement {
@property({ type: Boolean }) public disabled = false; @property({ type: Boolean }) public disabled = false;
public static get defaultConfig(): StateCondition { public static get defaultConfig(): StateCondition {
return { condition: "state", entity: "" }; return { condition: "state", entity: "", state: "" };
} }
protected willUpdate(changedProperties: PropertyValues): void { protected willUpdate(changedProperties: PropertyValues): void {

View File

@ -1,31 +1,48 @@
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 { mdiCodeBraces, mdiContentCopy, mdiListBoxOutline } from "@mdi/js"; import {
mdiCodeBraces,
mdiContentCopy,
mdiContentDuplicate,
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 "../../../../components/entity/ha-entity-picker"; import { stopPropagation } from "../../../../common/dom/stop_propagation";
import "../../../../components/ha-button";
import "../../../../components/ha-list-item";
import "../../../../components/ha-menu-button";
import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-svg-icon";
import type { import type {
LovelaceCardConfig, LovelaceCardConfig,
LovelaceConfig, LovelaceConfig,
} 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 "../../common/ha-card-condition-editor"; 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/types/ha-card-condition-responsive";
import "../conditions/types/ha-card-condition-state";
import { LovelaceConditionEditorConstructor } from "../conditions/types";
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: Condition["condition"][] = ["state", "responsive"];
const cardConfigStruct = assign( const cardConfigStruct = assign(
baseLovelaceCardConfig, baseLovelaceCardConfig,
object({ object({
@ -158,13 +175,35 @@ export class HuiConditionalCardEditor
</div> </div>
` `
)} )}
<div class="condition"> <div>
<div class="content"> <ha-button-menu
<ha-entity-picker @action=${this._addCondition}
.hass=${this.hass} fixed
@change=${this._addCondition} @closed=${stopPropagation}
></ha-entity-picker> >
</div> <ha-button
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=${mdiContentDuplicate}
></ha-svg-icon>
</ha-list-item>
`
)}
</ha-button-menu>
</div> </div>
</div> </div>
`} `}
@ -233,19 +272,24 @@ export class HuiConditionalCardEditor
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
private _addCondition(ev: Event): void { private _addCondition(ev: CustomEvent): void {
const target = ev.target! as any; const condition = (ev.currentTarget as HaSelect).items[ev.detail.index]
if (target.value === "" || !this._config) { .value as Condition["condition"];
if (!this._config) {
return; return;
} }
const conditions = [...this._config.conditions]; const conditions = [...this._config.conditions];
conditions.push({
condition: "state", const elClass = customElements.get(`ha-card-condition-${condition}`) as
entity: target.value, | LovelaceConditionEditorConstructor
state: "", | undefined;
});
conditions.push(
elClass?.defaultConfig
? { ...elClass.defaultConfig }
: { condition: condition }
);
this._config = { ...this._config, conditions }; this._config = { ...this._config, conditions };
target.value = "";
fireEvent(this, "config-changed", { config: this._config }); fireEvent(this, "config-changed", { config: this._config });
} }
@ -282,6 +326,9 @@ export class HuiConditionalCardEditor
.condition .content { .condition .content {
padding: 12px; 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

@ -4769,6 +4769,7 @@
"current_state": "current", "current_state": "current",
"condition_explanation": "The card will be shown when ALL conditions below are fulfilled.", "condition_explanation": "The card will be shown when ALL conditions below are fulfilled.",
"change_type": "Change type", "change_type": "Change type",
"add_condition": "Add condition",
"condition": { "condition": {
"responsive": { "responsive": {
"label": "Responsive", "label": "Responsive",