Combine more info and entity registry editor (#13416)

This commit is contained in:
Paulus Schoutsen 2022-08-19 22:27:07 -04:00 committed by GitHub
parent 38fd6108b4
commit b33c546610
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 495 additions and 620 deletions

View File

@ -166,46 +166,6 @@ export const DOMAINS_WITH_CARD = [
"water_heater", "water_heater",
]; ];
/** Domains with separate more info dialog. */
export const DOMAINS_WITH_MORE_INFO = [
"alarm_control_panel",
"automation",
"camera",
"climate",
"configurator",
"counter",
"cover",
"fan",
"group",
"humidifier",
"input_datetime",
"light",
"lock",
"media_player",
"person",
"remote",
"script",
"scene",
"sun",
"timer",
"update",
"vacuum",
"water_heater",
"weather",
];
/** Domains that do not show the default more info dialog content (e.g. the attribute section)
* and do not have a separate more info (so not in DOMAINS_WITH_MORE_INFO). */
export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
"input_number",
"input_select",
"input_text",
"number",
"scene",
"update",
"select",
];
/** Domains that render an input element instead of a text value when displayed in a row. /** Domains that render an input element instead of a text value when displayed in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally * Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements * be the default) unless the element itself enforces it (e.g. a button). Also those elements
@ -237,9 +197,6 @@ export const DOMAINS_INPUT_ROW = [
"vacuum", "vacuum",
]; ];
/** Domains that should have the history hidden in the more info dialog. */
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator"];
/** States that we consider "off". */ /** States that we consider "off". */
export const STATES_OFF = ["closed", "locked", "off"]; export const STATES_OFF = ["closed", "locked", "off"];

View File

@ -326,6 +326,9 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
line-height: var(--paper-font-title_-_line-height); line-height: var(--paper-font-title_-_line-height);
opacity: var(--dark-primary-opacity); opacity: var(--dark-primary-opacity);
} }
h3:first-child {
margin-top: 0;
}
`; `;
} }
} }

View File

@ -0,0 +1,89 @@
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { computeDomain } from "../../common/entity/compute_domain";
import { CONTINUOUS_DOMAINS } from "../../data/logbook";
import { HomeAssistant } from "../../types";
export const DOMAINS_NO_INFO = ["camera", "configurator"];
/**
* Entity domains that should be editable *if* they have an id present;
* {@see shouldShowEditIcon}.
* */
export const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
/**
* Entity Domains that should always be editable; {@see shouldShowEditIcon}.
* */
export const EDITABLE_DOMAINS = ["script"];
/** Domains with separate more info dialog. */
export const DOMAINS_WITH_MORE_INFO = [
"alarm_control_panel",
"automation",
"camera",
"climate",
"configurator",
"counter",
"cover",
"fan",
"group",
"humidifier",
"input_datetime",
"light",
"lock",
"media_player",
"person",
"remote",
"script",
"scene",
"sun",
"timer",
"update",
"vacuum",
"water_heater",
"weather",
];
/** Domains that do not show the default more info dialog content (e.g. the attribute section)
* and do not have a separate more info (so not in DOMAINS_WITH_MORE_INFO). */
export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
"input_number",
"input_select",
"input_text",
"number",
"scene",
"update",
"select",
];
/** Domains that should have the history hidden in the more info dialog. */
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator"];
export const computeShowHistoryComponent = (
hass: HomeAssistant,
entityId: string
) =>
isComponentLoaded(hass, "history") &&
!DOMAINS_MORE_INFO_NO_HISTORY.includes(computeDomain(entityId));
export const computeShowLogBookComponent = (
hass: HomeAssistant,
entityId: string
): boolean => {
if (!isComponentLoaded(hass, "logbook")) {
return false;
}
const stateObj = hass.states[entityId];
if (!stateObj || stateObj.attributes.unit_of_measurement) {
return false;
}
const domain = computeDomain(entityId);
if (
CONTINUOUS_DOMAINS.includes(domain) ||
DOMAINS_MORE_INFO_NO_HISTORY.includes(domain)
) {
return false;
}
return true;
};

View File

@ -1,15 +1,11 @@
import type { HassEntity } from "home-assistant-js-websocket";
import "@material/mwc-button"; import "@material/mwc-button";
import "@material/mwc-tab"; import "@material/mwc-tab";
import "@material/mwc-tab-bar"; import "@material/mwc-tab-bar";
import { mdiClose, mdiCog, mdiPencil } from "@mdi/js"; import { mdiClose, mdiPencil } from "@mdi/js";
import { css, html, LitElement } from "lit"; import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache"; import { cache } from "lit/directives/cache";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import {
DOMAINS_MORE_INFO_NO_HISTORY,
DOMAINS_WITH_MORE_INFO,
} from "../../common/const";
import { fireEvent } from "../../common/dom/fire_event"; import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain"; import { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name"; import { computeStateName } from "../../common/entity/compute_state_name";
@ -17,34 +13,28 @@ import { navigate } from "../../common/navigate";
import "../../components/ha-dialog"; import "../../components/ha-dialog";
import "../../components/ha-header-bar"; import "../../components/ha-header-bar";
import "../../components/ha-icon-button"; import "../../components/ha-icon-button";
import { removeEntityRegistryEntry } from "../../data/entity_registry"; import "../../components/ha-related-items";
import { CONTINUOUS_DOMAINS } from "../../data/logbook";
import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor";
import { haStyleDialog } from "../../resources/styles"; import { haStyleDialog } from "../../resources/styles";
import "../../state-summary/state-card-content"; import "../../state-summary/state-card-content";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import { showConfirmationDialog } from "../generic/show-dialog-box"; import {
import { replaceDialog } from "../make-dialog-manager"; EDITABLE_DOMAINS_WITH_ID,
EDITABLE_DOMAINS,
DOMAINS_MORE_INFO_NO_HISTORY,
} from "./const";
import "./controls/more-info-default"; import "./controls/more-info-default";
import "./ha-more-info-history"; import "./ha-more-info-info";
import "./ha-more-info-logbook"; import "./ha-more-info-settings";
import "./ha-more-info-history-and-logbook";
import "./more-info-content"; import "./more-info-content";
const DOMAINS_NO_INFO = ["camera", "configurator"];
/**
* Entity domains that should be editable *if* they have an id present;
* {@see shouldShowEditIcon}.
* */
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
/**
* Entity Domains that should always be editable; {@see shouldShowEditIcon}.
* */
const EDITABLE_DOMAINS = ["script"];
export interface MoreInfoDialogParams { export interface MoreInfoDialogParams {
entityId: string | null; entityId: string | null;
tab?: Tab;
} }
type Tab = "info" | "history" | "settings" | "related";
@customElement("ha-more-info-dialog") @customElement("ha-more-info-dialog")
export class MoreInfoDialog extends LitElement { export class MoreInfoDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@ -53,10 +43,13 @@ export class MoreInfoDialog extends LitElement {
@state() private _entityId?: string | null; @state() private _entityId?: string | null;
@state() private _currTabIndex = 0; @state() private _currTab: Tab = "info";
public showDialog(params: MoreInfoDialogParams) { public showDialog(params: MoreInfoDialogParams) {
this._entityId = params.entityId; this._entityId = params.entityId;
if (params.tab) {
this._currTab = params.tab;
}
if (!this._entityId) { if (!this._entityId) {
this.closeDialog(); this.closeDialog();
return; return;
@ -66,12 +59,14 @@ export class MoreInfoDialog extends LitElement {
public closeDialog() { public closeDialog() {
this._entityId = undefined; this._entityId = undefined;
this._currTabIndex = 0;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected shouldShowEditIcon(domain, stateObj): boolean { protected shouldShowEditIcon(
if (__DEMO__) { domain: string,
stateObj: HassEntity | undefined
): boolean {
if (__DEMO__ || !stateObj) {
return false; return false;
} }
if (EDITABLE_DOMAINS_WITH_ID.includes(domain) && stateObj.attributes.id) { if (EDITABLE_DOMAINS_WITH_ID.includes(domain) && stateObj.attributes.id) {
@ -94,12 +89,9 @@ export class MoreInfoDialog extends LitElement {
const entityId = this._entityId; const entityId = this._entityId;
const stateObj = this.hass.states[entityId]; const stateObj = this.hass.states[entityId];
if (!stateObj) {
return html``;
}
const domain = computeDomain(entityId); const domain = computeDomain(entityId);
const name = computeStateName(stateObj); const name = stateObj ? computeStateName(stateObj) : entityId;
const tabs = this._getTabs(entityId, this.hass.user!.is_admin);
return html` return html`
<ha-dialog <ha-dialog
@ -127,18 +119,6 @@ export class MoreInfoDialog extends LitElement {
> >
${name} ${name}
</div> </div>
${this.hass.user!.is_admin
? html`
<ha-icon-button
slot="actionItems"
.label=${this.hass.localize(
"ui.dialogs.more_info_control.settings"
)}
.path=${mdiCog}
@click=${this._gotoSettings}
></ha-icon-button>
`
: ""}
${this.shouldShowEditIcon(domain, stateObj) ${this.shouldShowEditIcon(domain, stateObj)
? html` ? html`
<ha-icon-button <ha-icon-button
@ -152,92 +132,56 @@ export class MoreInfoDialog extends LitElement {
` `
: ""} : ""}
</ha-header-bar> </ha-header-bar>
${DOMAINS_WITH_MORE_INFO.includes(domain) &&
(this._computeShowHistoryComponent(entityId) || ${tabs.length > 1
this._computeShowLogBookComponent(entityId))
? html` ? html`
<mwc-tab-bar <mwc-tab-bar
.activeIndex=${this._currTabIndex} .activeIndex=${tabs.indexOf(this._currTab)}
@MDCTabBar:activated=${this._handleTabChanged} @MDCTabBar:activated=${this._handleTabChanged}
> >
${tabs.map(
(tab) => html`
<mwc-tab <mwc-tab
.label=${this.hass.localize( .label=${this.hass.localize(
"ui.dialogs.more_info_control.details" `ui.dialogs.more_info_control.${tab}`
)}
dialogInitialFocus
></mwc-tab>
<mwc-tab
.label=${this.hass.localize(
"ui.dialogs.more_info_control.history"
)} )}
></mwc-tab> ></mwc-tab>
`
)}
</mwc-tab-bar> </mwc-tab-bar>
` `
: ""} : ""}
</div> </div>
<div class="content" tabindex="-1" dialogInitialFocus> <div class="content" tabindex="-1" dialogInitialFocus>
${cache( ${cache(
this._currTabIndex === 0 this._currTab === "info"
? html` ? html`
${DOMAINS_NO_INFO.includes(domain) <ha-more-info-info
? ""
: html`
<state-card-content
in-dialog
.stateObj=${stateObj}
.hass=${this.hass}
></state-card-content>
`}
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!this._computeShowHistoryComponent(entityId)
? ""
: html`<ha-more-info-history
.hass=${this.hass} .hass=${this.hass}
.entityId=${this._entityId} .entityId=${this._entityId}
></ha-more-info-history>`} ></ha-more-info-info>
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
!this._computeShowLogBookComponent(entityId)
? ""
: html`<ha-more-info-logbook
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-logbook>`}
<more-info-content
.stateObj=${stateObj}
.hass=${this.hass}
></more-info-content>
${stateObj.attributes.restored
? html`
<p>
${this.hass.localize(
"ui.dialogs.more_info_control.restored.not_provided"
)}
</p>
<p>
${this.hass.localize(
"ui.dialogs.more_info_control.restored.remove_intro"
)}
</p>
<mwc-button
class="warning"
@click=${this._removeEntity}
>
${this.hass.localize(
"ui.dialogs.more_info_control.restored.remove_action"
)}
</mwc-button>
` `
: ""} : this._currTab === "history"
? html`
<ha-more-info-history-and-logbook
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-history-and-logbook>
`
: this._currTab === "settings"
? html`
<ha-more-info-settings
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-settings>
` `
: html` : html`
<ha-more-info-history <ha-related-items
class="content"
.hass=${this.hass} .hass=${this.hass}
.entityId=${this._entityId} .itemId=${entityId}
></ha-more-info-history> itemType="entity"
<ha-more-info-logbook ></ha-related-items>
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-logbook>
` `
)} )}
</div> </div>
@ -245,63 +189,49 @@ export class MoreInfoDialog extends LitElement {
`; `;
} }
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.addEventListener("close-dialog", () => this.closeDialog());
}
protected willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (!this._entityId) {
return;
}
const tabs = this._getTabs(this._entityId, this.hass.user!.is_admin);
if (!tabs.includes(this._currTab)) {
this._currTab = tabs[0];
}
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("_currTab")) {
this.setAttribute("tab", this._currTab);
}
}
private _getTabs(entityId: string, isAdmin: boolean): Tab[] {
const domain = computeDomain(entityId);
const tabs: Tab[] = ["info"];
if (!DOMAINS_MORE_INFO_NO_HISTORY.includes(domain)) {
tabs.push("history");
}
if (isAdmin) {
tabs.push("settings");
tabs.push("related");
}
return tabs;
}
private _enlarge() { private _enlarge() {
this.large = !this.large; this.large = !this.large;
} }
private _computeShowHistoryComponent(entityId) {
return (
isComponentLoaded(this.hass, "history") &&
!DOMAINS_MORE_INFO_NO_HISTORY.includes(computeDomain(entityId))
);
}
private _computeShowLogBookComponent(entityId): boolean {
if (!isComponentLoaded(this.hass, "logbook")) {
return false;
}
const stateObj = this.hass.states[entityId];
if (!stateObj || stateObj.attributes.unit_of_measurement) {
return false;
}
const domain = computeDomain(entityId);
if (
CONTINUOUS_DOMAINS.includes(domain) ||
DOMAINS_MORE_INFO_NO_HISTORY.includes(domain)
) {
return false;
}
return true;
}
private _removeEntity() {
const entityId = this._entityId!;
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.dialogs.more_info_control.restored.confirm_remove_title"
),
text: this.hass.localize(
"ui.dialogs.more_info_control.restored.confirm_remove_text"
),
confirmText: this.hass.localize("ui.common.remove"),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: () => {
removeEntityRegistryEntry(this.hass, entityId);
},
});
}
private _gotoSettings() {
replaceDialog(this);
showEntityEditorDialog(this, {
entity_id: this._entityId!,
});
this.closeDialog();
}
private _gotoEdit() { private _gotoEdit() {
const stateObj = this.hass.states[this._entityId!]; const stateObj = this.hass.states[this._entityId!];
const domain = computeDomain(this._entityId!); const domain = computeDomain(this._entityId!);
@ -315,12 +245,14 @@ export class MoreInfoDialog extends LitElement {
} }
private _handleTabChanged(ev: CustomEvent): void { private _handleTabChanged(ev: CustomEvent): void {
const newTab = ev.detail.index; const newTab = this._getTabs(this._entityId!, this.hass.user!.is_admin)[
if (newTab === this._currTabIndex) { ev.detail.index
];
if (newTab === this._currTab) {
return; return;
} }
this._currTabIndex = ev.detail.index; this._currTab = newTab;
} }
static get styles() { static get styles() {
@ -354,17 +286,21 @@ export class MoreInfoDialog extends LitElement {
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12)); var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
} }
@media all and (min-width: 451px) and (min-height: 501px) { :host([tab="settings"]) ha-dialog {
--dialog-content-padding: 0px;
}
@media all and (min-width: 600px) and (min-height: 501px) {
ha-dialog { ha-dialog {
--mdc-dialog-max-width: 90vw; --mdc-dialog-min-width: 560px;
--mdc-dialog-max-width: 560px;
--dialog-surface-position: fixed;
--dialog-surface-top: 40px;
--mdc-dialog-max-height: calc(100% - 72px);
} }
.content { ha-icon-button[slot="navigationIcon"] {
width: 352px; display: none;
}
ha-header-bar {
width: 400px;
} }
.main-title { .main-title {
@ -373,18 +309,10 @@ export class MoreInfoDialog extends LitElement {
cursor: default; cursor: default;
} }
ha-dialog[data-domain="camera"] .content, :host([large]) ha-dialog,
ha-dialog[data-domain="camera"] ha-header-bar { ha-dialog[data-domain="camera"] {
width: auto; --mdc-dialog-min-width: 90vw;
} --mdc-dialog-max-width: 90vw;
:host([large]) .content {
width: calc(90vw - 48px);
}
:host([large]) ha-dialog[data-domain="camera"] .content,
:host([large]) ha-header-bar {
width: 90vw;
} }
} }

View File

@ -0,0 +1,43 @@
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators";
import { HomeAssistant } from "../../types";
import {
computeShowHistoryComponent,
computeShowLogBookComponent,
} from "./const";
import "./ha-more-info-history";
import "./ha-more-info-logbook";
@customElement("ha-more-info-history-and-logbook")
export class MoreInfoHistoryAndLogbook extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId!: string;
protected render() {
return html`
${computeShowHistoryComponent(this.hass, this.entityId)
? html`
<ha-more-info-history
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-history>
`
: ""}
${computeShowLogBookComponent(this.hass, this.entityId)
? html`
<ha-more-info-logbook
.hass=${this.hass}
.entityId=${this.entityId}
></ha-more-info-logbook>
`
: ""}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-history-and-logbook": MoreInfoHistoryAndLogbook;
}
}

View File

@ -0,0 +1,80 @@
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators";
import { computeDomain } from "../../common/entity/compute_domain";
import { removeEntityRegistryEntry } from "../../data/entity_registry";
import type { HomeAssistant } from "../../types";
import { showConfirmationDialog } from "../generic/show-dialog-box";
import { DOMAINS_NO_INFO } from "./const";
import "./ha-more-info-history";
import "./ha-more-info-logbook";
@customElement("ha-more-info-info")
export class MoreInfoInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entityId!: string;
protected render() {
const entityId = this.entityId;
const stateObj = this.hass.states[entityId];
const domain = computeDomain(entityId);
return html`
${DOMAINS_NO_INFO.includes(domain)
? ""
: html`
<state-card-content
in-dialog
.stateObj=${stateObj}
.hass=${this.hass}
></state-card-content>
`}
<more-info-content
.stateObj=${stateObj}
.hass=${this.hass}
></more-info-content>
${stateObj.attributes.restored
? html`
<p>
${this.hass.localize(
"ui.dialogs.more_info_control.restored.not_provided"
)}
</p>
<p>
${this.hass.localize(
"ui.dialogs.more_info_control.restored.remove_intro"
)}
</p>
<mwc-button class="warning" @click=${this._removeEntity}>
${this.hass.localize(
"ui.dialogs.more_info_control.restored.remove_action"
)}
</mwc-button>
`
: ""}
`;
}
private _removeEntity() {
const entityId = this.entityId!;
showConfirmationDialog(this, {
title: this.hass.localize(
"ui.dialogs.more_info_control.restored.confirm_remove_title"
),
text: this.hass.localize(
"ui.dialogs.more_info_control.restored.confirm_remove_text"
),
confirmText: this.hass.localize("ui.common.remove"),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: () => {
removeEntityRegistryEntry(this.hass, entityId);
},
});
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-info": MoreInfoInfo;
}
}

View File

@ -0,0 +1,117 @@
import "@material/mwc-tab";
import "@material/mwc-tab-bar";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
import {
EntityRegistryEntry,
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry,
} from "../../data/entity_registry";
import { PLATFORMS_WITH_SETTINGS_TAB } from "../../panels/config/entities/const";
import type { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import "../../panels/config/entities/entity-registry-settings";
@customElement("ha-more-info-settings")
export class HaMoreInfoSettings extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public entityId!: string;
@state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null;
@state() private _settingsElementTag?: string;
protected render() {
// loading.
if (this._entry === undefined) {
return html``;
}
// No unique ID
if (this._entry === null) {
return html`
<div class="content">
${this.hass.localize(
"ui.dialogs.entity_registry.no_unique_id",
"entity_id",
this.entityId,
"faq_link",
html`<a
href=${documentationUrl(this.hass, "/faq/unique_id")}
target="_blank"
rel="noreferrer"
>${this.hass.localize("ui.dialogs.entity_registry.faq")}</a
>`
)}
</div>
`;
}
if (!this._settingsElementTag) {
return html``;
}
return html`
${dynamicElement(this._settingsElementTag, {
hass: this.hass,
entry: this._entry,
entityId: this.entityId,
})}
`;
}
protected willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (changedProps.has("entityId")) {
this._entry = undefined;
if (this.entityId) {
this._getEntityReg();
}
}
}
private async _getEntityReg() {
try {
this._entry = await getExtendedEntityRegistryEntry(
this.hass,
this.entityId
);
this._loadPlatformSettingTabs();
} catch {
this._entry = null;
}
}
private async _loadPlatformSettingTabs(): Promise<void> {
if (!this._entry) {
return;
}
if (
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform)
) {
this._settingsElementTag = "entity-registry-settings";
return;
}
const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform];
await import(`../../panels/config/entities/editor-tabs/settings/${tag}`);
this._settingsElementTag = tag;
}
static get styles(): CSSResultGroup {
return [
css`
.content {
padding: 24px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-more-info-settings": HaMoreInfoSettings;
}
}

View File

@ -0,0 +1,10 @@
import { fireEvent } from "../../common/dom/fire_event";
import type { MoreInfoDialogParams } from "./ha-more-info-dialog";
export const showMoreInfoDialog = (
element: HTMLElement,
params: MoreInfoDialogParams
) => fireEvent(element, "hass-more-info", params);
export const hideMoreInfoDialog = (element: HTMLElement) =>
fireEvent(element, "hass-more-info", { entityId: null });

View File

@ -1,8 +1,8 @@
import type { HassEntity } from "home-assistant-js-websocket"; import type { HassEntity } from "home-assistant-js-websocket";
import { import {
DOMAINS_HIDE_DEFAULT_MORE_INFO,
DOMAINS_WITH_MORE_INFO, DOMAINS_WITH_MORE_INFO,
} from "../../common/const"; DOMAINS_HIDE_DEFAULT_MORE_INFO,
} from "./const";
import { computeStateDomain } from "../../common/entity/compute_state_domain"; import { computeStateDomain } from "../../common/entity/compute_state_domain";
const LAZY_LOADED_MORE_INFO_CONTROL = { const LAZY_LOADED_MORE_INFO_CONTROL = {

View File

@ -42,11 +42,11 @@ import { SceneEntity } from "../../../data/scene";
import { ScriptEntity } from "../../../data/script"; import { ScriptEntity } from "../../../data/script";
import { findRelated, RelatedResult } from "../../../data/search"; import { findRelated, RelatedResult } from "../../../data/search";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import "../../logbook/ha-logbook"; import "../../logbook/ha-logbook";
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { import {
loadAreaRegistryDetailDialog, loadAreaRegistryDetailDialog,
@ -620,9 +620,8 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
private _openEntity(ev) { private _openEntity(ev) {
const entry: EntityRegistryEntry = (ev.currentTarget as any).entity; const entry: EntityRegistryEntry = (ev.currentTarget as any).entity;
showEntityEditorDialog(this, { showMoreInfoDialog(this, {
entity_id: entry.entity_id, entityId: entry.entity_id,
entry,
}); });
} }

View File

@ -21,13 +21,13 @@ import {
ExtEntityRegistryEntry, ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry, getExtendedEntityRegistryEntry,
} from "../../../../data/entity_registry"; } from "../../../../data/entity_registry";
import { showMoreInfoDialog } from "../../../../dialogs/more-info/show-ha-more-info-dialog";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import type { HuiErrorCard } from "../../../lovelace/cards/hui-error-card"; import type { HuiErrorCard } from "../../../lovelace/cards/hui-error-card";
import { createRowElement } from "../../../lovelace/create-element/create-row-element"; import { createRowElement } from "../../../lovelace/create-element/create-row-element";
import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view"; import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view";
import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types"; import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types";
import { LovelaceRow } from "../../../lovelace/entity-rows/types"; import { LovelaceRow } from "../../../lovelace/entity-rows/types";
import { showEntityEditorDialog } from "../../entities/show-dialog-entity-editor";
import { EntityRegistryStateEntry } from "../ha-config-device-page"; import { EntityRegistryStateEntry } from "../ha-config-device-page";
@customElement("ha-device-entities-card") @customElement("ha-device-entities-card")
@ -90,7 +90,7 @@ export class HaDeviceEntitiesCard extends LitElement {
return html` return html`
<ha-card outlined .header=${this.header}> <ha-card outlined .header=${this.header}>
<div id="entities" @hass-more-info=${this._overrideMoreInfo}> <div id="entities">
${shownEntities.map((entry) => ${shownEntities.map((entry) =>
this.hass.states[entry.entity_id] this.hass.states[entry.entity_id]
? this._renderEntity(entry) ? this._renderEntity(entry)
@ -221,20 +221,11 @@ export class HaDeviceEntitiesCard extends LitElement {
`; `;
} }
private _overrideMoreInfo(ev: Event): void {
ev.stopPropagation();
const entry = (ev.target! as any).entry;
showEntityEditorDialog(this, {
entry,
entity_id: entry.entity_id,
});
}
private _openEditEntry(ev: Event): void { private _openEditEntry(ev: Event): void {
const entry = (ev.currentTarget! as any).entry; const entry = (ev.currentTarget! as any).entry;
showEntityEditorDialog(this, { showMoreInfoDialog(this, {
entry, entityId: entry.entity_id,
entity_id: entry.entity_id, tab: "settings",
}); });
} }

View File

@ -1,287 +0,0 @@
import "@material/mwc-tab";
import "@material/mwc-tab-bar";
import { mdiClose, mdiTune } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache";
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/ha-dialog";
import "../../../components/ha-header-bar";
import "../../../components/ha-icon-button";
import "../../../components/ha-related-items";
import {
EntityRegistryEntry,
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry,
} from "../../../data/entity_registry";
import { replaceDialog } from "../../../dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { PLATFORMS_WITH_SETTINGS_TAB } from "./const";
import "./entity-registry-settings";
import type { EntityRegistryDetailDialogParams } from "./show-dialog-entity-editor";
interface Tabs {
[key: string]: Tab;
}
interface Tab {
component: string;
translationKey: Parameters<HomeAssistant["localize"]>[0];
}
@customElement("dialog-entity-editor")
export class DialogEntityEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: EntityRegistryDetailDialogParams;
@state() private _entry?: EntityRegistryEntry | ExtEntityRegistryEntry | null;
@state() private _curTab = "tab-settings";
@state() private _extraTabs: Tabs = {};
@state() private _settingsElementTag?: string;
private _curTabIndex = 0;
public showDialog(params: EntityRegistryDetailDialogParams): void {
this._params = params;
this._entry = undefined;
this._settingsElementTag = undefined;
this._extraTabs = {};
this._getEntityReg();
}
public closeDialog(): void {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._params || this._entry === undefined) {
return html``;
}
const entityId = this._params.entity_id;
const entry = this._entry;
const stateObj: HassEntity | undefined = this.hass.states[entityId];
return html`
<ha-dialog
open
.heading=${stateObj
? computeStateName(stateObj)
: entry?.name || entityId}
hideActions
@closed=${this.closeDialog}
@close-dialog=${this.closeDialog}
>
<div slot="heading">
<ha-header-bar>
<ha-icon-button
slot="navigationIcon"
.label=${this.hass.localize("ui.dialogs.entity_registry.dismiss")}
.path=${mdiClose}
dialogAction="cancel"
></ha-icon-button>
<span slot="title">
${stateObj ? computeStateName(stateObj) : entry?.name || entityId}
</span>
${stateObj
? html`
<ha-icon-button
slot="actionItems"
.label=${this.hass.localize(
"ui.dialogs.entity_registry.control"
)}
.path=${mdiTune}
@click=${this._openMoreInfo}
></ha-icon-button>
`
: ""}
</ha-header-bar>
<mwc-tab-bar
.activeIndex=${this._curTabIndex}
@MDCTabBar:activated=${this._handleTabActivated}
@MDCTab:interacted=${this._handleTabInteracted}
>
<mwc-tab
id="tab-settings"
.label=${this.hass.localize(
"ui.dialogs.entity_registry.settings"
)}
dialogInitialFocus
>
</mwc-tab>
${Object.entries(this._extraTabs).map(
([key, tab]) => html`
<mwc-tab
id=${key}
.label=${this.hass.localize(tab.translationKey) || key}
>
</mwc-tab>
`
)}
<mwc-tab
id="tab-related"
.label=${this.hass.localize("ui.dialogs.entity_registry.related")}
>
</mwc-tab>
</mwc-tab-bar>
</div>
<div class="wrapper">${cache(this._renderTab())}</div>
</ha-dialog>
`;
}
private _renderTab() {
switch (this._curTab) {
case "tab-settings":
if (this._entry) {
if (this._settingsElementTag) {
return html`
${dynamicElement(this._settingsElementTag, {
hass: this.hass,
entry: this._entry,
entityId: this._params!.entity_id,
})}
`;
}
return html``;
}
return html`
<div class="content">
${this.hass.localize(
"ui.dialogs.entity_registry.no_unique_id",
"entity_id",
this._params!.entity_id,
"faq_link",
html`<a
href=${documentationUrl(this.hass, "/faq/unique_id")}
target="_blank"
rel="noreferrer"
>${this.hass.localize("ui.dialogs.entity_registry.faq")}</a
>`
)}
</div>
`;
case "tab-related":
return html`
<ha-related-items
class="content"
.hass=${this.hass}
.itemId=${this._params!.entity_id}
itemType="entity"
></ha-related-items>
`;
default:
return html``;
}
}
private async _getEntityReg() {
try {
this._entry = await getExtendedEntityRegistryEntry(
this.hass,
this._params!.entity_id
);
this._loadPlatformSettingTabs();
} catch {
this._entry = null;
}
}
private _handleTabActivated(ev: CustomEvent): void {
this._curTabIndex = ev.detail.index;
}
private _handleTabInteracted(ev: CustomEvent): void {
this._curTab = ev.detail.tabId;
}
private async _loadPlatformSettingTabs(): Promise<void> {
if (!this._entry) {
return;
}
if (
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform)
) {
this._settingsElementTag = "entity-registry-settings";
return;
}
const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform];
await import(`./editor-tabs/settings/${tag}`);
this._settingsElementTag = tag;
}
private _openMoreInfo(): void {
replaceDialog(this);
fireEvent(this, "hass-more-info", {
entityId: this._params!.entity_id,
});
this.closeDialog();
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
}
mwc-tab-bar {
border-bottom: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
ha-dialog {
--dialog-content-position: static;
--dialog-content-padding: 0;
--dialog-z-index: 6;
}
@media all and (min-width: 451px) and (min-height: 501px) {
.wrapper {
min-width: 400px;
}
}
.content {
display: block;
padding: 20px 24px;
}
/* overrule the ha-style-dialog max-height on small screens */
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-header-bar {
--mdc-theme-primary: var(--app-header-background-color);
--mdc-theme-on-primary: var(--app-header-text-color, white);
}
}
mwc-button.warning {
--mdc-theme-primary: var(--error-color);
}
:host([rtl]) app-toolbar {
direction: rtl;
text-align: right;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-entity-editor": DialogEntityEditor;
}
}

View File

@ -182,18 +182,12 @@ export class EntityRegistrySettingsHelper extends LitElement {
} }
.form { .form {
padding: 20px 24px; padding: 20px 24px;
margin-bottom: 53px;
} }
.buttons { .buttons {
position: absolute;
bottom: 0;
width: 100%;
box-sizing: border-box; box-sizing: border-box;
border-top: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding: 8px; padding: 0 24px 24px 24px;
background-color: var(--mdc-theme-surface, #fff); background-color: var(--mdc-theme-surface, #fff);
} }
.error { .error {

View File

@ -66,11 +66,11 @@ import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box"; } from "../../../dialogs/generic/show-dialog-box";
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles"; import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail"; import { showDeviceRegistryDetailDialog } from "../devices/device-registry-detail/show-dialog-device-registry-detail";
import { showEntityEditorDialog } from "./show-dialog-entity-editor";
const OVERRIDE_DEVICE_CLASSES = { const OVERRIDE_DEVICE_CLASSES = {
cover: [ cover: [
@ -977,8 +977,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
if (!entity) { if (!entity) {
return; return;
} }
showEntityEditorDialog(parent, { showMoreInfoDialog(parent, {
entity_id: entity.entity_id, entityId: entity.entity_id,
tab: "settings",
}); });
}); });
}, "entity_registry_updated"); }, "entity_registry_updated");
@ -1046,17 +1047,11 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
.container { .container {
padding: 20px 24px; padding: 20px 24px;
} }
.form {
margin-bottom: 53px;
}
.buttons { .buttons {
position: absolute;
bottom: 0;
width: 100%;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
padding: 0 24px 24px 24px; padding: 0 24px 24px 24px;
justify-content: flex-end; justify-content: space-between;
padding-bottom: max(env(safe-area-inset-bottom), 24px); padding-bottom: max(env(safe-area-inset-bottom), 24px);
background-color: var(--mdc-theme-surface, #fff); background-color: var(--mdc-theme-surface, #fff);
} }

View File

@ -55,6 +55,10 @@ import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box"; } from "../../../dialogs/generic/show-dialog-box";
import {
hideMoreInfoDialog,
showMoreInfoDialog,
} from "../../../dialogs/more-info/show-ha-more-info-dialog";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table"; import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
@ -63,11 +67,6 @@ import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types"; import type { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu"; import "../integrations/ha-integration-overflow-menu";
import { DialogEntityEditor } from "./dialog-entity-editor";
import {
loadEntityEditorDialog,
showEntityEditorDialog,
} from "./show-dialog-entity-editor";
export interface StateEntity extends EntityRegistryEntry { export interface StateEntity extends EntityRegistryEntry {
readonly?: boolean; readonly?: boolean;
@ -121,8 +120,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
@query("hass-tabs-subpage-data-table", true) @query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable; private _dataTable!: HaTabsSubpageDataTable;
private getDialog?: () => DialogEntityEditor | undefined;
private _activeFilters = memoize( private _activeFilters = memoize(
( (
filters: URLSearchParams, filters: URLSearchParams,
@ -454,14 +451,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
public disconnectedCallback() { public disconnectedCallback() {
super.disconnectedCallback(); super.disconnectedCallback();
if (!this.getDialog) { hideMoreInfoDialog(this);
return;
}
const dialog = this.getDialog();
if (!dialog) {
return;
}
dialog.closeDialog();
} }
protected render(): TemplateResult { protected render(): TemplateResult {
@ -695,11 +685,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
`; `;
} }
protected firstUpdated(changedProps): void {
super.firstUpdated(changedProps);
loadEntityEditorDialog();
}
public willUpdate(changedProps): void { public willUpdate(changedProps): void {
super.willUpdate(changedProps); super.willUpdate(changedProps);
const oldHass = changedProps.get("hass"); const oldHass = changedProps.get("hass");
@ -923,12 +908,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
private _openEditEntry(ev: CustomEvent): void { private _openEditEntry(ev: CustomEvent): void {
const entityId = (ev.detail as RowClickedEvent).id; const entityId = (ev.detail as RowClickedEvent).id;
const entry = this._entities!.find( showMoreInfoDialog(this, {
(entity) => entity.entity_id === entityId entityId,
); tab: "settings",
this.getDialog = showEntityEditorDialog(this, {
entry,
entity_id: entityId,
}); });
} }

View File

@ -1,30 +0,0 @@
import { fireEvent } from "../../../common/dom/fire_event";
import { EntityRegistryEntry } from "../../../data/entity_registry";
import type { DialogEntityEditor } from "./dialog-entity-editor";
export interface EntityRegistryDetailDialogParams {
entry?: EntityRegistryEntry;
entity_id: string;
tab?: string;
}
export const loadEntityEditorDialog = () => import("./dialog-entity-editor");
const getDialog = () =>
document
.querySelector("home-assistant")!
.shadowRoot!.querySelector("dialog-entity-editor") as
| DialogEntityEditor
| undefined;
export const showEntityEditorDialog = (
element: HTMLElement,
entityDetailParams: EntityRegistryDetailDialogParams
): (() => DialogEntityEditor | undefined) => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-entity-editor",
dialogImport: loadEntityEditorDialog,
dialogParams: entityDetailParams,
});
return getDialog;
};

View File

@ -29,11 +29,11 @@ import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box"; } from "../../../dialogs/generic/show-dialog-box";
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu"; import "../integrations/ha-integration-overflow-menu";
import { HELPER_DOMAINS } from "./const"; import { HELPER_DOMAINS } from "./const";
@ -357,8 +357,9 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
private async _openEditDialog(ev: CustomEvent): Promise<void> { private async _openEditDialog(ev: CustomEvent): Promise<void> {
const entityId = (ev.detail as RowClickedEvent).id; const entityId = (ev.detail as RowClickedEvent).id;
showEntityEditorDialog(this, { showMoreInfoDialog(this, {
entity_id: entityId, entityId,
tab: "settings",
}); });
} }

View File

@ -29,6 +29,7 @@ export default <T extends Constructor<HassBaseEl>>(superClass: T) =>
"ha-more-info-dialog", "ha-more-info-dialog",
{ {
entityId: ev.detail.entityId, entityId: ev.detail.entityId,
tab: ev.detail.tab,
}, },
() => import("../dialogs/more-info/ha-more-info-dialog") () => import("../dialogs/more-info/ha-more-info-dialog")
); );

View File

@ -738,9 +738,11 @@
}, },
"more_info_control": { "more_info_control": {
"dismiss": "Dismiss dialog", "dismiss": "Dismiss dialog",
"settings": "Entity settings", "settings": "Settings",
"edit": "Edit entity", "edit": "Edit entity",
"details": "Details", "details": "Details",
"info": "Info",
"related": "Related",
"history": "History", "history": "History",
"logbook": "Logbook", "logbook": "Logbook",
"last_changed": "Last changed", "last_changed": "Last changed",