diff --git a/src/data/script.ts b/src/data/script.ts
index 2a2cf1912c..7a8e83d717 100644
--- a/src/data/script.ts
+++ b/src/data/script.ts
@@ -58,6 +58,31 @@ export interface WaitAction {
timeout?: number;
}
+export interface RepeatAction {
+ repeat: CountRepeat | WhileRepeat | UntilRepeat;
+}
+
+interface BaseRepeat {
+ sequence: Action[];
+}
+
+export interface CountRepeat extends BaseRepeat {
+ count: number;
+}
+
+export interface WhileRepeat extends BaseRepeat {
+ while: Condition[];
+}
+
+export interface UntilRepeat extends BaseRepeat {
+ until: Condition[];
+}
+
+export interface ChooseAction {
+ choose: [{ conditions: Condition[]; sequence: Action[] }];
+ default?: Action[];
+}
+
export type Action =
| EventAction
| DeviceAction
@@ -65,7 +90,9 @@ export type Action =
| Condition
| DelayAction
| SceneAction
- | WaitAction;
+ | WaitAction
+ | RepeatAction
+ | ChooseAction;
export const triggerScript = (
hass: HomeAssistant,
diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts
index 12b819078a..4a40b0ee51 100644
--- a/src/panels/config/automation/action/ha-automation-action-row.ts
+++ b/src/panels/config/automation/action/ha-automation-action-row.ts
@@ -15,6 +15,7 @@ import {
LitElement,
property,
internalProperty,
+ PropertyValues,
} from "lit-element";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../../../common/dom/fire_event";
@@ -29,6 +30,8 @@ import "./types/ha-automation-action-event";
import "./types/ha-automation-action-scene";
import "./types/ha-automation-action-service";
import "./types/ha-automation-action-wait_template";
+import "./types/ha-automation-action-repeat";
+import "./types/ha-automation-action-choose";
import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import { haStyle } from "../../../../resources/styles";
@@ -41,6 +44,8 @@ const OPTIONS = [
"scene",
"service",
"wait_template",
+ "repeat",
+ "choose",
];
const getType = (action: Action) => {
@@ -96,6 +101,16 @@ export default class HaAutomationActionRow extends LitElement {
@internalProperty() private _yamlMode = false;
+ protected updated(changedProperties: PropertyValues) {
+ if (!changedProperties.has("action")) {
+ return;
+ }
+ this._uiModeAvailable = Boolean(getType(this.action));
+ if (!this._uiModeAvailable && !this._yamlMode) {
+ this._yamlMode = true;
+ }
+ }
+
protected render() {
const type = getType(this.action);
const selected = type ? OPTIONS.indexOf(type) : -1;
diff --git a/src/panels/config/automation/action/types/ha-automation-action-choose.ts b/src/panels/config/automation/action/types/ha-automation-action-choose.ts
new file mode 100644
index 0000000000..0bca89bf91
--- /dev/null
+++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts
@@ -0,0 +1,176 @@
+import "@polymer/paper-input/paper-input";
+import {
+ customElement,
+ LitElement,
+ property,
+ CSSResult,
+ css,
+} from "lit-element";
+import { html } from "lit-html";
+import { Action, ChooseAction } from "../../../../../data/script";
+import { HomeAssistant } from "../../../../../types";
+import { ActionElement } from "../ha-automation-action-row";
+import "../../condition/ha-automation-condition-editor";
+import "@polymer/paper-listbox/paper-listbox";
+import { fireEvent } from "../../../../../common/dom/fire_event";
+import "../ha-automation-action";
+import { Condition } from "../../../../../data/automation";
+import { haStyle } from "../../../../../resources/styles";
+import { mdiDelete } from "@mdi/js";
+
+@customElement("ha-automation-action-choose")
+export class HaChooseAction extends LitElement implements ActionElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property() public action!: ChooseAction;
+
+ public static get defaultConfig() {
+ return { choose: [{ conditions: [], sequence: [] }], default: [] };
+ }
+
+ protected render() {
+ const action = this.action;
+
+ return html`
+ ${action.choose.map(
+ (option, idx) => html`
+
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.automation.editor.actions.type.choose.option",
+ "number",
+ idx + 1
+ )}:
+
+
+ ${this.hass.localize(
+ "ui.panel.config.automation.editor.actions.type.choose.conditions"
+ )}:
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.automation.editor.actions.type.choose.sequence"
+ )}:
+
+
+
+ `
+ )}
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.automation.editor.actions.type.choose.add_option"
+ )}
+
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.automation.editor.actions.type.choose.default"
+ )}:
+
+
+ `;
+ }
+
+ private _conditionChanged(ev: CustomEvent) {
+ ev.stopPropagation();
+ const value = ev.detail.value as Condition[];
+ const index = (ev.target as any).idx;
+ const choose = [...this.action.choose];
+ choose[index].conditions = value;
+ fireEvent(this, "value-changed", {
+ value: { ...this.action, choose },
+ });
+ }
+
+ private _actionChanged(ev: CustomEvent) {
+ ev.stopPropagation();
+ const value = ev.detail.value as Action[];
+ const index = (ev.target as any).idx;
+ const choose = [...this.action.choose];
+ choose[index].sequence = value;
+ fireEvent(this, "value-changed", {
+ value: { ...this.action, choose },
+ });
+ }
+
+ private _addOption() {
+ const choose = [...this.action.choose];
+ choose.push({ conditions: [], sequence: [] });
+ fireEvent(this, "value-changed", {
+ value: { ...this.action, choose },
+ });
+ }
+
+ private _removeOption(ev: CustomEvent) {
+ const index = (ev.currentTarget as any).idx;
+ const choose = [...this.action.choose];
+ choose.splice(index, 1);
+ fireEvent(this, "value-changed", {
+ value: { ...this.action, choose },
+ });
+ }
+
+ private _defaultChanged(ev: CustomEvent) {
+ ev.stopPropagation();
+ const value = ev.detail.value as Action[];
+ fireEvent(this, "value-changed", {
+ value: {
+ ...this.action,
+ default: value,
+ },
+ });
+ }
+
+ static get styles(): CSSResult[] {
+ return [
+ haStyle,
+ css`
+ ha-card {
+ margin-top: 16px;
+ }
+ .add-card mwc-button {
+ display: block;
+ text-align: center;
+ }
+ mwc-icon-button {
+ position: absolute;
+ right: 0;
+ padding: 4px;
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-choose": HaChooseAction;
+ }
+}
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
new file mode 100644
index 0000000000..3fd2cd95cb
--- /dev/null
+++ b/src/panels/config/automation/action/types/ha-automation-action-repeat.ts
@@ -0,0 +1,180 @@
+import "@polymer/paper-input/paper-input";
+import { customElement, LitElement, property, CSSResult } from "lit-element";
+import { html } from "lit-html";
+import {
+ RepeatAction,
+ Action,
+ CountRepeat,
+ WhileRepeat,
+ UntilRepeat,
+} from "../../../../../data/script";
+import { HomeAssistant } from "../../../../../types";
+import { ActionElement } from "../ha-automation-action-row";
+import "../../condition/ha-automation-condition-editor";
+import type { PaperListboxElement } from "@polymer/paper-listbox";
+import "@polymer/paper-listbox/paper-listbox";
+import { fireEvent } from "../../../../../common/dom/fire_event";
+import "../ha-automation-action";
+import { Condition } from "../../../../lovelace/common/validate-condition";
+import { haStyle } from "../../../../../resources/styles";
+
+const OPTIONS = ["count", "while", "until"];
+
+const getType = (action) => {
+ return OPTIONS.find((option) => option in action);
+};
+
+@customElement("ha-automation-action-repeat")
+export class HaRepeatAction extends LitElement implements ActionElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property({ attribute: false }) public action!: RepeatAction;
+
+ public static get defaultConfig() {
+ return { repeat: { count: 2, sequence: [] } };
+ }
+
+ protected render() {
+ const action = this.action.repeat;
+
+ const type = getType(action);
+ const selected = type ? OPTIONS.indexOf(type) : -1;
+
+ 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"
+ )}:
+
+
+ `;
+ }
+
+ private _typeChanged(ev: CustomEvent) {
+ const type = ((ev.target as PaperListboxElement)?.selectedItem as any)
+ ?.action;
+
+ if (!type || type === getType(this.action.repeat)) {
+ return;
+ }
+
+ const value = type === "count" ? 2 : [];
+
+ fireEvent(this, "value-changed", {
+ value: {
+ 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: {
+ 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: {
+ repeat: {
+ ...this.action.repeat,
+ sequence: value,
+ },
+ },
+ });
+ }
+
+ private _countChanged(ev: CustomEvent): void {
+ const newVal = ev.detail.value;
+ if ((this.action.repeat as CountRepeat).count === newVal) {
+ return;
+ }
+ fireEvent(this, "value-changed", {
+ value: {
+ repeat: {
+ ...this.action.repeat,
+ count: newVal,
+ },
+ },
+ });
+ }
+
+ static get styles(): CSSResult {
+ return haStyle;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-repeat": HaRepeatAction;
+ }
+}
diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts
index ecb06696b3..63a3cfe573 100644
--- a/src/panels/config/integrations/ha-integration-card.ts
+++ b/src/panels/config/integrations/ha-integration-card.ts
@@ -106,9 +106,9 @@ export class HaIntegrationCard extends LitElement {
@error=${this._onImageError}
@load=${this._onImageLoad}
/>
-
+
${domainToName(this.hass.localize, this.domain)}
-
+
${this.items.map(
@@ -156,12 +156,12 @@ export class HaIntegrationCard extends LitElement {
@load=${this._onImageLoad}
/>
-
- ${item.localized_domain_name}
-
- ${item.localized_domain_name === item.title ? "" : item.title}
+ ${item.localized_domain_name}
+
+ ${item.localized_domain_name === item.title ? "" : item.title}
+
${devices.length || entities.length
? html`
diff --git a/src/resources/styles.ts b/src/resources/styles.ts
index 48cbb59181..0ea48f1096 100644
--- a/src/resources/styles.ts
+++ b/src/resources/styles.ts
@@ -79,6 +79,17 @@ export const haStyle = css`
}
h1 {
+ font-family: var(--paper-font-headline_-_font-family);
+ -webkit-font-smoothing: var(--paper-font-headline_-_-webkit-font-smoothing);
+ white-space: var(--paper-font-headline_-_white-space);
+ overflow: var(--paper-font-headline_-_overflow);
+ text-overflow: var(--paper-font-headline_-_text-overflow);
+ font-size: var(--paper-font-headline_-_font-size);
+ font-weight: var(--paper-font-headline_-_font-weight);
+ line-height: var(--paper-font-headline_-_line-height);
+ }
+
+ h2 {
font-family: var(--paper-font-title_-_font-family);
-webkit-font-smoothing: var(--paper-font-title_-_-webkit-font-smoothing);
white-space: var(--paper-font-title_-_white-space);
@@ -89,7 +100,7 @@ export const haStyle = css`
line-height: var(--paper-font-title_-_line-height);
}
- h2 {
+ h3 {
font-family: var(--paper-font-subhead_-_font-family);
-webkit-font-smoothing: var(--paper-font-subhead_-_-webkit-font-smoothing);
white-space: var(--paper-font-subhead_-_white-space);
diff --git a/src/translations/en.json b/src/translations/en.json
index 98b8d8503e..66b83f4f93 100755
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -1058,6 +1058,31 @@
},
"scene": {
"label": "Activate scene"
+ },
+ "repeat": {
+ "label": "Repeat",
+ "type_select": "Repeat type",
+ "type": {
+ "count": { "label": "Count" },
+ "while": {
+ "label": "While",
+ "conditions": "While conditions"
+ },
+ "until": {
+ "label": "Until",
+ "conditions": "Until conditions"
+ }
+ },
+ "sequence": "Actions"
+ },
+ "choose": {
+ "label": "Choose",
+ "default": "Default actions",
+ "option": "Option {number}",
+ "add_option": "Add option",
+ "remove_option": "Remove option",
+ "conditions": "Conditions",
+ "sequence": "Actions"
}
}
}