From c506face8c0f2053a4957d66b6e244e5e3d0f4bc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 21 Feb 2022 21:13:34 -0800 Subject: [PATCH 1/7] Show triggered in automation editor --- src/common/util/debounce.ts | 6 +- .../trigger/ha-automation-trigger-row.ts | 103 +++++++++++++++++- 2 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/common/util/debounce.ts b/src/common/util/debounce.ts index 70aaddf2e0a8..cd61f072b95d 100644 --- a/src/common/util/debounce.ts +++ b/src/common/util/debounce.ts @@ -11,7 +11,7 @@ export const debounce = ( immediate = false ) => { let timeout: number | undefined; - return (...args: T): void => { + const debouncedFunc = (...args: T): void => { const later = () => { timeout = undefined; if (!immediate) { @@ -25,4 +25,8 @@ export const debounce = ( func(...args); } }; + debouncedFunc.cancel = () => { + clearTimeout(timeout); + }; + return debouncedFunc; }; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index ecf1f1a4b721..68c94ec2223b 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -3,9 +3,10 @@ import "@material/mwc-list/mwc-list-item"; import { mdiDotsVertical } from "@mdi/js"; import "@material/mwc-select"; import type { Select } from "@material/mwc-select"; -import { css, CSSResultGroup, html, LitElement } from "lit"; +import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { classMap } from "lit/directives/class-map"; import { dynamicElement } from "../../../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../../../common/dom/fire_event"; import { stringCompare } from "../../../../common/string/compare"; @@ -16,7 +17,7 @@ import "../../../../components/ha-card"; import "../../../../components/ha-alert"; import "../../../../components/ha-textfield"; import "../../../../components/ha-icon-button"; -import type { Trigger } from "../../../../data/automation"; +import { subscribeTrigger, Trigger } from "../../../../data/automation"; import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; @@ -34,6 +35,7 @@ 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 { debounce } from "../../../../common/util/debounce"; const OPTIONS = [ "device", @@ -90,6 +92,12 @@ export default class HaAutomationTriggerRow extends LitElement { @state() private _requestShowId = false; + @state() private _triggered = false; + + @state() private _triggerColor = false; + + private _triggerUnsub?: Promise<() => void>; + private _processedTypes = memoizeOne( (localize: LocalizeFunc): [string, string][] => OPTIONS.map( @@ -111,6 +119,15 @@ export default class HaAutomationTriggerRow extends LitElement { return html`
+ + TRIGGERED +
unsub()); + } + + const oldTrigger = changedProps.get("trigger") as Trigger | undefined; + + // The first time we change a trigger it's always invalid so we skip subscribing + if (oldTrigger?.platform === this.trigger.platform) { + this._subscribeTrigger(); + } + } + + public connectedCallback(): void { + super.connectedCallback(); + if (this.trigger) { + this._subscribeTrigger(); + } + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + if (this._triggerUnsub) { + this._triggerUnsub.then((unsub) => unsub()); + this._triggerUnsub = undefined; + } + this._subscribeTrigger.cancel(); + } + + private _subscribeTrigger = debounce(() => { + let untriggerTimeout: number | undefined; + const showTriggeredTime = 5000; + + const triggerUnsub = subscribeTrigger( + this.hass, + () => { + if (untriggerTimeout !== undefined) { + clearTimeout(untriggerTimeout); + this._triggerColor = !this._triggerColor; + } else { + this._triggerColor = false; + } + this._triggered = true; + untriggerTimeout = window.setTimeout(() => { + this._triggered = false; + untriggerTimeout = undefined; + }, showTriggeredTime); + }, + this.trigger + ); + triggerUnsub.catch(() => { + triggerUnsub.then((unsub) => unsub()); + if (this._triggerUnsub === triggerUnsub) { + this._triggerUnsub = undefined; + } + }); + this._triggerUnsub = triggerUnsub; + }, 5000); + private _handleUiModeNotAvailable(ev: CustomEvent) { this._warnings = handleStructError(this.hass, ev.detail).warnings; if (!this._yamlMode) { @@ -327,6 +409,23 @@ export default class HaAutomationTriggerRow extends LitElement { z-index: 3; --mdc-theme-text-primary-on-background: var(--primary-text-color); } + .triggered { + position: absolute; + top: 2px; + right: 2px; + pointer-events: none; + font-weight: bold; + font-size: 14px; + color: var(--primary-color); + opacity: 0; + transition: opacity 0.3s; + } + .triggered.active { + opacity: 1; + } + .triggered.accent { + color: var(--accent-color); + } .rtl .card-menu { float: left; } From f94455e3d0e5ca6d620646cf8f4e2442f591920d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 12:18:46 -0800 Subject: [PATCH 2/7] Use validate config --- src/data/config.ts | 19 ++++++++++ .../trigger/ha-automation-trigger-row.ts | 36 ++++++++++--------- 2 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 src/data/config.ts diff --git a/src/data/config.ts b/src/data/config.ts new file mode 100644 index 000000000000..281e2debbf66 --- /dev/null +++ b/src/data/config.ts @@ -0,0 +1,19 @@ +import { HomeAssistant } from "../types"; + +interface ValidationResult { + valid: boolean; + error: string | null; +} + +type ValidKeys = "trigger" | "action" | "condition"; + +export const validateConfig = < + T extends Partial<{ [key in ValidKeys]: unknown }> +>( + hass: HomeAssistant, + config: T +): Promise> => + hass.callWS({ + type: "validate_config", + ...config, + }); diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 68c94ec2223b..9db35af2969d 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -36,6 +36,7 @@ import "./types/ha-automation-trigger-time_pattern"; import "./types/ha-automation-trigger-webhook"; import "./types/ha-automation-trigger-zone"; import { debounce } from "../../../../common/util/debounce"; +import { validateConfig } from "../../../../data/config"; const OPTIONS = [ "device", @@ -242,19 +243,7 @@ export default class HaAutomationTriggerRow extends LitElement { protected override updated(changedProps: PropertyValues): void { super.updated(changedProps); - - if (!changedProps.has("trigger")) { - return; - } - - if (this._triggerUnsub) { - this._triggerUnsub.then((unsub) => unsub()); - } - - const oldTrigger = changedProps.get("trigger") as Trigger | undefined; - - // The first time we change a trigger it's always invalid so we skip subscribing - if (oldTrigger?.platform === this.trigger.platform) { + if (changedProps.has("trigger")) { this._subscribeTrigger(); } } @@ -275,9 +264,25 @@ export default class HaAutomationTriggerRow extends LitElement { this._subscribeTrigger.cancel(); } - private _subscribeTrigger = debounce(() => { + private _subscribeTrigger = debounce(async () => { let untriggerTimeout: number | undefined; const showTriggeredTime = 5000; + const trigger = this.trigger; + + // Clean up old trigger subscription. + if (this._triggerUnsub) { + this._triggerUnsub.then((unsub) => unsub()); + this._triggerUnsub = undefined; + } + + const validateResult = await validateConfig(this.hass, { + trigger: this.trigger, + }); + + // Don't do anything if trigger not valid or if trigger changed. + if (!validateResult.trigger.valid || this.trigger !== trigger) { + return; + } const triggerUnsub = subscribeTrigger( this.hass, @@ -294,10 +299,9 @@ export default class HaAutomationTriggerRow extends LitElement { untriggerTimeout = undefined; }, showTriggeredTime); }, - this.trigger + trigger ); triggerUnsub.catch(() => { - triggerUnsub.then((unsub) => unsub()); if (this._triggerUnsub === triggerUnsub) { this._triggerUnsub = undefined; } From 044ffa322de94f1969676f95e596830d6240bfbf Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 12:21:09 -0800 Subject: [PATCH 3/7] Update styling --- .../config/automation/trigger/ha-automation-trigger-row.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 9db35af2969d..8a4a235b3a9f 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -415,7 +415,7 @@ export default class HaAutomationTriggerRow extends LitElement { } .triggered { position: absolute; - top: 2px; + top: -1px; right: 2px; pointer-events: none; font-weight: bold; From 2bf53efeb1eaea573a5e73313f77cfb61616e9e9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 13:11:39 -0800 Subject: [PATCH 4/7] Update styling --- .../trigger/ha-automation-trigger-row.ts | 42 ++++++++++++------- src/translations/en.json | 1 + 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 8a4a235b3a9f..1a300ce2ef95 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -120,15 +120,6 @@ export default class HaAutomationTriggerRow extends LitElement { return html`
- - TRIGGERED -
`}
+
+ ${this.hass.localize( + "ui.panel.config.automation.editor.triggers.triggered" + )} +
`; } @@ -415,20 +417,28 @@ export default class HaAutomationTriggerRow extends LitElement { } .triggered { position: absolute; - top: -1px; - right: 2px; + top: 0px; + right: 0px; + left: 0px; + text-transform: uppercase; pointer-events: none; font-weight: bold; font-size: 14px; - color: var(--primary-color); - opacity: 0; - transition: opacity 0.3s; + background-color: var(--primary-color); + color: var(--text-primary-color); + max-height: 0px; + overflow: hidden; + transition: max-height 0.3s; + text-align: center; + border-top-right-radius: var(--ha-card-border-radius, 4px); + border-top-left-radius: var(--ha-card-border-radius, 4px); } .triggered.active { - opacity: 1; + max-height: 100px; } .triggered.accent { - color: var(--accent-color); + background-color: var(--accent-color); + color: var(--text-accent-color, var(--text-primary-color)); } .rtl .card-menu { float: left; diff --git a/src/translations/en.json b/src/translations/en.json index 36998964a584..044f66234463 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1622,6 +1622,7 @@ "header": "Triggers", "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", + "triggered": "Triggered", "add": "Add trigger", "id": "Trigger ID", "edit_id": "Edit trigger ID", From 306837df25b5d05bbac9d90abc5fb7a7ced360a8 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 13:18:13 -0800 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: Bram Kragten --- .../automation/trigger/ha-automation-trigger-row.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 1a300ce2ef95..c2fc2394cc7f 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -97,7 +97,7 @@ export default class HaAutomationTriggerRow extends LitElement { @state() private _triggerColor = false; - private _triggerUnsub?: Promise<() => void>; + private _triggerUnsub?: Promise; private _processedTypes = memoizeOne( (localize: LocalizeFunc): [string, string][] => @@ -229,11 +229,10 @@ export default class HaAutomationTriggerRow extends LitElement { `}
${this.hass.localize( "ui.panel.config.automation.editor.triggers.triggered" @@ -252,7 +251,7 @@ export default class HaAutomationTriggerRow extends LitElement { public connectedCallback(): void { super.connectedCallback(); - if (this.trigger) { + if (this.hasUpdated && this.trigger) { this._subscribeTrigger(); } } From 379a9eb9545700440ebfc0662446794b7179dbf2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 13:18:41 -0800 Subject: [PATCH 6/7] Import --- .../config/automation/trigger/ha-automation-trigger-row.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index c2fc2394cc7f..180e22eb6344 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -1,3 +1,4 @@ +import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import { ActionDetail } from "@material/mwc-list/mwc-list-foundation"; import "@material/mwc-list/mwc-list-item"; import { mdiDotsVertical } from "@mdi/js"; From 69619c80f39902e9b5ff3eb2606c08c13e9b094a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 22 Feb 2022 13:33:41 -0800 Subject: [PATCH 7/7] Help with cleanup --- .../trigger/ha-automation-trigger-row.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 180e22eb6344..7d3c15c3ed51 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -263,10 +263,20 @@ export default class HaAutomationTriggerRow extends LitElement { this._triggerUnsub.then((unsub) => unsub()); this._triggerUnsub = undefined; } - this._subscribeTrigger.cancel(); + this._doSubscribeTrigger.cancel(); } - private _subscribeTrigger = debounce(async () => { + private _subscribeTrigger() { + // Clean up old trigger subscription. + if (this._triggerUnsub) { + this._triggerUnsub.then((unsub) => unsub()); + this._triggerUnsub = undefined; + } + + this._doSubscribeTrigger(); + } + + private _doSubscribeTrigger = debounce(async () => { let untriggerTimeout: number | undefined; const showTriggeredTime = 5000; const trigger = this.trigger;