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",
];
/** 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.
* 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
@ -237,9 +197,6 @@ export const DOMAINS_INPUT_ROW = [
"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". */
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);
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-tab";
import "@material/mwc-tab-bar";
import { mdiClose, mdiCog, mdiPencil } from "@mdi/js";
import { css, html, LitElement } from "lit";
import { mdiClose, mdiPencil } from "@mdi/js";
import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
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 { computeDomain } from "../../common/entity/compute_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
@ -17,34 +13,28 @@ import { navigate } from "../../common/navigate";
import "../../components/ha-dialog";
import "../../components/ha-header-bar";
import "../../components/ha-icon-button";
import { removeEntityRegistryEntry } from "../../data/entity_registry";
import { CONTINUOUS_DOMAINS } from "../../data/logbook";
import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor";
import "../../components/ha-related-items";
import { haStyleDialog } from "../../resources/styles";
import "../../state-summary/state-card-content";
import { HomeAssistant } from "../../types";
import { showConfirmationDialog } from "../generic/show-dialog-box";
import { replaceDialog } from "../make-dialog-manager";
import {
EDITABLE_DOMAINS_WITH_ID,
EDITABLE_DOMAINS,
DOMAINS_MORE_INFO_NO_HISTORY,
} from "./const";
import "./controls/more-info-default";
import "./ha-more-info-history";
import "./ha-more-info-logbook";
import "./ha-more-info-info";
import "./ha-more-info-settings";
import "./ha-more-info-history-and-logbook";
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 {
entityId: string | null;
tab?: Tab;
}
type Tab = "info" | "history" | "settings" | "related";
@customElement("ha-more-info-dialog")
export class MoreInfoDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -53,10 +43,13 @@ export class MoreInfoDialog extends LitElement {
@state() private _entityId?: string | null;
@state() private _currTabIndex = 0;
@state() private _currTab: Tab = "info";
public showDialog(params: MoreInfoDialogParams) {
this._entityId = params.entityId;
if (params.tab) {
this._currTab = params.tab;
}
if (!this._entityId) {
this.closeDialog();
return;
@ -66,12 +59,14 @@ export class MoreInfoDialog extends LitElement {
public closeDialog() {
this._entityId = undefined;
this._currTabIndex = 0;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected shouldShowEditIcon(domain, stateObj): boolean {
if (__DEMO__) {
protected shouldShowEditIcon(
domain: string,
stateObj: HassEntity | undefined
): boolean {
if (__DEMO__ || !stateObj) {
return false;
}
if (EDITABLE_DOMAINS_WITH_ID.includes(domain) && stateObj.attributes.id) {
@ -94,12 +89,9 @@ export class MoreInfoDialog extends LitElement {
const entityId = this._entityId;
const stateObj = this.hass.states[entityId];
if (!stateObj) {
return html``;
}
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`
<ha-dialog
@ -127,18 +119,6 @@ export class MoreInfoDialog extends LitElement {
>
${name}
</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)
? html`
<ha-icon-button
@ -152,92 +132,56 @@ export class MoreInfoDialog extends LitElement {
`
: ""}
</ha-header-bar>
${DOMAINS_WITH_MORE_INFO.includes(domain) &&
(this._computeShowHistoryComponent(entityId) ||
this._computeShowLogBookComponent(entityId))
${tabs.length > 1
? html`
<mwc-tab-bar
.activeIndex=${this._currTabIndex}
.activeIndex=${tabs.indexOf(this._currTab)}
@MDCTabBar:activated=${this._handleTabChanged}
>
${tabs.map(
(tab) => html`
<mwc-tab
.label=${this.hass.localize(
"ui.dialogs.more_info_control.details"
)}
dialogInitialFocus
></mwc-tab>
<mwc-tab
.label=${this.hass.localize(
"ui.dialogs.more_info_control.history"
`ui.dialogs.more_info_control.${tab}`
)}
></mwc-tab>
`
)}
</mwc-tab-bar>
`
: ""}
</div>
<div class="content" tabindex="-1" dialogInitialFocus>
${cache(
this._currTabIndex === 0
this._currTab === "info"
? html`
${DOMAINS_NO_INFO.includes(domain)
? ""
: 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
<ha-more-info-info
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-history>`}
${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>
></ha-more-info-info>
`
: ""}
: 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`
<ha-more-info-history
<ha-related-items
class="content"
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-history>
<ha-more-info-logbook
.hass=${this.hass}
.entityId=${this._entityId}
></ha-more-info-logbook>
.itemId=${entityId}
itemType="entity"
></ha-related-items>
`
)}
</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() {
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() {
const stateObj = this.hass.states[this._entityId!];
const domain = computeDomain(this._entityId!);
@ -315,12 +245,14 @@ export class MoreInfoDialog extends LitElement {
}
private _handleTabChanged(ev: CustomEvent): void {
const newTab = ev.detail.index;
if (newTab === this._currTabIndex) {
const newTab = this._getTabs(this._entityId!, this.hass.user!.is_admin)[
ev.detail.index
];
if (newTab === this._currTab) {
return;
}
this._currTabIndex = ev.detail.index;
this._currTab = newTab;
}
static get styles() {
@ -354,17 +286,21 @@ export class MoreInfoDialog extends LitElement {
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 {
--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 {
width: 352px;
}
ha-header-bar {
width: 400px;
ha-icon-button[slot="navigationIcon"] {
display: none;
}
.main-title {
@ -373,18 +309,10 @@ export class MoreInfoDialog extends LitElement {
cursor: default;
}
ha-dialog[data-domain="camera"] .content,
ha-dialog[data-domain="camera"] ha-header-bar {
width: auto;
}
:host([large]) .content {
width: calc(90vw - 48px);
}
:host([large]) ha-dialog[data-domain="camera"] .content,
:host([large]) ha-header-bar {
width: 90vw;
:host([large]) ha-dialog,
ha-dialog[data-domain="camera"] {
--mdc-dialog-min-width: 90vw;
--mdc-dialog-max-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 {
DOMAINS_HIDE_DEFAULT_MORE_INFO,
DOMAINS_WITH_MORE_INFO,
} from "../../common/const";
DOMAINS_HIDE_DEFAULT_MORE_INFO,
} from "./const";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
const LAZY_LOADED_MORE_INFO_CONTROL = {

View File

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

View File

@ -21,13 +21,13 @@ import {
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry,
} from "../../../../data/entity_registry";
import { showMoreInfoDialog } from "../../../../dialogs/more-info/show-ha-more-info-dialog";
import type { HomeAssistant } from "../../../../types";
import type { HuiErrorCard } from "../../../lovelace/cards/hui-error-card";
import { createRowElement } from "../../../lovelace/create-element/create-row-element";
import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view";
import type { LovelaceRowConfig } 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";
@customElement("ha-device-entities-card")
@ -90,7 +90,7 @@ export class HaDeviceEntitiesCard extends LitElement {
return html`
<ha-card outlined .header=${this.header}>
<div id="entities" @hass-more-info=${this._overrideMoreInfo}>
<div id="entities">
${shownEntities.map((entry) =>
this.hass.states[entry.entity_id]
? 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 {
const entry = (ev.currentTarget! as any).entry;
showEntityEditorDialog(this, {
entry,
entity_id: entry.entity_id,
showMoreInfoDialog(this, {
entityId: 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 {
padding: 20px 24px;
margin-bottom: 53px;
}
.buttons {
position: absolute;
bottom: 0;
width: 100%;
box-sizing: border-box;
border-top: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
display: flex;
justify-content: space-between;
padding: 8px;
padding: 0 24px 24px 24px;
background-color: var(--mdc-theme-surface, #fff);
}
.error {

View File

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

View File

@ -55,6 +55,10 @@ import {
showAlertDialog,
showConfirmationDialog,
} 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-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 { configSections } from "../ha-panel-config";
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 {
readonly?: boolean;
@ -121,8 +120,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
@query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable;
private getDialog?: () => DialogEntityEditor | undefined;
private _activeFilters = memoize(
(
filters: URLSearchParams,
@ -454,14 +451,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
public disconnectedCallback() {
super.disconnectedCallback();
if (!this.getDialog) {
return;
}
const dialog = this.getDialog();
if (!dialog) {
return;
}
dialog.closeDialog();
hideMoreInfoDialog(this);
}
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 {
super.willUpdate(changedProps);
const oldHass = changedProps.get("hass");
@ -923,12 +908,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
private _openEditEntry(ev: CustomEvent): void {
const entityId = (ev.detail as RowClickedEvent).id;
const entry = this._entities!.find(
(entity) => entity.entity_id === entityId
);
this.getDialog = showEntityEditorDialog(this, {
entry,
entity_id: entityId,
showMoreInfoDialog(this, {
entityId,
tab: "settings",
});
}

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

View File

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

View File

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