diff --git a/src/data/automation.ts b/src/data/automation.ts index cf4ce18ebf..f495c59b48 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -20,6 +20,8 @@ export interface AutomationConfig { trigger: Trigger[]; condition?: Condition[]; action: Action[]; + mode?: "single" | "restart" | "queued" | "parallel"; + max?: number; } export interface ForDict { diff --git a/src/data/script.ts b/src/data/script.ts index 5f027ce413..5fdbbcfa46 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -16,6 +16,8 @@ export interface ScriptEntity extends HassEntityBase { export interface ScriptConfig { alias: string; sequence: Action[]; + mode?: "single" | "restart" | "queued" | "parallel"; + max?: number; } export interface EventAction { diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 8129605c23..cc70db4fe5 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -1,5 +1,6 @@ import "@polymer/app-layout/app-header/app-header"; import "@polymer/app-layout/app-toolbar/app-toolbar"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; import "@polymer/paper-input/paper-textarea"; import "../../../components/ha-icon-button"; import { @@ -43,6 +44,10 @@ import "./condition/ha-automation-condition"; import "./trigger/ha-automation-trigger"; import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device"; import { mdiContentSave } from "@mdi/js"; +import { PaperListboxElement } from "@polymer/paper-listbox"; + +const MODES = ["parallel", "single", "restart", "queued"]; +const MODES_MAX = ["queued", "parallel"]; export class HaAutomationEditor extends LitElement { @property() public hass!: HomeAssistant; @@ -129,6 +134,57 @@ export class HaAutomationEditor extends LitElement { .value=${this._config.description} @value-changed=${this._valueChanged} > +

+ ${this.hass.localize( + "ui.panel.config.automation.editor.modes.description", + "documentation_link", + html`${this.hass.localize( + "ui.panel.config.automation.editor.modes.documentation" + )}` + )} +

+ + + ${MODES.map( + (mode) => html` + + ${this.hass.localize( + `ui.panel.config.automation.editor.modes.${mode}` + ) || mode} + + ` + )} + + + ${this._config.mode && + MODES_MAX.includes(this._config.mode) + ? html` + ` + : html``} ${stateObj ? html` @@ -343,14 +399,28 @@ export class HaAutomationEditor extends LitElement { this._entityId = automation?.entity_id; } + private _modeChanged(ev: CustomEvent) { + const mode = ((ev.target as PaperListboxElement)?.selectedItem as any) + ?.mode; + + this._config = { ...this._config!, mode }; + if (!MODES_MAX.includes(mode)) { + delete this._config.max; + } + this._dirty = true; + } + private _valueChanged(ev: CustomEvent) { ev.stopPropagation(); - const name = (ev.target as any)?.name; + const target = ev.target as any; + const name = target.name; if (!name) { return; } - const newVal = ev.detail.value; - + let newVal = ev.detail.value; + if (target.type === "number") { + newVal = Number(newVal); + } if ((this._config![name] || "") === newVal) { return; } @@ -453,6 +523,9 @@ export class HaAutomationEditor extends LitElement { span[slot="introduction"] a { color: var(--primary-color); } + p { + margin-bottom: 0; + } ha-entity-toggle { margin-right: 8px; } diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 85c35f7415..be5422de69 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -1,4 +1,5 @@ import "@polymer/app-layout/app-header/app-header"; +import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; import "@polymer/app-layout/app-toolbar/app-toolbar"; import "../../../components/ha-icon-button"; import { @@ -32,6 +33,10 @@ import "../ha-config-section"; import { configSections } from "../ha-panel-config"; import "../../../components/ha-svg-icon"; import { mdiContentSave } from "@mdi/js"; +import { PaperListboxElement } from "@polymer/paper-listbox"; + +const MODES = ["single", "restart", "queued", "parallel"]; +const MODES_MAX = ["queued", "parallel"]; export class HaScriptEditor extends LitElement { @property() public hass!: HomeAssistant; @@ -105,6 +110,58 @@ export class HaScriptEditor extends LitElement { @value-changed=${this._valueChanged} > + +

+ ${this.hass.localize( + "ui.panel.config.script.editor.modes.description", + "documentation_link", + html`${this.hass.localize( + "ui.panel.config.script.editor.modes.documentation" + )}` + )} +

+ + + ${MODES.map( + (mode) => html` + + ${this.hass.localize( + `ui.panel.config.script.editor.modes.${mode}` + ) || mode} + + ` + )} + + + ${this._config.mode && + MODES_MAX.includes(this._config.mode) + ? html` + ` + : html``} @@ -216,14 +273,28 @@ export class HaScriptEditor extends LitElement { } } + private _modeChanged(ev: CustomEvent) { + const mode = ((ev.target as PaperListboxElement)?.selectedItem as any) + ?.mode; + + this._config = { ...this._config!, mode }; + if (!MODES_MAX.includes(mode)) { + delete this._config.max; + } + this._dirty = true; + } + private _valueChanged(ev: CustomEvent) { ev.stopPropagation(); - const name = (ev.target as any)?.name; + const target = ev.target as any; + const name = target.name; if (!name) { return; } - const newVal = ev.detail.value; - + let newVal = ev.detail.value; + if (target.type === "number") { + newVal = Number(newVal); + } if ((this._config![name] || "") === newVal) { return; } @@ -292,6 +363,9 @@ export class HaScriptEditor extends LitElement { ha-card { overflow: hidden; } + p { + margin-bottom: 0; + } .errors { padding: 20px; font-weight: bold; diff --git a/src/resources/styles.ts b/src/resources/styles.ts index 0b3c1b2678..9eeeb4821d 100644 --- a/src/resources/styles.ts +++ b/src/resources/styles.ts @@ -100,6 +100,10 @@ export const haStyle = css` line-height: var(--paper-font-subhead_-_line-height); } + a { + color: var(--primary-color); + } + .secondary { color: var(--secondary-text-color); } diff --git a/src/translations/en.json b/src/translations/en.json index 5004b80530..1f454dbe12 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -841,6 +841,19 @@ "label": "Description", "placeholder": "Optional description" }, + "modes": { + "label": "Mode", + "description": "The mode controls what happens when the automation is triggered while the actions are still running from a previous trigger. Check the {documentation_link} for more info.", + "documentation": "automation documentation", + "single": "Single", + "restart": "Restart", + "queued": "Queued", + "parallel": "Parallel (default)" + }, + "max": { + "queued": "Queue length", + "parallel": "Max number of parallel runs" + }, "edit_yaml": "Edit as YAML", "edit_ui": "Edit with UI", "triggers": { @@ -1068,6 +1081,19 @@ "introduction": "Use scripts to execute a sequence of actions.", "header": "Script: {name}", "default_name": "New Script", + "modes": { + "label": "Mode", + "description": "The mode controls what happens when script is invoked while it is still running from one or more previous invocations. Check the {documentation_link} for more info.", + "documentation": "script documentation", + "single": "Single (default)", + "restart": "Restart", + "queued": "Queued", + "parallel": "Parallel" + }, + "max": { + "queued": "Queue length", + "parallel": "Max number of parallel runs" + }, "load_error_not_editable": "Only scripts inside scripts.yaml are editable.", "delete_confirm": "Are you sure you want to delete this script?", "delete_script": "Delete script",