diff --git a/src/data/automation.ts b/src/data/automation.ts
index 71a7e549c2..6359264eac 100644
--- a/src/data/automation.ts
+++ b/src/data/automation.ts
@@ -5,6 +5,7 @@ import {
import { HomeAssistant } from "../types";
import { navigate } from "../common/navigate";
import { DeviceCondition, DeviceTrigger } from "./device_automation";
+import { Action } from "./script";
export interface AutomationEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
@@ -18,7 +19,7 @@ export interface AutomationConfig {
description: string;
trigger: Trigger[];
condition?: Condition[];
- action: any[];
+ action: Action[];
}
export interface ForDict {
diff --git a/src/data/script.ts b/src/data/script.ts
index 5eb2982f2f..a4e5055acc 100644
--- a/src/data/script.ts
+++ b/src/data/script.ts
@@ -1,5 +1,6 @@
import { HomeAssistant } from "../types";
import { computeObjectId } from "../common/entity/compute_object_id";
+import { Condition } from "./automation";
export interface EventAction {
event: string;
@@ -7,12 +8,40 @@ export interface EventAction {
event_data_template?: { [key: string]: any };
}
+export interface ServiceAction {
+ service: string;
+ entity_id?: string;
+ data?: { [key: string]: any };
+}
+
export interface DeviceAction {
device_id: string;
domain: string;
entity_id: string;
}
+export interface DelayAction {
+ delay: number;
+}
+
+export interface SceneAction {
+ scene: string;
+}
+
+export interface WaitAction {
+ wait_template: string;
+ timeout?: number;
+}
+
+export type Action =
+ | EventAction
+ | DeviceAction
+ | ServiceAction
+ | Condition
+ | DelayAction
+ | SceneAction
+ | WaitAction;
+
export const triggerScript = (
hass: HomeAssistant,
entityId: string,
diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts
new file mode 100644
index 0000000000..df0b925ea8
--- /dev/null
+++ b/src/panels/config/automation/action/ha-automation-action-row.ts
@@ -0,0 +1,272 @@
+import "@polymer/paper-icon-button/paper-icon-button";
+import "@polymer/paper-item/paper-item";
+import "@polymer/paper-listbox/paper-listbox";
+// tslint:disable-next-line
+import { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
+import "@polymer/paper-menu-button/paper-menu-button";
+import {
+ css,
+ CSSResult,
+ customElement,
+ html,
+ LitElement,
+ property,
+} from "lit-element";
+import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
+import { fireEvent } from "../../../../common/dom/fire_event";
+import "../../../../components/ha-card";
+import { HomeAssistant } from "../../../../types";
+
+import { Action } from "../../../../data/script";
+
+import "./types/ha-automation-action-service";
+import "./types/ha-automation-action-device_id";
+import "./types/ha-automation-action-delay";
+import "./types/ha-automation-action-event";
+import "./types/ha-automation-action-condition";
+import "./types/ha-automation-action-scene";
+import "./types/ha-automation-action-wait_template";
+
+const OPTIONS = [
+ "condition",
+ "delay",
+ "device_id",
+ "event",
+ "scene",
+ "service",
+ "wait_template",
+];
+
+const getType = (action: Action) => {
+ return OPTIONS.find((option) => option in action);
+};
+
+declare global {
+ // for fire event
+ interface HASSDomEvents {
+ "move-action": { direction: "up" | "down" };
+ }
+}
+
+export interface ActionElement extends LitElement {
+ action: Action;
+}
+
+export const handleChangeEvent = (element: ActionElement, ev: CustomEvent) => {
+ ev.stopPropagation();
+ const name = (ev.target as any)?.name;
+ if (!name) {
+ return;
+ }
+ const newVal = ev.detail.value;
+
+ if ((element.action[name] || "") === newVal) {
+ return;
+ }
+
+ let newAction: Action;
+ if (!newVal) {
+ newAction = { ...element.action };
+ delete newAction[name];
+ } else {
+ newAction = { ...element.action, [name]: newVal };
+ }
+ fireEvent(element, "value-changed", { value: newAction });
+};
+
+@customElement("ha-automation-action-row")
+export default class HaAutomationActionRow extends LitElement {
+ @property() public hass!: HomeAssistant;
+ @property() public action!: Action;
+ @property() public index!: number;
+ @property() public totalActions!: number;
+ @property() private _yamlMode = false;
+
+ protected render() {
+ const type = getType(this.action);
+ const selected = type ? OPTIONS.indexOf(type) : -1;
+ const yamlMode = this._yamlMode || selected === -1;
+
+ return html`
+
+
+
+ ${yamlMode
+ ? html`
+
+ ${selected === -1
+ ? html`
+ ${this.hass.localize(
+ "ui.panel.config.automation.editor.actions.unsupported_action",
+ "action",
+ type
+ )}
+ `
+ : ""}
+
+
+ `
+ : html`
+
+
+ ${OPTIONS.map(
+ (opt) => html`
+
+ ${this.hass.localize(
+ `ui.panel.config.automation.editor.actions.type.${opt}.label`
+ )}
+
+ `
+ )}
+
+
+
+ ${dynamicElement(`ha-automation-action-${type}`, {
+ hass: this.hass,
+ action: this.action,
+ })}
+
+ `}
+
+
+ `;
+ }
+
+ private _moveUp() {
+ fireEvent(this, "move-action", { direction: "up" });
+ }
+
+ private _moveDown() {
+ fireEvent(this, "move-action", { direction: "down" });
+ }
+
+ private _onDelete() {
+ if (
+ confirm(
+ this.hass.localize(
+ "ui.panel.config.automation.editor.actions.delete_confirm"
+ )
+ )
+ ) {
+ fireEvent(this, "value-changed", { value: null });
+ }
+ }
+
+ private _typeChanged(ev: CustomEvent) {
+ const type = ((ev.target as PaperListboxElement)?.selectedItem as any)
+ ?.action;
+
+ if (!type) {
+ return;
+ }
+
+ if (type !== getType(this.action)) {
+ const elClass = customElements.get(`ha-automation-action-${type}`);
+
+ fireEvent(this, "value-changed", {
+ value: {
+ ...elClass.defaultConfig,
+ },
+ });
+ }
+ }
+
+ private _onYamlChange(ev: CustomEvent) {
+ ev.stopPropagation();
+ fireEvent(this, "value-changed", { value: ev.detail.value });
+ }
+
+ private _switchYamlMode() {
+ this._yamlMode = !this._yamlMode;
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ .card-menu {
+ position: absolute;
+ top: 0;
+ right: 0;
+ z-index: 3;
+ color: var(--primary-text-color);
+ }
+ .rtl .card-menu {
+ right: auto;
+ left: 0;
+ }
+ .card-menu paper-item {
+ cursor: pointer;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-row": HaAutomationActionRow;
+ }
+}
diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts
new file mode 100644
index 0000000000..ac2e45ff10
--- /dev/null
+++ b/src/panels/config/automation/action/ha-automation-action.ts
@@ -0,0 +1,98 @@
+import "@material/mwc-button";
+import {
+ css,
+ CSSResult,
+ customElement,
+ html,
+ LitElement,
+ property,
+} from "lit-element";
+import { fireEvent } from "../../../../common/dom/fire_event";
+import "../../../../components/ha-card";
+import { Action } from "../../../../data/script";
+import { HomeAssistant } from "../../../../types";
+import "./ha-automation-action-row";
+
+@customElement("ha-automation-action")
+export default class HaAutomationAction extends LitElement {
+ @property() public hass!: HomeAssistant;
+ @property() public actions!: Action[];
+
+ protected render() {
+ return html`
+ ${this.actions.map(
+ (action, idx) => html`
+
+ `
+ )}
+
+
+
+ ${this.hass.localize(
+ "ui.panel.config.automation.editor.actions.add"
+ )}
+
+
+
+ `;
+ }
+
+ private _addAction() {
+ const actions = this.actions.concat({
+ service: "",
+ });
+
+ fireEvent(this, "value-changed", { value: actions });
+ }
+
+ private _move(ev: CustomEvent) {
+ const index = (ev.target as any).index;
+ const newIndex = ev.detail.direction === "up" ? index - 1 : index + 1;
+ const actions = this.actions.concat();
+ const action = actions.splice(index, 1)[0];
+ actions.splice(newIndex, 0, action);
+ fireEvent(this, "value-changed", { value: actions });
+ }
+
+ private _actionChanged(ev: CustomEvent) {
+ ev.stopPropagation();
+ const actions = [...this.actions];
+ const newValue = ev.detail.value;
+ const index = (ev.target as any).index;
+
+ if (newValue === null) {
+ actions.splice(index, 1);
+ } else {
+ actions[index] = newValue;
+ }
+
+ fireEvent(this, "value-changed", { value: actions });
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ ha-automation-action-row,
+ ha-card {
+ display: block;
+ margin-top: 16px;
+ }
+ .add-card mwc-button {
+ display: block;
+ text-align: center;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action": HaAutomationAction;
+ }
+}
diff --git a/src/panels/config/automation/action/types/ha-automation-action-condition.ts b/src/panels/config/automation/action/types/ha-automation-action-condition.ts
new file mode 100644
index 0000000000..967455ffbb
--- /dev/null
+++ b/src/panels/config/automation/action/types/ha-automation-action-condition.ts
@@ -0,0 +1,41 @@
+import "../../condition/ha-automation-condition-editor";
+
+import { LitElement, property, customElement, html } from "lit-element";
+import { ActionElement } from "../ha-automation-action-row";
+import { HomeAssistant } from "../../../../../types";
+import { fireEvent } from "../../../../../common/dom/fire_event";
+import { Condition } from "../../../../../data/automation";
+
+@customElement("ha-automation-action-condition")
+export class HaConditionAction extends LitElement implements ActionElement {
+ @property() public hass!: HomeAssistant;
+ @property() public action!: Condition;
+
+ public static get defaultConfig() {
+ return { condition: "state" };
+ }
+
+ public render() {
+ return html`
+
+ `;
+ }
+
+ private _conditionChanged(ev: CustomEvent) {
+ ev.stopPropagation();
+
+ fireEvent(this, "value-changed", {
+ value: ev.detail.value,
+ });
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-condition": HaConditionAction;
+ }
+}
diff --git a/src/panels/config/automation/action/types/ha-automation-action-delay.ts b/src/panels/config/automation/action/types/ha-automation-action-delay.ts
new file mode 100644
index 0000000000..6dd9abeafe
--- /dev/null
+++ b/src/panels/config/automation/action/types/ha-automation-action-delay.ts
@@ -0,0 +1,44 @@
+import "@polymer/paper-input/paper-input";
+import "../../../../../components/ha-service-picker";
+import "../../../../../components/entity/ha-entity-picker";
+import "../../../../../components/ha-yaml-editor";
+
+import { LitElement, property, customElement, html } from "lit-element";
+import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
+import { HomeAssistant } from "../../../../../types";
+import { DelayAction } from "../../../../../data/script";
+
+@customElement("ha-automation-action-delay")
+export class HaDelayAction extends LitElement implements ActionElement {
+ @property() public hass!: HomeAssistant;
+ @property() public action!: DelayAction;
+
+ public static get defaultConfig() {
+ return { delay: "" };
+ }
+
+ public render() {
+ const { delay } = this.action;
+
+ return html`
+
+ `;
+ }
+
+ private _valueChanged(ev: CustomEvent): void {
+ handleChangeEvent(this, ev);
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-delay": HaDelayAction;
+ }
+}
diff --git a/src/panels/config/automation/action/types/ha-automation-action-device_id.ts b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts
new file mode 100644
index 0000000000..003ddd55c0
--- /dev/null
+++ b/src/panels/config/automation/action/types/ha-automation-action-device_id.ts
@@ -0,0 +1,129 @@
+import "../../../../../components/device/ha-device-picker";
+import "../../../../../components/device/ha-device-action-picker";
+import "../../../../../components/ha-form/ha-form";
+
+import {
+ fetchDeviceActionCapabilities,
+ deviceAutomationsEqual,
+ DeviceAction,
+} from "../../../../../data/device_automation";
+import { LitElement, customElement, property, html } from "lit-element";
+import { fireEvent } from "../../../../../common/dom/fire_event";
+import { HomeAssistant } from "../../../../../types";
+
+@customElement("ha-automation-action-device_id")
+export class HaDeviceAction extends LitElement {
+ @property() public hass!: HomeAssistant;
+ @property() public action!: DeviceAction;
+ @property() private _deviceId?: string;
+ @property() private _capabilities?;
+ private _origAction?: DeviceAction;
+
+ public static get defaultConfig() {
+ return {
+ device_id: "",
+ domain: "",
+ entity_id: "",
+ };
+ }
+
+ protected render() {
+ const deviceId = this._deviceId || this.action.device_id;
+ const extraFieldsData =
+ this._capabilities && this._capabilities.extra_fields
+ ? this._capabilities.extra_fields.map((item) => {
+ return { [item.name]: this.action[item.name] };
+ })
+ : undefined;
+
+ return html`
+
+
+ ${extraFieldsData
+ ? html`
+
+ `
+ : ""}
+ `;
+ }
+
+ protected firstUpdated() {
+ if (!this._capabilities) {
+ this._getCapabilities();
+ }
+ if (this.action) {
+ this._origAction = this.action;
+ }
+ }
+
+ protected updated(changedPros) {
+ const prevAction = changedPros.get("action");
+ if (prevAction && !deviceAutomationsEqual(prevAction, this.action)) {
+ this._getCapabilities();
+ }
+ }
+
+ private async _getCapabilities() {
+ const action = this.action;
+
+ this._capabilities = action.domain
+ ? await fetchDeviceActionCapabilities(this.hass, action)
+ : null;
+ }
+
+ private _devicePicked(ev) {
+ ev.stopPropagation();
+ this._deviceId = ev.target.value;
+ }
+
+ private _deviceActionPicked(ev) {
+ ev.stopPropagation();
+ let action = ev.detail.value;
+ if (this._origAction && deviceAutomationsEqual(this._origAction, action)) {
+ action = this._origAction;
+ }
+ fireEvent(this, "value-changed", { value: action });
+ }
+
+ private _extraFieldsChanged(ev) {
+ ev.stopPropagation();
+ fireEvent(this, "value-changed", {
+ value: {
+ ...this.action,
+ ...ev.detail.value,
+ },
+ });
+ }
+
+ private _extraFieldsComputeLabelCallback(localize) {
+ // Returns a callback for ha-form to calculate labels per schema object
+ return (schema) =>
+ localize(
+ `ui.panel.config.automation.editor.actions.type.device.extra_fields.${schema.name}`
+ ) || schema.name;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-device_id": HaDeviceAction;
+ }
+}
diff --git a/src/panels/config/automation/action/types/ha-automation-action-event.ts b/src/panels/config/automation/action/types/ha-automation-action-event.ts
new file mode 100644
index 0000000000..b1c1759788
--- /dev/null
+++ b/src/panels/config/automation/action/types/ha-automation-action-event.ts
@@ -0,0 +1,53 @@
+import "@polymer/paper-input/paper-input";
+import "../../../../../components/ha-service-picker";
+import "../../../../../components/entity/ha-entity-picker";
+import "../../../../../components/ha-yaml-editor";
+
+import { LitElement, property, customElement } from "lit-element";
+import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
+import { HomeAssistant } from "../../../../../types";
+import { html } from "lit-html";
+import { EventAction } from "../../../../../data/script";
+
+@customElement("ha-automation-action-event")
+export class HaEventAction extends LitElement implements ActionElement {
+ @property() public hass!: HomeAssistant;
+ @property() public action!: EventAction;
+
+ public static get defaultConfig(): EventAction {
+ return { event: "", event_data: {} };
+ }
+
+ public render() {
+ const { event, event_data } = this.action;
+
+ return html`
+
+
+ `;
+ }
+
+ private _valueChanged(ev: CustomEvent): void {
+ handleChangeEvent(this, ev);
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-event": HaEventAction;
+ }
+}
diff --git a/src/panels/config/automation/action/types/ha-automation-action-scene.ts b/src/panels/config/automation/action/types/ha-automation-action-scene.ts
new file mode 100644
index 0000000000..112ab89b81
--- /dev/null
+++ b/src/panels/config/automation/action/types/ha-automation-action-scene.ts
@@ -0,0 +1,45 @@
+import "../../../../../components/entity/ha-entity-picker";
+
+import { LitElement, property, customElement, html } from "lit-element";
+import { ActionElement } from "../ha-automation-action-row";
+import { HomeAssistant } from "../../../../../types";
+import { PolymerChangedEvent } from "../../../../../polymer-types";
+import { fireEvent } from "../../../../../common/dom/fire_event";
+import { SceneAction } from "../../../../../data/script";
+
+@customElement("ha-automation-action-scene")
+export class HaSceneAction extends LitElement implements ActionElement {
+ @property() public hass!: HomeAssistant;
+ @property() public action!: SceneAction;
+
+ public static get defaultConfig(): SceneAction {
+ return { scene: "" };
+ }
+
+ protected render() {
+ const { scene } = this.action;
+
+ return html`
+
+ `;
+ }
+
+ private _entityPicked(ev: PolymerChangedEvent) {
+ ev.stopPropagation();
+ fireEvent(this, "value-changed", {
+ value: { ...this.action, scene: ev.detail.value },
+ });
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-scene": HaSceneAction;
+ }
+}
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
new file mode 100644
index 0000000000..f00f854eae
--- /dev/null
+++ b/src/panels/config/automation/action/types/ha-automation-action-service.ts
@@ -0,0 +1,107 @@
+import "@polymer/paper-input/paper-input";
+import "../../../../../components/ha-service-picker";
+import "../../../../../components/entity/ha-entity-picker";
+import "../../../../../components/ha-yaml-editor";
+
+import { LitElement, property, customElement } from "lit-element";
+import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
+import { HomeAssistant } from "../../../../../types";
+import { html } from "lit-html";
+import memoizeOne from "memoize-one";
+import { computeDomain } from "../../../../../common/entity/compute_domain";
+import { computeObjectId } from "../../../../../common/entity/compute_object_id";
+import { PolymerChangedEvent } from "../../../../../polymer-types";
+import { fireEvent } from "../../../../../common/dom/fire_event";
+import { ServiceAction } from "../../../../../data/script";
+
+@customElement("ha-automation-action-service")
+export class HaServiceAction extends LitElement implements ActionElement {
+ @property() public hass!: HomeAssistant;
+ @property() public action!: ServiceAction;
+
+ public static get defaultConfig() {
+ return { service: "", data: {} };
+ }
+
+ private _getServiceData = memoizeOne((service: string) => {
+ if (!service) {
+ return [];
+ }
+ const domain = computeDomain(service);
+ const serviceName = computeObjectId(service);
+ const serviceDomains = this.hass.services;
+ if (!(domain in serviceDomains)) {
+ return [];
+ }
+ if (!(serviceName in serviceDomains[domain])) {
+ return [];
+ }
+
+ const fields = serviceDomains[domain][serviceName].fields;
+ return Object.keys(fields).map((field) => {
+ return { key: field, ...fields[field] };
+ });
+ });
+
+ public render() {
+ const { service, data, entity_id } = this.action;
+
+ const serviceData = this._getServiceData(service);
+ const entity = serviceData.find((attr) => attr.key === "entity_id");
+
+ return html`
+
+ ${entity
+ ? html`
+
+ `
+ : ""}
+
+ `;
+ }
+
+ private _valueChanged(ev: CustomEvent): void {
+ handleChangeEvent(this, ev);
+ }
+
+ private _serviceChanged(ev: PolymerChangedEvent) {
+ ev.stopPropagation();
+ if (ev.detail.value === this.action.service) {
+ return;
+ }
+ fireEvent(this, "value-changed", {
+ value: { ...this.action, service: ev.detail.value },
+ });
+ }
+
+ private _entityPicked(ev: PolymerChangedEvent) {
+ ev.stopPropagation();
+ fireEvent(this, "value-changed", {
+ value: { ...this.action, entity_id: ev.detail.value },
+ });
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-service": HaServiceAction;
+ }
+}
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
new file mode 100644
index 0000000000..5d54469acd
--- /dev/null
+++ b/src/panels/config/automation/action/types/ha-automation-action-wait_template.ts
@@ -0,0 +1,51 @@
+import "@polymer/paper-input/paper-input";
+
+import { LitElement, property, customElement } from "lit-element";
+import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
+import { HomeAssistant } from "../../../../../types";
+import { html } from "lit-html";
+import { WaitAction } from "../../../../../data/script";
+
+@customElement("ha-automation-action-wait_template")
+export class HaWaitAction extends LitElement implements ActionElement {
+ @property() public hass!: HomeAssistant;
+ @property() public action!: WaitAction;
+
+ public static get defaultConfig() {
+ return { wait_template: "", timeout: "" };
+ }
+
+ protected render() {
+ const { wait_template, timeout } = this.action;
+
+ return html`
+
+
+ `;
+ }
+
+ private _valueChanged(ev: CustomEvent): void {
+ handleChangeEvent(this, ev);
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-automation-action-wait_template": HaWaitAction;
+ }
+}
diff --git a/src/panels/config/automation/condition/ha-automation-condition-editor.ts b/src/panels/config/automation/condition/ha-automation-condition-editor.ts
index 5b84048539..57a9726bc3 100644
--- a/src/panels/config/automation/condition/ha-automation-condition-editor.ts
+++ b/src/panels/config/automation/condition/ha-automation-condition-editor.ts
@@ -38,9 +38,6 @@ export default class HaAutomationConditionEditor extends LitElement {
@property() public yamlMode = false;
protected render() {
- if (!this.condition) {
- return html``;
- }
const selected = OPTIONS.indexOf(this.condition.condition);
const yamlMode = this.yamlMode || selected === -1;
return html`
diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-device.ts b/src/panels/config/automation/condition/types/ha-automation-condition-device.ts
index 02f36ea432..1d5bfd4ff1 100644
--- a/src/panels/config/automation/condition/types/ha-automation-condition-device.ts
+++ b/src/panels/config/automation/condition/types/ha-automation-condition-device.ts
@@ -28,9 +28,8 @@ export class HaDeviceCondition extends LitElement {
}
protected render() {
- if (this._deviceId === undefined) {
- this._deviceId = this.condition.device_id;
- }
+ const deviceId = this._deviceId || this.condition.device_id;
+
const extraFieldsData =
this._capabilities && this._capabilities.extra_fields
? this._capabilities.extra_fields.map((item) => {
@@ -40,14 +39,14 @@ export class HaDeviceCondition extends LitElement {
return html`
{
@@ -40,14 +39,14 @@ export class HaDeviceTrigger extends LitElement {
return html`
extends Component {
- // @ts-ignore
- protected initialized: boolean;
-
- constructor(props?, context?) {
- super(props, context);
- this.initialized = false;
- }
-
- public componentDidMount() {
- this.initialized = true;
- }
-
- public componentWillUnmount() {
- this.initialized = false;
- }
-
- public render(_props?, _state?, _context?: any): ComponentChild {
- return
;
- }
-}
diff --git a/src/panels/config/js/automation.tsx b/src/panels/config/js/automation.tsx
index 41b4ee7bb9..5d146db3ed 100644
--- a/src/panels/config/js/automation.tsx
+++ b/src/panels/config/js/automation.tsx
@@ -7,8 +7,7 @@ import "../../../components/ha-textarea";
import "../automation/trigger/ha-automation-trigger";
import "../automation/condition/ha-automation-condition";
-
-import Script from "./script/index";
+import "../automation/action/ha-automation-action";
export default class Automation extends Component {
constructor() {
@@ -38,8 +37,8 @@ export default class Automation extends Component {
});
}
- public actionChanged(action) {
- this.props.onChange({ ...this.props.automation, action });
+ public actionChanged(ev: CustomEvent) {
+ this.props.onChange({ ...this.props.automation, action: ev.detail.value });
}
public render({ automation, isWide, hass, localize }) {
@@ -144,11 +143,10 @@ export default class Automation extends Component {
{localize("ui.panel.config.automation.editor.actions.learn_more")}
-
diff --git a/src/panels/config/js/json_textarea.tsx b/src/panels/config/js/json_textarea.tsx
deleted file mode 100644
index 2fd947acdf..0000000000
--- a/src/panels/config/js/json_textarea.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { h, Component } from "preact";
-import "../../../components/ha-textarea";
-
-export default class JSONTextArea extends Component {
- constructor(props) {
- super(props);
- this.state = {
- isvalid: true,
- value: JSON.stringify(props.value || {}, null, 2),
- };
-
- this.onChange = this.onChange.bind(this);
- }
-
- public onChange(ev) {
- const value = ev.target.value;
- let parsed;
- let isValid;
-
- try {
- parsed = JSON.parse(value);
- isValid = true;
- } catch (err) {
- // Invalid JSON
- isValid = false;
- }
-
- this.setState({
- value,
- isValid,
- });
- if (isValid) {
- this.props.onChange(parsed);
- }
- }
-
- public componentWillReceiveProps({ value }) {
- if (value === this.props.value) {
- return;
- }
- this.setState({
- value: JSON.stringify(value, null, 2),
- isValid: true,
- });
- }
-
- public render({ label }, { value, isValid }) {
- const style: any = {
- minWidth: 300,
- width: "100%",
- };
- if (!isValid) {
- style.border = "1px solid red";
- }
- return (
-
- );
- }
-}
diff --git a/src/panels/config/js/preact-types.ts b/src/panels/config/js/preact-types.ts
index 09aaf79064..bd1396513b 100644
--- a/src/panels/config/js/preact-types.ts
+++ b/src/panels/config/js/preact-types.ts
@@ -26,6 +26,7 @@ declare global {
"ha-automation-trigger": any;
"ha-automation-condition": any;
"ha-automation-condition-editor": any;
+ "ha-automation-action": any;
"ha-device-trigger-picker": any;
"ha-device-action-picker": any;
"ha-form": any;
diff --git a/src/panels/config/js/script.tsx b/src/panels/config/js/script.tsx
index c61da7248e..c6de67b420 100644
--- a/src/panels/config/js/script.tsx
+++ b/src/panels/config/js/script.tsx
@@ -4,7 +4,7 @@ import "@polymer/paper-input/paper-input";
import "../ha-config-section";
import "../../../components/ha-card";
-import Script from "./script/index";
+import "../automation/action/ha-automation-action";
export default class ScriptEditor extends Component<{
onChange: (...args: any[]) => any;
@@ -27,8 +27,8 @@ export default class ScriptEditor extends Component<{
});
}
- public sequenceChanged(sequence) {
- this.props.onChange({ ...this.props.script, sequence });
+ public sequenceChanged(ev: CustomEvent) {
+ this.props.onChange({ ...this.props.script, sequence: ev.detail.value });
}
// @ts-ignore
@@ -68,11 +68,10 @@ export default class ScriptEditor extends Component<{
-
diff --git a/src/panels/config/js/script/action_edit.tsx b/src/panels/config/js/script/action_edit.tsx
deleted file mode 100644
index 74970d18b0..0000000000
--- a/src/panels/config/js/script/action_edit.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import { h, Component } from "preact";
-import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
-import "@polymer/paper-listbox/paper-listbox";
-import "@polymer/paper-item/paper-item";
-
-import YAMLTextArea from "../yaml_textarea";
-
-import CallServiceAction from "./call_service";
-import ConditionAction from "./condition";
-import DelayAction from "./delay";
-import DeviceAction from "./device";
-import EventAction from "./event";
-import SceneAction from "./scene";
-import WaitAction from "./wait";
-
-const TYPES = {
- service: CallServiceAction,
- delay: DelayAction,
- wait_template: WaitAction,
- condition: ConditionAction,
- event: EventAction,
- device_id: DeviceAction,
- scene: SceneAction,
-};
-
-const OPTIONS = Object.keys(TYPES).sort();
-
-function getType(action) {
- const keys = Object.keys(TYPES);
- // tslint:disable-next-line: prefer-for-of
- for (let i = 0; i < keys.length; i++) {
- if (keys[i] in action) {
- return keys[i];
- }
- }
- return null;
-}
-
-export default class Action extends Component {
- constructor() {
- super();
-
- this.typeChanged = this.typeChanged.bind(this);
- this.onYamlChange = this.onYamlChange.bind(this);
- }
-
- public typeChanged(ev) {
- const newType = ev.target.selectedItem.attributes.action.value;
- const oldType = getType(this.props.action);
-
- if (oldType !== newType) {
- this.props.onChange(this.props.index, TYPES[newType].defaultConfig);
- }
- }
-
- public render({ index, action, onChange, hass, localize, yamlMode }) {
- const type = getType(action);
- // tslint:disable-next-line: variable-name
- const Comp = type && TYPES[type];
- // @ts-ignore
- const selected = OPTIONS.indexOf(type);
-
- if (yamlMode || !Comp) {
- return (
-
- {!Comp && (
-
- {localize(
- "ui.panel.config.automation.editor.actions.unsupported_action",
- "action",
- type
- )}
-
- )}
-
-
- );
- }
-
- return (
-
-
-
- {OPTIONS.map((opt) => (
-
- {localize(
- `ui.panel.config.automation.editor.actions.type.${opt}.label`
- )}
-
- ))}
-
-
-
-
- );
- }
-
- private onYamlChange(condition) {
- this.props.onChange(this.props.index, condition);
- }
-}
diff --git a/src/panels/config/js/script/action_row.tsx b/src/panels/config/js/script/action_row.tsx
deleted file mode 100644
index 31b4397b44..0000000000
--- a/src/panels/config/js/script/action_row.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import { h, Component } from "preact";
-import "@polymer/paper-menu-button/paper-menu-button";
-import "@polymer/paper-icon-button/paper-icon-button";
-import "@polymer/paper-item/paper-item";
-import "@polymer/paper-listbox/paper-listbox";
-import "../../../../components/ha-card";
-
-import ActionEdit from "./action_edit";
-
-export default class Action extends Component {
- public state: { yamlMode: boolean };
- private moveUp: (event: Event) => void;
- private moveDown: (event: Event) => void;
- constructor(props) {
- super(props);
-
- this.state = {
- yamlMode: false,
- };
-
- this.onDelete = this.onDelete.bind(this);
- this.switchYamlMode = this.switchYamlMode.bind(this);
- this.moveUp = props.moveUp.bind(this, props.index);
- this.moveDown = props.moveDown.bind(this, props.index);
- }
-
- public onDelete() {
- // eslint-disable-next-line
- if (
- confirm(
- this.props.localize(
- "ui.panel.config.automation.editor.actions.delete_confirm"
- )
- )
- ) {
- this.props.onChange(this.props.index, null);
- }
- }
-
- public render(props, { yamlMode }) {
- return (
-
-
-
- );
- }
-
- private switchYamlMode() {
- this.setState({
- yamlMode: !this.state.yamlMode,
- });
- }
-}
diff --git a/src/panels/config/js/script/call_service.tsx b/src/panels/config/js/script/call_service.tsx
deleted file mode 100644
index a9a1f61872..0000000000
--- a/src/panels/config/js/script/call_service.tsx
+++ /dev/null
@@ -1,112 +0,0 @@
-import { h } from "preact";
-import "../../../../components/ha-service-picker";
-import "../../../../components/entity/ha-entity-picker";
-
-import YAMLTextArea from "../yaml_textarea";
-import { AutomationComponent } from "../automation-component";
-import { computeDomain } from "../../../../common/entity/compute_domain";
-import { computeObjectId } from "../../../../common/entity/compute_object_id";
-import memoizeOne from "memoize-one";
-
-export default class CallServiceAction extends AutomationComponent {
- private _getServiceData = memoizeOne((service: string) => {
- if (!service) {
- return [];
- }
- const domain = computeDomain(service);
- const serviceName = computeObjectId(service);
- const serviceDomains = this.props.hass.services;
- if (!(domain in serviceDomains)) {
- return [];
- }
- if (!(serviceName in serviceDomains[domain])) {
- return [];
- }
-
- const fields = serviceDomains[domain][serviceName].fields;
- return Object.keys(fields).map((field) => {
- return { key: field, ...fields[field] };
- });
- });
-
- constructor() {
- super();
-
- this.serviceChanged = this.serviceChanged.bind(this);
- this.entityChanged = this.entityChanged.bind(this);
- this.serviceDataChanged = this.serviceDataChanged.bind(this);
- }
-
- public serviceChanged(ev) {
- if (!this.initialized) {
- return;
- }
- const newAction = {
- ...this.props.action,
- service: ev.target.value,
- };
- if (
- computeDomain(this.props.action.service) !==
- computeDomain(ev.target.value)
- ) {
- delete newAction.entity_id;
- }
- this.props.onChange(this.props.index, newAction);
- }
-
- public entityChanged(ev) {
- if (!this.initialized) {
- return;
- }
- this.props.onChange(this.props.index, {
- ...this.props.action,
- entity_id: ev.target.value,
- });
- }
-
- public serviceDataChanged(data) {
- if (!this.initialized) {
- return;
- }
- this.props.onChange(this.props.index, { ...this.props.action, data });
- }
-
- public render({ action, hass, localize }) {
- const { service, data, entity_id } = action;
- const serviceData = this._getServiceData(service);
- const entity = serviceData.find((attr) => attr.key === "entity_id");
-
- return (
-
-
- {entity && (
-
- )}
-
-
- );
- }
-}
-
-(CallServiceAction as any).defaultConfig = {
- alias: "",
- service: "",
- data: {},
-};
diff --git a/src/panels/config/js/script/condition.tsx b/src/panels/config/js/script/condition.tsx
deleted file mode 100644
index 7a1568fe6d..0000000000
--- a/src/panels/config/js/script/condition.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { h, Component } from "preact";
-
-import "../../automation/condition/ha-automation-condition-editor";
-
-export default class ConditionAction extends Component {
- constructor() {
- super();
-
- this.conditionChanged = this.conditionChanged.bind(this);
- }
-
- public conditionChanged(ev) {
- this.props.onChange(this.props.index, ev.detail.value);
- }
-
- // eslint-disable-next-line
- public render({ action, hass }) {
- return (
-
-
-
- );
- }
-}
-
-(ConditionAction as any).defaultConfig = {
- condition: "state",
-};
diff --git a/src/panels/config/js/script/delay.tsx b/src/panels/config/js/script/delay.tsx
deleted file mode 100644
index 3f1d69da80..0000000000
--- a/src/panels/config/js/script/delay.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { h } from "preact";
-import "@polymer/paper-input/paper-input";
-import { AutomationComponent } from "../automation-component";
-
-export default class DelayAction extends AutomationComponent {
- constructor() {
- super();
-
- this.onChange = this.onChange.bind(this);
- }
-
- public render({ action, localize }) {
- const { delay } = action;
- return (
-
- );
- }
-
- private onChange(ev) {
- if (
- !this.initialized ||
- ev.target.value === this.props.action[ev.target.name]
- ) {
- return;
- }
-
- this.props.onChange(this.props.index, {
- ...this.props.action,
- [ev.target.name]: ev.target.value,
- });
- }
-}
-
-(DelayAction as any).defaultConfig = {
- delay: "",
-};
diff --git a/src/panels/config/js/script/device.tsx b/src/panels/config/js/script/device.tsx
deleted file mode 100644
index 0a59eedbcd..0000000000
--- a/src/panels/config/js/script/device.tsx
+++ /dev/null
@@ -1,144 +0,0 @@
-import { h } from "preact";
-
-import "../../../../components/device/ha-device-picker";
-import "../../../../components/device/ha-device-action-picker";
-import "../../../../components/ha-form/ha-form";
-import { AutomationComponent } from "../automation-component";
-
-import {
- fetchDeviceActionCapabilities,
- deviceAutomationsEqual,
-} from "../../../../data/device_automation";
-import { DeviceAction } from "../../../../data/script";
-import { HomeAssistant } from "../../../../types";
-
-export default class DeviceActionEditor extends AutomationComponent<
- {
- index: number;
- action: DeviceAction;
- hass: HomeAssistant;
- onChange(index: number, action: DeviceAction);
- },
- {
- device_id: string | undefined;
- capabilities: any | undefined;
- }
-> {
- public static defaultConfig: DeviceAction = {
- device_id: "",
- domain: "",
- entity_id: "",
- };
-
- private _origAction;
-
- constructor() {
- super();
- this.devicePicked = this.devicePicked.bind(this);
- this.deviceActionPicked = this.deviceActionPicked.bind(this);
- this._extraFieldsChanged = this._extraFieldsChanged.bind(this);
- this.state = { device_id: undefined, capabilities: undefined };
- }
-
- public render() {
- const { action, hass } = this.props;
- const deviceId = this.state.device_id || action.device_id;
- const capabilities = this.state.capabilities;
- const extraFieldsData =
- capabilities && capabilities.extra_fields
- ? capabilities.extra_fields.map((item) => {
- return { [item.name]: this.props.action[item.name] };
- })
- : undefined;
-
- return (
-
-
-
- {extraFieldsData && (
-
- )}
-
- );
- }
-
- public componentDidMount() {
- this.initialized = true;
- if (!this.state.capabilities) {
- this._getCapabilities();
- }
- if (this.props.action) {
- this._origAction = this.props.action;
- }
- }
-
- public componentDidUpdate(prevProps) {
- if (!deviceAutomationsEqual(prevProps.action, this.props.action)) {
- this._getCapabilities();
- }
- }
-
- private devicePicked(ev) {
- if (!this.initialized) {
- return;
- }
- this.setState({ ...this.state, device_id: ev.target.value });
- }
-
- private deviceActionPicked(ev) {
- if (!this.initialized) {
- return;
- }
- let deviceAction = ev.target.value;
- if (
- this._origAction &&
- deviceAutomationsEqual(this._origAction, deviceAction)
- ) {
- deviceAction = this._origAction;
- }
- this.props.onChange(this.props.index, deviceAction);
- }
-
- private async _getCapabilities() {
- const action = this.props.action;
-
- const capabilities = action.domain
- ? await fetchDeviceActionCapabilities(this.props.hass, action)
- : null;
- this.setState({ ...this.state, capabilities });
- }
-
- private _extraFieldsChanged(ev) {
- if (!this.initialized) {
- return;
- }
- this.props.onChange(this.props.index, {
- ...this.props.action,
- ...ev.detail.value,
- });
- }
-
- private _extraFieldsComputeLabelCallback(localize) {
- // Returns a callback for ha-form to calculate labels per schema object
- return (schema) =>
- localize(
- `ui.panel.config.automation.editor.actions.type.device_id.extra_fields.${schema.name}`
- ) || schema.name;
- }
-}
diff --git a/src/panels/config/js/script/event.tsx b/src/panels/config/js/script/event.tsx
deleted file mode 100644
index 77513d5e9c..0000000000
--- a/src/panels/config/js/script/event.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import { h } from "preact";
-import "@polymer/paper-input/paper-input";
-
-import YAMLTextArea from "../yaml_textarea";
-import { onChangeEvent } from "../../../../common/preact/event";
-import { LocalizeFunc } from "../../../../common/translations/localize";
-import { EventAction } from "../../../../data/script";
-
-import { AutomationComponent } from "../automation-component";
-
-interface Props {
- index: number;
- action: EventAction;
- localize: LocalizeFunc;
- onChange: (index: number, action: EventAction) => void;
-}
-
-export default class EventActionForm extends AutomationComponent {
- private onChange: (event: Event) => void;
-
- static get defaultConfig(): EventAction {
- return {
- event: "",
- event_data: {},
- };
- }
-
- constructor() {
- super();
-
- this.onChange = onChangeEvent.bind(this, "action");
- this.serviceDataChanged = this.serviceDataChanged.bind(this);
- }
-
- public render() {
- const {
- action: { event, event_data },
- localize,
- } = this.props;
- return (
-
- );
- }
-
- private serviceDataChanged(eventData) {
- if (!this.initialized) {
- return;
- }
- this.props.onChange(this.props.index, {
- ...this.props.action,
- event_data: eventData,
- });
- }
-}
diff --git a/src/panels/config/js/script/index.tsx b/src/panels/config/js/script/index.tsx
deleted file mode 100644
index a9b0aa7b11..0000000000
--- a/src/panels/config/js/script/index.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { h, Component } from "preact";
-import "@material/mwc-button";
-import "../../../../components/ha-card";
-
-import ActionRow from "./action_row";
-
-export default class Script extends Component {
- constructor() {
- super();
-
- this.addAction = this.addAction.bind(this);
- this.actionChanged = this.actionChanged.bind(this);
- this.moveUp = this.moveUp.bind(this);
- this.moveDown = this.moveDown.bind(this);
- }
-
- public addAction() {
- const script = this.props.script.concat({
- service: "",
- });
-
- this.props.onChange(script);
- }
-
- public actionChanged(index, newValue) {
- const script = this.props.script.concat();
-
- if (newValue === null) {
- script.splice(index, 1);
- } else {
- script[index] = newValue;
- }
-
- this.props.onChange(script);
- }
-
- public moveUp(index: number) {
- this.move(index, index - 1);
- }
-
- public moveDown(index: number) {
- this.move(index, index + 1);
- }
-
- public render({ script, hass, localize }) {
- return (
-
- {script.map((act, idx) => (
-
- ))}
-
-
-
- {localize("ui.panel.config.automation.editor.actions.add")}
-
-
-
-
- );
- }
-
- private move(index: number, newIndex: number) {
- const script = this.props.script.concat();
- const action = script.splice(index, 1)[0];
- script.splice(newIndex, 0, action);
- this.props.onChange(script);
- }
-}
diff --git a/src/panels/config/js/script/scene.tsx b/src/panels/config/js/script/scene.tsx
deleted file mode 100644
index 7fc52d50c9..0000000000
--- a/src/panels/config/js/script/scene.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { h } from "preact";
-import "../../../../components/entity/ha-entity-picker";
-import { AutomationComponent } from "../automation-component";
-
-export default class SceneAction extends AutomationComponent {
- constructor() {
- super();
-
- this.sceneChanged = this.sceneChanged.bind(this);
- }
-
- public sceneChanged(ev: any) {
- this.props.onChange(this.props.index, {
- ...this.props.action,
- scene: ev.target.value,
- });
- }
-
- public render({ action, hass }) {
- const { scene } = action;
-
- return (
-
-
-
- );
- }
-}
-
-(SceneAction as any).defaultConfig = {
- scene: "",
-};
diff --git a/src/panels/config/js/script/wait.tsx b/src/panels/config/js/script/wait.tsx
deleted file mode 100644
index d9692f6cac..0000000000
--- a/src/panels/config/js/script/wait.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { h } from "preact";
-import "@polymer/paper-input/paper-input";
-
-import "../../../../components/ha-textarea";
-
-import { onChangeEvent } from "../../../../common/preact/event";
-import { AutomationComponent } from "../automation-component";
-
-export default class WaitAction extends AutomationComponent {
- private onChange: (obj: any) => void;
- constructor() {
- super();
-
- this.onChange = onChangeEvent.bind(this, "action");
- this.onTemplateChange = this.onTemplateChange.bind(this);
- }
-
- // Gets fired on mount. If empty, onChangeEvent removes attribute.
- // Without the attribute this action is no longer matched to this component.
- public onTemplateChange(ev) {
- if (!this.initialized) {
- return;
- }
- this.props.onChange(this.props.index, {
- ...this.props.action,
- [ev.target.getAttribute("name")]: ev.target.value,
- });
- }
-
- public render({ action, localize }) {
- /* eslint-disable camelcase */
- const { wait_template, timeout } = action;
- return (
-
- );
- }
-}
-
-(WaitAction as any).defaultConfig = {
- wait_template: "",
- timeout: "",
-};
diff --git a/src/panels/config/js/yaml_textarea.tsx b/src/panels/config/js/yaml_textarea.tsx
deleted file mode 100644
index db283b174f..0000000000
--- a/src/panels/config/js/yaml_textarea.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import { h, Component } from "preact";
-import { safeDump, safeLoad } from "js-yaml";
-import "../../../components/ha-code-editor";
-
-const isEmpty = (obj: object) => {
- for (const key in obj) {
- if (obj.hasOwnProperty(key)) {
- return false;
- }
- }
- return true;
-};
-
-export default class YAMLTextArea extends Component {
- constructor(props) {
- super(props);
-
- let value: string | undefined;
- try {
- value =
- props.value && !isEmpty(props.value)
- ? safeDump(props.value)
- : undefined;
- } catch (err) {
- alert(`There was an error converting to YAML: ${err}`);
- }
-
- this.state = {
- isvalid: true,
- value,
- };
-
- this.onChange = this.onChange.bind(this);
- }
-
- public onChange(ev) {
- const value = ev.detail.value;
- let parsed;
- let isValid = true;
-
- if (value) {
- try {
- parsed = safeLoad(value);
- isValid = true;
- } catch (err) {
- // Invalid YAML
- isValid = false;
- }
- } else {
- parsed = {};
- }
-
- this.setState({
- value,
- isValid,
- });
- if (isValid) {
- this.props.onChange(parsed);
- }
- }
-
- public render({ label }, { value, isValid }) {
- const style: any = {
- minWidth: 300,
- width: "100%",
- };
- return (
-
- );
- }
-}