From de7ffb10cbca7a5250b77bb964afc9ef10452362 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 7 Sep 2020 19:53:10 +0200 Subject: [PATCH] Automation editor tweaks (#6713) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Joakim Sørensen --- .../entity/ha-entity-attribute-picker.ts | 178 ++++++++++++++++++ src/components/entity/ha-entity-picker.ts | 15 +- src/data/automation.ts | 9 +- src/data/script.ts | 10 +- src/dialogs/generic/dialog-box.ts | 8 +- .../action/ha-automation-action-row.ts | 27 +-- .../automation/action/ha-automation-action.ts | 9 + .../types/ha-automation-action-choose.ts | 17 +- .../types/ha-automation-action-repeat.ts | 27 ++- .../types/ha-automation-action-service.ts | 6 +- .../ha-automation-action-wait_for_trigger.ts | 70 +++++++ .../ha-automation-action-wait_template.ts | 20 +- .../condition/ha-automation-condition-row.ts | 13 +- .../condition/ha-automation-condition.ts | 39 ++++ .../ha-automation-condition-numeric_state.ts | 34 ++-- .../types/ha-automation-condition-state.ts | 26 +-- .../types/ha-automation-condition-time.ts | 131 +++++++++++-- .../config/automation/ha-automation-editor.ts | 55 +++++- .../trigger/ha-automation-trigger-row.ts | 19 +- .../trigger/ha-automation-trigger.ts | 9 + .../ha-automation-trigger-numeric_state.ts | 26 +-- .../types/ha-automation-trigger-state.ts | 26 +-- .../types/ha-automation-trigger-time.ts | 75 +++++++- .../config-elements/hui-entity-card-editor.ts | 11 +- src/state/url-sync-mixin.ts | 10 +- src/translations/en.json | 21 ++- 26 files changed, 738 insertions(+), 153 deletions(-) create mode 100644 src/components/entity/ha-entity-attribute-picker.ts create mode 100644 src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts diff --git a/src/components/entity/ha-entity-attribute-picker.ts b/src/components/entity/ha-entity-attribute-picker.ts new file mode 100644 index 0000000000..ee2958323b --- /dev/null +++ b/src/components/entity/ha-entity-attribute-picker.ts @@ -0,0 +1,178 @@ +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-item/paper-item"; +import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light"; +import { HassEntity } from "home-assistant-js-websocket"; +import { + css, + CSSResult, + customElement, + html, + LitElement, + property, + PropertyValues, + query, + TemplateResult, +} from "lit-element"; +import { fireEvent } from "../../common/dom/fire_event"; +import { PolymerChangedEvent } from "../../polymer-types"; +import { HomeAssistant } from "../../types"; +import "../ha-icon-button"; +import "./state-badge"; + +export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean; + +const rowRenderer = (root: HTMLElement, _owner, model: { item: string }) => { + if (!root.firstElementChild) { + root.innerHTML = ` + + + `; + } + root.querySelector("paper-item")!.textContent = model.item; +}; + +@customElement("ha-entity-attribute-picker") +class HaEntityAttributePicker extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public entityId?: string; + + @property({ type: Boolean }) public autofocus = false; + + @property({ type: Boolean }) public disabled = false; + + @property({ type: Boolean, attribute: "allow-custom-value" }) + public allowCustomValue; + + @property() public label?: string; + + @property() public value?: string; + + @property({ type: Boolean }) private _opened = false; + + @query("vaadin-combo-box-light") private _comboBox!: HTMLElement; + + protected shouldUpdate(changedProps: PropertyValues) { + return !(!changedProps.has("_opened") && this._opened); + } + + protected updated(changedProps: PropertyValues) { + if (changedProps.has("_opened") && this._opened) { + const state = this.entityId ? this.hass.states[this.entityId] : undefined; + (this._comboBox as any).items = state + ? Object.keys(state.attributes) + : []; + } + } + + protected render(): TemplateResult { + if (!this.hass) { + return html``; + } + + return html` + + + ${this.value + ? html` + + Clear + + ` + : ""} + + + Toggle + + + + `; + } + + private _clearValue(ev: Event) { + ev.stopPropagation(); + this._setValue(""); + } + + private get _value() { + return this.value || ""; + } + + private _openedChanged(ev: PolymerChangedEvent) { + this._opened = ev.detail.value; + } + + private _valueChanged(ev: PolymerChangedEvent) { + const newValue = ev.detail.value; + if (newValue !== this._value) { + this._setValue(newValue); + } + } + + private _setValue(value: string) { + this.value = value; + setTimeout(() => { + fireEvent(this, "value-changed", { value }); + fireEvent(this, "change"); + }, 0); + } + + static get styles(): CSSResult { + return css` + paper-input > ha-icon-button { + --mdc-icon-button-size: 24px; + padding: 0px 2px; + color: var(--secondary-text-color); + } + [hidden] { + display: none; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-entity-attribute-picker": HaEntityAttributePicker; + } +} diff --git a/src/components/entity/ha-entity-picker.ts b/src/components/entity/ha-entity-picker.ts index 300ae57a79..51ddf0d30f 100644 --- a/src/components/entity/ha-entity-picker.ts +++ b/src/components/entity/ha-entity-picker.ts @@ -1,4 +1,3 @@ -import "../ha-icon-button"; import "@polymer/paper-input/paper-input"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-item-body"; @@ -20,6 +19,7 @@ import { computeDomain } from "../../common/entity/compute_domain"; import { computeStateName } from "../../common/entity/compute_state_name"; import { PolymerChangedEvent } from "../../polymer-types"; import { HomeAssistant } from "../../types"; +import "../ha-icon-button"; import "./state-badge"; export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean; @@ -95,6 +95,8 @@ class HaEntityPicker extends LitElement { @query("vaadin-combo-box-light") private _comboBox!: HTMLElement; + private _initedStates = false; + private _getStates = memoizeOne( ( _opened: boolean, @@ -148,11 +150,18 @@ class HaEntityPicker extends LitElement { ); protected shouldUpdate(changedProps: PropertyValues) { + if ( + changedProps.has("value") || + changedProps.has("label") || + changedProps.has("disabled") + ) { + return true; + } return !(!changedProps.has("_opened") && this._opened); } protected updated(changedProps: PropertyValues) { - if (changedProps.has("_opened") && this._opened) { + if (!this._initedStates || (changedProps.has("_opened") && this._opened)) { const states = this._getStates( this._opened, this.hass, @@ -162,6 +171,7 @@ class HaEntityPicker extends LitElement { this.includeDeviceClasses ); (this._comboBox as any).items = states; + this._initedStates = true; } } @@ -169,7 +179,6 @@ class HaEntityPicker extends LitElement { if (!this.hass) { return html``; } - return html` - + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" )} @@ -261,6 +263,7 @@ export default class HaAutomationActionRow extends LitElement { this._switchYamlMode(); break; case 1: + fireEvent(this, "duplicate"); break; case 2: this._onDelete(); diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index d6e815ea63..77db982c3b 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -28,6 +28,7 @@ export default class HaAutomationAction extends LitElement { .index=${idx} .totalActions=${this.actions.length} .action=${action} + @duplicate=${this._duplicateAction} @move-action=${this._move} @value-changed=${this._actionChanged} .hass=${this.hass} @@ -78,6 +79,14 @@ export default class HaAutomationAction extends LitElement { fireEvent(this, "value-changed", { value: actions }); } + private _duplicateAction(ev: CustomEvent) { + ev.stopPropagation(); + const index = (ev.target as any).index; + fireEvent(this, "value-changed", { + value: this.actions.concat(this.actions[index]), + }); + } + static get styles(): CSSResult { return css` ha-automation-action-row, 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 index 0bca89bf91..fa1f52f349 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-choose.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-choose.ts @@ -1,22 +1,21 @@ +import { mdiDelete } from "@mdi/js"; import "@polymer/paper-input/paper-input"; +import "@polymer/paper-listbox/paper-listbox"; import { + css, + CSSResult, 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 { Action, ChooseAction } from "../../../../../data/script"; import { haStyle } from "../../../../../resources/styles"; -import { mdiDelete } from "@mdi/js"; +import { HomeAssistant } from "../../../../../types"; +import "../ha-automation-action"; +import { ActionElement } from "../ha-automation-action-row"; @customElement("ha-automation-action-choose") export class HaChooseAction extends LitElement implements ActionElement { 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 3fd2cd95cb..13adc9aa74 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,22 +1,21 @@ 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 { CSSResult, customElement, LitElement, property } from "lit-element"; +import { html } from "lit-html"; import { fireEvent } from "../../../../../common/dom/fire_event"; -import "../ha-automation-action"; -import { Condition } from "../../../../lovelace/common/validate-condition"; +import { + Action, + CountRepeat, + RepeatAction, + UntilRepeat, + WhileRepeat, +} from "../../../../../data/script"; import { haStyle } from "../../../../../resources/styles"; +import { HomeAssistant } from "../../../../../types"; +import { Condition } from "../../../../lovelace/common/validate-condition"; +import "../ha-automation-action"; +import { ActionElement } from "../ha-automation-action-row"; const OPTIONS = ["count", "while", "until"]; diff --git a/src/panels/config/automation/action/types/ha-automation-action-service.ts b/src/panels/config/automation/action/types/ha-automation-action-service.ts index 5ef46a6875..c7e564bcb8 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-service.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-service.ts @@ -8,6 +8,7 @@ import { } from "lit-element"; import { html } from "lit-html"; import memoizeOne from "memoize-one"; +import { any, assert, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { computeDomain } from "../../../../../common/entity/compute_domain"; import { computeObjectId } from "../../../../../common/entity/compute_object_id"; @@ -18,14 +19,13 @@ import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor"; import { ServiceAction } from "../../../../../data/script"; import type { PolymerChangedEvent } from "../../../../../polymer-types"; import type { HomeAssistant } from "../../../../../types"; -import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; -import { assert, optional, object, string } from "superstruct"; import { EntityId } from "../../../../lovelace/common/structs/is-entity-id"; +import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; const actionStruct = object({ service: optional(string()), entity_id: optional(EntityId), - data: optional(object()), + data: optional(any()), }); @customElement("ha-automation-action-service") diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts new file mode 100644 index 0000000000..2d99185f5a --- /dev/null +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger.ts @@ -0,0 +1,70 @@ +import "@polymer/paper-input/paper-input"; +import "@polymer/paper-input/paper-textarea"; +import { customElement, LitElement, property } from "lit-element"; +import { html } from "lit-html"; +import { fireEvent } from "../../../../../common/dom/fire_event"; +import "../../../../../components/ha-formfield"; +import { WaitForTriggerAction } from "../../../../../data/script"; +import { HomeAssistant } from "../../../../../types"; +import "../../trigger/ha-automation-trigger"; +import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; + +@customElement("ha-automation-action-wait_for_trigger") +export class HaWaitForTriggerAction extends LitElement + implements ActionElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public action!: WaitForTriggerAction; + + public static get defaultConfig() { + return { wait_for_trigger: [], timeout: "" }; + } + + protected render() { + const { wait_for_trigger, continue_on_timeout, timeout } = this.action; + + return html` + +
+ + + + + `; + } + + private _continueChanged(ev) { + fireEvent(this, "value-changed", { + value: { ...this.action, continue_on_timeout: ev.target.checked }, + }); + } + + private _valueChanged(ev: CustomEvent): void { + handleChangeEvent(this, ev); + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-action-wait_for_trigger": HaWaitForTriggerAction; + } +} diff --git a/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts b/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts index e585d52032..2cf6a86299 100644 --- a/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts +++ b/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts @@ -2,6 +2,7 @@ import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-textarea"; import { customElement, LitElement, property } from "lit-element"; import { html } from "lit-html"; +import { fireEvent } from "../../../../../common/dom/fire_event"; import { WaitAction } from "../../../../../data/script"; import { HomeAssistant } from "../../../../../types"; import { ActionElement, handleChangeEvent } from "../ha-automation-action-row"; @@ -13,11 +14,11 @@ export class HaWaitAction extends LitElement implements ActionElement { @property() public action!: WaitAction; public static get defaultConfig() { - return { wait_template: "", timeout: "" }; + return { wait_template: "" }; } protected render() { - const { wait_template, timeout } = this.action; + const { wait_template, timeout, continue_on_timeout } = this.action; return html` +
+ + + `; } + private _continueChanged(ev) { + fireEvent(this, "value-changed", { + value: { ...this.action, continue_on_timeout: ev.target.checked }, + }); + } + private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 1afc44967c..1f51b47f13 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -1,24 +1,24 @@ -import "../../../../components/ha-icon-button"; -import "@polymer/paper-item/paper-item"; +import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; -import "../../../../components/ha-button-menu"; import { mdiDotsVertical } from "@mdi/js"; +import "@polymer/paper-item/paper-item"; import { css, CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, } from "lit-element"; import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; +import "../../../../components/ha-icon-button"; import { Condition } from "../../../../data/automation"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { HomeAssistant } from "../../../../types"; import "./ha-automation-condition-editor"; -import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; export interface ConditionElement extends LitElement { condition: Condition; @@ -81,7 +81,7 @@ export default class HaAutomationConditionRow extends LitElement { "ui.panel.config.automation.editor.edit_yaml" )}
- + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" )} @@ -109,6 +109,7 @@ export default class HaAutomationConditionRow extends LitElement { this._switchYamlMode(); break; case 1: + fireEvent(this, "duplicate"); break; case 2: this._onDelete(); diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 05367bd6a2..b4529c4650 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -6,6 +6,7 @@ import { html, LitElement, property, + PropertyValues, } from "lit-element"; import { fireEvent } from "../../../../common/dom/fire_event"; import "../../../../components/ha-card"; @@ -20,13 +21,43 @@ export default class HaAutomationCondition extends LitElement { @property() public conditions!: Condition[]; + protected updated(changedProperties: PropertyValues) { + if (!changedProperties.has("conditions")) { + return; + } + let updatedConditions: Condition[] | undefined; + if (!Array.isArray(this.conditions)) { + updatedConditions = [this.conditions]; + } + + (updatedConditions || this.conditions).forEach((condition, index) => { + if (typeof condition === "string") { + updatedConditions = updatedConditions || [...this.conditions]; + updatedConditions[index] = { + condition: "template", + value_template: condition, + }; + } + }); + + if (updatedConditions) { + fireEvent(this, "value-changed", { + value: updatedConditions, + }); + } + } + protected render() { + if (!Array.isArray(this.conditions)) { + return html``; + } return html` ${this.conditions.map( (cond, idx) => html` @@ -68,6 +99,14 @@ export default class HaAutomationCondition extends LitElement { fireEvent(this, "value-changed", { value: conditions }); } + private _duplicateCondition(ev: CustomEvent) { + ev.stopPropagation(); + const index = (ev.target as any).index; + fireEvent(this, "value-changed", { + value: this.conditions.concat(this.conditions[index]), + }); + } + static get styles(): CSSResult { return css` ha-automation-condition-row, diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts index 656fc999e6..71259efe5d 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-numeric_state.ts @@ -1,7 +1,6 @@ import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-textarea"; import { customElement, html, LitElement, property } from "lit-element"; -import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/entity/ha-entity-picker"; import { NumericStateCondition } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; @@ -19,16 +18,34 @@ export default class HaNumericStateCondition extends LitElement { }; } - protected render() { - const { value_template, entity_id, below, above } = this.condition; + public render() { + const { + value_template, + entity_id, + attribute, + below, + above, + } = this.condition; return html` + + ) { - ev.stopPropagation(); - fireEvent(this, "value-changed", { - value: { ...this.condition, entity_id: ev.detail.value }, - }); - } } declare global { diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts index 7f790194fc..baac37cf35 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-time.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-time.ts @@ -1,5 +1,14 @@ +import { Radio } from "@material/mwc-radio"; import "@polymer/paper-input/paper-input"; -import { customElement, html, LitElement, property } from "lit-element"; +import { + customElement, + html, + internalProperty, + LitElement, + property, +} from "lit-element"; +import "../../../../../components/ha-formfield"; +import "../../../../../components/ha-radio"; import { TimeCondition } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; import { @@ -7,38 +16,130 @@ import { handleChangeEvent, } from "../ha-automation-condition-row"; +const includeDomains = ["input_datetime"]; + @customElement("ha-automation-condition-time") export class HaTimeCondition extends LitElement implements ConditionElement { @property({ attribute: false }) public hass!: HomeAssistant; @property() public condition!: TimeCondition; + @internalProperty() private _inputModeBefore?: boolean; + + @internalProperty() private _inputModeAfter?: boolean; + public static get defaultConfig() { return {}; } protected render() { const { after, before } = this.condition; + + const inputModeBefore = + this._inputModeBefore ?? before?.startsWith("input_datetime."); + const inputModeAfter = + this._inputModeAfter ?? after?.startsWith("input_datetime."); + return html` - - + + + + > + + + ${inputModeAfter + ? html`` + : html``} + + + + + + + + ${inputModeBefore + ? html`` + : html``} `; } + private _handleModeChanged(ev: Event) { + const target = ev.target as Radio; + if (target.getAttribute("name") === "mode_after") { + this._inputModeAfter = target.value === "input"; + } else { + this._inputModeBefore = target.value === "input"; + } + } + private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 255edefff7..1c6f0c125c 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -1,28 +1,32 @@ +import "@material/mwc-fab"; +import { mdiContentDuplicate, mdiContentSave, mdiDelete } from "@mdi/js"; 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 { PaperListboxElement } from "@polymer/paper-listbox"; import { css, CSSResult, html, + internalProperty, LitElement, property, - internalProperty, PropertyValues, TemplateResult, } from "lit-element"; +import { classMap } from "lit-html/directives/class-map"; import { navigate } from "../../../common/navigate"; import "../../../components/ha-card"; +import "../../../components/ha-icon-button"; import "../../../components/ha-svg-icon"; -import "@material/mwc-fab"; import { AutomationConfig, AutomationEntity, Condition, deleteAutomation, getAutomationEditorInitData, + showAutomationEditor, Trigger, triggerAutomation, } from "../../../data/automation"; @@ -42,9 +46,6 @@ import { HaDeviceAction } from "./action/types/ha-automation-action-device_id"; 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"; -import { classMap } from "lit-html/directives/class-map"; const MODES = ["single", "restart", "queued", "parallel"]; const MODES_MAX = ["queued", "parallel"]; @@ -53,6 +54,7 @@ declare global { // for fire event interface HASSDomEvents { "ui-mode-not-available": Error; + duplicate: undefined; } } @@ -92,14 +94,24 @@ export class HaAutomationEditor extends LitElement { ${!this.automationId ? "" : html` - + + + + > + + `} ${this._config ? html` @@ -473,6 +485,31 @@ export class HaAutomationEditor extends LitElement { } } + private async _duplicate() { + if (this._dirty) { + if ( + !(await showConfirmationDialog(this, { + text: this.hass!.localize( + "ui.panel.config.automation.editor.unsaved_confirm" + ), + confirmText: this.hass!.localize("ui.common.yes"), + dismissText: this.hass!.localize("ui.common.no"), + })) + ) { + return; + } + // Wait for dialog to complate closing + await new Promise((resolve) => setTimeout(resolve, 0)); + } + showAutomationEditor(this, { + ...this._config, + id: undefined, + alias: `${this._config?.alias} (${this.hass.localize( + "ui.panel.config.automation.picker.duplicate" + )})`, + }); + } + private async _deleteConfirm() { showConfirmationDialog(this, { text: this.hass.localize( diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index a50373bbc8..feeda91773 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -1,25 +1,27 @@ +import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; +import "@material/mwc-list/mwc-list-item"; +import { mdiDotsVertical } from "@mdi/js"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; -import "../../../../components/ha-icon-button"; import "@polymer/paper-item/paper-item"; import "@polymer/paper-listbox/paper-listbox"; -import "@material/mwc-list/mwc-list-item"; -import "../../../../components/ha-button-menu"; -import { mdiDotsVertical } from "@mdi/js"; import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox"; import { css, CSSResult, customElement, html, + internalProperty, LitElement, property, - internalProperty, } from "lit-element"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; +import "../../../../components/ha-button-menu"; import "../../../../components/ha-card"; +import "../../../../components/ha-icon-button"; import type { Trigger } from "../../../../data/automation"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; +import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; import "./types/ha-automation-trigger-device"; import "./types/ha-automation-trigger-event"; @@ -29,14 +31,12 @@ import "./types/ha-automation-trigger-mqtt"; import "./types/ha-automation-trigger-numeric_state"; import "./types/ha-automation-trigger-state"; import "./types/ha-automation-trigger-sun"; +import "./types/ha-automation-trigger-tag"; import "./types/ha-automation-trigger-template"; import "./types/ha-automation-trigger-time"; import "./types/ha-automation-trigger-time_pattern"; import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-zone"; -import "./types/ha-automation-trigger-tag"; -import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; -import { haStyle } from "../../../../resources/styles"; const OPTIONS = [ "device", @@ -113,7 +113,7 @@ export default class HaAutomationTriggerRow extends LitElement { "ui.panel.config.automation.editor.edit_yaml" )} - + ${this.hass.localize( "ui.panel.config.automation.editor.actions.duplicate" )} @@ -183,6 +183,7 @@ export default class HaAutomationTriggerRow extends LitElement { this._switchYamlMode(); break; case 1: + fireEvent(this, "duplicate"); break; case 2: this._onDelete(); diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index a074cbb881..953ec67037 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -27,6 +27,7 @@ export default class HaAutomationTrigger extends LitElement { @@ -68,6 +69,14 @@ export default class HaAutomationTrigger extends LitElement { fireEvent(this, "value-changed", { value: triggers }); } + private _duplicateTrigger(ev: CustomEvent) { + ev.stopPropagation(); + const index = (ev.target as any).index; + fireEvent(this, "value-changed", { + value: this.triggers.concat(this.triggers[index]), + }); + } + static get styles(): CSSResult { return css` ha-automation-trigger-row, diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts index 068dbd6856..0c54205b2b 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state.ts @@ -1,7 +1,6 @@ import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-textarea"; import { customElement, html, LitElement, property } from "lit-element"; -import { fireEvent } from "../../../../../common/dom/fire_event"; import "../../../../../components/entity/ha-entity-picker"; import { ForDict, NumericStateTrigger } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; @@ -19,8 +18,8 @@ export default class HaNumericStateTrigger extends LitElement { }; } - protected render() { - const { value_template, entity_id, below, above } = this.trigger; + public render() { + const { value_template, entity_id, attribute, below, above } = this.trigger; let trgFor = this.trigger.for; if ( @@ -41,10 +40,22 @@ export default class HaNumericStateTrigger extends LitElement { return html` + + ) { - ev.stopPropagation(); - fireEvent(this, "value-changed", { - value: { ...this.trigger, entity_id: ev.detail.value }, - }); - } } declare global { diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts index 7ea3043660..1d9e6fa677 100644 --- a/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts +++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-time.ts @@ -1,5 +1,14 @@ import "@polymer/paper-input/paper-input"; -import { customElement, html, LitElement, property } from "lit-element"; +import { + customElement, + html, + internalProperty, + LitElement, + property, +} from "lit-element"; +import "../../../../../components/entity/ha-entity-picker"; +import "../../../../../components/ha-formfield"; +import "../../../../../components/ha-radio"; import { TimeTrigger } from "../../../../../data/automation"; import { HomeAssistant } from "../../../../../types"; import { @@ -7,31 +16,81 @@ import { TriggerElement, } from "../ha-automation-trigger-row"; +const includeDomains = ["input_datetime"]; + @customElement("ha-automation-trigger-time") export class HaTimeTrigger extends LitElement implements TriggerElement { @property({ attribute: false }) public hass!: HomeAssistant; @property() public trigger!: TimeTrigger; + @internalProperty() private _inputMode?: boolean; + public static get defaultConfig() { return { at: "" }; } protected render() { const { at } = this.trigger; + const inputMode = this._inputMode ?? at?.startsWith("input_datetime."); return html` - + > + + + + + + ${inputMode + ? html`` + : html``} `; } + private _handleModeChanged(ev: Event) { + this._inputMode = (ev.target as any).value === "input"; + } + private _valueChanged(ev: CustomEvent): void { handleChangeEvent(this, ev); } } + +declare global { + interface HTMLElementTagNameMap { + "ha-automation-trigger-time": HaTimeTrigger; + } +} diff --git a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts index cd230be535..49a9db601c 100644 --- a/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-entity-card-editor.ts @@ -2,13 +2,15 @@ import "@polymer/paper-input/paper-input"; import { customElement, html, + internalProperty, LitElement, property, - internalProperty, TemplateResult, } from "lit-element"; +import { assert, object, optional, string } from "superstruct"; import { fireEvent } from "../../../../common/dom/fire_event"; import { stateIcon } from "../../../../common/entity/state_icon"; +import "../../../../components/entity/ha-entity-attribute-picker"; import "../../../../components/ha-icon-input"; import { HomeAssistant } from "../../../../types"; import { EntityCardConfig } from "../../cards/types"; @@ -19,7 +21,6 @@ import { headerFooterConfigStructs } from "../../header-footer/types"; import { LovelaceCardEditor } from "../../types"; import { EditorTarget, EntitiesEditorEvent } from "../types"; import { configElementStyle } from "./config-elements-style"; -import { string, object, optional, assert } from "superstruct"; const cardConfigStruct = object({ type: string(), @@ -113,7 +114,9 @@ export class HuiEntityCardEditor extends LitElement >
- + >