diff --git a/src/components/ha-related-items.ts b/src/components/ha-related-items.ts
new file mode 100644
index 0000000000..0039e16575
--- /dev/null
+++ b/src/components/ha-related-items.ts
@@ -0,0 +1,323 @@
+import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
+import {
+ customElement,
+ html,
+ LitElement,
+ property,
+ PropertyValues,
+ TemplateResult,
+ CSSResult,
+ css,
+} from "lit-element";
+import { fireEvent } from "../common/dom/fire_event";
+import {
+ AreaRegistryEntry,
+ subscribeAreaRegistry,
+} from "../data/area_registry";
+import { ConfigEntry, getConfigEntries } from "../data/config_entries";
+import {
+ DeviceRegistryEntry,
+ subscribeDeviceRegistry,
+} from "../data/device_registry";
+import { SceneEntity } from "../data/scene";
+import { findRelated, ItemType, RelatedResult } from "../data/search";
+import { SubscribeMixin } from "../mixins/subscribe-mixin";
+import { HomeAssistant } from "../types";
+import "./ha-switch";
+
+@customElement("ha-related-items")
+export class HaRelatedItems extends SubscribeMixin(LitElement) {
+ @property() public hass!: HomeAssistant;
+ @property() public itemType!: ItemType;
+ @property() public itemId!: string;
+ @property() private _entries?: ConfigEntry[];
+ @property() private _devices?: DeviceRegistryEntry[];
+ @property() private _areas?: AreaRegistryEntry[];
+ @property() private _related?: RelatedResult;
+
+ public hassSubscribe(): UnsubscribeFunc[] {
+ return [
+ subscribeDeviceRegistry(this.hass.connection!, (devices) => {
+ this._devices = devices;
+ }),
+ subscribeAreaRegistry(this.hass.connection!, (areas) => {
+ this._areas = areas;
+ }),
+ ];
+ }
+
+ protected firstUpdated(changedProps: PropertyValues) {
+ super.firstUpdated(changedProps);
+ getConfigEntries(this.hass).then((configEntries) => {
+ this._entries = configEntries;
+ });
+ }
+
+ protected updated(changedProps: PropertyValues) {
+ super.updated(changedProps);
+ if (
+ (changedProps.has("itemId") || changedProps.has("itemType")) &&
+ this.itemId &&
+ this.itemType
+ ) {
+ this._findRelated();
+ }
+ }
+
+ protected render(): TemplateResult | void {
+ if (!this._related) {
+ return html``;
+ }
+ return html`
+ ${this._related.config_entry && this._entries
+ ? this._related.config_entry.map((relatedConfigEntryId) => {
+ const entry: ConfigEntry | undefined = this._entries!.find(
+ (configEntry) => configEntry.entry_id === relatedConfigEntryId
+ );
+ if (!entry) {
+ return;
+ }
+ return html`
+
+ ${this.hass.localize(
+ "ui.components.related-items.integration"
+ )}:
+
+
+ ${this.hass.localize(`component.${entry.domain}.config.title`)}:
+ ${entry.title}
+
+ `;
+ })
+ : ""}
+ ${this._related.device && this._devices
+ ? this._related.device.map((relatedDeviceId) => {
+ const device: DeviceRegistryEntry | undefined = this._devices!.find(
+ (dev) => dev.id === relatedDeviceId
+ );
+ if (!device) {
+ return;
+ }
+ return html`
+
+ ${this.hass.localize("ui.components.related-items.device")}:
+
+
+ ${device.name_by_user || device.name}
+
+ `;
+ })
+ : ""}
+ ${this._related.area && this._areas
+ ? this._related.area.map((relatedAreaId) => {
+ const area: AreaRegistryEntry | undefined = this._areas!.find(
+ (ar) => ar.area_id === relatedAreaId
+ );
+ if (!area) {
+ return;
+ }
+ return html`
+
+ ${this.hass.localize("ui.components.related-items.area")}:
+
+ ${area.name}
+ `;
+ })
+ : ""}
+ ${this._related.entity
+ ? html`
+
+ ${this.hass.localize("ui.components.related-items.entity")}:
+
+
+ ${this._related.entity.map((entityId) => {
+ const entity: HassEntity | undefined = this.hass.states[
+ entityId
+ ];
+ if (!entity) {
+ return;
+ }
+ return html`
+ -
+
+
+ `;
+ })}
+
+ `
+ : ""}
+ ${this._related.group
+ ? html`
+ ${this.hass.localize("ui.components.related-items.group")}:
+
+ ${this._related.group.map((groupId) => {
+ const group: HassEntity | undefined = this.hass.states[groupId];
+ if (!group) {
+ return;
+ }
+ return html`
+ -
+
+
+ `;
+ })}
+
+ `
+ : ""}
+ ${this._related.scene
+ ? html`
+ ${this.hass.localize("ui.components.related-items.scene")}:
+
+ ${this._related.scene.map((sceneId) => {
+ const scene: SceneEntity | undefined = this.hass.states[
+ sceneId
+ ];
+ if (!scene) {
+ return;
+ }
+ return html`
+ -
+
+
+ `;
+ })}
+
+ `
+ : ""}
+ ${this._related.automation
+ ? html`
+
+ ${this.hass.localize("ui.components.related-items.automation")}:
+
+
+ ${this._related.automation.map((automationId) => {
+ const automation: HassEntity | undefined = this.hass.states[
+ automationId
+ ];
+ if (!automation) {
+ return;
+ }
+ return html`
+ -
+
+
+ `;
+ })}
+
+ `
+ : ""}
+ ${this._related.script
+ ? html`
+
+ ${this.hass.localize("ui.components.related-items.script")}:
+
+
+ ${this._related.script.map((scriptId) => {
+ const script: HassEntity | undefined = this.hass.states[
+ scriptId
+ ];
+ if (!script) {
+ return;
+ }
+ return html`
+ -
+
+
+ `;
+ })}
+
+ `
+ : ""}
+ `;
+ }
+
+ private async _findRelated() {
+ this._related = await findRelated(this.hass, this.itemType, this.itemId);
+ await this.updateComplete;
+ fireEvent(this, "iron-resize");
+ }
+
+ private _openMoreInfo(ev: CustomEvent) {
+ const entityId = (ev.target as any).entityId;
+ fireEvent(this, "hass-more-info", { entityId });
+ }
+
+ private _close() {
+ fireEvent(this, "close-dialog");
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ a {
+ color: var(--primary-color);
+ }
+ button.link {
+ color: var(--primary-color);
+ text-align: left;
+ cursor: pointer;
+ background: none;
+ border-width: initial;
+ border-style: none;
+ border-color: initial;
+ border-image: initial;
+ padding: 0px;
+ font: inherit;
+ text-decoration: underline;
+ }
+ h3 {
+ font-family: var(--paper-font-title_-_font-family);
+ -webkit-font-smoothing: var(
+ --paper-font-title_-_-webkit-font-smoothing
+ );
+ font-size: var(--paper-font-title_-_font-size);
+ font-weight: var(--paper-font-headline-_font-weight);
+ letter-spacing: var(--paper-font-title_-_letter-spacing);
+ line-height: var(--paper-font-title_-_line-height);
+ opacity: var(--dark-primary-opacity);
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-related-items": HaRelatedItems;
+ }
+}
diff --git a/src/data/search.ts b/src/data/search.ts
new file mode 100644
index 0000000000..1e00372c9d
--- /dev/null
+++ b/src/data/search.ts
@@ -0,0 +1,33 @@
+import { HomeAssistant } from "../types";
+
+export interface RelatedResult {
+ area?: string[];
+ automation?: string[];
+ config_entry?: string[];
+ device?: string[];
+ entity?: string[];
+ group?: string[];
+ scene?: string[];
+ script?: string[];
+}
+
+export type ItemType =
+ | "area"
+ | "automation"
+ | "config_entry"
+ | "device"
+ | "entity"
+ | "group"
+ | "scene"
+ | "script";
+
+export const findRelated = (
+ hass: HomeAssistant,
+ itemType: ItemType,
+ itemId: string
+): Promise =>
+ hass.callWS({
+ type: "search/related",
+ item_type: itemType,
+ item_id: itemId,
+ });
diff --git a/src/dialogs/ha-more-info-dialog.js b/src/dialogs/ha-more-info-dialog.js
index 1d95816055..3a34c1cfc9 100644
--- a/src/dialogs/ha-more-info-dialog.js
+++ b/src/dialogs/ha-more-info-dialog.js
@@ -6,7 +6,6 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../resources/ha-style";
import "./more-info/more-info-controls";
-import "./more-info/more-info-settings";
import { computeStateDomain } from "../common/entity/compute_state_domain";
import { isComponentLoaded } from "../common/config/is_component_loaded";
@@ -26,8 +25,7 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
border-radius: 2px;
}
- more-info-controls,
- more-info-settings {
+ more-info-controls {
--more-info-header-background: var(--secondary-background-color);
--more-info-header-color: var(--primary-text-color);
--ha-more-info-app-toolbar-title: {
@@ -46,8 +44,7 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
/* overrule the ha-style-dialog max-height on small screens */
@media all and (max-width: 450px), all and (max-height: 500px) {
- more-info-controls,
- more-info-settings {
+ more-info-controls {
--more-info-header-background: var(--primary-color);
--more-info-header-color: var(--text-primary-color);
}
@@ -79,24 +76,14 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
}
-
-
-
-
-
-
+
`;
}
@@ -118,11 +105,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
_dialogElement: Object,
_registryInfo: Object,
- _page: {
- type: String,
- value: null,
- },
-
dataDomain: {
computed: "_computeDomain(stateObj)",
reflectToAttribute: true,
@@ -137,9 +119,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
ready() {
super.ready();
this._dialogElement = this;
- this.addEventListener("more-info-page", (ev) => {
- this._page = ev.detail.page;
- });
}
_computeDomain(stateObj) {
@@ -154,7 +133,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
if (!newVal) {
this.setProperties({
opened: false,
- _page: null,
_registryInfo: null,
large: false,
});
diff --git a/src/dialogs/make-dialog-manager.ts b/src/dialogs/make-dialog-manager.ts
index 41e2cc5cba..02af7d4e96 100644
--- a/src/dialogs/make-dialog-manager.ts
+++ b/src/dialogs/make-dialog-manager.ts
@@ -5,6 +5,7 @@ declare global {
// for fire event
interface HASSDomEvents {
"show-dialog": ShowDialogParams;
+ "close-dialog": undefined;
}
// for add event listener
interface HTMLElementEventMap {
diff --git a/src/dialogs/more-info/more-info-controls.js b/src/dialogs/more-info/more-info-controls.js
index d0ac64aad9..65c24a5846 100644
--- a/src/dialogs/more-info/more-info-controls.js
+++ b/src/dialogs/more-info/more-info-controls.js
@@ -22,6 +22,7 @@ import LocalizeMixin from "../../mixins/localize-mixin";
import { computeRTL } from "../../common/util/compute_rtl";
import { removeEntityRegistryEntry } from "../../data/entity_registry";
import { showConfirmationDialog } from "../confirmation/show-dialog-confirmation";
+import { showEntityRegistryDetailDialog } from "../../panels/config/entities/show-dialog-entity-registry-detail";
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
@@ -87,7 +88,7 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
[[_computeStateName(stateObj)]]
-
+
- app-toolbar {
- color: var(--more-info-header-color);
- background-color: var(--more-info-header-background);
-
- /* to fit save button */
- padding-right: 0;
- }
-
- app-toolbar [main-title] {
- @apply --ha-more-info-app-toolbar-title;
- }
-
- app-toolbar mwc-button {
- font-size: 0.8em;
- margin: 0;
- --mdc-theme-primary: var(--more-info-header-color);
- }
-
- .form {
- padding: 0 24px 24px;
- }
-
-
-
-
- [[_computeStateName(stateObj)]]
- [[localize('ui.dialogs.more_info_settings.save')]]
-
-
-
- `;
- }
-
- static get properties() {
- return {
- hass: Object,
- stateObj: Object,
-
- registryInfo: {
- type: Object,
- observer: "_registryInfoChanged",
- notify: true,
- },
-
- _name: String,
- _entityId: String,
- };
- }
-
- _computeStateName(stateObj) {
- if (!stateObj) return "";
- return computeStateName(stateObj);
- }
-
- _computeInvalid(entityId) {
- return computeDomain(this.stateObj.entity_id) !== computeDomain(entityId);
- }
-
- _registryInfoChanged(newVal) {
- if (newVal) {
- this.setProperties({
- _name: newVal.name,
- _entityId: newVal.entity_id,
- });
- } else {
- this.setProperties({
- _name: "",
- _entityId: "",
- });
- }
- }
-
- _backTapped() {
- this.fire("more-info-page", { page: null });
- }
-
- async _save() {
- try {
- const info = await updateEntityRegistryEntry(
- this.hass,
- this.stateObj.entity_id,
- {
- name: this._name,
- new_entity_id: this._entityId,
- }
- );
-
- showSaveSuccessToast(this, this.hass);
-
- this.registryInfo = info;
-
- // Keep the more info dialog open at the new entity.
- if (this.stateObj.entity_id !== this._entityId) {
- this.fire("hass-more-info", { entityId: this._entityId });
- }
- } catch (err) {
- alert(`save failed: ${err.message}`);
- }
- }
-}
-customElements.define("more-info-settings", MoreInfoSettings);
diff --git a/src/panels/config/entities/dialog-entity-registry-detail.ts b/src/panels/config/entities/dialog-entity-registry-detail.ts
index 9fe44d8920..2e2c96c446 100644
--- a/src/panels/config/entities/dialog-entity-registry-detail.ts
+++ b/src/panels/config/entities/dialog-entity-registry-detail.ts
@@ -1,206 +1,151 @@
+import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
+import "@polymer/paper-tabs/paper-tab";
+import "@polymer/paper-tabs/paper-tabs";
+import { HassEntity } from "home-assistant-js-websocket";
import {
- LitElement,
- html,
css,
CSSResult,
- TemplateResult,
+ customElement,
+ html,
+ LitElement,
property,
+ query,
+ TemplateResult,
} from "lit-element";
-import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
-import "@polymer/paper-input/paper-input";
-
+import { cache } from "lit-html/directives/cache";
+import { fireEvent } from "../../../common/dom/fire_event";
+import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/dialog/ha-paper-dialog";
-import "../../../components/ha-switch";
-
-import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-registry-detail";
+// tslint:disable-next-line: no-duplicate-imports
+import { HaPaperDialog } from "../../../components/dialog/ha-paper-dialog";
+import "../../../components/ha-related-items";
+import "../../../dialogs/more-info/controls/more-info-content";
import { PolymerChangedEvent } from "../../../polymer-types";
import { haStyleDialog } from "../../../resources/styles";
+import "../../../state-summary/state-card-content";
import { HomeAssistant } from "../../../types";
-import { HassEntity } from "home-assistant-js-websocket";
-// tslint:disable-next-line: no-duplicate-imports
-import { HaSwitch } from "../../../components/ha-switch";
+import "./entity-registry-settings";
+import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-registry-detail";
-import { computeDomain } from "../../../common/entity/compute_domain";
-import { computeStateName } from "../../../common/entity/compute_state_name";
-import {
- updateEntityRegistryEntry,
- removeEntityRegistryEntry,
-} from "../../../data/entity_registry";
-import { showConfirmationDialog } from "../../../dialogs/confirmation/show-dialog-confirmation";
-
-class DialogEntityRegistryDetail extends LitElement {
+@customElement("dialog-entity-registry-detail")
+export class DialogEntityRegistryDetail extends LitElement {
@property() public hass!: HomeAssistant;
- @property() private _name!: string;
- @property() private _entityId!: string;
- @property() private _disabledBy!: string | null;
- @property() private _error?: string;
@property() private _params?: EntityRegistryDetailDialogParams;
- @property() private _submitting?: boolean;
- private _origEntityId!: string;
+ @property() private _curTab?: string;
+ @query("ha-paper-dialog") private _dialog!: HaPaperDialog;
+ private _curTabIndex = 0;
public async showDialog(
params: EntityRegistryDetailDialogParams
): Promise {
this._params = params;
- this._error = undefined;
- this._name = this._params.entry.name || "";
- this._origEntityId = this._params.entry.entity_id;
- this._entityId = this._params.entry.entity_id;
- this._disabledBy = this._params.entry.disabled_by;
await this.updateComplete;
}
+ public closeDialog(): void {
+ this._params = undefined;
+ }
+
protected render(): TemplateResult | void {
if (!this._params) {
return html``;
}
const entry = this._params.entry;
const stateObj: HassEntity | undefined = this.hass.states[entry.entity_id];
- const invalidDomainUpdate =
- computeDomain(this._entityId.trim()) !==
- computeDomain(this._params.entry.entity_id);
return html`
-
- ${stateObj
- ? computeStateName(stateObj)
- : entry.name || entry.entity_id}
-
-
- ${!stateObj
- ? html`
-
- ${this.hass!.localize(
- "ui.panel.config.entities.editor.unavailable"
- )}
-
- `
- : ""}
- ${this._error
- ? html`
- ${this._error}
- `
- : ""}
-