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
This commit is contained in:
karwosts 2023-08-24 09:13:10 -07:00 committed by GitHub
parent fc1782e676
commit f68823a09e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 176 additions and 143 deletions

View File

@ -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`<label>${this.label}</label>` : nothing}
<ha-automation-action
.disabled=${this.disabled}
.actions=${this.value || []}
.hass=${this.hass}
.nested=${this.selector.action?.nested}
.reOrderMode=${this.selector.action?.reorder_mode}
></ha-automation-action>
`;
}
@ -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;
}
`;
}
}

View File

@ -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`<label>${this.label}</label>` : nothing}
<ha-automation-condition
.disabled=${this.disabled}
.conditions=${this.value || []}
.hass=${this.hass}
.nested=${this.selector.condition?.nested}
.reOrderMode=${this.selector.condition?.reorder_mode}
></ha-automation-condition>
`;
}
@ -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;
}
`;
}
}

View File

@ -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 {

View File

@ -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`
<ha-select
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.repeat.type_select"
)}
.value=${type}
.disabled=${this.disabled}
@selected=${this._typeChanged}
>
${OPTIONS.map(
(opt) => html`
<mwc-list-item .value=${opt}>
${this.hass.localize(
`ui.panel.config.automation.editor.actions.type.repeat.type.${opt}.label`
)}
</mwc-list-item>
`
)}
</ha-select>
<div>
${type === "count"
? html`
<ha-textfield
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.repeat.type.count.label"
)}
name="count"
.value=${(action as CountRepeat).count || "0"}
.disabled=${this.disabled}
@change=${this._countChanged}
></ha-textfield>
`
: type === "while"
? html` <h3>
${this.hass.localize(
`ui.panel.config.automation.editor.actions.type.repeat.type.while.conditions`
)}:
</h3>
<ha-automation-condition
nested
.conditions=${(action as WhileRepeat).while || []}
.hass=${this.hass}
.disabled=${this.disabled}
@value-changed=${this._conditionChanged}
></ha-automation-condition>`
: type === "until"
? html` <h3>
${this.hass.localize(
`ui.panel.config.automation.editor.actions.type.repeat.type.until.conditions`
)}:
</h3>
<ha-automation-condition
nested
.conditions=${(action as UntilRepeat).until || []}
.hass=${this.hass}
.disabled=${this.disabled}
@value-changed=${this._conditionChanged}
></ha-automation-condition>`
: ""}
</div>
<h3>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.repeat.sequence"
)}:
</h3>
<ha-automation-action
nested
.actions=${action.sequence}
.reOrderMode=${this.reOrderMode}
.disabled=${this.disabled}
@value-changed=${this._actionChanged}
.hass=${this.hass}
></ha-automation-action>
`;
const schema = this._schema(
this.hass.localize,
type ?? "count",
this.reOrderMode
);
const data = { ...action, type };
return html` <ha-form
.hass=${this.hass}
.data=${data}
.schema=${schema}
.disabled=${this.disabled}
@value-changed=${this._valueChanged}
.computeLabel=${this._computeLabelCallback}
></ha-form>`;
}
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<ReturnType<typeof this._schema>>
): 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 {

View File

@ -2735,6 +2735,10 @@
"until": {
"label": "Until",
"conditions": "Until conditions"
},
"for_each": {
"label": "For each",
"items": "For each item in list"
}
},
"sequence": "Actions",