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")}`, }, ], }, 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 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" 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/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/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`

- ${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", 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/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/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/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/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 32c55ddef7..029a4cffc8 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1191,7 +1191,9 @@ "skip": "Skip", "clear_skipped": "Clear skipped", "install": "Install", - "create_backup": "Create backup before updating" + "create_backup": "Create backup before updating", + "auto_update_enabled_title": "Can not skip version", + "auto_update_enabled_text": "Automatic updates for this item have been enabled; skipping it is, therefore, unavailable. You can either install this update now or wait for Home Assistant to do it automatically." }, "updater": { "title": "Update instructions" @@ -4039,18 +4041,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 +4070,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", @@ -4590,6 +4607,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", @@ -6881,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",