mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-04 06:57:47 +00:00
Migrate state condition ui to ha-form
This commit is contained in:
parent
0a4401b417
commit
5b4504688a
102
src/panels/lovelace/common/ha-card-condition-editor.ts
Normal file
102
src/panels/lovelace/common/ha-card-condition-editor.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { mdiCodeBraces, mdiDelete, mdiListBoxOutline } from "@mdi/js";
|
||||||
|
import { LitElement, css, html } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||||
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import "../../../components/ha-icon-button";
|
||||||
|
import "../../../components/ha-yaml-editor";
|
||||||
|
import { haStyle } from "../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
import "./types/ha-card-condition-state";
|
||||||
|
import { Condition } from "./validate-condition";
|
||||||
|
|
||||||
|
@customElement("ha-card-condition-editor")
|
||||||
|
export default class HaCardConditionEditor extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) condition!: Condition;
|
||||||
|
|
||||||
|
@state() public _yamlMode = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const condition = this.condition;
|
||||||
|
const supported =
|
||||||
|
customElements.get(`ha-card-condition-${condition.condition}`) !==
|
||||||
|
undefined;
|
||||||
|
const yamlMode = this._yamlMode || !supported;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="header">
|
||||||
|
<ha-icon-button
|
||||||
|
@click=${this._toggleMode}
|
||||||
|
.disabled=${!supported}
|
||||||
|
.label=${this.hass!.localize(
|
||||||
|
yamlMode
|
||||||
|
? "ui.panel.lovelace.editor.edit_card.show_visual_editor"
|
||||||
|
: "ui.panel.lovelace.editor.edit_card.show_code_editor"
|
||||||
|
)}
|
||||||
|
.path=${yamlMode ? mdiListBoxOutline : mdiCodeBraces}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass!.localize("ui.common.delete")}
|
||||||
|
.path=${mdiDelete}
|
||||||
|
@click=${this._delete}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
${yamlMode
|
||||||
|
? html`
|
||||||
|
<ha-yaml-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.defaultValue=${this.condition}
|
||||||
|
@value-changed=${this._onYamlChange}
|
||||||
|
></ha-yaml-editor>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
${dynamicElement(`ha-card-condition-${condition.condition}`, {
|
||||||
|
hass: this.hass,
|
||||||
|
condition: condition,
|
||||||
|
})}
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toggleMode() {
|
||||||
|
this._yamlMode = !this._yamlMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _delete() {
|
||||||
|
fireEvent(this, "value-changed", { value: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onYamlChange(ev: CustomEvent) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
if (!ev.detail.isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
fireEvent(this, "value-changed", { value: ev.detail.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = [
|
||||||
|
haStyle,
|
||||||
|
css`
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-card-condition-editor": HaCardConditionEditor;
|
||||||
|
}
|
||||||
|
}
|
134
src/panels/lovelace/common/types/ha-card-condition-state.ts
Normal file
134
src/panels/lovelace/common/types/ha-card-condition-state.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
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 { StateCondition } from "../validate-condition";
|
||||||
|
|
||||||
|
type StateConditionData = {
|
||||||
|
condition: "state";
|
||||||
|
entity: string;
|
||||||
|
invert: "true" | "false";
|
||||||
|
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;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public condition!: StateCondition;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
public static get defaultConfig(): StateCondition {
|
||||||
|
return { condition: "state", entity: "", state: "" };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const { state, state_not, ...content } = this.condition;
|
||||||
|
|
||||||
|
const data: StateConditionData = {
|
||||||
|
...content,
|
||||||
|
invert: this.condition.state_not ? "true" : "false",
|
||||||
|
state: this.condition.state_not ?? this.condition.state ?? "",
|
||||||
|
};
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${data}
|
||||||
|
.schema=${SCHEMA}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@value-changed=${this._valueChanged}
|
||||||
|
.computeLabel=${this._computeLabelCallback}
|
||||||
|
></ha-form>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
const data = ev.detail.value as StateConditionData;
|
||||||
|
|
||||||
|
const { invert, state, entity, condition: _, ...content } = data;
|
||||||
|
|
||||||
|
const condition: StateCondition = {
|
||||||
|
condition: "state",
|
||||||
|
...content,
|
||||||
|
entity: entity ?? "",
|
||||||
|
state: invert === "false" ? state ?? "" : undefined,
|
||||||
|
state_not: invert === "true" ? state ?? "" : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
fireEvent(this, "value-changed", { value: condition });
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeLabelCallback = (
|
||||||
|
schema: SchemaUnion<typeof SCHEMA>
|
||||||
|
): string => {
|
||||||
|
const entity = this.hass.states[this.condition.entity] as
|
||||||
|
| HassEntity
|
||||||
|
| undefined;
|
||||||
|
switch (schema.name) {
|
||||||
|
case "entity":
|
||||||
|
return this.hass.localize("ui.components.entity.entity-picker.entity");
|
||||||
|
case "state":
|
||||||
|
if (entity) {
|
||||||
|
return `${this.hass.localize(
|
||||||
|
"ui.components.entity.entity-state-picker.state"
|
||||||
|
)} (${this.hass.formatEntityState(entity)})`;
|
||||||
|
}
|
||||||
|
return `${this.hass.localize(
|
||||||
|
"ui.components.entity.entity-state-picker.state"
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-card-condition-state": HaCardConditionState;
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,10 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
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 { mdiCodeBraces, mdiContentCopy, mdiListBoxOutline } from "@mdi/js";
|
import { mdiCodeBraces, mdiContentCopy, mdiListBoxOutline } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import type { MDCTabBarActivatedEvent } from "@material/tab-bar";
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import {
|
import {
|
||||||
any,
|
any,
|
||||||
@ -19,8 +19,7 @@ import {
|
|||||||
union,
|
union,
|
||||||
} from "superstruct";
|
} from "superstruct";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
import { HASSDomEvent, fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
import "../../../../components/entity/ha-entity-picker";
|
||||||
import "../../../../components/ha-select";
|
import "../../../../components/ha-select";
|
||||||
import "../../../../components/ha-textfield";
|
import "../../../../components/ha-textfield";
|
||||||
@ -30,6 +29,7 @@ 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 "../../common/ha-card-condition-editor";
|
||||||
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";
|
||||||
@ -39,7 +39,6 @@ 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";
|
||||||
import { StateCondition } from "../../common/validate-condition";
|
|
||||||
|
|
||||||
const stateConditionStruct = object({
|
const stateConditionStruct = object({
|
||||||
condition: optional(literal("state")),
|
condition: optional(literal("state")),
|
||||||
@ -177,66 +176,25 @@ export class HuiConditionalCardEditor
|
|||||||
${this.hass!.localize(
|
${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
|
"ui.panel.lovelace.editor.card.conditional.condition_explanation"
|
||||||
)}
|
)}
|
||||||
${this._config.conditions.map((cond, idx) => {
|
${this._config.conditions.map(
|
||||||
if (cond.condition && cond.condition !== "state")
|
(cond, idx) => html`
|
||||||
return nothing;
|
|
||||||
return html`
|
|
||||||
<div class="condition">
|
<div class="condition">
|
||||||
<div class="entity">
|
<ha-card-condition-editor
|
||||||
<ha-entity-picker
|
.index=${idx}
|
||||||
.hass=${this.hass}
|
@value-changed=${this._conditionChanged}
|
||||||
.value=${cond.entity}
|
.hass=${this.hass}
|
||||||
.idx=${idx}
|
.condition=${cond}
|
||||||
.configValue=${"entity"}
|
></ha-card-condition-editor>
|
||||||
@change=${this._changeCondition}
|
|
||||||
allow-custom-entity
|
|
||||||
></ha-entity-picker>
|
|
||||||
</div>
|
|
||||||
<div class="state">
|
|
||||||
<ha-select
|
|
||||||
.value=${cond.state_not !== undefined
|
|
||||||
? "true"
|
|
||||||
: "false"}
|
|
||||||
.idx=${idx}
|
|
||||||
.configValue=${"invert"}
|
|
||||||
@selected=${this._changeCondition}
|
|
||||||
@closed=${stopPropagation}
|
|
||||||
naturalMenuWidth
|
|
||||||
fixedMenuPosition
|
|
||||||
>
|
|
||||||
<mwc-list-item value="false">
|
|
||||||
${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.conditional.state_equal"
|
|
||||||
)}
|
|
||||||
</mwc-list-item>
|
|
||||||
<mwc-list-item value="true">
|
|
||||||
${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.conditional.state_not_equal"
|
|
||||||
)}
|
|
||||||
</mwc-list-item>
|
|
||||||
</ha-select>
|
|
||||||
<ha-textfield
|
|
||||||
.label="${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.generic.state"
|
|
||||||
)} (${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.conditional.current_state"
|
|
||||||
)}: ${this.hass?.states[cond.entity].state})"
|
|
||||||
.value=${cond.state_not !== undefined
|
|
||||||
? cond.state_not
|
|
||||||
: cond.state}
|
|
||||||
.idx=${idx}
|
|
||||||
.configValue=${"state"}
|
|
||||||
@input=${this._changeCondition}
|
|
||||||
></ha-textfield>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`
|
||||||
})}
|
)}
|
||||||
<div class="condition">
|
<div class="condition">
|
||||||
<ha-entity-picker
|
<div class="content">
|
||||||
.hass=${this.hass}
|
<ha-entity-picker
|
||||||
@change=${this._addCondition}
|
.hass=${this.hass}
|
||||||
></ha-entity-picker>
|
@change=${this._addCondition}
|
||||||
|
></ha-entity-picker>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
@ -321,39 +279,19 @@ export class HuiConditionalCardEditor
|
|||||||
fireEvent(this, "config-changed", { config: this._config });
|
fireEvent(this, "config-changed", { config: this._config });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _changeCondition(ev: Event): void {
|
private _conditionChanged(ev: CustomEvent) {
|
||||||
const target = ev.target as any;
|
ev.stopPropagation();
|
||||||
if (!this._config || !target) {
|
const conditions = [...this._config!.conditions];
|
||||||
return;
|
const newValue = ev.detail.value;
|
||||||
}
|
const index = (ev.target as any).index;
|
||||||
const conditions = [...this._config.conditions];
|
|
||||||
if (target.configValue === "entity" && target.value === "") {
|
if (newValue === null) {
|
||||||
conditions.splice(target.idx, 1);
|
conditions.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
// Only state condition are supported by the editor
|
conditions[index] = newValue;
|
||||||
const condition = { ...conditions[target.idx] } as StateCondition;
|
|
||||||
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.value === "true") {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conditions[target.idx] = 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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,22 +308,10 @@ export class HuiConditionalCardEditor
|
|||||||
.condition {
|
.condition {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
border: 1px solid var(--divider-color);
|
border: 1px solid var(--divider-color);
|
||||||
|
}
|
||||||
|
.condition .content {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
.condition .state {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
.condition .state ha-select {
|
|
||||||
margin-right: 16px;
|
|
||||||
margin-inline-end: 16px;
|
|
||||||
margin-inline-start: initial;
|
|
||||||
direction: var(--direction);
|
|
||||||
}
|
|
||||||
.condition .state ha-textfield {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
border: 1px solid var(--divider-color);
|
border: 1px solid var(--divider-color);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user