From f68823a09eeb54563c1619b53bb4e1959e018f90 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Thu, 24 Aug 2023 09:13:10 -0700 Subject: [PATCH] Add for_each to repeat action UI. Convert repeat to ha-form (#17688) * Add for_each to repeat action UI. Convert repeat to ha-form * reordermode as a selector option * css styling --- .../ha-selector/ha-selector-action.ts | 10 +- .../ha-selector/ha-selector-condition.ts | 10 +- src/data/selector.ts | 12 +- .../types/ha-automation-action-repeat.ts | 283 +++++++++--------- src/translations/en.json | 4 + 5 files changed, 176 insertions(+), 143 deletions(-) diff --git a/src/components/ha-selector/ha-selector-action.ts b/src/components/ha-selector/ha-selector-action.ts index f5c770ebf0..b8a083a536 100644 --- a/src/components/ha-selector/ha-selector-action.ts +++ b/src/components/ha-selector/ha-selector-action.ts @@ -1,4 +1,4 @@ -import { css, CSSResultGroup, html, LitElement } from "lit"; +import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { Action } from "../../data/script"; import { ActionSelector } from "../../data/selector"; @@ -19,10 +19,13 @@ export class HaActionSelector extends LitElement { protected render() { return html` + ${this.label ? html`` : nothing} `; } @@ -37,6 +40,11 @@ export class HaActionSelector extends LitElement { opacity: var(--light-disabled-opacity); pointer-events: none; } + label { + display: block; + margin-bottom: 4px; + font-weight: 500; + } `; } } diff --git a/src/components/ha-selector/ha-selector-condition.ts b/src/components/ha-selector/ha-selector-condition.ts index fe76180793..29fbaa49e6 100644 --- a/src/components/ha-selector/ha-selector-condition.ts +++ b/src/components/ha-selector/ha-selector-condition.ts @@ -1,4 +1,4 @@ -import { css, CSSResultGroup, html, LitElement } from "lit"; +import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; import { Condition } from "../../data/automation"; import { ConditionSelector } from "../../data/selector"; @@ -19,10 +19,13 @@ export class HaConditionSelector extends LitElement { protected render() { return html` + ${this.label ? html`` : nothing} `; } @@ -37,6 +40,11 @@ export class HaConditionSelector extends LitElement { opacity: var(--light-disabled-opacity); pointer-events: none; } + label { + display: block; + margin-bottom: 4px; + font-weight: 500; + } `; } } diff --git a/src/data/selector.ts b/src/data/selector.ts index 148a654603..fe4b940a9f 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -54,8 +54,10 @@ export type Selector = | UiColorSelector; export interface ActionSelector { - // eslint-disable-next-line @typescript-eslint/ban-types - action: {} | null; + action: { + reorder_mode?: boolean; + nested?: boolean; + } | null; } export interface AddonSelector { @@ -98,8 +100,10 @@ export interface ColorTempSelector { } export interface ConditionSelector { - // eslint-disable-next-line @typescript-eslint/ban-types - condition: {} | null; + condition: { + reorder_mode?: boolean; + nested?: boolean; + } | null; } export interface ConversationAgentSelector { diff --git a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts index 5f6e449eed..fa4d36a69a 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-repeat.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts @@ -1,21 +1,19 @@ import { css, CSSResultGroup, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; +import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/ha-textfield"; -import { - Action, - CountRepeat, - RepeatAction, - UntilRepeat, - WhileRepeat, -} from "../../../../../data/script"; +import { RepeatAction } from "../../../../../data/script"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; -import type { Condition } from "../../../../lovelace/common/validate-condition"; import "../ha-automation-action"; import type { ActionElement } from "../ha-automation-action-row"; -const OPTIONS = ["count", "while", "until"] as const; +import type { LocalizeFunc } from "../../../../../common/translations/localize"; +import "../../../../../components/ha-form/ha-form"; +import type { SchemaUnion } from "../../../../../components/ha-form/types"; + +const OPTIONS = ["count", "while", "until", "for_each"] as const; const getType = (action) => OPTIONS.find((option) => option in action); @@ -33,144 +31,115 @@ export class HaRepeatAction extends LitElement implements ActionElement { return { repeat: { count: 2, sequence: [] } }; } + private _schema = memoizeOne( + (localize: LocalizeFunc, type: string, reOrderMode: boolean) => + [ + { + name: "type", + selector: { + select: { + mode: "dropdown", + options: OPTIONS.map((opt) => ({ + value: opt, + label: localize( + `ui.panel.config.automation.editor.actions.type.repeat.type.${opt}.label` + ), + })), + }, + }, + }, + ...(type === "count" + ? ([ + { + name: "count", + required: true, + selector: { number: { mode: "box", min: 1 } }, + }, + ] as const) + : []), + ...(type === "until" || type === "while" + ? ([ + { + name: type, + selector: { + condition: { nested: true, reorder_mode: reOrderMode }, + }, + }, + ] as const) + : []), + ...(type === "for_each" + ? ([ + { + name: "for_each", + required: true, + selector: { object: {} }, + }, + ] as const) + : []), + { + name: "sequence", + selector: { action: { nested: true, reorder_mode: reOrderMode } }, + }, + ] as const + ); + protected render() { const action = this.action.repeat; - const type = getType(action); - - return html` - - ${OPTIONS.map( - (opt) => html` - - ${this.hass.localize( - `ui.panel.config.automation.editor.actions.type.repeat.type.${opt}.label` - )} - - ` - )} - -
- ${type === "count" - ? html` - - ` - : type === "while" - ? html`

- ${this.hass.localize( - `ui.panel.config.automation.editor.actions.type.repeat.type.while.conditions` - )}: -

- ` - : type === "until" - ? html`

- ${this.hass.localize( - `ui.panel.config.automation.editor.actions.type.repeat.type.until.conditions` - )}: -

- ` - : ""} -
-

- ${this.hass.localize( - "ui.panel.config.automation.editor.actions.type.repeat.sequence" - )}: -

- - `; + const schema = this._schema( + this.hass.localize, + type ?? "count", + this.reOrderMode + ); + const data = { ...action, type }; + return html` `; } - private _typeChanged(ev) { - const type = ev.target.value; + private _valueChanged(ev: CustomEvent): void { + ev.stopPropagation(); + const newVal = ev.detail.value; - if (!type || type === getType(this.action.repeat)) { - return; + const newType = newVal.type; + delete newVal.type; + const oldType = getType(this.action.repeat); + + if (newType !== oldType) { + if (newType === "count") { + newVal.count = 2; + delete newVal.while; + delete newVal.until; + delete newVal.for_each; + } + if (newType === "while") { + newVal.while = newVal.until ?? []; + delete newVal.count; + delete newVal.until; + delete newVal.for_each; + } + if (newType === "until") { + newVal.until = newVal.while ?? []; + delete newVal.count; + delete newVal.while; + delete newVal.for_each; + } + if (newType === "for_each") { + newVal.for_each = {}; + delete newVal.count; + delete newVal.while; + delete newVal.until; + } } - const value = type === "count" ? 2 : []; - fireEvent(this, "value-changed", { value: { - ...this.action, - repeat: { [type]: value, sequence: this.action.repeat.sequence }, - }, - }); - } - - private _conditionChanged(ev: CustomEvent) { - ev.stopPropagation(); - const value = ev.detail.value as Condition[]; - fireEvent(this, "value-changed", { - value: { - ...this.action, - repeat: { - ...this.action.repeat, - [getType(this.action.repeat)!]: value, - }, - }, - }); - } - - private _actionChanged(ev: CustomEvent) { - ev.stopPropagation(); - const value = ev.detail.value as Action[]; - fireEvent(this, "value-changed", { - value: { - ...this.action, - repeat: { - ...this.action.repeat, - sequence: value, - }, - }, - }); - } - - private _countChanged(ev: CustomEvent): void { - const newVal = (ev.target as any).value; - if ((this.action.repeat as CountRepeat).count === newVal) { - return; - } - fireEvent(this, "value-changed", { - value: { - ...this.action, - repeat: { - ...this.action.repeat, - count: newVal, - }, + repeat: { ...newVal }, }, }); } @@ -185,6 +154,46 @@ export class HaRepeatAction extends LitElement implements ActionElement { `, ]; } + + private _computeLabelCallback = ( + schema: SchemaUnion> + ): string => { + switch (schema.name) { + case "type": + return this.hass.localize( + "ui.panel.config.automation.editor.actions.type.repeat.type_select" + ); + case "count": + return this.hass.localize( + "ui.panel.config.automation.editor.actions.type.repeat.type.count.label" + ); + case "while": + return ( + this.hass.localize( + "ui.panel.config.automation.editor.actions.type.repeat.type.while.conditions" + ) + ":" + ); + case "until": + return ( + this.hass.localize( + "ui.panel.config.automation.editor.actions.type.repeat.type.until.conditions" + ) + ":" + ); + case "for_each": + return ( + this.hass.localize( + "ui.panel.config.automation.editor.actions.type.repeat.type.for_each.items" + ) + ":" + ); + case "sequence": + return ( + this.hass.localize( + "ui.panel.config.automation.editor.actions.type.repeat.sequence" + ) + ":" + ); + } + return ""; + }; } declare global { diff --git a/src/translations/en.json b/src/translations/en.json index c27789a9ae..6e87925df5 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2735,6 +2735,10 @@ "until": { "label": "Until", "conditions": "Until conditions" + }, + "for_each": { + "label": "For each", + "items": "For each item in list" } }, "sequence": "Actions",