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",