1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-18 07:56:44 +01:00

Support more-info-view query param (#30282)

* Support more-info-view query param

* Remove unintended subview logic and add details view

* Reset childView
This commit is contained in:
Aidan Timson
2026-03-24 09:26:08 +00:00
committed by GitHub
parent a8070b322c
commit 5bbfa36228
3 changed files with 108 additions and 73 deletions

View File

@@ -7,6 +7,22 @@ import { CONTINUOUS_DOMAINS } from "../../data/logbook";
import type { HomeAssistant } from "../../types"; import type { HomeAssistant } from "../../types";
import { isNumericEntity } from "../../data/history"; 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"]; export const DOMAINS_NO_INFO = ["camera", "configurator"];
/** /**
* Entity domains that should be editable *if* they have an id present; * Entity domains that should be editable *if* they have an id present;

View File

@@ -22,7 +22,7 @@ import { customElement, property, query, state } from "lit/decorators";
import { cache } from "lit/directives/cache"; import { cache } from "lit/directives/cache";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { keyed } from "lit/directives/keyed"; 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 { fireEvent } from "../../common/dom/fire_event";
import { stopPropagation } from "../../common/dom/stop_propagation"; import { stopPropagation } from "../../common/dom/stop_propagation";
import { computeAreaName } from "../../common/entity/compute_area_name"; import { computeAreaName } from "../../common/entity/compute_area_name";
@@ -72,6 +72,7 @@ import {
DOMAINS_WITH_MORE_INFO, DOMAINS_WITH_MORE_INFO,
EDITABLE_DOMAINS_WITH_ID, EDITABLE_DOMAINS_WITH_ID,
EDITABLE_DOMAINS_WITH_UNIQUE_ID, EDITABLE_DOMAINS_WITH_UNIQUE_ID,
type MoreInfoView,
computeShowHistoryComponent, computeShowHistoryComponent,
computeShowLogBookComponent, computeShowLogBookComponent,
} from "./const"; } from "./const";
@@ -79,6 +80,7 @@ import "./controls/more-info-default";
import type { FavoritesDialogContext } from "./favorites"; import type { FavoritesDialogContext } from "./favorites";
import { getFavoritesDialogHandler } from "./favorites"; import { getFavoritesDialogHandler } from "./favorites";
import "./ha-more-info-add-to"; import "./ha-more-info-add-to";
import "./ha-more-info-details";
import "./ha-more-info-history-and-logbook"; import "./ha-more-info-history-and-logbook";
import "./ha-more-info-info"; import "./ha-more-info-info";
import "./ha-more-info-settings"; import "./ha-more-info-settings";
@@ -86,16 +88,14 @@ import "./more-info-content";
export interface MoreInfoDialogParams { export interface MoreInfoDialogParams {
entityId: string | null; entityId: string | null;
view?: View; view?: MoreInfoView;
/** @deprecated Use `view` instead */ /** @deprecated Use `view` instead */
tab?: View; tab?: MoreInfoView;
large?: boolean; large?: boolean;
data?: Record<string, any>; data?: Record<string, any>;
parentElement?: LitElement; parentElement?: LitElement;
} }
type View = "info" | "history" | "settings" | "related" | "add_to";
interface ChildView { interface ChildView {
viewTag: string; viewTag: string;
viewTitle?: string; viewTitle?: string;
@@ -112,7 +112,7 @@ declare global {
} }
} }
const DEFAULT_VIEW: View = "info"; const DEFAULT_VIEW: MoreInfoView = "info";
@customElement("ha-more-info-dialog") @customElement("ha-more-info-dialog")
export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) { export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
@@ -134,9 +134,9 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
@state() private _data?: Record<string, any>; @state() private _data?: Record<string, any>;
@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; @state() private _childView?: ChildView;
@@ -163,10 +163,15 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
return; return;
} }
const view = params.view || params.tab || DEFAULT_VIEW;
this._data = params.data; this._data = params.data;
this._currView = params.view || DEFAULT_VIEW; this._currView = view;
this._initialView = params.view || DEFAULT_VIEW; this._initialView = view;
this._childView = undefined; this._childView = undefined;
this._infoEditMode = false;
this._detailsYamlMode = false;
this.large = params.large ?? false; this.large = params.large ?? false;
this._fill = false; this._fill = false;
this._open = true; this._open = true;
@@ -253,7 +258,7 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
return entity?.device_id ?? null; return entity?.device_id ?? null;
} }
private _setView(view: View) { private _setView(view: MoreInfoView) {
history.replaceState( history.replaceState(
{ {
...history.state, ...history.state,
@@ -278,6 +283,13 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
this._detailsYamlMode = false; this._detailsYamlMode = false;
return; return;
} }
if (
this._initialView !== DEFAULT_VIEW &&
this._currView === this._initialView
) {
this._resetInitialView();
return;
}
if (this._initialView !== this._currView) { if (this._initialView !== this._currView) {
this._setView(this._initialView); this._setView(this._initialView);
return; return;
@@ -404,7 +416,9 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
this._resetInitialView(); this._resetInitialView();
break; break;
case "details": case "details":
this._showDetails(); this._setView("details");
break;
default:
break; break;
} }
} }
@@ -449,15 +463,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
this._entry = result.entity_entry; 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() { private async _copyFavorites() {
const favoritesContext = this._getFavoritesContext(); const favoritesContext = this._getFavoritesContext();
@@ -509,13 +514,8 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
(deviceId && this.hass.devices[deviceId].entry_type) || "device"; (deviceId && this.hass.devices[deviceId].entry_type) || "device";
const isDefaultView = this._currView === DEFAULT_VIEW && !this._childView; const isDefaultView = this._currView === DEFAULT_VIEW && !this._childView;
const isSpecificInitialView =
this._initialView !== DEFAULT_VIEW && !this._childView;
const showCloseIcon = const showCloseIcon =
(isDefaultView && isDefaultView && this._parentEntityIds.length === 0 && !this._childView;
this._parentEntityIds.length === 0 &&
!this._childView) ||
(isSpecificInitialView && !this._childView);
const context = stateObj const context = stateObj
? getEntityContext( ? getEntityContext(
@@ -549,7 +549,11 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
const breadcrumb = [areaName, deviceName, entityName].filter( const breadcrumb = [areaName, deviceName, entityName].filter(
(v): v is string => Boolean(v) (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 = const favoritesContext =
this._entry && stateObj this._entry && stateObj
@@ -774,26 +778,16 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
` `
: nothing} : nothing}
` `
: isSpecificInitialView : this._currView === "details"
? html` ? html`
<ha-dropdown <ha-icon-button
slot="headerActionItems" slot="headerActionItems"
@closed=${stopPropagation} .label=${this.hass.localize(
@wa-select=${this._handleMenuAction} "ui.dialogs.more_info_control.toggle_yaml_mode"
placement="bottom-end" )}
> .path=${mdiCodeBraces}
<ha-icon-button @click=${this._toggleDetailsYamlMode}
slot="trigger" ></ha-icon-button>
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-dropdown-item value="info">
<ha-svg-icon slot="icon" .path=${mdiInformationOutline}>
</ha-svg-icon>
${this.hass.localize("ui.dialogs.more_info_control.info")}
</ha-dropdown-item>
</ha-dropdown>
` `
: this._childView?.viewTag === "ha-more-info-details" : this._childView?.viewTag === "ha-more-info-details"
? html` ? html`
@@ -828,12 +822,24 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
this._childView this._childView
? html` ? html`
<div class="child-view"> <div class="child-view">
${dynamicElement(this._childView.viewTag, { ${this._childView.viewTag ===
hass: this.hass, "ha-more-info-view-voice-assistants"
entry: this._entry, ? html`
params: this._childView.viewParams, <ha-more-info-view-voice-assistants
yamlMode: this._detailsYamlMode, .hass=${this.hass}
})} .entry=${this._entry!}
.params=${this._childView.viewParams}
></ha-more-info-view-voice-assistants>
`
: this._childView.viewTag ===
"ha-more-info-view-vacuum-segment-mapping"
? html`
<ha-more-info-view-vacuum-segment-mapping
.hass=${this.hass}
.params=${this._childView.viewParams}
></ha-more-info-view-vacuum-segment-mapping>
`
: nothing}
</div> </div>
` `
: this._currView === "info" : this._currView === "info"
@@ -879,7 +885,16 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
@add-to-action-selected=${this._goBack} @add-to-action-selected=${this._goBack}
></ha-more-info-add-to> ></ha-more-info-add-to>
` `
: nothing : this._currView === "details"
? html`
<ha-more-info-details
.hass=${this.hass}
.entry=${this._entry}
.params=${{ entityId }}
.yamlMode=${this._detailsYamlMode}
></ha-more-info-details>
`
: nothing
)} )}
</div> </div>
` `
@@ -898,14 +913,11 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
super.updated(changedProps); super.updated(changedProps);
const previousChildView = changedProps.get("_childView") as const previousView = changedProps.get("_currView") as
| ChildView | MoreInfoView
| undefined; | undefined;
if ( if (previousView === "details" && this._currView !== "details") {
previousChildView?.viewTag === "ha-more-info-details" &&
this._childView?.viewTag !== "ha-more-info-details"
) {
const dialog = const dialog =
this._dialogElement?.shadowRoot?.querySelector("ha-dialog"); this._dialogElement?.shadowRoot?.querySelector("ha-dialog");
if (dialog) { if (dialog) {
@@ -914,7 +926,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
} }
if (changedProps.has("_currView")) { if (changedProps.has("_currView")) {
this._childView = undefined;
this._infoEditMode = false; this._infoEditMode = false;
this._detailsYamlMode = false; this._detailsYamlMode = false;
} }
@@ -935,15 +946,25 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
window.addEventListener("show-dialog", this._disableEscapeKeyClose); window.addEventListener("show-dialog", this._disableEscapeKeyClose);
} }
private _handleMoreInfoEvent(ev) { private _handleMoreInfoEvent(ev: HASSDomEvent<MoreInfoDialogParams>) {
ev.stopPropagation(); ev.stopPropagation();
const entityId = ev.detail.entityId; const entityId = ev.detail.entityId;
if (!entityId) { if (!entityId) {
return; 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._parentEntityIds = [...this._parentEntityIds, this._entityId!];
this._entityId = 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._childView = undefined;
this._loadEntityRegistryEntry(); 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 { ha-more-info-history-and-logbook {
padding: var(--ha-space-2) var(--ha-space-6) var(--ha-space-6) padding: var(--ha-space-2) var(--ha-space-6) var(--ha-space-6)
var(--ha-space-6); var(--ha-space-6);

View File

@@ -70,6 +70,7 @@ import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
} from "../../dialogs/generic/show-dialog-box"; } 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 { showMoreInfoDialog } from "../../dialogs/more-info/show-ha-more-info-dialog";
import { showQuickBar } from "../../dialogs/quick-bar/show-dialog-quick-bar"; import { showQuickBar } from "../../dialogs/quick-bar/show-dialog-quick-bar";
import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog"; import { showVoiceCommandDialog } from "../../dialogs/voice-command-dialog/show-ha-voice-command-dialog";
@@ -716,11 +717,18 @@ class HUIRoot extends LitElement {
this._showVoiceCommandDialog(); this._showVoiceCommandDialog();
} else if (searchParams["more-info-entity-id"]) { } else if (searchParams["more-info-entity-id"]) {
const entityId = searchParams["more-info-entity-id"]; const entityId = searchParams["more-info-entity-id"];
const view = searchParams["more-info-view"];
this._clearParam("more-info-entity-id"); 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 // Wait for the next render to ensure the view is fully loaded
// because the more info dialog is closed when the url changes // because the more info dialog is closed when the url changes
afterNextRender(() => { 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" }); showVoiceCommandDialog(this, this.hass, { pipeline_id: "last_used" });
}; };
private _showMoreInfoDialog(entityId: string): void {
showMoreInfoDialog(this, { entityId });
}
private _enableEditMode = async () => { private _enableEditMode = async () => {
if (this._yamlMode) { if (this._yamlMode) {
showAlertDialog(this, { showAlertDialog(this, {