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