Add support for enabling/disabling trigger/condition/action (#12493)

* Add support for enabling/disabling trigger/condition/action

* Add more visual indication of disabled

* review

* margin

* Dont make overflow transparent

* Change color of bar
This commit is contained in:
Bram Kragten 2022-04-28 18:37:58 +02:00 committed by GitHub
parent b8c55f2f65
commit 38b4090daa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 311 additions and 166 deletions

View File

@ -65,6 +65,7 @@ export interface BaseTrigger {
platform: string; platform: string;
id?: string; id?: string;
variables?: Record<string, unknown>; variables?: Record<string, unknown>;
enabled?: boolean;
} }
export interface StateTrigger extends BaseTrigger { export interface StateTrigger extends BaseTrigger {
@ -178,6 +179,7 @@ export type Trigger =
interface BaseCondition { interface BaseCondition {
condition: string; condition: string;
alias?: string; alias?: string;
enabled?: boolean;
} }
export interface LogicalCondition extends BaseCondition { export interface LogicalCondition extends BaseCondition {

View File

@ -11,6 +11,7 @@ export interface DeviceAutomation {
type?: string; type?: string;
subtype?: string; subtype?: string;
event?: string; event?: string;
enabled?: boolean;
metadata?: { secondary: boolean }; metadata?: { secondary: boolean };
} }

View File

@ -13,6 +13,7 @@ import {
literal, literal,
is, is,
Describe, Describe,
boolean,
} from "superstruct"; } from "superstruct";
import { computeObjectId } from "../common/entity/compute_object_id"; import { computeObjectId } from "../common/entity/compute_object_id";
import { navigate } from "../common/navigate"; import { navigate } from "../common/navigate";
@ -25,6 +26,7 @@ export const MODES_MAX = ["queued", "parallel"];
export const baseActionStruct = object({ export const baseActionStruct = object({
alias: optional(string()), alias: optional(string()),
enabled: optional(boolean()),
}); });
const targetStruct = object({ const targetStruct = object({
@ -88,15 +90,18 @@ export interface BlueprintScriptConfig extends ManualScriptConfig {
use_blueprint: { path: string; input?: BlueprintInput }; use_blueprint: { path: string; input?: BlueprintInput };
} }
export interface EventAction { interface BaseAction {
alias?: string; alias?: string;
enabled?: boolean;
}
export interface EventAction extends BaseAction {
event: string; event: string;
event_data?: Record<string, any>; event_data?: Record<string, any>;
event_data_template?: Record<string, any>; event_data_template?: Record<string, any>;
} }
export interface ServiceAction { export interface ServiceAction extends BaseAction {
alias?: string;
service?: string; service?: string;
service_template?: string; service_template?: string;
entity_id?: string; entity_id?: string;
@ -104,55 +109,48 @@ export interface ServiceAction {
data?: Record<string, unknown>; data?: Record<string, unknown>;
} }
export interface DeviceAction { export interface DeviceAction extends BaseAction {
alias?: string;
type: string; type: string;
device_id: string; device_id: string;
domain: string; domain: string;
entity_id: string; entity_id: string;
} }
export interface DelayActionParts { export interface DelayActionParts extends BaseAction {
milliseconds?: number; milliseconds?: number;
seconds?: number; seconds?: number;
minutes?: number; minutes?: number;
hours?: number; hours?: number;
days?: number; days?: number;
} }
export interface DelayAction { export interface DelayAction extends BaseAction {
alias?: string;
delay: number | Partial<DelayActionParts> | string; delay: number | Partial<DelayActionParts> | string;
} }
export interface ServiceSceneAction { export interface ServiceSceneAction extends BaseAction {
alias?: string;
service: "scene.turn_on"; service: "scene.turn_on";
target?: { entity_id?: string }; target?: { entity_id?: string };
entity_id?: string; entity_id?: string;
metadata: Record<string, unknown>; metadata: Record<string, unknown>;
} }
export interface LegacySceneAction { export interface LegacySceneAction extends BaseAction {
alias?: string;
scene: string; scene: string;
} }
export type SceneAction = ServiceSceneAction | LegacySceneAction; export type SceneAction = ServiceSceneAction | LegacySceneAction;
export interface WaitAction { export interface WaitAction extends BaseAction {
alias?: string;
wait_template: string; wait_template: string;
timeout?: number; timeout?: number;
continue_on_timeout?: boolean; continue_on_timeout?: boolean;
} }
export interface WaitForTriggerAction { export interface WaitForTriggerAction extends BaseAction {
alias?: string;
wait_for_trigger: Trigger | Trigger[]; wait_for_trigger: Trigger | Trigger[];
timeout?: number; timeout?: number;
continue_on_timeout?: boolean; continue_on_timeout?: boolean;
} }
export interface PlayMediaAction { export interface PlayMediaAction extends BaseAction {
alias?: string;
service: "media_player.play_media"; service: "media_player.play_media";
target?: { entity_id?: string }; target?: { entity_id?: string };
entity_id?: string; entity_id?: string;
@ -160,13 +158,11 @@ export interface PlayMediaAction {
metadata: Record<string, unknown>; metadata: Record<string, unknown>;
} }
export interface RepeatAction { export interface RepeatAction extends BaseAction {
alias?: string;
repeat: CountRepeat | WhileRepeat | UntilRepeat; repeat: CountRepeat | WhileRepeat | UntilRepeat;
} }
interface BaseRepeat { interface BaseRepeat extends BaseAction {
alias?: string;
sequence: Action | Action[]; sequence: Action | Action[];
} }
@ -182,38 +178,32 @@ export interface UntilRepeat extends BaseRepeat {
until: Condition[]; until: Condition[];
} }
export interface ChooseActionChoice { export interface ChooseActionChoice extends BaseAction {
alias?: string;
conditions: string | Condition[]; conditions: string | Condition[];
sequence: Action | Action[]; sequence: Action | Action[];
} }
export interface ChooseAction { export interface ChooseAction extends BaseAction {
alias?: string;
choose: ChooseActionChoice | ChooseActionChoice[] | null; choose: ChooseActionChoice | ChooseActionChoice[] | null;
default?: Action | Action[]; default?: Action | Action[];
} }
export interface IfAction { export interface IfAction extends BaseAction {
alias?: string;
if: string | Condition[]; if: string | Condition[];
then: Action | Action[]; then: Action | Action[];
else?: Action | Action[]; else?: Action | Action[];
} }
export interface VariablesAction { export interface VariablesAction extends BaseAction {
alias?: string;
variables: Record<string, unknown>; variables: Record<string, unknown>;
} }
export interface StopAction { export interface StopAction extends BaseAction {
alias?: string;
stop: string; stop: string;
error?: boolean; error?: boolean;
} }
interface UnknownAction { interface UnknownAction extends BaseAction {
alias?: string;
[key: string]: unknown; [key: string]: unknown;
} }

View File

@ -160,7 +160,13 @@ export default class HaAutomationActionRow extends LitElement {
return html` return html`
<ha-card> <ha-card>
<div class="card-content"> ${this.action.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.disabled"
)}
</div>`
: ""}
<div class="card-menu"> <div class="card-menu">
${this.index !== 0 ${this.index !== 0
? html` ? html`
@ -209,6 +215,15 @@ export default class HaAutomationActionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
)} )}
</mwc-list-item> </mwc-list-item>
<mwc-list-item>
${this.action.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
</mwc-list-item>
<mwc-list-item class="warning"> <mwc-list-item class="warning">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
@ -216,6 +231,11 @@ export default class HaAutomationActionRow extends LitElement {
</mwc-list-item> </mwc-list-item>
</ha-button-menu> </ha-button-menu>
</div> </div>
<div
class="card-content ${this.action.enabled === false
? "disabled"
: ""}"
>
${this._warnings ${this._warnings
? html`<ha-alert ? html`<ha-alert
alert-type="warning" alert-type="warning"
@ -314,11 +334,23 @@ export default class HaAutomationActionRow extends LitElement {
fireEvent(this, "duplicate"); fireEvent(this, "duplicate");
break; break;
case 3: case 3:
this._onDisable();
break;
case 4:
this._onDelete(); this._onDelete();
break; break;
} }
} }
private _onDisable() {
const enabled = !(this.action.enabled ?? true);
const value = { ...this.action, enabled };
fireEvent(this, "value-changed", { value });
if (this._yamlMode) {
this._yamlEditor?.setValue(value);
}
}
private async _runAction() { private async _runAction() {
const validated = await validateConfig(this.hass, { const validated = await validateConfig(this.hass, {
action: this.action, action: this.action,
@ -408,11 +440,27 @@ export default class HaAutomationActionRow extends LitElement {
return [ return [
haStyle, haStyle,
css` css`
.disabled {
opacity: 0.5;
pointer-events: none;
}
.card-content {
padding-top: 16px;
margin-top: 0;
}
.disabled-bar {
background: var(--divider-color, #e0e0e0);
text-align: center;
border-top-right-radius: var(--ha-card-border-radius);
border-top-left-radius: var(--ha-card-border-radius);
}
.card-menu { .card-menu {
position: absolute; float: right;
right: 16px;
z-index: 3; z-index: 3;
margin: 4px;
--mdc-theme-text-primary-on-background: var(--primary-text-color); --mdc-theme-text-primary-on-background: var(--primary-text-color);
display: flex;
align-items: center;
} }
:host-context([style*="direction: rtl;"]) .card-menu { :host-context([style*="direction: rtl;"]) .card-menu {
right: initial; right: initial;

View File

@ -2,7 +2,7 @@ import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement } from "lit"; import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../common/dom/fire_event"; import { fireEvent } from "../../../../common/dom/fire_event";
import { handleStructError } from "../../../../common/structs/handle-errors"; import { handleStructError } from "../../../../common/structs/handle-errors";
import "../../../../components/ha-button-menu"; import "../../../../components/ha-button-menu";
@ -19,6 +19,7 @@ import { haStyle } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import "./ha-automation-condition-editor"; import "./ha-automation-condition-editor";
import { validateConfig } from "../../../../data/config"; import { validateConfig } from "../../../../data/config";
import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
export interface ConditionElement extends LitElement { export interface ConditionElement extends LitElement {
condition: Condition; condition: Condition;
@ -59,13 +60,21 @@ export default class HaAutomationConditionRow extends LitElement {
@state() private _warnings?: string[]; @state() private _warnings?: string[];
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
protected render() { protected render() {
if (!this.condition) { if (!this.condition) {
return html``; return html``;
} }
return html` return html`
<ha-card> <ha-card>
<div class="card-content"> ${this.condition.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.disabled"
)}
</div>`
: ""}
<div class="card-menu"> <div class="card-menu">
<ha-progress-button @click=${this._testCondition}> <ha-progress-button @click=${this._testCondition}>
${this.hass.localize( ${this.hass.localize(
@ -93,6 +102,15 @@ export default class HaAutomationConditionRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
)} )}
</mwc-list-item> </mwc-list-item>
<mwc-list-item>
${this.condition.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
</mwc-list-item>
<mwc-list-item class="warning"> <mwc-list-item class="warning">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
@ -100,6 +118,11 @@ export default class HaAutomationConditionRow extends LitElement {
</mwc-list-item> </mwc-list-item>
</ha-button-menu> </ha-button-menu>
</div> </div>
<div
class="card-content ${this.condition.enabled === false
? "disabled"
: ""}"
>
${this._warnings ${this._warnings
? html`<ha-alert ? html`<ha-alert
alert-type="warning" alert-type="warning"
@ -153,11 +176,23 @@ export default class HaAutomationConditionRow extends LitElement {
fireEvent(this, "duplicate"); fireEvent(this, "duplicate");
break; break;
case 2: case 2:
this._onDisable();
break;
case 3:
this._onDelete(); this._onDelete();
break; break;
} }
} }
private _onDisable() {
const enabled = !(this.condition.enabled ?? true);
const value = { ...this.condition, enabled };
fireEvent(this, "value-changed", { value });
if (this._yamlMode) {
this._yamlEditor?.setValue(value);
}
}
private _onDelete() { private _onDelete() {
showConfirmationDialog(this, { showConfirmationDialog(this, {
text: this.hass.localize( text: this.hass.localize(
@ -238,9 +273,24 @@ export default class HaAutomationConditionRow extends LitElement {
return [ return [
haStyle, haStyle,
css` css`
.disabled {
opacity: 0.5;
pointer-events: none;
}
.card-content {
padding-top: 16px;
margin-top: 0;
}
.disabled-bar {
background: var(--divider-color, #e0e0e0);
text-align: center;
border-top-right-radius: var(--ha-card-border-radius);
border-top-left-radius: var(--ha-card-border-radius);
}
.card-menu { .card-menu {
float: right; float: right;
z-index: 3; z-index: 3;
margin: 4px;
--mdc-theme-text-primary-on-background: var(--primary-text-color); --mdc-theme-text-primary-on-background: var(--primary-text-color);
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -1,8 +1,9 @@
import { object, optional, number, string } from "superstruct"; import { object, optional, number, string, boolean } from "superstruct";
export const baseTriggerStruct = object({ export const baseTriggerStruct = object({
platform: string(), platform: string(),
id: optional(string()), id: optional(string()),
enabled: optional(boolean()),
}); });
export const forDictStruct = object({ export const forDictStruct = object({

View File

@ -3,7 +3,7 @@ import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
@ -16,7 +16,7 @@ import "../../../../components/ha-alert";
import "../../../../components/ha-button-menu"; import "../../../../components/ha-button-menu";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import "../../../../components/ha-icon-button"; import "../../../../components/ha-icon-button";
import "../../../../components/ha-yaml-editor"; import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
import "../../../../components/ha-select"; import "../../../../components/ha-select";
import type { HaSelect } from "../../../../components/ha-select"; import type { HaSelect } from "../../../../components/ha-select";
import "../../../../components/ha-textfield"; import "../../../../components/ha-textfield";
@ -104,6 +104,8 @@ export default class HaAutomationTriggerRow extends LitElement {
@state() private _triggerColor = false; @state() private _triggerColor = false;
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
private _triggerUnsub?: Promise<UnsubscribeFunc>; private _triggerUnsub?: Promise<UnsubscribeFunc>;
private _processedTypes = memoizeOne( private _processedTypes = memoizeOne(
@ -126,7 +128,13 @@ export default class HaAutomationTriggerRow extends LitElement {
return html` return html`
<ha-card> <ha-card>
<div class="card-content"> ${this.trigger.enabled === false
? html`<div class="disabled-bar">
${this.hass.localize(
"ui.panel.config.automation.editor.actions.disabled"
)}
</div>`
: ""}
<div class="card-menu"> <div class="card-menu">
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}> <ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-icon-button <ha-icon-button
@ -153,6 +161,15 @@ export default class HaAutomationTriggerRow extends LitElement {
"ui.panel.config.automation.editor.actions.duplicate" "ui.panel.config.automation.editor.actions.duplicate"
)} )}
</mwc-list-item> </mwc-list-item>
<mwc-list-item>
${this.trigger.enabled === false
? this.hass.localize(
"ui.panel.config.automation.editor.actions.enable"
)
: this.hass.localize(
"ui.panel.config.automation.editor.actions.disable"
)}
</mwc-list-item>
<mwc-list-item class="warning"> <mwc-list-item class="warning">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete" "ui.panel.config.automation.editor.actions.delete"
@ -160,6 +177,11 @@ export default class HaAutomationTriggerRow extends LitElement {
</mwc-list-item> </mwc-list-item>
</ha-button-menu> </ha-button-menu>
</div> </div>
<div
class="card-content ${this.trigger.enabled === false
? "disabled"
: ""}"
>
${this._warnings ${this._warnings
? html`<ha-alert ? html`<ha-alert
alert-type="warning" alert-type="warning"
@ -214,7 +236,6 @@ export default class HaAutomationTriggerRow extends LitElement {
` `
)} )}
</ha-select> </ha-select>
${showId ${showId
? html` ? html`
<ha-textfield <ha-textfield
@ -250,7 +271,7 @@ export default class HaAutomationTriggerRow extends LitElement {
`; `;
} }
protected override updated(changedProps: PropertyValues): void { protected override updated(changedProps: PropertyValues<this>): void {
super.updated(changedProps); super.updated(changedProps);
if (changedProps.has("trigger")) { if (changedProps.has("trigger")) {
this._subscribeTrigger(); this._subscribeTrigger();
@ -347,6 +368,9 @@ export default class HaAutomationTriggerRow extends LitElement {
fireEvent(this, "duplicate"); fireEvent(this, "duplicate");
break; break;
case 3: case 3:
this._onDisable();
break;
case 4:
this._onDelete(); this._onDelete();
break; break;
} }
@ -365,6 +389,15 @@ export default class HaAutomationTriggerRow extends LitElement {
}); });
} }
private _onDisable() {
const enabled = !(this.trigger.enabled ?? true);
const value = { ...this.trigger, enabled };
fireEvent(this, "value-changed", { value });
if (this._yamlMode) {
this._yamlEditor?.setValue(value);
}
}
private _typeChanged(ev: CustomEvent) { private _typeChanged(ev: CustomEvent) {
const type = (ev.target as HaSelect).value; const type = (ev.target as HaSelect).value;
@ -439,10 +472,27 @@ export default class HaAutomationTriggerRow extends LitElement {
return [ return [
haStyle, haStyle,
css` css`
.disabled {
opacity: 0.5;
pointer-events: none;
}
.card-content {
padding-top: 16px;
margin-top: 0;
}
.disabled-bar {
background: var(--divider-color, #e0e0e0);
text-align: center;
border-top-right-radius: var(--ha-card-border-radius);
border-top-left-radius: var(--ha-card-border-radius);
}
.card-menu { .card-menu {
float: right; float: right;
z-index: 3; z-index: 3;
margin: 4px;
--mdc-theme-text-primary-on-background: var(--primary-text-color); --mdc-theme-text-primary-on-background: var(--primary-text-color);
display: flex;
align-items: center;
} }
:host-context([style*="direction: rtl;"]) .card-menu { :host-context([style*="direction: rtl;"]) .card-menu {
float: left; float: left;

View File

@ -1675,7 +1675,7 @@
"introduction": "The automation editor allows you to create and edit automations. Please follow the link below to read the instructions to make sure that you have configured Home Assistant correctly.", "introduction": "The automation editor allows you to create and edit automations. Please follow the link below to read the instructions to make sure that you have configured Home Assistant correctly.",
"learn_more": "Learn more about automations", "learn_more": "Learn more about automations",
"pick_automation": "Pick automation to edit", "pick_automation": "Pick automation to edit",
"no_automations": "We couldnt find any automations", "no_automations": "We couldn't find any automations",
"add_automation": "Create automation", "add_automation": "Create automation",
"only_editable": "Only automations in automations.yaml are editable.", "only_editable": "Only automations in automations.yaml are editable.",
"dev_only_editable": "Only automations that have a unique ID assigned are debuggable.", "dev_only_editable": "Only automations that have a unique ID assigned are debuggable.",
@ -1961,6 +1961,9 @@
"run_action_error": "Error running action", "run_action_error": "Error running action",
"run_action_success": "Action run successfully", "run_action_success": "Action run successfully",
"duplicate": "[%key:ui::panel::config::automation::editor::triggers::duplicate%]", "duplicate": "[%key:ui::panel::config::automation::editor::triggers::duplicate%]",
"enable": "Enable",
"disable": "Disable",
"disabled": "Disabled",
"delete": "[%key:ui::panel::mailbox::delete_button%]", "delete": "[%key:ui::panel::mailbox::delete_button%]",
"delete_confirm": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm%]", "delete_confirm": "[%key:ui::panel::config::automation::editor::triggers::delete_confirm%]",
"unsupported_action": "No visual editor support for action: {action}", "unsupported_action": "No visual editor support for action: {action}",