From fe0fb2382afa6c32763103c278ccf48949b7745f Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Wed, 2 Oct 2024 15:06:06 +0200 Subject: [PATCH 1/8] Allow to transfer all Thread datasets with TLV (#22183) * Allow to transfer all Thread datasets with TLV This commit allows to transfer all Thread datasets with TLV. Since PR #22022 the preferred dataset is transmitted when using Matter external commissioning. This commit makes the Thread configuration dialog to have feature parity. * Drop preferred border agent id as additional metric for default router We always have the extended address, so use this as primary and only metric which router is the default. The preferred border agent id gets updated best effort. Also use isDefaultRouter consistently in the code. --- src/data/thread.ts | 2 +- src/external_app/external_messaging.ts | 5 +- .../thread/thread-config-panel.ts | 64 +++++++++++-------- src/translations/en.json | 1 + 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/data/thread.ts b/src/data/thread.ts index 82c19fbe03..d999690a68 100644 --- a/src/data/thread.ts +++ b/src/data/thread.ts @@ -18,7 +18,7 @@ export interface ThreadDataSet { channel: number | null; created: string; dataset_id: string; - extended_pan_id: string | null; + extended_pan_id: string; network_name: string; pan_id: string | null; preferred_border_agent_id: string | null; diff --git a/src/external_app/external_messaging.ts b/src/external_app/external_messaging.ts index e8a436e203..5e0461585b 100644 --- a/src/external_app/external_messaging.ts +++ b/src/external_app/external_messaging.ts @@ -141,9 +141,10 @@ interface EMOutgoingMessageImprovScan extends EMMessage { interface EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage { type: "thread/store_in_platform_keychain"; payload: { - mac_extended_address: string; - border_agent_id: string; + mac_extended_address: string | null; + border_agent_id: string | null; active_operational_dataset: string; + extended_pan_id: string; }; } diff --git a/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts b/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts index 23dfce2198..206ab96194 100644 --- a/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts +++ b/src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts @@ -37,6 +37,7 @@ import { ThreadDataSet, ThreadRouter, addThreadDataSet, + getThreadDataSetTLV, listThreadDataSets, removeThreadDataSet, setPreferredBorderAgent, @@ -168,8 +169,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { (otbr) => otbr.extended_pan_id === network.dataset!.extended_pan_id )); const canImportKeychain = - this.hass.auth.external?.config.canTransferThreadCredentialsToKeychain && - otbrForNetwork; + this.hass.auth.external?.config.canTransferThreadCredentialsToKeychain; return html`
@@ -208,8 +208,12 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { ${network.routers.map((router) => { const otbr = this._otbrInfo && this._otbrInfo[router.extended_address]; - const showOverflow = - ("dataset" in network && router.border_agent_id) || otbr; + const showDefaultRouter = !!network.dataset; + const isDefaultRouter = + showDefaultRouter && + router.extended_address === + network.dataset!.preferred_extended_address; + const showOverflow = showDefaultRouter || otbr; return html`${router.server} ${showOverflow - ? html`${network.dataset && - router.extended_address === - network.dataset.preferred_extended_address + ? html`${isDefaultRouter ? html` - ${network.dataset && router.border_agent_id - ? html` - ${router.border_agent_id === - network.dataset.preferred_border_agent_id + ${showDefaultRouter + ? html` + ${isDefaultRouter ? this.hass.localize( "ui.panel.config.thread.default_router" ) @@ -321,9 +319,13 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { >
` : ""} - ${canImportKeychain + ${canImportKeychain && + network.dataset?.preferred && + network.routers?.length ? html`
- Send credentials to phone
` @@ -331,17 +333,30 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
`; } - private _sendCredentials(ev) { - const otbr = (ev.currentTarget as any).otbr as OTBRInfo; - if (!otbr) { + private async _sendCredentials(ev) { + const dataset = (ev.currentTarget as any).networkDataset as ThreadDataSet; + if (!dataset) { + return; + } + if ( + !dataset.preferred_extended_address && + !dataset.preferred_border_agent_id + ) { + showAlertDialog(this, { + title: "Error", + text: this.hass.localize("ui.panel.config.thread.no_preferred_router"), + }); return; } this.hass.auth.external!.fireMessage({ type: "thread/store_in_platform_keychain", payload: { - mac_extended_address: otbr.extended_address, - border_agent_id: otbr.border_agent_id, - active_operational_dataset: otbr.active_dataset_tlvs, + mac_extended_address: dataset.preferred_extended_address, + border_agent_id: dataset.preferred_border_agent_id, + active_operational_dataset: ( + await getThreadDataSetTLV(this.hass, dataset.dataset_id) + ).tlv, + extended_pan_id: dataset.extended_pan_id, }, }); } @@ -467,10 +482,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) { const network = (ev.currentTarget as any).network as ThreadNetwork; const router = (ev.currentTarget as any).router as ThreadRouter; const otbr = (ev.currentTarget as any).otbr as OTBRInfo; - const index = - network.dataset && router.border_agent_id - ? Number(ev.detail.index) - : Number(ev.detail.index) + 1; + const index = Number(ev.detail.index); switch (index) { case 0: this._setPreferredBorderAgent(network.dataset!, router); diff --git a/src/translations/en.json b/src/translations/en.json index 32c55ddef7..92d5b38048 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4590,6 +4590,7 @@ "confirm_delete_dataset": "Delete {name} dataset?", "confirm_delete_dataset_text": "This network will be removed from Home Assistant.", "no_border_routers": "No border routers found", + "no_preferred_router": "No preferred border router defined", "border_routers": "{count} border {count, plural,\n one {router}\n other {routers}\n}", "managed_by_home_assistant": "Managed by Home Assistant", "operational_dataset": "Operational dataset", From faf872bfb80634a8080bdd71eb468054ff2b4a54 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 2 Oct 2024 15:13:21 +0200 Subject: [PATCH 2/8] Simplify create automation from device dialog (#22190) * Simplify automation dialog * Fix translations * Auto expand trigger action and condition * Improve wording * Expand all * Remove unused translations --- src/data/automation.ts | 9 +- src/data/script.ts | 9 +- .../automation/action/ha-automation-action.ts | 9 + .../condition/ha-automation-condition.ts | 9 + .../automation/manual-automation-editor.ts | 42 +++- .../trigger/ha-automation-trigger.ts | 9 + .../device-detail/ha-device-actions-card.ts | 23 -- .../ha-device-automation-card.ts | 142 ----------- .../ha-device-automation-dialog.ts | 231 +++++++++++++----- .../ha-device-conditions-card.ts | 23 -- .../device-detail/ha-device-triggers-card.ts | 23 -- .../config/script/manual-script-editor.ts | 40 ++- src/translations/en.json | 23 +- 13 files changed, 314 insertions(+), 278 deletions(-) delete mode 100644 src/panels/config/devices/device-detail/ha-device-actions-card.ts delete mode 100644 src/panels/config/devices/device-detail/ha-device-automation-card.ts delete mode 100644 src/panels/config/devices/device-detail/ha-device-conditions-card.ts delete mode 100644 src/panels/config/devices/device-detail/ha-device-triggers-card.ts diff --git a/src/data/automation.ts b/src/data/automation.ts index 725e549160..0e3b2fb6df 100644 --- a/src/data/automation.ts +++ b/src/data/automation.ts @@ -8,6 +8,7 @@ import { Context, HomeAssistant } from "../types"; import { BlueprintInput } from "./blueprint"; import { DeviceCondition, DeviceTrigger } from "./device_automation"; import { Action, MODES, migrateAutomationAction } from "./script"; +import { createSearchParam } from "../common/url/search-params"; export const AUTOMATION_DEFAULT_MODE: (typeof MODES)[number] = "single"; export const AUTOMATION_DEFAULT_MAX = 10; @@ -462,9 +463,13 @@ export const flattenTriggers = ( return flatTriggers; }; -export const showAutomationEditor = (data?: Partial) => { +export const showAutomationEditor = ( + data?: Partial, + expanded?: boolean +) => { initialAutomationEditorData = data; - navigate("/config/automation/edit/new"); + const params = expanded ? `?${createSearchParam({ expanded: "1" })}` : ""; + navigate(`/config/automation/edit/new${params}`); }; export const duplicateAutomation = (config: AutomationConfig) => { diff --git a/src/data/script.ts b/src/data/script.ts index 631882a7cb..c2273d18d5 100644 --- a/src/data/script.ts +++ b/src/data/script.ts @@ -28,6 +28,7 @@ import { } from "./automation"; import { BlueprintInput } from "./blueprint"; import { computeObjectId } from "../common/entity/compute_object_id"; +import { createSearchParam } from "../common/url/search-params"; export const MODES = ["single", "restart", "queued", "parallel"] as const; export const MODES_MAX = ["queued", "parallel"] as const; @@ -347,9 +348,13 @@ export const getScriptStateConfig = (hass: HomeAssistant, entity_id: string) => entity_id, }); -export const showScriptEditor = (data?: Partial) => { +export const showScriptEditor = ( + data?: Partial, + expanded?: boolean +) => { inititialScriptEditorData = data; - navigate("/config/script/edit/new"); + const params = expanded ? `?${createSearchParam({ expanded: "1" })}` : ""; + navigate(`/config/script/edit/new${params}`); }; export const getScriptEditorInitData = () => { diff --git a/src/panels/config/automation/action/ha-automation-action.ts b/src/panels/config/automation/action/ha-automation-action.ts index f3dc0257ce..301dc97411 100644 --- a/src/panels/config/automation/action/ha-automation-action.ts +++ b/src/panels/config/automation/action/ha-automation-action.ts @@ -156,6 +156,15 @@ export default class HaAutomationAction extends LitElement { } } + public expandAll() { + const rows = this.shadowRoot!.querySelectorAll( + "ha-automation-action-row" + )!; + rows.forEach((row) => { + row.expand(); + }); + } + private _addActionDialog() { showAddAutomationElementDialog(this, { type: "action", diff --git a/src/panels/config/automation/condition/ha-automation-condition.ts b/src/panels/config/automation/condition/ha-automation-condition.ts index 4ee3e674b4..481a34eb6e 100644 --- a/src/panels/config/automation/condition/ha-automation-condition.ts +++ b/src/panels/config/automation/condition/ha-automation-condition.ts @@ -106,6 +106,15 @@ export default class HaAutomationCondition extends LitElement { } } + public expandAll() { + const rows = this.shadowRoot!.querySelectorAll( + "ha-automation-condition-row" + )!; + rows.forEach((row) => { + row.expand(); + }); + } + private get nested() { return this.path !== undefined; } diff --git a/src/panels/config/automation/manual-automation-editor.ts b/src/panels/config/automation/manual-automation-editor.ts index 741ecb99d2..ca4f8179aa 100644 --- a/src/panels/config/automation/manual-automation-editor.ts +++ b/src/panels/config/automation/manual-automation-editor.ts @@ -1,7 +1,14 @@ import "@material/mwc-button/mwc-button"; import { mdiHelpCircle } from "@mdi/js"; import { HassEntity } from "home-assistant-js-websocket"; -import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; +import { + css, + CSSResultGroup, + html, + LitElement, + nothing, + PropertyValues, +} from "lit"; import { customElement, property } from "lit/decorators"; import { ensureArray } from "../../../common/array/ensure-array"; import { fireEvent } from "../../../common/dom/fire_event"; @@ -21,6 +28,14 @@ import { documentationUrl } from "../../../util/documentation-url"; import "./action/ha-automation-action"; import "./condition/ha-automation-condition"; import "./trigger/ha-automation-trigger"; +import type HaAutomationTrigger from "./trigger/ha-automation-trigger"; +import type HaAutomationAction from "./action/ha-automation-action"; +import type HaAutomationCondition from "./condition/ha-automation-condition"; +import { + extractSearchParam, + removeSearchParam, +} from "../../../common/url/search-params"; +import { constructUrlCurrentPath } from "../../../common/url/construct-url"; @customElement("manual-automation-editor") export class HaManualAutomationEditor extends LitElement { @@ -36,6 +51,31 @@ export class HaManualAutomationEditor extends LitElement { @property({ attribute: false }) public stateObj?: HassEntity; + protected firstUpdated(changedProps: PropertyValues): void { + super.firstUpdated(changedProps); + const expanded = extractSearchParam("expanded"); + if (expanded === "1") { + this._clearParam("expanded"); + const items = this.shadowRoot!.querySelectorAll< + HaAutomationTrigger | HaAutomationCondition | HaAutomationAction + >("ha-automation-trigger, ha-automation-condition, ha-automation-action"); + + items.forEach((el) => { + el.updateComplete.then(() => { + el.expandAll(); + }); + }); + } + } + + private _clearParam(param: string) { + window.history.replaceState( + null, + "", + constructUrlCurrentPath(removeSearchParam(param)) + ); + } + protected render() { return html` ${this.stateObj?.state === "off" diff --git a/src/panels/config/automation/trigger/ha-automation-trigger.ts b/src/panels/config/automation/trigger/ha-automation-trigger.ts index 5fe2a100a1..2a1d254799 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger.ts @@ -179,6 +179,15 @@ export default class HaAutomationTrigger extends LitElement { } } + public expandAll() { + const rows = this.shadowRoot!.querySelectorAll( + "ha-automation-trigger-row" + )!; + rows.forEach((row) => { + row.expand(); + }); + } + private _getKey(action: Trigger) { if (!this._triggerKeys.has(action)) { this._triggerKeys.set(action, Math.random().toString()); diff --git a/src/panels/config/devices/device-detail/ha-device-actions-card.ts b/src/panels/config/devices/device-detail/ha-device-actions-card.ts deleted file mode 100644 index 6498b7df9a..0000000000 --- a/src/panels/config/devices/device-detail/ha-device-actions-card.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { customElement } from "lit/decorators"; -import { - DeviceAction, - localizeDeviceAutomationAction, -} from "../../../../data/device_automation"; -import { HaDeviceAutomationCard } from "./ha-device-automation-card"; - -@customElement("ha-device-actions-card") -export class HaDeviceActionsCard extends HaDeviceAutomationCard { - readonly type = "action"; - - readonly headerKey = "ui.panel.config.devices.automation.actions.caption"; - - constructor() { - super(localizeDeviceAutomationAction); - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-device-actions-card": HaDeviceActionsCard; - } -} diff --git a/src/panels/config/devices/device-detail/ha-device-automation-card.ts b/src/panels/config/devices/device-detail/ha-device-automation-card.ts deleted file mode 100644 index 26ff027d77..0000000000 --- a/src/panels/config/devices/device-detail/ha-device-automation-card.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { css, html, LitElement, nothing } from "lit"; -import { property, state } from "lit/decorators"; -import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/chips/ha-assist-chip"; -import "../../../../components/chips/ha-chip-set"; -import { showAutomationEditor } from "../../../../data/automation"; -import { - DeviceAction, - DeviceAutomation, -} from "../../../../data/device_automation"; -import { EntityRegistryEntry } from "../../../../data/entity_registry"; -import { showScriptEditor } from "../../../../data/script"; -import { buttonLinkStyle } from "../../../../resources/styles"; -import { HomeAssistant } from "../../../../types"; - -declare global { - interface HASSDomEvents { - "entry-selected": undefined; - } -} - -export abstract class HaDeviceAutomationCard< - T extends DeviceAutomation, -> extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property() public deviceId?: string; - - @property({ type: Boolean }) public script = false; - - @property({ attribute: false }) public automations: T[] = []; - - @property({ attribute: false }) entityReg?: EntityRegistryEntry[]; - - @state() public _showSecondary = false; - - abstract headerKey: Parameters[0]; - - abstract type: "action" | "condition" | "trigger"; - - private _localizeDeviceAutomation: ( - hass: HomeAssistant, - entityRegistry: EntityRegistryEntry[], - automation: T - ) => string; - - constructor( - localizeDeviceAutomation: HaDeviceAutomationCard["_localizeDeviceAutomation"] - ) { - super(); - this._localizeDeviceAutomation = localizeDeviceAutomation; - } - - protected shouldUpdate(changedProps): boolean { - if (changedProps.has("deviceId") || changedProps.has("automations")) { - return true; - } - const oldHass = changedProps.get("hass") as HomeAssistant | undefined; - if (!oldHass || oldHass.language !== this.hass.language) { - return true; - } - return false; - } - - protected render() { - if (this.automations.length === 0 || !this.entityReg) { - return nothing; - } - const automations = this._showSecondary - ? this.automations - : this.automations.filter( - (automation) => automation.metadata?.secondary === false - ); - return html` -

${this.hass.localize(this.headerKey)}

-
- - ${automations.map( - (automation, idx) => html` - - - ` - )} - - ${!this._showSecondary && automations.length < this.automations.length - ? html`` - : ""} -
- `; - } - - private _toggleSecondary() { - this._showSecondary = !this._showSecondary; - } - - private _handleAutomationClicked(ev: CustomEvent) { - const automation = { ...this.automations[(ev.currentTarget as any).index] }; - if (!automation) { - return; - } - delete automation.metadata; - if (this.script) { - showScriptEditor({ sequence: [automation as DeviceAction] }); - fireEvent(this, "entry-selected"); - return; - } - const data = {}; - data[this.type] = [automation]; - showAutomationEditor(data); - fireEvent(this, "entry-selected"); - } - - static styles = [ - buttonLinkStyle, - css` - h3 { - color: var(--primary-text-color); - } - .secondary { - --ha-assist-chip-filled-container-color: rgba( - var(--rgb-primary-text-color), - 0.07 - ); - } - button.link { - color: var(--primary-color); - } - `, - ]; -} diff --git a/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts index f3d1835b1c..c79347c67e 100644 --- a/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts +++ b/src/panels/config/devices/device-detail/ha-device-automation-dialog.ts @@ -1,8 +1,18 @@ -import "@material/mwc-button/mwc-button"; -import { CSSResultGroup, html, LitElement, nothing } from "lit"; +import { + mdiAbTesting, + mdiGestureTap, + mdiPencilOutline, + mdiRoomService, +} from "@mdi/js"; +import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; -import "../../../../components/ha-dialog"; +import { shouldHandleRequestSelectedEvent } from "../../../../common/mwc/handle-request-selected-event"; +import { createCloseHeading } from "../../../../components/ha-dialog"; +import { + AutomationConfig, + showAutomationEditor, +} from "../../../../data/automation"; import { DeviceAction, DeviceCondition, @@ -12,11 +22,9 @@ import { fetchDeviceTriggers, sortDeviceAutomations, } from "../../../../data/device_automation"; -import { haStyleDialog } from "../../../../resources/styles"; +import { ScriptConfig, showScriptEditor } from "../../../../data/script"; +import { haStyle, haStyleDialog } from "../../../../resources/styles"; import { HomeAssistant } from "../../../../types"; -import "./ha-device-actions-card"; -import "./ha-device-conditions-card"; -import "./ha-device-triggers-card"; import { DeviceAutomationDialogParams } from "./show-dialog-device-automation"; @customElement("dialog-device-automation") @@ -77,75 +85,184 @@ export class DialogDeviceAutomation extends LitElement { }); } + private _handleRowClick = (ev) => { + if (!shouldHandleRequestSelectedEvent(ev) || !this._params) { + return; + } + const type = (ev.currentTarget as any).type; + const isScript = this._params.script; + + this.closeDialog(); + + if (isScript) { + const newScript = {} as ScriptConfig; + if (type === "action") { + newScript.sequence = [this._actions[0]]; + } + showScriptEditor(newScript, true); + } else { + const newAutomation = {} as AutomationConfig; + if (type === "trigger") { + newAutomation.triggers = [this._triggers[0]]; + } + if (type === "condition") { + newAutomation.conditions = [this._conditions[0]]; + } + if (type === "action") { + newAutomation.actions = [this._actions[0]]; + } + showAutomationEditor(newAutomation, true); + } + }; + protected render() { if (!this._params) { return nothing; } + const mode = this._params.script ? "script" : "automation"; + + const title = this.hass.localize(`ui.panel.config.devices.${mode}.create`, { + type: this.hass.localize( + `ui.panel.config.devices.type.${ + this._params.device.entry_type || "device" + }` + ), + }); + return html` -
+ + ${this._triggers.length + ? html` + + + ${this.hass.localize( + `ui.panel.config.devices.automation.triggers.title` + )} + + ${this.hass.localize( + `ui.panel.config.devices.automation.triggers.description` + )} + + + + ` + : nothing} + ${this._conditions.length + ? html` + + + ${this.hass.localize( + `ui.panel.config.devices.automation.conditions.title` + )} + + ${this.hass.localize( + `ui.panel.config.devices.automation.conditions.description` + )} + + + + ` + : nothing} + ${this._actions.length + ? html` + + + ${this.hass.localize( + `ui.panel.config.devices.${mode}.actions.title` + )} + + ${this.hass.localize( + `ui.panel.config.devices.${mode}.actions.description` + )} + + + + ` + : nothing} ${this._triggers.length || this._conditions.length || this._actions.length - ? html` - ${this._triggers.length - ? html` - - ` - : ""} - ${this._conditions.length - ? html` - - ` - : ""} - ${this._actions.length - ? html` - - ` - : ""} - ` - : this.hass.localize( - "ui.panel.config.devices.automation.no_device_automations" + ? html`
  • ` + : nothing} + + + ${this.hass.localize(`ui.panel.config.devices.${mode}.new.title`)} + + ${this.hass.localize( + `ui.panel.config.devices.${mode}.new.description` )} -
    - - ${this.hass.localize("ui.common.close")} - + + + +
    `; } static get styles(): CSSResultGroup { - return haStyleDialog; + return [ + haStyle, + haStyleDialog, + css` + ha-dialog { + --dialog-content-padding: 0; + --mdc-dialog-max-height: 60vh; + } + @media all and (min-width: 550px) { + ha-dialog { + --mdc-dialog-min-width: 500px; + } + } + ha-icon-next { + width: 24px; + } + `, + ]; } } diff --git a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts b/src/panels/config/devices/device-detail/ha-device-conditions-card.ts deleted file mode 100644 index 3fb7e150d3..0000000000 --- a/src/panels/config/devices/device-detail/ha-device-conditions-card.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { customElement } from "lit/decorators"; -import { - DeviceCondition, - localizeDeviceAutomationCondition, -} from "../../../../data/device_automation"; -import { HaDeviceAutomationCard } from "./ha-device-automation-card"; - -@customElement("ha-device-conditions-card") -export class HaDeviceConditionsCard extends HaDeviceAutomationCard { - readonly type = "condition"; - - readonly headerKey = "ui.panel.config.devices.automation.conditions.caption"; - - constructor() { - super(localizeDeviceAutomationCondition); - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-device-conditions-card": HaDeviceConditionsCard; - } -} diff --git a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts b/src/panels/config/devices/device-detail/ha-device-triggers-card.ts deleted file mode 100644 index c65acc738a..0000000000 --- a/src/panels/config/devices/device-detail/ha-device-triggers-card.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { customElement } from "lit/decorators"; -import { - DeviceTrigger, - localizeDeviceAutomationTrigger, -} from "../../../../data/device_automation"; -import { HaDeviceAutomationCard } from "./ha-device-automation-card"; - -@customElement("ha-device-triggers-card") -export class HaDeviceTriggersCard extends HaDeviceAutomationCard { - readonly type = "trigger"; - - readonly headerKey = "ui.panel.config.devices.automation.triggers.caption"; - - constructor() { - super(localizeDeviceAutomationTrigger); - } -} - -declare global { - interface HTMLElementTagNameMap { - "ha-device-triggers-card": HaDeviceTriggersCard; - } -} diff --git a/src/panels/config/script/manual-script-editor.ts b/src/panels/config/script/manual-script-editor.ts index 63f0b555e4..240dc31437 100644 --- a/src/panels/config/script/manual-script-editor.ts +++ b/src/panels/config/script/manual-script-editor.ts @@ -1,8 +1,20 @@ import "@material/mwc-button/mwc-button"; import { mdiHelpCircle } from "@mdi/js"; -import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; import { customElement, property, query } from "lit/decorators"; import { fireEvent } from "../../../common/dom/fire_event"; +import { constructUrlCurrentPath } from "../../../common/url/construct-url"; +import { + extractSearchParam, + removeSearchParam, +} from "../../../common/url/search-params"; import { nestedArrayMove } from "../../../common/util/array-move"; import "../../../components/ha-card"; import "../../../components/ha-icon-button"; @@ -12,6 +24,7 @@ import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; import "../automation/action/ha-automation-action"; +import type HaAutomationAction from "../automation/action/ha-automation-action"; import "./ha-script-fields"; import type HaScriptFields from "./ha-script-fields"; @@ -58,6 +71,31 @@ export class HaManualScriptEditor extends LitElement { } } + protected firstUpdated(changedProps: PropertyValues): void { + super.firstUpdated(changedProps); + const expanded = extractSearchParam("expanded"); + if (expanded === "1") { + this._clearParam("expanded"); + const items = this.shadowRoot!.querySelectorAll( + "ha-automation-action" + ); + + items.forEach((el) => { + el.updateComplete.then(() => { + el.expandAll(); + }); + }); + } + } + + private _clearParam(param: string) { + window.history.replaceState( + null, + "", + constructUrlCurrentPath(removeSearchParam(param)) + ); + } + protected render() { return html` ${this.config.description diff --git a/src/translations/en.json b/src/translations/en.json index 92d5b38048..ecb40e647a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4039,18 +4039,25 @@ "unknown_automation": "Unknown automation", "create": "Create automation with {type}", "create_disable": "Can't create automation with disabled {type}", + "new": { + "title": "Create new automation", + "description": "Start with an empty automation from scratch" + }, "triggers": { - "caption": "Do something when…", + "title": "Use device as trigger", + "description": "When something happens to the device", "no_triggers": "No triggers", "unknown_trigger": "Unknown trigger" }, "conditions": { - "caption": "Only do something if…", + "title": "Use device as condition", + "description": "Only if a condition is met for the device", "no_conditions": "No conditions", "unknown_condition": "Unknown condition" }, "actions": { - "caption": "When something is triggered…", + "title": "Use device as action", + "description": "Do something on the device", "no_actions": "No actions", "unknown_action": "Unknown action" }, @@ -4061,7 +4068,15 @@ "scripts": "scripts", "no_scripts": "No scripts", "create": "Create script with {type}", - "create_disable": "Can't create script with disabled {type}" + "create_disable": "Can't create script with disabled {type}", + "new": { + "title": "Create new script", + "description": "Start with an empty script from scratch" + }, + "actions": { + "title": "Use device as action", + "description": "Do something on this device." + } }, "scene": { "scenes_heading": "Scenes", From adbcdc62eb56e6414d1dd17ee0ba07ab4faab18f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 2 Oct 2024 15:41:15 +0200 Subject: [PATCH 3/8] Alert user when auto update is enabled instead of hiding the button (#22187) --- .../more-info/controls/more-info-update.ts | 56 +++++++++++-------- src/translations/en.json | 4 +- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/dialogs/more-info/controls/more-info-update.ts b/src/dialogs/more-info/controls/more-info-update.ts index 1829325fc7..521d924589 100644 --- a/src/dialogs/more-info/controls/more-info-update.ts +++ b/src/dialogs/more-info/controls/more-info-update.ts @@ -18,6 +18,7 @@ import { updateReleaseNotes, } from "../../../data/update"; import type { HomeAssistant } from "../../../types"; +import { showAlertDialog } from "../../generic/show-dialog-box"; @customElement("more-info-update") class MoreInfoUpdate extends LitElement { @@ -127,29 +128,27 @@ class MoreInfoUpdate extends LitElement { ` : ""}
    - ${this.stateObj.attributes.auto_update - ? "" - : this.stateObj.state === BINARY_STATE_OFF && - this.stateObj.attributes.skipped_version - ? html` - - ${this.hass.localize( - "ui.dialogs.more_info_control.update.clear_skipped" - )} - - ` - : html` - - ${this.hass.localize( - "ui.dialogs.more_info_control.update.skip" - )} - - `} + ${this.stateObj.state === BINARY_STATE_OFF && + this.stateObj.attributes.skipped_version + ? html` + + ${this.hass.localize( + "ui.dialogs.more_info_control.update.clear_skipped" + )} + + ` + : html` + + ${this.hass.localize( + "ui.dialogs.more_info_control.update.skip" + )} + + `} ${supportsFeature(this.stateObj, UpdateEntityFeature.INSTALL) ? html` Date: Wed, 2 Oct 2024 16:13:26 +0200 Subject: [PATCH 4/8] Use heading card in demo dashboard (#22193) --- demo/src/configs/sections/entities.ts | 26 ++++++ demo/src/configs/sections/lovelace.ts | 113 ++++++++++++++++++++++---- 2 files changed, 123 insertions(+), 16 deletions(-) diff --git a/demo/src/configs/sections/entities.ts b/demo/src/configs/sections/entities.ts index 4ce452b787..b1b50f04ee 100644 --- a/demo/src/configs/sections/entities.ts +++ b/demo/src/configs/sections/entities.ts @@ -111,6 +111,16 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) => friendly_name: "Living room Temperature", }, }, + "sensor.living_room_humidity": { + entity_id: "sensor.living_room_humidity", + state: "57", + attributes: { + state_class: "measurement", + unit_of_measurement: "%", + device_class: "humidity", + friendly_name: "Living room Humidity", + }, + }, "sensor.outdoor_temperature": { entity_id: "sensor.outdoor_temperature", state: "10.5", @@ -189,6 +199,14 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) => supported_features: 32, }, }, + "binary_sensor.kitchen_motion": { + entity_id: "light.kitchen_motion", + state: "on", + attributes: { + device_class: "motion", + friendly_name: "Kitchen motion", + }, + }, "light.worktop_spotlights": { entity_id: "light.worktop_spotlights", state: "off", @@ -423,6 +441,14 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) => supported_features: 64063, }, }, + "switch.in_meeting": { + entity_id: "switch.in_meeting", + state: "on", + attributes: { + icon: "mdi:laptop-account", + friendly_name: "In a meeting", + }, + }, "sensor.standing_desk_height": { entity_id: "sensor.standing_desk_height", state: "72", diff --git a/demo/src/configs/sections/lovelace.ts b/demo/src/configs/sections/lovelace.ts index 5fa8597c6e..22c564ed72 100644 --- a/demo/src/configs/sections/lovelace.ts +++ b/demo/src/configs/sections/lovelace.ts @@ -30,12 +30,36 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ ? [] : [ { - title: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`, - cards: [{ type: "custom:ha-demo-card" }], + cards: [ + { + type: "heading", + heading: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`, + }, + { type: "custom:ha-demo-card" }, + ], }, ]), { cards: [ + { + type: "heading", + heading: localize( + "ui.panel.page-demo.config.sections.titles.living_room" + ), + icon: "mdi:sofa", + badges: [ + { + type: "entity", + entity: "sensor.living_room_temperature", + color: "red", + }, + { + type: "entity", + entity: "sensor.living_room_humidity", + color: "indigo", + }, + ], + }, { type: "tile", entity: "light.floor_lamp", @@ -54,13 +78,6 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ type: "tile", entity: "light.bar_lamp", }, - { - graph: "line", - type: "sensor", - entity: "sensor.living_room_temperature", - detail: 1, - name: "Temperature", - }, { type: "tile", entity: "cover.living_room_garden_shutter", @@ -71,11 +88,25 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ entity: "media_player.living_room_nest_mini", }, ], - title: `🛋️ ${localize("ui.panel.page-demo.config.sections.titles.living_room")} `, }, { type: "grid", cards: [ + { + type: "heading", + heading: localize( + "ui.panel.page-demo.config.sections.titles.kitchen" + ), + icon: "mdi:fridge", + badges: [ + { + type: "entity", + entity: "binary_sensor.kitchen_motion", + show_state: false, + color: "blue", + }, + ], + }, { type: "tile", entity: "cover.kitchen_shutter", @@ -106,11 +137,17 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ entity: "media_player.kitchen_nest_audio", }, ], - title: `👩‍🍳 ${localize("ui.panel.page-demo.config.sections.titles.kitchen")}`, }, { type: "grid", cards: [ + { + type: "heading", + heading: localize( + "ui.panel.page-demo.config.sections.titles.energy" + ), + icon: "mdi:transmission-tower", + }, { type: "tile", entity: "binary_sensor.tesla_wall_connector_vehicle_connected", @@ -148,11 +185,17 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ color: "dark-grey", }, ], - title: `⚡️ ${localize("ui.panel.page-demo.config.sections.titles.energy")}`, }, { type: "grid", cards: [ + { + type: "heading", + heading: localize( + "ui.panel.page-demo.config.sections.titles.climate" + ), + icon: "mdi:thermometer", + }, { type: "tile", entity: "sun.sun", @@ -185,16 +228,38 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ state_content: ["preset_mode", "current_temperature"], }, ], - title: `🌤️ ${localize("ui.panel.page-demo.config.sections.titles.climate")}`, }, { type: "grid", cards: [ + { + type: "heading", + heading: localize( + "ui.panel.page-demo.config.sections.titles.study" + ), + icon: "mdi:desk-lamp", + badges: [ + { + type: "entity", + entity: "switch.in_meeting", + state: "on", + state_content: "name", + visibility: [ + { + condition: "state", + state: "on", + entity: "switch.in_meeting", + }, + ], + }, + ], + }, { type: "tile", entity: "cover.study_shutter", name: "Shutter", }, + { type: "tile", entity: "light.study_spotlights", @@ -211,12 +276,23 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ color: "brown", icon: "mdi:desk", }, + { + type: "tile", + entity: "switch.in_meeting", + name: "Meeting mode", + }, ], - title: `🧑‍💻 ${localize("ui.panel.page-demo.config.sections.titles.study")}`, }, { type: "grid", cards: [ + { + type: "heading", + heading: localize( + "ui.panel.page-demo.config.sections.titles.outdoor" + ), + icon: "mdi:tree", + }, { type: "tile", entity: "light.outdoor_light", @@ -246,11 +322,17 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ name: "Illuminance", }, ], - title: `🌳 ${localize("ui.panel.page-demo.config.sections.titles.outdoor")}`, }, { type: "grid", cards: [ + { + type: "heading", + heading: localize( + "ui.panel.page-demo.config.sections.titles.updates" + ), + icon: "mdi:update", + }, { type: "tile", entity: "automation.home_assistant_auto_update", @@ -276,7 +358,6 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ icon: "mdi:home-assistant", }, ], - title: `🎉 ${localize("ui.panel.page-demo.config.sections.titles.updates")}`, }, ], }, From 0c1b8abe039c7e86f8c6e9f8ca610f9cc7acde06 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 2 Oct 2024 16:20:53 +0200 Subject: [PATCH 5/8] Fix hassio entrypoint (#22194) --- hassio/src/entrypoint.js.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hassio/src/entrypoint.js.template b/hassio/src/entrypoint.js.template index 8cc7ba82fd..db0cf25251 100644 --- a/hassio/src/entrypoint.js.template +++ b/hassio/src/entrypoint.js.template @@ -13,10 +13,11 @@ <% for (const entry of es5EntryJS) { %> loadES5("<%= entry %>"); <% } %> + } } else { <% for (const entry of es5EntryJS) { %> loadES5("<%= entry %>"); <% } %> } - } })(); + \ No newline at end of file From a30e0d33f9ff3bc54625f8972dbdfadaa5845b33 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Wed, 2 Oct 2024 17:34:28 +0300 Subject: [PATCH 6/8] Handle exceptions when subscribing from the event dev tool (#22191) * Handle exceptions when subscribing from the event dev tool * use ha-alert for the error msg * import ha-alert element * use undefined instead of null to align with the rest of the code base --- .../event/event-subscribe-card.ts | 47 +++++++++++++------ src/translations/en.json | 3 +- 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/panels/developer-tools/event/event-subscribe-card.ts b/src/panels/developer-tools/event/event-subscribe-card.ts index 564af64c4b..5c613a13c7 100644 --- a/src/panels/developer-tools/event/event-subscribe-card.ts +++ b/src/panels/developer-tools/event/event-subscribe-card.ts @@ -7,6 +7,7 @@ import "../../../components/ha-card"; import "../../../components/ha-textfield"; import "../../../components/ha-yaml-editor"; import "../../../components/ha-button"; +import "../../../components/ha-alert"; import { HomeAssistant } from "../../../types"; @customElement("event-subscribe-card") @@ -22,6 +23,8 @@ class EventSubscribeCard extends LitElement { event: HassEvent; }> = []; + @state() private _error?: string; + private _eventCount = 0; public disconnectedCallback() { @@ -52,6 +55,9 @@ class EventSubscribeCard extends LitElement { .value=${this._eventType} @input=${this._valueChanged} > + ${this._error + ? html`${this._error}` + : ""}
    { if (this._subscribed) { this._subscribed(); this._subscribed = undefined; + this._error = undefined; } else { - this._subscribed = await this.hass!.connection.subscribeEvents( - (event) => { - const tail = - this._events.length > 30 ? this._events.slice(0, 29) : this._events; - this._events = [ - { - event, - id: this._eventCount++, - }, - ...tail, - ]; - }, - this._eventType - ); + try { + this._subscribed = + await this.hass!.connection.subscribeEvents((event) => { + const tail = + this._events.length > 30 + ? this._events.slice(0, 29) + : this._events; + this._events = [ + { + event, + id: this._eventCount++, + }, + ...tail, + ]; + }, this._eventType); + } catch (error: any) { + this._error = this.hass!.localize( + "ui.panel.developer-tools.tabs.events.subscribe_failed", + { error: error.message || "Unknown error" } + ); + } } } private _clearEvents(): void { this._events = []; this._eventCount = 0; + this._error = undefined; } static get styles(): CSSResultGroup { @@ -145,6 +161,9 @@ class EventSubscribeCard extends LitElement { display: block; margin-bottom: 16px; } + .error-message { + margin-top: 8px; + } .event { border-top: 1px solid var(--divider-color); padding-top: 8px; diff --git a/src/translations/en.json b/src/translations/en.json index 125b34c89b..029a4cffc8 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -6899,7 +6899,8 @@ "stop_listening": "Stop listening", "clear_events": "Clear events", "alert_event_type": "Event type is a mandatory field", - "notification_event_fired": "Event {type} successfully fired!" + "notification_event_fired": "Event {type} successfully fired!", + "subscribe_failed": "Failed to subscribe to event: {error}" }, "actions": { "title": "Actions", From 487795b7c4e0623d7d81f24afe8c12e166f6edb3 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 2 Oct 2024 16:42:27 +0200 Subject: [PATCH 7/8] handle unknown state for update voice assitant (#22196) * handle unknown state for update voice assitant * Update voice-assistant-setup-step-update.ts --- .../voice-assistant-setup-step-update.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-update.ts b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-update.ts index 0f7b14e403..a103fb6a9a 100644 --- a/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-update.ts +++ b/src/dialogs/voice-assistant-setup/voice-assistant-setup-step-update.ts @@ -2,7 +2,7 @@ import { css, html, LitElement, nothing, PropertyValues } from "lit"; import { customElement, property } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; import "../../components/ha-circular-progress"; -import { OFF, ON, UNAVAILABLE } from "../../data/entity"; +import { OFF, ON, UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { HomeAssistant } from "../../types"; import { AssistantSetupStyles } from "./styles"; @@ -32,10 +32,11 @@ export class HaVoiceAssistantSetupStepUpdate extends LitElement { if ( (oldState?.state === UNAVAILABLE && newState?.state !== UNAVAILABLE) || - (oldState?.state === OFF && newState?.state === ON) + (oldState?.state !== ON && newState?.state === ON) ) { // Device is rebooted, let's move on this._tryUpdate(false); + return; } } } @@ -58,7 +59,7 @@ export class HaVoiceAssistantSetupStepUpdate extends LitElement { return html`

    - ${stateObj.state === OFF + ${stateObj.state === OFF || stateObj.state === UNKNOWN ? "Checking for updates" : "Updating your voice assistant"}

    @@ -88,10 +89,7 @@ export class HaVoiceAssistantSetupStepUpdate extends LitElement { return; } const updateEntity = this.hass.states[this.updateEntityId]; - if ( - updateEntity && - this.hass.states[updateEntity.entity_id].state === "on" - ) { + if (updateEntity && this.hass.states[updateEntity.entity_id].state === ON) { this._updated = true; await this.hass.callService( "update", From 67217b9dd0f5ad43aafb3bba2fe2821407b25243 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 2 Oct 2024 16:42:46 +0200 Subject: [PATCH 8/8] Bumped version to 20241002.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8e3b81412d..f536a80661 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20241002.1" +version = "20241002.2" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md"