Automation editor tweaks (#6713)

Co-authored-by: Joakim Sørensen <joasoe@gmail.com>
This commit is contained in:
Bram Kragten 2020-09-07 19:53:10 +02:00 committed by GitHub
parent 80224e6974
commit de7ffb10cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 738 additions and 153 deletions

View File

@ -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 = `
<style>
paper-item {
margin: -10px;
padding: 0;
}
</style>
<paper-item></paper-item>
`;
}
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`
<vaadin-combo-box-light
.value=${this._value}
.allowCustomValue=${this.allowCustomValue}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._valueChanged}
>
<paper-input
.autofocus=${this.autofocus}
.label=${this.label ??
this.hass.localize(
"ui.components.entity.entity-attribute-picker.attribute"
)}
.value=${this._value}
.disabled=${this.disabled || !this.entityId}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
>
${this.value
? html`
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.entity.entity-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
Clear
</ha-icon-button>
`
: ""}
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.entity.entity-attribute-picker.show_attributes"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
Toggle
</ha-icon-button>
</paper-input>
</vaadin-combo-box-light>
`;
}
private _clearValue(ev: Event) {
ev.stopPropagation();
this._setValue("");
}
private get _value() {
return this.value || "";
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private _valueChanged(ev: PolymerChangedEvent<string>) {
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;
}
}

View File

@ -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`
<vaadin-combo-box-light
item-value-path="entity_id"

View File

@ -3,7 +3,7 @@ import {
HassEntityBase,
} from "home-assistant-js-websocket";
import { navigate } from "../common/navigate";
import { HomeAssistant, Context } from "../types";
import { Context, HomeAssistant } from "../types";
import { DeviceCondition, DeviceTrigger } from "./device_automation";
import { Action } from "./script";
@ -15,6 +15,7 @@ export interface AutomationEntity extends HassEntityBase {
}
export interface AutomationConfig {
id?: string;
alias: string;
description: string;
trigger: Trigger[];
@ -32,7 +33,8 @@ export interface ForDict {
export interface StateTrigger {
platform: "state";
entity_id?: string;
entity_id: string;
attribute?: string;
from?: string | number;
to?: string | number;
for?: string | number | ForDict;
@ -59,6 +61,7 @@ export interface HassTrigger {
export interface NumericStateTrigger {
platform: "numeric_state";
entity_id: string;
attribute?: string;
above?: number;
below?: number;
value_template?: string;
@ -136,12 +139,14 @@ export interface LogicalCondition {
export interface StateCondition {
condition: "state";
entity_id: string;
attribute?: string;
state: string | number;
}
export interface NumericStateCondition {
condition: "numeric_state";
entity_id: string;
attribute?: string;
above?: number;
below?: number;
value_template?: string;

View File

@ -5,7 +5,7 @@ import {
import { computeObjectId } from "../common/entity/compute_object_id";
import { navigate } from "../common/navigate";
import { HomeAssistant } from "../types";
import { Condition } from "./automation";
import { Condition, Trigger } from "./automation";
export const MODES = ["single", "restart", "queued", "parallel"];
export const MODES_MAX = ["queued", "parallel"];
@ -56,6 +56,13 @@ export interface SceneAction {
export interface WaitAction {
wait_template: string;
timeout?: number;
continue_on_timeout?: boolean;
}
export interface WaitForTriggerAction {
wait_for_trigger: Trigger[];
timeout?: number;
continue_on_timeout?: boolean;
}
export interface RepeatAction {
@ -91,6 +98,7 @@ export type Action =
| DelayAction
| SceneAction
| WaitAction
| WaitForTriggerAction
| RepeatAction
| ChooseAction;

View File

@ -5,19 +5,19 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { fireEvent } from "../../common/dom/fire_event";
import "../../components/ha-dialog";
import "../../components/ha-switch";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { DialogParams } from "./show-dialog-box";
import { fireEvent } from "../../common/dom/fire_event";
@customElement("dialog-box")
class DialogBox extends LitElement {
@ -114,8 +114,8 @@ class DialogBox extends LitElement {
}
private _dismiss(): void {
if (this._params!.cancel) {
this._params!.cancel();
if (this._params?.cancel) {
this._params.cancel();
}
this._close();
}

View File

@ -1,9 +1,8 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-icon-button";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-svg-icon";
import { mdiDotsVertical, mdiArrowUp, mdiArrowDown } from "@mdi/js";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
@ -12,29 +11,31 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
PropertyValues,
} 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-svg-icon";
import type { Action } from "../../../../data/script";
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../resources/styles";
import type { HomeAssistant } from "../../../../types";
import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
import "./types/ha-automation-action-choose";
import "./types/ha-automation-action-condition";
import "./types/ha-automation-action-delay";
import "./types/ha-automation-action-device_id";
import "./types/ha-automation-action-event";
import "./types/ha-automation-action-repeat";
import "./types/ha-automation-action-scene";
import "./types/ha-automation-action-service";
import "./types/ha-automation-action-wait_for_trigger";
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";
const OPTIONS = [
"condition",
@ -44,6 +45,7 @@ const OPTIONS = [
"scene",
"service",
"wait_template",
"wait_for_trigger",
"repeat",
"choose",
];
@ -166,7 +168,7 @@ export default class HaAutomationActionRow extends LitElement {
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
<mwc-list-item>
${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();

View File

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

View File

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

View File

@ -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"];

View File

@ -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")

View File

@ -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`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.wait_for_trigger.timeout"
)}
.name=${"timeout"}
.value=${timeout}
@value-changed=${this._valueChanged}
></paper-input>
<br />
<ha-formfield
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.wait_for_trigger.timeout"
)}
>
<ha-switch
.checked=${continue_on_timeout}
@change=${this._continueChanged}
></ha-switch>
</ha-formfield>
<ha-automation-trigger
.triggers=${wait_for_trigger}
.hass=${this.hass}
.name=${"wait_for_trigger"}
@value-changed=${this._valueChanged}
></ha-automation-trigger>
`;
}
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;
}
}

View File

@ -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`
<paper-textarea
@ -37,9 +38,24 @@ export class HaWaitAction extends LitElement implements ActionElement {
.value=${timeout}
@value-changed=${this._valueChanged}
></paper-input>
<br />
<ha-formfield
.label=${this.hass.localize("ui.panel.config.automation.editor.actions.type.wait_template.continue_timeout")}
>
<ha-switch
.checked=${continue_on_timeout}
@change=${this._continueChanged}
></ha-switch>
</ha-formfield>
`;
}
private _continueChanged(ev) {
fireEvent(this, "value-changed", {
value: { ...this.action, continue_on_timeout: ev.target.checked },
});
}
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}

View File

@ -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"
)}
</mwc-list-item>
<mwc-list-item disabled>
<mwc-list-item>
${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();

View File

@ -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`
<ha-automation-condition-row
.index=${idx}
.condition=${cond}
@duplicate=${this._duplicateCondition}
@value-changed=${this._conditionChanged}
.hass=${this.hass}
></ha-automation-condition-row>
@ -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,

View File

@ -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`
<ha-entity-picker
.value="${entity_id}"
@value-changed="${this._entityPicked}"
.value=${entity_id}
.name=${"entity_id"}
@value-changed=${this._valueChanged}
.hass=${this.hass}
allow-custom-entity
></ha-entity-picker>
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${entity_id}
.value=${attribute}
.name=${"attribute"}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.attribute"
)}
@value-changed=${this._valueChanged}
allow-custom-value
></ha-entity-attribute-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.numeric_state.above"
@ -60,13 +77,6 @@ export default class HaNumericStateCondition extends LitElement {
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.condition, entity_id: ev.detail.value },
});
}
}
declare global {

View File

@ -1,9 +1,8 @@
import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import { StateCondition } from "../../../../../data/automation";
import { PolymerChangedEvent } from "../../../../../polymer-types";
import { HomeAssistant } from "../../../../../types";
import {
ConditionElement,
@ -21,15 +20,27 @@ export class HaStateCondition extends LitElement implements ConditionElement {
}
protected render() {
const { entity_id, state } = this.condition;
const { entity_id, attribute, state } = this.condition;
return html`
<ha-entity-picker
.value=${entity_id}
@value-changed=${this._entityPicked}
.name=${"entity_id"}
@value-changed=${this._valueChanged}
.hass=${this.hass}
allow-custom-entity
></ha-entity-picker>
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${entity_id}
.value=${attribute}
.name=${"attribute"}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.attribute"
)}
@value-changed=${this._valueChanged}
allow-custom-value
></ha-entity-attribute-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.state.state"
@ -44,13 +55,6 @@ export class HaStateCondition extends LitElement implements ConditionElement {
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev: PolymerChangedEvent<string>) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.condition, entity_id: ev.detail.value },
});
}
}
declare global {

View File

@ -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`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.after"
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.conditions.type.time.type_value"
)}
name="after"
.value=${after}
@value-changed=${this._valueChanged}
></paper-input>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.before"
>
<ha-radio
@change=${this._handleModeChanged}
name="mode_after"
value="value"
?checked=${!inputModeAfter}
></ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.conditions.type.time.type_input"
)}
name="before"
.value=${before}
@value-changed=${this._valueChanged}
></paper-input>
>
<ha-radio
@change=${this._handleModeChanged}
name="mode_after"
value="input"
?checked=${inputModeAfter}
></ha-radio>
</ha-formfield>
${inputModeAfter
? html`<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.after"
)}
.includeDomains=${includeDomains}
.name=${"after"}
.value=${after?.startsWith("input_datetime.") ? after : ""}
@value-changed=${this._valueChanged}
.hass=${this.hass}
></ha-entity-picker>`
: html`<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.after"
)}
name="after"
.value=${after?.startsWith("input_datetime.") ? "" : after}
@value-changed=${this._valueChanged}
></paper-input>`}
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.conditions.type.time.type_value"
)}
>
<ha-radio
@change=${this._handleModeChanged}
name="mode_before"
value="value"
?checked=${!inputModeBefore}
></ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.conditions.type.time.type_input"
)}
>
<ha-radio
@change=${this._handleModeChanged}
name="mode_before"
value="input"
?checked=${inputModeBefore}
></ha-radio>
</ha-formfield>
${inputModeBefore
? html`<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.before"
)}
.includeDomains=${includeDomains}
.name=${"before"}
.value=${before?.startsWith("input_datetime.") ? before : ""}
@value-changed=${this._valueChanged}
.hass=${this.hass}
></ha-entity-picker>`
: html`<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.time.before"
)}
name="before"
.value=${before?.startsWith("input_datetime.") ? "" : before}
@value-changed=${this._valueChanged}
></paper-input>`}
`;
}
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);
}

View File

@ -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`
<ha-icon-button
<mwc-icon-button
slot="toolbar-icon"
title="${this.hass.localize(
"ui.panel.config.automation.picker.duplicate_automation"
)}"
@click=${this._duplicate}
>
<ha-svg-icon .path=${mdiContentDuplicate}></ha-svg-icon>
</mwc-icon-button>
<mwc-icon-button
slot="toolbar-icon"
title="${this.hass.localize(
"ui.panel.config.automation.picker.delete_automation"
)}"
icon="hass:delete"
@click=${this._deleteConfirm}
></ha-icon-button>
>
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
`}
${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(

View File

@ -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"
)}
</mwc-list-item>
<mwc-list-item disabled>
<mwc-list-item>
${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();

View File

@ -27,6 +27,7 @@ export default class HaAutomationTrigger extends LitElement {
<ha-automation-trigger-row
.index=${idx}
.trigger=${trg}
@duplicate=${this._duplicateTrigger}
@value-changed=${this._triggerChanged}
.hass=${this.hass}
></ha-automation-trigger-row>
@ -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,

View File

@ -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`
<ha-entity-picker
.value="${entity_id}"
@value-changed="${this._entityPicked}"
@value-changed="${this._valueChanged}"
.name=${"entity_id"}
.hass=${this.hass}
allow-custom-entity
></ha-entity-picker>
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${entity_id}
.value=${attribute}
.name=${"attribute"}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.attribute"
)}
@value-changed=${this._valueChanged}
allow-custom-value
></ha-entity-attribute-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.numeric_state.above"
@ -84,13 +95,6 @@ export default class HaNumericStateTrigger extends LitElement {
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.trigger, entity_id: ev.detail.value },
});
}
}
declare global {

View File

@ -1,9 +1,8 @@
import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-attribute-picker";
import "../../../../../components/entity/ha-entity-picker";
import { ForDict, StateTrigger } from "../../../../../data/automation";
import { PolymerChangedEvent } from "../../../../../polymer-types";
import { HomeAssistant } from "../../../../../types";
import {
handleChangeEvent,
@ -21,7 +20,7 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
}
protected render() {
const { entity_id, to, from } = this.trigger;
const { entity_id, attribute, to, from } = this.trigger;
let trgFor = this.trigger.for;
if (
@ -43,10 +42,22 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
return html`
<ha-entity-picker
.value=${entity_id}
@value-changed=${this._entityPicked}
@value-changed=${this._valueChanged}
.name=${"entity_id"}
.hass=${this.hass}
allow-custom-entity
></ha-entity-picker>
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${entity_id}
.value=${attribute}
.name=${"attribute"}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.attribute"
)}
@value-changed=${this._valueChanged}
allow-custom-value
></ha-entity-attribute-picker>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.from"
@ -77,13 +88,6 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
private _valueChanged(ev: CustomEvent): void {
handleChangeEvent(this, ev);
}
private _entityPicked(ev: PolymerChangedEvent<string>) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: { ...this.trigger, entity_id: ev.detail.value },
});
}
}
declare global {

View File

@ -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`
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time.at"
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.triggers.type.time.type_value"
)}
name="at"
.value=${at}
@value-changed=${this._valueChanged}
></paper-input>
>
<ha-radio
@change=${this._handleModeChanged}
name="mode"
value="value"
?checked=${!inputMode}
></ha-radio>
</ha-formfield>
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.automation.editor.triggers.type.time.type_input"
)}
>
<ha-radio
@change=${this._handleModeChanged}
name="mode"
value="input"
?checked=${inputMode}
></ha-radio>
</ha-formfield>
${inputMode
? html`<ha-entity-picker
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time.at"
)}
.includeDomains=${includeDomains}
.name=${"at"}
.value=${at?.startsWith("input_datetime.") ? at : ""}
@value-changed=${this._valueChanged}
.hass=${this.hass}
></ha-entity-picker>`
: html`<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.time.at"
)}
name="at"
.value=${at?.startsWith("input_datetime.") ? "" : at}
@value-changed=${this._valueChanged}
></paper-input>`}
`;
}
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;
}
}

View File

@ -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
></ha-icon-input>
</div>
<div class="side-by-side">
<paper-input
<ha-entity-attribute-picker
.hass=${this.hass}
.entityId=${this._entity}
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.attribute"
)} (${this.hass.localize(
@ -122,7 +125,7 @@ export class HuiEntityCardEditor extends LitElement
.value=${this._attribute}
.configValue=${"attribute"}
@value-changed=${this._valueChanged}
></paper-input>
></ha-entity-attribute-picker>
<paper-input
.label="${this.hass.localize(
"ui.panel.lovelace.editor.card.generic.unit"

View File

@ -1,14 +1,14 @@
/* eslint-disable no-console */
import { UpdatingElement } from "lit-element";
import { HASSDomEvent } from "../common/dom/fire_event";
import {
closeDialog,
showDialog,
DialogState,
DialogClosedParams,
DialogState,
showDialog,
} from "../dialogs/make-dialog-manager";
import { Constructor } from "../types";
import { HASSDomEvent } from "../common/dom/fire_event";
import { UpdatingElement } from "lit-element";
import { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
import { Constructor } from "../types";
const DEBUG = false;

View File

@ -286,6 +286,10 @@
"entity": "Entity",
"clear": "Clear",
"show_entities": "Show entities"
},
"entity-attribute-picker": {
"attribute": "Attribute",
"show_attributes": "Show attributes"
}
},
"device-picker": {
@ -915,6 +919,8 @@
"show_info_automation": "Show info about automation",
"delete_automation": "Delete automation",
"delete_confirm": "Are you sure you want to delete this automation?",
"duplicate_automation": "Duplicate automation",
"duplicate": "Duplicate",
"headers": {
"name": "Name"
}
@ -985,6 +991,7 @@
},
"state": {
"label": "State",
"attribute": "Attribute (Optional)",
"from": "From",
"for": "For",
"to": "To"
@ -1021,8 +1028,10 @@
"value_template": "Value template"
},
"time": {
"type_value": "Fixed time",
"type_input": "Value of a date/time helper",
"label": "Time",
"at": "At"
"at": "At time"
},
"time_pattern": {
"label": "Time Pattern",
@ -1098,6 +1107,8 @@
"value_template": "[%key:ui::panel::config::automation::editor::triggers::type::template::value_template%]"
},
"time": {
"type_value": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_value%]",
"type_input": "[%key:ui::panel::config::automation::editor::triggers::type::time::type_input%]",
"label": "[%key:ui::panel::config::automation::editor::triggers::type::time::label%]",
"after": "After",
"before": "Before"
@ -1132,7 +1143,13 @@
"wait_template": {
"label": "Wait",
"wait_template": "Wait Template",
"timeout": "Timeout (optional)"
"timeout": "Timeout (optional)",
"continue_timeout": "Continue on timeout"
},
"wait_for_trigger": {
"label": "Wait for trigger",
"timeout": "[%key:ui::panel::config::automation::editor::actions::type::wait_template::timeout%]",
"continue_timeout": "[%key:ui::panel::config::automation::editor::actions::type::wait_template::continue_timeout%]"
},
"condition": {
"label": "Condition"