From c6e6255d1920caad2282cc43c367d79442605e87 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 27 Sep 2023 13:11:59 +0200 Subject: [PATCH] Add UI for responsive condition --- .../common/ha-card-condition-editor.ts | 1 + .../types/ha-card-condition-responsive.ts | 107 ++++++++++++++++ .../common/types/ha-card-condition-state.ts | 117 +++++++++++------- .../lovelace/common/validate-condition.ts | 2 +- .../hui-conditional-card-editor.ts | 33 +---- src/translations/en.json | 2 + 6 files changed, 185 insertions(+), 77 deletions(-) create mode 100644 src/panels/lovelace/common/types/ha-card-condition-responsive.ts diff --git a/src/panels/lovelace/common/ha-card-condition-editor.ts b/src/panels/lovelace/common/ha-card-condition-editor.ts index 7209868ed4..301cc6131e 100644 --- a/src/panels/lovelace/common/ha-card-condition-editor.ts +++ b/src/panels/lovelace/common/ha-card-condition-editor.ts @@ -8,6 +8,7 @@ import "../../../components/ha-yaml-editor"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import "./types/ha-card-condition-state"; +import "./types/ha-card-condition-responsive"; import { Condition } from "./validate-condition"; @customElement("ha-card-condition-editor") diff --git a/src/panels/lovelace/common/types/ha-card-condition-responsive.ts b/src/panels/lovelace/common/types/ha-card-condition-responsive.ts new file mode 100644 index 0000000000..0b753c2b89 --- /dev/null +++ b/src/panels/lovelace/common/types/ha-card-condition-responsive.ts @@ -0,0 +1,107 @@ +import { html, LitElement, PropertyValues } from "lit"; +import { customElement, property } from "lit/decorators"; +import { assert, literal, number, object, optional } from "superstruct"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../components/ha-form/types"; +import { HaFormSchema } from "../../../../components/ha-form/types"; +import type { HomeAssistant } from "../../../../types"; +import { ResponsiveCondition } from "../validate-condition"; + +const responsiveConditionStruct = object({ + condition: literal("responsive"), + max_width: optional(number()), + min_width: optional(number()), +}); + +const SCHEMA = [ + { + name: "", + type: "grid", + schema: [ + { + name: "min_width", + selector: { + number: { + mode: "box", + step: 1, + unit_of_measurement: "px", + }, + }, + }, + { + name: "max_width", + selector: { + number: { + mode: "box", + step: 1, + unit_of_measurement: "px", + }, + }, + }, + ], + }, +] as const satisfies readonly HaFormSchema[]; + +@customElement("ha-card-condition-responsive") +export class HaCardConditionResponsive extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public condition!: ResponsiveCondition; + + @property({ type: Boolean }) public disabled = false; + + public static get defaultConfig(): ResponsiveCondition { + return { condition: "responsive" }; + } + + protected willUpdate(changedProperties: PropertyValues): void { + if (!changedProperties.has("condition")) { + return; + } + try { + assert(this.condition, responsiveConditionStruct); + } catch (err: any) { + fireEvent(this, "ui-mode-not-available", err); + } + } + + protected render() { + return html` + + `; + } + + private _valueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const data = ev.detail.value as ResponsiveCondition; + fireEvent(this, "value-changed", { value: data }); + } + + private _computeLabelCallback = ( + schema: SchemaUnion + ): string => { + switch (schema.name) { + case "min_width": + case "max_width": + return this.hass.localize( + `ui.panel.lovelace.editor.card.conditional.${schema.name}` + ); + default: + return ""; + } + }; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-card-condition-responsive": HaCardConditionResponsive; + } +} diff --git a/src/panels/lovelace/common/types/ha-card-condition-state.ts b/src/panels/lovelace/common/types/ha-card-condition-state.ts index 436a3b3619..f728a54ff8 100644 --- a/src/panels/lovelace/common/types/ha-card-condition-state.ts +++ b/src/panels/lovelace/common/types/ha-card-condition-state.ts @@ -1,13 +1,22 @@ -import type { HassEntity } from "home-assistant-js-websocket"; -import { html, LitElement } from "lit"; +import { html, LitElement, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { assert, literal, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import { HaFormSchema } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; import { StateCondition } from "../validate-condition"; +const stateConditionStruct = object({ + condition: literal("state"), + entity: string(), + state: optional(string()), + state_not: optional(string()), +}); + type StateConditionData = { condition: "state"; entity: string; @@ -15,43 +24,6 @@ type StateConditionData = { state?: string; }; -const SCHEMA = [ - { name: "entity", selector: { entity: {} } }, - { - name: "", - type: "grid", - schema: [ - { - name: "invert", - selector: { - select: { - mode: "dropdown", - options: [ - { - label: "State equal", - value: "false", - }, - { - label: "State not equal", - value: "true", - }, - ], - }, - }, - }, - { - name: "state", - selector: { - state: {}, - }, - context: { - filter_entity: "entity", - }, - }, - ], - }, -] as const satisfies readonly HaFormSchema[]; - @customElement("ha-card-condition-state") export class HaCardConditionState extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -61,9 +33,64 @@ export class HaCardConditionState extends LitElement { @property({ type: Boolean }) public disabled = false; public static get defaultConfig(): StateCondition { - return { condition: "state", entity: "", state: "" }; + return { condition: "state", entity: "" }; } + protected willUpdate(changedProperties: PropertyValues): void { + if (!changedProperties.has("condition")) { + return; + } + try { + assert(this.condition, stateConditionStruct); + } catch (err: any) { + fireEvent(this, "ui-mode-not-available", err); + } + } + + private _schema = memoizeOne( + (localize: LocalizeFunc) => + [ + { name: "entity", selector: { entity: {} } }, + { + name: "", + type: "grid", + schema: [ + { + name: "invert", + selector: { + select: { + mode: "dropdown", + options: [ + { + label: localize( + "ui.panel.lovelace.editor.card.conditional.state_equal" + ), + value: "false", + }, + { + label: localize( + "ui.panel.lovelace.editor.card.conditional.state_not_equal" + ), + value: "true", + }, + ], + }, + }, + }, + { + name: "state", + selector: { + state: {}, + }, + context: { + filter_entity: "entity", + }, + }, + ], + }, + ] as const satisfies readonly HaFormSchema[] + ); + protected render() { const { state, state_not, ...content } = this.condition; @@ -77,7 +104,7 @@ export class HaCardConditionState extends LitElement { + schema: SchemaUnion> ): string => { - const entity = this.hass.states[this.condition.entity] as - | HassEntity - | undefined; + const entity = this.condition.entity + ? this.hass.states[this.condition.entity] + : undefined; switch (schema.name) { case "entity": return this.hass.localize("ui.components.entity.entity-picker.entity"); diff --git a/src/panels/lovelace/common/validate-condition.ts b/src/panels/lovelace/common/validate-condition.ts index 6c92dee597..66bb4d9f42 100644 --- a/src/panels/lovelace/common/validate-condition.ts +++ b/src/panels/lovelace/common/validate-condition.ts @@ -18,7 +18,7 @@ export type ResponsiveCondition = { function checkStateCondition(condition: StateCondition, hass: HomeAssistant) { const state = hass.states[condition.entity] - ? hass!.states[condition.entity].state + ? hass.states[condition.entity].state : UNAVAILABLE; return condition.state != null 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 index e4e098d323..26a490878c 100644 --- a/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-conditional-card-editor.ts @@ -1,4 +1,3 @@ -import "@material/mwc-list/mwc-list-item"; import "@material/mwc-tab-bar/mwc-tab-bar"; import "@material/mwc-tab/mwc-tab"; import type { MDCTabBarActivatedEvent } from "@material/tab-bar"; @@ -6,23 +5,10 @@ import { mdiCodeBraces, mdiContentCopy, mdiListBoxOutline } from "@mdi/js"; import deepClone from "deep-clone-simple"; import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; -import { - any, - array, - assert, - assign, - literal, - number, - object, - optional, - string, - union, -} from "superstruct"; +import { any, array, assert, assign, object, optional } from "superstruct"; import { storage } from "../../../../common/decorators/storage"; import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/entity/ha-entity-picker"; -import "../../../../components/ha-select"; -import "../../../../components/ha-textfield"; import type { LovelaceCardConfig, LovelaceConfig, @@ -40,26 +26,11 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct"; import type { GUIModeChangedEvent } from "../types"; import { configElementStyle } from "./config-elements-style"; -const stateConditionStruct = object({ - condition: optional(literal("state")), - entity: string(), - state: optional(string()), - state_not: optional(string()), -}); - -const responsiveConditionStruct = object({ - condition: literal("responsive"), - max_width: optional(number()), - min_width: optional(number()), -}); - const cardConfigStruct = assign( baseLovelaceCardConfig, object({ card: any(), - conditions: optional( - array(union([stateConditionStruct, responsiveConditionStruct])) - ), + conditions: optional(array(any())), }) ); diff --git a/src/translations/en.json b/src/translations/en.json index 3c9bfce684..aadbf74386 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4767,6 +4767,8 @@ "state_equal": "State is equal to", "state_not_equal": "State is not equal to", "current_state": "current", + "min_width": "Min width", + "max_width": "Max width", "condition_explanation": "The card will be shown when ALL conditions below are fulfilled.", "change_type": "Change type" },