mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-26 22:37:21 +00:00
490 lines
15 KiB
TypeScript
490 lines
15 KiB
TypeScript
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
|
import "@material/mwc-list/mwc-list-item";
|
|
import { mdiDotsVertical } from "@mdi/js";
|
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
|
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
|
import { customElement, property, query, state } from "lit/decorators";
|
|
import { classMap } from "lit/directives/class-map";
|
|
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
|
import { handleStructError } from "../../../../common/structs/handle-errors";
|
|
import { debounce } from "../../../../common/util/debounce";
|
|
import "../../../../components/ha-alert";
|
|
import "../../../../components/ha-button-menu";
|
|
import "../../../../components/ha-card";
|
|
import "../../../../components/ha-expansion-panel";
|
|
import "../../../../components/ha-icon-button";
|
|
import { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
|
import "../../../../components/ha-textfield";
|
|
import { subscribeTrigger, Trigger } from "../../../../data/automation";
|
|
import { validateConfig } from "../../../../data/config";
|
|
import {
|
|
showAlertDialog,
|
|
showConfirmationDialog,
|
|
} from "../../../../dialogs/generic/show-dialog-box";
|
|
import { haStyle } from "../../../../resources/styles";
|
|
import type { HomeAssistant } from "../../../../types";
|
|
import "./types/ha-automation-trigger-calendar";
|
|
import "./types/ha-automation-trigger-device";
|
|
import "./types/ha-automation-trigger-event";
|
|
import "./types/ha-automation-trigger-geo_location";
|
|
import "./types/ha-automation-trigger-homeassistant";
|
|
import "./types/ha-automation-trigger-mqtt";
|
|
import "./types/ha-automation-trigger-numeric_state";
|
|
import "./types/ha-automation-trigger-state";
|
|
import "./types/ha-automation-trigger-sun";
|
|
import "./types/ha-automation-trigger-tag";
|
|
import "./types/ha-automation-trigger-template";
|
|
import "./types/ha-automation-trigger-time";
|
|
import "./types/ha-automation-trigger-time_pattern";
|
|
import "./types/ha-automation-trigger-webhook";
|
|
import "./types/ha-automation-trigger-zone";
|
|
import { describeTrigger } from "../../../../data/automation_i18n";
|
|
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
|
|
|
export interface TriggerElement extends LitElement {
|
|
trigger: Trigger;
|
|
}
|
|
|
|
export const handleChangeEvent = (element: TriggerElement, ev: CustomEvent) => {
|
|
ev.stopPropagation();
|
|
const name = (ev.currentTarget as any)?.name;
|
|
if (!name) {
|
|
return;
|
|
}
|
|
const newVal = (ev.target as any)?.value;
|
|
|
|
if ((element.trigger[name] || "") === newVal) {
|
|
return;
|
|
}
|
|
|
|
let newTrigger: Trigger;
|
|
if (newVal === undefined || newVal === "") {
|
|
newTrigger = { ...element.trigger };
|
|
delete newTrigger[name];
|
|
} else {
|
|
newTrigger = { ...element.trigger, [name]: newVal };
|
|
}
|
|
fireEvent(element, "value-changed", { value: newTrigger });
|
|
};
|
|
|
|
const preventDefault = (ev) => ev.preventDefault();
|
|
|
|
@customElement("ha-automation-trigger-row")
|
|
export default class HaAutomationTriggerRow extends LitElement {
|
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
|
|
@property({ attribute: false }) public trigger!: Trigger;
|
|
|
|
@state() private _warnings?: string[];
|
|
|
|
@state() private _yamlMode = false;
|
|
|
|
@state() private _requestShowId = false;
|
|
|
|
@state() private _triggered?: Record<string, unknown>;
|
|
|
|
@state() private _triggerColor = false;
|
|
|
|
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
|
|
|
private _triggerUnsub?: Promise<UnsubscribeFunc>;
|
|
|
|
protected render() {
|
|
const supported =
|
|
customElements.get(`ha-automation-trigger-${this.trigger.platform}`) !==
|
|
undefined;
|
|
const yamlMode = this._yamlMode || !supported;
|
|
const showId = "id" in this.trigger || this._requestShowId;
|
|
|
|
return html`
|
|
<ha-card outlined>
|
|
${this.trigger.enabled === false
|
|
? html`
|
|
<div class="disabled-bar">
|
|
${this.hass.localize(
|
|
"ui.panel.config.automation.editor.actions.disabled"
|
|
)}
|
|
</div>
|
|
`
|
|
: ""}
|
|
|
|
<ha-expansion-panel
|
|
leftChevron
|
|
.header=${capitalizeFirstLetter(describeTrigger(this.trigger))}
|
|
>
|
|
<ha-button-menu
|
|
slot="icons"
|
|
fixed
|
|
corner="BOTTOM_START"
|
|
@action=${this._handleAction}
|
|
@click=${preventDefault}
|
|
>
|
|
<ha-icon-button
|
|
slot="trigger"
|
|
.label=${this.hass.localize("ui.common.menu")}
|
|
.path=${mdiDotsVertical}
|
|
></ha-icon-button>
|
|
<mwc-list-item>
|
|
${this.hass.localize(
|
|
"ui.panel.config.automation.editor.triggers.edit_id"
|
|
)}
|
|
</mwc-list-item>
|
|
<mwc-list-item .disabled=${!supported}>
|
|
${yamlMode
|
|
? this.hass.localize(
|
|
"ui.panel.config.automation.editor.edit_ui"
|
|
)
|
|
: this.hass.localize(
|
|
"ui.panel.config.automation.editor.edit_yaml"
|
|
)}
|
|
</mwc-list-item>
|
|
<mwc-list-item>
|
|
${this.hass.localize(
|
|
"ui.panel.config.automation.editor.actions.duplicate"
|
|
)}
|
|
</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">
|
|
${this.hass.localize(
|
|
"ui.panel.config.automation.editor.actions.delete"
|
|
)}
|
|
</mwc-list-item>
|
|
</ha-button-menu>
|
|
|
|
<div
|
|
class=${classMap({
|
|
"card-content": true,
|
|
disabled: this.trigger.enabled === false,
|
|
})}
|
|
>
|
|
${this._warnings
|
|
? html`<ha-alert
|
|
alert-type="warning"
|
|
.title=${this.hass.localize(
|
|
"ui.errors.config.editor_not_supported"
|
|
)}
|
|
>
|
|
${this._warnings.length && this._warnings[0] !== undefined
|
|
? html` <ul>
|
|
${this._warnings.map(
|
|
(warning) => html`<li>${warning}</li>`
|
|
)}
|
|
</ul>`
|
|
: ""}
|
|
${this.hass.localize(
|
|
"ui.errors.config.edit_in_yaml_supported"
|
|
)}
|
|
</ha-alert>`
|
|
: ""}
|
|
${yamlMode
|
|
? html`
|
|
${!supported
|
|
? html`
|
|
${this.hass.localize(
|
|
"ui.panel.config.automation.editor.triggers.unsupported_platform",
|
|
"platform",
|
|
this.trigger.platform
|
|
)}
|
|
`
|
|
: ""}
|
|
<ha-yaml-editor
|
|
.hass=${this.hass}
|
|
.defaultValue=${this.trigger}
|
|
@value-changed=${this._onYamlChange}
|
|
></ha-yaml-editor>
|
|
`
|
|
: html`
|
|
${showId
|
|
? html`
|
|
<ha-textfield
|
|
.label=${this.hass.localize(
|
|
"ui.panel.config.automation.editor.triggers.id"
|
|
)}
|
|
.value=${this.trigger.id || ""}
|
|
@change=${this._idChanged}
|
|
>
|
|
</ha-textfield>
|
|
`
|
|
: ""}
|
|
<div @ui-mode-not-available=${this._handleUiModeNotAvailable}>
|
|
${dynamicElement(
|
|
`ha-automation-trigger-${this.trigger.platform}`,
|
|
{ hass: this.hass, trigger: this.trigger }
|
|
)}
|
|
</div>
|
|
`}
|
|
</div>
|
|
</ha-expansion-panel>
|
|
|
|
<div
|
|
class="triggered ${classMap({
|
|
active: this._triggered !== undefined,
|
|
accent: this._triggerColor,
|
|
})}"
|
|
@click=${this._showTriggeredInfo}
|
|
>
|
|
${this.hass.localize(
|
|
"ui.panel.config.automation.editor.triggers.triggered"
|
|
)}
|
|
</div>
|
|
</ha-card>
|
|
`;
|
|
}
|
|
|
|
protected override updated(changedProps: PropertyValues<this>): void {
|
|
super.updated(changedProps);
|
|
if (changedProps.has("trigger")) {
|
|
this._subscribeTrigger();
|
|
}
|
|
}
|
|
|
|
public connectedCallback(): void {
|
|
super.connectedCallback();
|
|
if (this.hasUpdated && this.trigger) {
|
|
this._subscribeTrigger();
|
|
}
|
|
}
|
|
|
|
public disconnectedCallback(): void {
|
|
super.disconnectedCallback();
|
|
if (this._triggerUnsub) {
|
|
this._triggerUnsub.then((unsub) => unsub());
|
|
this._triggerUnsub = undefined;
|
|
}
|
|
this._doSubscribeTrigger.cancel();
|
|
}
|
|
|
|
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;
|
|
|
|
// Clean up old trigger subscription.
|
|
if (this._triggerUnsub) {
|
|
this._triggerUnsub.then((unsub) => unsub());
|
|
this._triggerUnsub = undefined;
|
|
}
|
|
|
|
const validateResult = await validateConfig(this.hass, {
|
|
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,
|
|
(result) => {
|
|
if (untriggerTimeout !== undefined) {
|
|
clearTimeout(untriggerTimeout);
|
|
this._triggerColor = !this._triggerColor;
|
|
} else {
|
|
this._triggerColor = false;
|
|
}
|
|
this._triggered = result;
|
|
untriggerTimeout = window.setTimeout(() => {
|
|
this._triggered = undefined;
|
|
untriggerTimeout = undefined;
|
|
}, showTriggeredTime);
|
|
},
|
|
trigger
|
|
);
|
|
triggerUnsub.catch(() => {
|
|
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) {
|
|
this._yamlMode = true;
|
|
}
|
|
}
|
|
|
|
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
|
switch (ev.detail.index) {
|
|
case 0:
|
|
this._requestShowId = true;
|
|
this.expand();
|
|
break;
|
|
case 1:
|
|
this._switchYamlMode();
|
|
this.expand();
|
|
break;
|
|
case 2:
|
|
fireEvent(this, "duplicate");
|
|
break;
|
|
case 3:
|
|
this._onDisable();
|
|
break;
|
|
case 4:
|
|
this._onDelete();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private _onDelete() {
|
|
showConfirmationDialog(this, {
|
|
text: this.hass.localize(
|
|
"ui.panel.config.automation.editor.triggers.delete_confirm"
|
|
),
|
|
dismissText: this.hass.localize("ui.common.cancel"),
|
|
confirmText: this.hass.localize("ui.common.delete"),
|
|
confirm: () => {
|
|
fireEvent(this, "value-changed", { value: null });
|
|
},
|
|
});
|
|
}
|
|
|
|
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 _idChanged(ev: CustomEvent) {
|
|
const newId = (ev.target as any).value;
|
|
if (newId === (this.trigger.id ?? "")) {
|
|
return;
|
|
}
|
|
this._requestShowId = true;
|
|
const value = { ...this.trigger };
|
|
if (!newId) {
|
|
delete value.id;
|
|
} else {
|
|
value.id = newId;
|
|
}
|
|
fireEvent(this, "value-changed", {
|
|
value,
|
|
});
|
|
}
|
|
|
|
private _onYamlChange(ev: CustomEvent) {
|
|
ev.stopPropagation();
|
|
if (!ev.detail.isValid) {
|
|
return;
|
|
}
|
|
this._warnings = undefined;
|
|
fireEvent(this, "value-changed", { value: ev.detail.value });
|
|
}
|
|
|
|
private _switchYamlMode() {
|
|
this._warnings = undefined;
|
|
this._yamlMode = !this._yamlMode;
|
|
}
|
|
|
|
private _showTriggeredInfo() {
|
|
showAlertDialog(this, {
|
|
text: html`
|
|
<ha-yaml-editor
|
|
readOnly
|
|
.hass=${this.hass}
|
|
.defaultValue=${this._triggered}
|
|
></ha-yaml-editor>
|
|
`,
|
|
});
|
|
}
|
|
|
|
public expand() {
|
|
this.updateComplete.then(() => {
|
|
this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true;
|
|
});
|
|
}
|
|
|
|
static get styles(): CSSResultGroup {
|
|
return [
|
|
haStyle,
|
|
css`
|
|
ha-button-menu {
|
|
--mdc-theme-text-primary-on-background: var(--primary-text-color);
|
|
}
|
|
.disabled {
|
|
opacity: 0.5;
|
|
pointer-events: none;
|
|
}
|
|
ha-expansion-panel {
|
|
--expansion-panel-summary-padding: 0 0 0 8px;
|
|
--expansion-panel-content-padding: 0;
|
|
}
|
|
.card-content {
|
|
padding: 16px;
|
|
}
|
|
.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);
|
|
}
|
|
.triggered {
|
|
cursor: pointer;
|
|
position: absolute;
|
|
top: 0px;
|
|
right: 0px;
|
|
left: 0px;
|
|
text-transform: uppercase;
|
|
font-weight: bold;
|
|
font-size: 14px;
|
|
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 {
|
|
max-height: 100px;
|
|
}
|
|
.triggered:hover {
|
|
opacity: 0.8;
|
|
}
|
|
.triggered.accent {
|
|
background-color: var(--accent-color);
|
|
color: var(--text-accent-color, var(--text-primary-color));
|
|
}
|
|
mwc-list-item[disabled] {
|
|
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
|
}
|
|
ha-textfield {
|
|
display: block;
|
|
margin-bottom: 24px;
|
|
}
|
|
`,
|
|
];
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface HTMLElementTagNameMap {
|
|
"ha-automation-trigger-row": HaAutomationTriggerRow;
|
|
}
|
|
}
|