From 5bbfa362287c4602b08a18e35bcf9462c92f65ed Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Tue, 24 Mar 2026 09:26:08 +0000 Subject: [PATCH] Support more-info-view query param (#30282) * Support more-info-view query param * Remove unintended subview logic and add details view * Reset childView --- src/dialogs/more-info/const.ts | 16 ++ src/dialogs/more-info/ha-more-info-dialog.ts | 151 ++++++++++--------- src/panels/lovelace/hui-root.ts | 14 +- 3 files changed, 108 insertions(+), 73 deletions(-) diff --git a/src/dialogs/more-info/const.ts b/src/dialogs/more-info/const.ts index 74b9eb7a49..8c6a6ea094 100644 --- a/src/dialogs/more-info/const.ts +++ b/src/dialogs/more-info/const.ts @@ -7,6 +7,22 @@ import { CONTINUOUS_DOMAINS } from "../../data/logbook"; import type { HomeAssistant } from "../../types"; import { isNumericEntity } from "../../data/history"; +export const MORE_INFO_VIEWS = [ + "info", + "history", + "settings", + "related", + "add_to", + "details", +] as const; + +export type MoreInfoView = (typeof MORE_INFO_VIEWS)[number]; + +export const isMoreInfoView = ( + value: string | undefined +): value is MoreInfoView => + value !== undefined && (MORE_INFO_VIEWS as readonly string[]).includes(value); + export const DOMAINS_NO_INFO = ["camera", "configurator"]; /** * Entity domains that should be editable *if* they have an id present; diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 3e379c1987..d83056f09b 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -22,7 +22,7 @@ import { customElement, property, query, state } from "lit/decorators"; import { cache } from "lit/directives/cache"; import { classMap } from "lit/directives/class-map"; import { keyed } from "lit/directives/keyed"; -import { dynamicElement } from "../../common/dom/dynamic-element-directive"; +import type { HASSDomEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event"; import { stopPropagation } from "../../common/dom/stop_propagation"; import { computeAreaName } from "../../common/entity/compute_area_name"; @@ -72,6 +72,7 @@ import { DOMAINS_WITH_MORE_INFO, EDITABLE_DOMAINS_WITH_ID, EDITABLE_DOMAINS_WITH_UNIQUE_ID, + type MoreInfoView, computeShowHistoryComponent, computeShowLogBookComponent, } from "./const"; @@ -79,6 +80,7 @@ import "./controls/more-info-default"; import type { FavoritesDialogContext } from "./favorites"; import { getFavoritesDialogHandler } from "./favorites"; import "./ha-more-info-add-to"; +import "./ha-more-info-details"; import "./ha-more-info-history-and-logbook"; import "./ha-more-info-info"; import "./ha-more-info-settings"; @@ -86,16 +88,14 @@ import "./more-info-content"; export interface MoreInfoDialogParams { entityId: string | null; - view?: View; + view?: MoreInfoView; /** @deprecated Use `view` instead */ - tab?: View; + tab?: MoreInfoView; large?: boolean; data?: Record; parentElement?: LitElement; } -type View = "info" | "history" | "settings" | "related" | "add_to"; - interface ChildView { viewTag: string; viewTitle?: string; @@ -112,7 +112,7 @@ declare global { } } -const DEFAULT_VIEW: View = "info"; +const DEFAULT_VIEW: MoreInfoView = "info"; @customElement("ha-more-info-dialog") export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { @@ -134,9 +134,9 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { @state() private _data?: Record; - @state() private _currView: View = DEFAULT_VIEW; + @state() private _currView: MoreInfoView = DEFAULT_VIEW; - @state() private _initialView: View = DEFAULT_VIEW; + @state() private _initialView: MoreInfoView = DEFAULT_VIEW; @state() private _childView?: ChildView; @@ -163,10 +163,15 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { return; } + const view = params.view || params.tab || DEFAULT_VIEW; + this._data = params.data; - this._currView = params.view || DEFAULT_VIEW; - this._initialView = params.view || DEFAULT_VIEW; + this._currView = view; + this._initialView = view; this._childView = undefined; + this._infoEditMode = false; + this._detailsYamlMode = false; + this.large = params.large ?? false; this._fill = false; this._open = true; @@ -253,7 +258,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { return entity?.device_id ?? null; } - private _setView(view: View) { + private _setView(view: MoreInfoView) { history.replaceState( { ...history.state, @@ -278,6 +283,13 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { this._detailsYamlMode = false; return; } + if ( + this._initialView !== DEFAULT_VIEW && + this._currView === this._initialView + ) { + this._resetInitialView(); + return; + } if (this._initialView !== this._currView) { this._setView(this._initialView); return; @@ -404,7 +416,9 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { this._resetInitialView(); break; case "details": - this._showDetails(); + this._setView("details"); + break; + default: break; } } @@ -449,15 +463,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { this._entry = result.entity_entry; } - private _showDetails(): void { - import("./ha-more-info-details"); - this._childView = { - viewTag: "ha-more-info-details", - viewTitle: this.hass.localize("ui.dialogs.more_info_control.details"), - viewParams: { entityId: this._entityId }, - }; - } - private async _copyFavorites() { const favoritesContext = this._getFavoritesContext(); @@ -509,13 +514,8 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { (deviceId && this.hass.devices[deviceId].entry_type) || "device"; const isDefaultView = this._currView === DEFAULT_VIEW && !this._childView; - const isSpecificInitialView = - this._initialView !== DEFAULT_VIEW && !this._childView; const showCloseIcon = - (isDefaultView && - this._parentEntityIds.length === 0 && - !this._childView) || - (isSpecificInitialView && !this._childView); + isDefaultView && this._parentEntityIds.length === 0 && !this._childView; const context = stateObj ? getEntityContext( @@ -549,7 +549,11 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { const breadcrumb = [areaName, deviceName, entityName].filter( (v): v is string => Boolean(v) ); - const title = this._childView?.viewTitle || breadcrumb.pop() || entityId; + const defaultTitle = breadcrumb.pop() || entityId; + const title = + this._currView === "details" + ? this.hass.localize("ui.dialogs.more_info_control.details") + : this._childView?.viewTitle || defaultTitle; const favoritesContext = this._entry && stateObj @@ -774,26 +778,16 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { ` : nothing} ` - : isSpecificInitialView + : this._currView === "details" ? html` - - - - - - - ${this.hass.localize("ui.dialogs.more_info_control.info")} - - + .label=${this.hass.localize( + "ui.dialogs.more_info_control.toggle_yaml_mode" + )} + .path=${mdiCodeBraces} + @click=${this._toggleDetailsYamlMode} + > ` : this._childView?.viewTag === "ha-more-info-details" ? html` @@ -828,12 +822,24 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { this._childView ? html`
- ${dynamicElement(this._childView.viewTag, { - hass: this.hass, - entry: this._entry, - params: this._childView.viewParams, - yamlMode: this._detailsYamlMode, - })} + ${this._childView.viewTag === + "ha-more-info-view-voice-assistants" + ? html` + + ` + : this._childView.viewTag === + "ha-more-info-view-vacuum-segment-mapping" + ? html` + + ` + : nothing}
` : this._currView === "info" @@ -879,7 +885,16 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { @add-to-action-selected=${this._goBack} > ` - : nothing + : this._currView === "details" + ? html` + + ` + : nothing )} ` @@ -898,14 +913,11 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { protected updated(changedProps: PropertyValues) { super.updated(changedProps); - const previousChildView = changedProps.get("_childView") as - | ChildView + const previousView = changedProps.get("_currView") as + | MoreInfoView | undefined; - if ( - previousChildView?.viewTag === "ha-more-info-details" && - this._childView?.viewTag !== "ha-more-info-details" - ) { + if (previousView === "details" && this._currView !== "details") { const dialog = this._dialogElement?.shadowRoot?.querySelector("ha-dialog"); if (dialog) { @@ -914,7 +926,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { } if (changedProps.has("_currView")) { - this._childView = undefined; this._infoEditMode = false; this._detailsYamlMode = false; } @@ -935,15 +946,25 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { window.addEventListener("show-dialog", this._disableEscapeKeyClose); } - private _handleMoreInfoEvent(ev) { + private _handleMoreInfoEvent(ev: HASSDomEvent) { ev.stopPropagation(); const entityId = ev.detail.entityId; if (!entityId) { return; } + const view = ev.detail.view || ev.detail.tab || DEFAULT_VIEW; + if (entityId === this._entityId) { + this._infoEditMode = false; + this._detailsYamlMode = false; + this._setView(view); + return; + } this._parentEntityIds = [...this._parentEntityIds, this._entityId!]; this._entityId = entityId; - this._currView = DEFAULT_VIEW; + this._currView = view === "details" ? view : DEFAULT_VIEW; + this._initialView = view; + this._infoEditMode = false; + this._detailsYamlMode = false; this._childView = undefined; this._loadEntityRegistryEntry(); } @@ -998,12 +1019,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { ); } - .child-view { - display: flex; - flex-direction: column; - flex: 1; - } - ha-more-info-history-and-logbook { padding: var(--ha-space-2) var(--ha-space-6) var(--ha-space-6) var(--ha-space-6); diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts index 2f84ee0056..bddd5888ce 100644 --- a/src/panels/lovelace/hui-root.ts +++ b/src/panels/lovelace/hui-root.ts @@ -70,6 +70,7 @@ import { showAlertDialog, showConfirmationDialog, } from "../../dialogs/generic/show-dialog-box"; +import { isMoreInfoView } from "../../dialogs/more-info/const"; import { showMoreInfoDialog } from "../../dialogs/more-info/show-ha-more-info-dialog"; import { showQuickBar } from "../../dialogs/quick-bar/show-dialog-quick-bar"; import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog"; @@ -716,11 +717,18 @@ class HUIRoot extends LitElement { this._showVoiceCommandDialog(); } else if (searchParams["more-info-entity-id"]) { const entityId = searchParams["more-info-entity-id"]; + const view = searchParams["more-info-view"]; this._clearParam("more-info-entity-id"); + if (view) { + this._clearParam("more-info-view"); + } // Wait for the next render to ensure the view is fully loaded // because the more info dialog is closed when the url changes afterNextRender(() => { - this._showMoreInfoDialog(entityId); + showMoreInfoDialog(this, { + entityId, + view: isMoreInfoView(view) ? view : undefined, + }); }); } } @@ -975,10 +983,6 @@ class HUIRoot extends LitElement { showVoiceCommandDialog(this, this.hass, { pipeline_id: "last_used" }); }; - private _showMoreInfoDialog(entityId: string): void { - showMoreInfoDialog(this, { entityId }); - } - private _enableEditMode = async () => { if (this._yamlMode) { showAlertDialog(this, {