Add UI for trigger condition (#9505)

This commit is contained in:
Bram Kragten 2021-07-06 10:43:07 +02:00 committed by GitHub
parent 4970f640fa
commit de5a817953
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 215 additions and 45 deletions

View File

@ -51,7 +51,18 @@ export interface ForDict {
seconds?: number | string; seconds?: number | string;
} }
export interface StateTrigger { export interface ContextConstraint {
context_id?: string;
parent_id?: string;
user_id?: string | string[];
}
export interface BaseTrigger {
platform: string;
id?: string;
}
export interface StateTrigger extends BaseTrigger {
platform: "state"; platform: "state";
entity_id: string; entity_id: string;
attribute?: string; attribute?: string;
@ -60,25 +71,25 @@ export interface StateTrigger {
for?: string | number | ForDict; for?: string | number | ForDict;
} }
export interface MqttTrigger { export interface MqttTrigger extends BaseTrigger {
platform: "mqtt"; platform: "mqtt";
topic: string; topic: string;
payload?: string; payload?: string;
} }
export interface GeoLocationTrigger { export interface GeoLocationTrigger extends BaseTrigger {
platform: "geo_location"; platform: "geo_location";
source: string; source: string;
zone: string; zone: string;
event: "enter" | "leave"; event: "enter" | "leave";
} }
export interface HassTrigger { export interface HassTrigger extends BaseTrigger {
platform: "homeassistant"; platform: "homeassistant";
event: "start" | "shutdown"; event: "start" | "shutdown";
} }
export interface NumericStateTrigger { export interface NumericStateTrigger extends BaseTrigger {
platform: "numeric_state"; platform: "numeric_state";
entity_id: string; entity_id: string;
attribute?: string; attribute?: string;
@ -88,54 +99,48 @@ export interface NumericStateTrigger {
for?: string | number | ForDict; for?: string | number | ForDict;
} }
export interface SunTrigger { export interface SunTrigger extends BaseTrigger {
platform: "sun"; platform: "sun";
offset: number; offset: number;
event: "sunrise" | "sunset"; event: "sunrise" | "sunset";
} }
export interface TimePatternTrigger { export interface TimePatternTrigger extends BaseTrigger {
platform: "time_pattern"; platform: "time_pattern";
hours?: number | string; hours?: number | string;
minutes?: number | string; minutes?: number | string;
seconds?: number | string; seconds?: number | string;
} }
export interface WebhookTrigger { export interface WebhookTrigger extends BaseTrigger {
platform: "webhook"; platform: "webhook";
webhook_id: string; webhook_id: string;
} }
export interface ZoneTrigger { export interface ZoneTrigger extends BaseTrigger {
platform: "zone"; platform: "zone";
entity_id: string; entity_id: string;
zone: string; zone: string;
event: "enter" | "leave"; event: "enter" | "leave";
} }
export interface TagTrigger { export interface TagTrigger extends BaseTrigger {
platform: "tag"; platform: "tag";
tag_id: string; tag_id: string;
device_id?: string; device_id?: string;
} }
export interface TimeTrigger { export interface TimeTrigger extends BaseTrigger {
platform: "time"; platform: "time";
at: string; at: string;
} }
export interface TemplateTrigger { export interface TemplateTrigger extends BaseTrigger {
platform: "template"; platform: "template";
value_template: string; value_template: string;
} }
export interface ContextConstraint { export interface EventTrigger extends BaseTrigger {
context_id?: string;
parent_id?: string;
user_id?: string | string[];
}
export interface EventTrigger {
platform: "event"; platform: "event";
event_type: string; event_type: string;
event_data?: any; event_data?: any;
@ -158,24 +163,26 @@ export type Trigger =
| EventTrigger | EventTrigger
| DeviceTrigger; | DeviceTrigger;
export interface LogicalCondition { interface BaseCondition {
condition: "and" | "not" | "or"; condition: string;
alias?: string; alias?: string;
}
export interface LogicalCondition extends BaseCondition {
condition: "and" | "not" | "or";
conditions: Condition | Condition[]; conditions: Condition | Condition[];
} }
export interface StateCondition { export interface StateCondition extends BaseCondition {
condition: "state"; condition: "state";
alias?: string;
entity_id: string; entity_id: string;
attribute?: string; attribute?: string;
state: string | number; state: string | number;
for?: string | number | ForDict; for?: string | number | ForDict;
} }
export interface NumericStateCondition { export interface NumericStateCondition extends BaseCondition {
condition: "numeric_state"; condition: "numeric_state";
alias?: string;
entity_id: string; entity_id: string;
attribute?: string; attribute?: string;
above?: number; above?: number;
@ -183,36 +190,37 @@ export interface NumericStateCondition {
value_template?: string; value_template?: string;
} }
export interface SunCondition { export interface SunCondition extends BaseCondition {
condition: "sun"; condition: "sun";
alias?: string;
after_offset: number; after_offset: number;
before_offset: number; before_offset: number;
after: "sunrise" | "sunset"; after: "sunrise" | "sunset";
before: "sunrise" | "sunset"; before: "sunrise" | "sunset";
} }
export interface ZoneCondition { export interface ZoneCondition extends BaseCondition {
condition: "zone"; condition: "zone";
alias?: string;
entity_id: string; entity_id: string;
zone: string; zone: string;
} }
export interface TimeCondition { export interface TimeCondition extends BaseCondition {
condition: "time"; condition: "time";
alias?: string;
after?: string; after?: string;
before?: string; before?: string;
weekday?: string | string[]; weekday?: string | string[];
} }
export interface TemplateCondition { export interface TemplateCondition extends BaseCondition {
condition: "template"; condition: "template";
alias?: string;
value_template: string; value_template: string;
} }
export interface TriggerCondition extends BaseCondition {
condition: "trigger";
id: string;
}
export type Condition = export type Condition =
| StateCondition | StateCondition
| NumericStateCondition | NumericStateCondition
@ -221,7 +229,8 @@ export type Condition =
| TimeCondition | TimeCondition
| TemplateCondition | TemplateCondition
| DeviceCondition | DeviceCondition
| LogicalCondition; | LogicalCondition
| TriggerCondition;
export const triggerAutomationActions = ( export const triggerAutomationActions = (
hass: HomeAssistant, hass: HomeAssistant,

View File

@ -1,6 +1,7 @@
import { computeStateName } from "../common/entity/compute_state_name"; import { computeStateName } from "../common/entity/compute_state_name";
import { HaFormSchema } from "../components/ha-form/ha-form"; import { HaFormSchema } from "../components/ha-form/ha-form";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import { BaseTrigger } from "./automation";
export interface DeviceAutomation { export interface DeviceAutomation {
alias?: string; alias?: string;
@ -20,9 +21,10 @@ export interface DeviceCondition extends DeviceAutomation {
condition: "device"; condition: "device";
} }
export interface DeviceTrigger extends DeviceAutomation { export type DeviceTrigger = DeviceAutomation &
BaseTrigger & {
platform: "device"; platform: "device";
} };
export interface DeviceCapabilities { export interface DeviceCapabilities {
extra_fields: HaFormSchema[]; extra_fields: HaFormSchema[];

View File

@ -20,6 +20,7 @@ import "./types/ha-automation-condition-state";
import "./types/ha-automation-condition-sun"; import "./types/ha-automation-condition-sun";
import "./types/ha-automation-condition-template"; import "./types/ha-automation-condition-template";
import "./types/ha-automation-condition-time"; import "./types/ha-automation-condition-time";
import "./types/ha-automation-condition-trigger";
import "./types/ha-automation-condition-zone"; import "./types/ha-automation-condition-zone";
const OPTIONS = [ const OPTIONS = [
@ -32,6 +33,7 @@ const OPTIONS = [
"sun", "sun",
"template", "template",
"time", "time",
"trigger",
"zone", "zone",
]; ];

View File

@ -0,0 +1,99 @@
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import {
AutomationConfig,
Trigger,
TriggerCondition,
} from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import { ensureArray } from "../../../../../common/ensure-array";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
@customElement("ha-automation-condition-trigger")
export class HaTriggerCondition extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public condition!: TriggerCondition;
@state() private _triggers?: Trigger | Trigger[];
private _unsub?: UnsubscribeFunc;
public static get defaultConfig() {
return {
id: "",
};
}
connectedCallback() {
super.connectedCallback();
const details = { callback: (config) => this._automationUpdated(config) };
fireEvent(this, "subscribe-automation-config", details);
this._unsub = (details as any).unsub;
}
disconnectedCallback() {
super.disconnectedCallback();
if (this._unsub) {
this._unsub();
}
}
protected render() {
const { id } = this.condition;
if (!this._triggers) {
return this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.trigger.no_triggers"
);
}
return html`<paper-dropdown-menu-light
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.trigger.id"
)}
no-animations
>
<paper-listbox
slot="dropdown-content"
.selected=${id}
attr-for-selected="data-trigger-id"
@selected-item-changed=${this._triggerPicked}
>
${ensureArray(this._triggers).map((trigger) =>
trigger.id
? html`
<paper-item data-trigger-id=${trigger.id}>
${trigger.id}
</paper-item>
`
: ""
)}
</paper-listbox>
</paper-dropdown-menu-light>`;
}
private _automationUpdated(config?: AutomationConfig) {
this._triggers = config?.trigger;
}
private _triggerPicked(ev: CustomEvent) {
ev.stopPropagation();
if (!ev.detail.value) {
return;
}
const newTrigger = ev.detail.value.dataset.triggerId;
if (this.condition.id === newTrigger) {
return;
}
fireEvent(this, "value-changed", {
value: { ...this.condition, id: newTrigger },
});
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-automation-condition-trigger": HaTriggerCondition;
}
}

View File

@ -1,4 +1,3 @@
import "@polymer/paper-radio-button/paper-radio-button";
import { html, LitElement } from "lit"; import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event"; import { fireEvent } from "../../../../../common/dom/fire_event";

View File

@ -11,6 +11,7 @@ import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar"; import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@polymer/paper-input/paper-textarea"; import "@polymer/paper-input/paper-textarea";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
@ -51,12 +52,9 @@ import { HomeAssistant, Route } from "../../../types";
import { showToast } from "../../../util/toast"; import { showToast } from "../../../util/toast";
import "../ha-config-section"; import "../ha-config-section";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import "./action/ha-automation-action";
import { HaDeviceAction } from "./action/types/ha-automation-action-device_id"; import { HaDeviceAction } from "./action/types/ha-automation-action-device_id";
import "./blueprint-automation-editor"; import "./blueprint-automation-editor";
import "./condition/ha-automation-condition";
import "./manual-automation-editor"; import "./manual-automation-editor";
import "./trigger/ha-automation-trigger";
import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device"; import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device";
declare global { declare global {
@ -65,6 +63,10 @@ declare global {
} }
// for fire event // for fire event
interface HASSDomEvents { interface HASSDomEvents {
"subscribe-automation-config": {
callback: (config: AutomationConfig) => void;
unsub?: UnsubscribeFunc;
};
"ui-mode-not-available": Error; "ui-mode-not-available": Error;
duplicate: undefined; duplicate: undefined;
} }
@ -95,6 +97,13 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
@query("ha-yaml-editor", true) private _editor?: HaYamlEditor; @query("ha-yaml-editor", true) private _editor?: HaYamlEditor;
private _configSubscriptions: Record<
string,
(config?: AutomationConfig) => void
> = {};
private _configSubscriptionsId = 1;
protected render(): TemplateResult { protected render(): TemplateResult {
const stateObj = this._entityId const stateObj = this._entityId
? this.hass.states[this._entityId] ? this.hass.states[this._entityId]
@ -200,6 +209,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
class="content ${classMap({ class="content ${classMap({
"yaml-mode": this._mode === "yaml", "yaml-mode": this._mode === "yaml",
})}" })}"
@subscribe-automation-config=${this._subscribeAutomationConfig}
> >
${this._errors ${this._errors
? html` <div class="errors">${this._errors}</div> ` ? html` <div class="errors">${this._errors}</div> `
@ -336,6 +346,12 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
) { ) {
this._setEntityId(); this._setEntityId();
} }
if (changedProps.has("_config")) {
Object.values(this._configSubscriptions).forEach((sub) =>
sub(this._config)
);
}
} }
private _setEntityId() { private _setEntityId() {
@ -516,6 +532,15 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
); );
} }
private _subscribeAutomationConfig(ev) {
const id = this._configSubscriptionsId++;
this._configSubscriptions[id] = ev.detail.callback;
ev.detail.unsub = () => {
delete this._configSubscriptions[id];
};
ev.detail.callback(this._config);
}
protected handleKeyboardSave() { protected handleKeyboardSave() {
this._saveAutomation(); this._saveAutomation();
} }

View File

@ -162,6 +162,14 @@ export default class HaAutomationTriggerRow extends LitElement {
)} )}
</paper-listbox> </paper-listbox>
</paper-dropdown-menu-light> </paper-dropdown-menu-light>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.id"
)}
.value=${this.trigger.id}
@value-changed=${this._idChanged}
>
</paper-input>
<div> <div>
${dynamicElement( ${dynamicElement(
`ha-automation-trigger-${this.trigger.platform}`, `ha-automation-trigger-${this.trigger.platform}`,
@ -212,15 +220,35 @@ export default class HaAutomationTriggerRow extends LitElement {
const elClass = customElements.get(`ha-automation-trigger-${type}`); const elClass = customElements.get(`ha-automation-trigger-${type}`);
if (type !== this.trigger.platform) { if (type !== this.trigger.platform) {
fireEvent(this, "value-changed", { const value = {
value: {
platform: type, platform: type,
...elClass.defaultConfig, ...elClass.defaultConfig,
}, };
if (this.trigger.id) {
value.id = this.trigger.id;
}
fireEvent(this, "value-changed", {
value,
}); });
} }
} }
private _idChanged(ev: CustomEvent) {
const newId = ev.detail.value;
if (newId === this.trigger.id) {
return;
}
const value = { ...this.trigger };
if (!newId) {
delete value.id;
} else {
value.id = newId;
}
fireEvent(this, "value-changed", {
value,
});
}
private _onYamlChange(ev: CustomEvent) { private _onYamlChange(ev: CustomEvent) {
ev.stopPropagation(); ev.stopPropagation();
if (!ev.detail.isValid) { if (!ev.detail.isValid) {

View File

@ -1308,6 +1308,7 @@
"introduction": "Triggers are what starts the processing of an automation rule. It is possible to specify multiple triggers for the same rule. Once a trigger starts, Home Assistant will validate the conditions, if any, and call the action.", "introduction": "Triggers are what starts the processing of an automation rule. It is possible to specify multiple triggers for the same rule. Once a trigger starts, Home Assistant will validate the conditions, if any, and call the action.",
"learn_more": "Learn more about triggers", "learn_more": "Learn more about triggers",
"add": "Add trigger", "add": "Add trigger",
"id": "Trigger ID (used by the trigger condition)",
"duplicate": "Duplicate", "duplicate": "Duplicate",
"delete": "[%key:ui::panel::mailbox::delete_button%]", "delete": "[%key:ui::panel::mailbox::delete_button%]",
"delete_confirm": "Are you sure you want to delete this?", "delete_confirm": "Are you sure you want to delete this?",
@ -1475,6 +1476,11 @@
"sun": "Sunday" "sun": "Sunday"
} }
}, },
"trigger": {
"label": "Trigger",
"no_triggers": "No triggers available",
"id": "Trigger Id"
},
"zone": { "zone": {
"label": "[%key:ui::panel::config::automation::editor::triggers::type::zone::label%]", "label": "[%key:ui::panel::config::automation::editor::triggers::type::zone::label%]",
"entity": "[%key:ui::panel::config::automation::editor::triggers::type::zone::entity%]", "entity": "[%key:ui::panel::config::automation::editor::triggers::type::zone::entity%]",