diff --git a/src/data/threshold.ts b/src/data/threshold.ts new file mode 100644 index 000000000000..886ebcf0d97f --- /dev/null +++ b/src/data/threshold.ts @@ -0,0 +1,21 @@ +import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { HomeAssistant } from "../types"; + +export interface ThresholdPreview { + state: string; + attributes: Record; +} + +export const subscribePreviewThreshold = ( + hass: HomeAssistant, + flow_id: string, + flow_type: "config_flow" | "options_flow", + user_input: Record, + callback: (preview: ThresholdPreview) => void +): Promise => + hass.connection.subscribeMessage(callback, { + type: "threshold/start_preview", + flow_id, + flow_type, + user_input, + }); diff --git a/src/dialogs/config-flow/previews/flow-preview-threshold.ts b/src/dialogs/config-flow/previews/flow-preview-threshold.ts new file mode 100644 index 000000000000..74f2daa8e971 --- /dev/null +++ b/src/dialogs/config-flow/previews/flow-preview-threshold.ts @@ -0,0 +1,108 @@ +import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; +import { LitElement, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { FlowType } from "../../../data/data_entry_flow"; +import { + ThresholdPreview, + subscribePreviewThreshold, +} from "../../../data/threshold"; +import { HomeAssistant } from "../../../types"; +import "./entity-preview-row"; +import { debounce } from "../../../common/util/debounce"; +import { fireEvent } from "../../../common/dom/fire_event"; + +@customElement("flow-preview-threshold") +class FlowPreviewThreshold extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property() public flowType!: FlowType; + + public handler!: string; + + @property() public stepId!: string; + + @property() public flowId!: string; + + @property() public stepData!: Record; + + @state() private _preview?: HassEntity; + + @state() private _error?: string; + + private _unsub?: Promise; + + disconnectedCallback(): void { + super.disconnectedCallback(); + if (this._unsub) { + this._unsub.then((unsub) => unsub()); + this._unsub = undefined; + } + } + + willUpdate(changedProps) { + if (changedProps.has("stepData")) { + this._debouncedSubscribePreview(); + } + } + + protected render() { + if (this._error) { + return html`${this._error}`; + } + return html``; + } + + private _setPreview = (preview: ThresholdPreview) => { + const now = new Date().toISOString(); + this._preview = { + entity_id: `${this.stepId}.___flow_preview___`, + last_changed: now, + last_updated: now, + context: { id: "", parent_id: null, user_id: null }, + ...preview, + }; + }; + + private _debouncedSubscribePreview = debounce(() => { + this._subscribePreview(); + }, 250); + + private async _subscribePreview() { + if (this._unsub) { + (await this._unsub)(); + this._unsub = undefined; + } + if (this.flowType === "repair_flow") { + return; + } + try { + this._unsub = subscribePreviewThreshold( + this.hass, + this.flowId, + this.flowType, + this.stepData, + this._setPreview + ); + await this._unsub; + fireEvent(this, "set-flow-errors", { errors: {} }); + } catch (err: any) { + if (typeof err.message === "string") { + this._error = err.message; + } else { + this._error = undefined; + fireEvent(this, "set-flow-errors", err.message); + } + this._unsub = undefined; + this._preview = undefined; + } + } +} + +declare global { + interface HTMLElementTagNameMap { + "flow-preview-threshold": FlowPreviewThreshold; + } +}