Add entity registry functions to device page (#3843)

* Add entity registry functions to device page

* Move update/remove logic to dialog

* Hide disabled entities by default
This commit is contained in:
Bram Kragten 2019-10-02 18:23:42 +02:00 committed by Paulus Schoutsen
parent 76d3218130
commit b927a3ef29
7 changed files with 254 additions and 69 deletions

View File

@ -123,24 +123,26 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
</template>
</div>
<div class="device-entities">
<template
is="dom-repeat"
items="[[_computeDeviceEntities(hass, device, entities)]]"
as="entity"
>
<paper-icon-item on-click="_openMoreInfo">
<state-badge
state-obj="[[_computeStateObj(entity, hass)]]"
slot="item-icon"
></state-badge>
<paper-item-body>
<div class="name">[[_computeEntityName(entity, hass)]]</div>
<div class="secondary entity-id">[[entity.entity_id]]</div>
</paper-item-body>
</paper-icon-item>
</template>
</div>
<template is="dom-if" if="[[!hideEntities]]">
<div class="device-entities">
<template
is="dom-repeat"
items="[[_computeDeviceEntities(hass, device, entities)]]"
as="entity"
>
<paper-icon-item on-click="_openMoreInfo">
<state-badge
state-obj="[[_computeStateObj(entity, hass)]]"
slot="item-icon"
></state-badge>
<paper-item-body>
<div class="name">[[_computeEntityName(entity, hass)]]</div>
<div class="secondary entity-id">[[entity.entity_id]]</div>
</paper-item-body>
</paper-icon-item>
</template>
</div>
</template>
</ha-card>
`;
}
@ -157,6 +159,7 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
reflectToAttribute: true,
},
hideSettings: { type: Boolean, value: false },
hideEntities: { type: Boolean, value: false },
_childDevices: {
type: Array,
computed: "_computeChildDevices(device, devices)",

View File

@ -0,0 +1,171 @@
import {
LitElement,
TemplateResult,
html,
property,
customElement,
css,
CSSResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { HomeAssistant } from "../../../../types";
import memoizeOne from "memoize-one";
import { compare } from "../../../../common/string/compare";
import "../../../../components/entity/state-badge";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
import "../../../../components/ha-switch";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { EntityRegistryEntry } from "../../../../data/entity_registry";
import { showEntityRegistryDetailDialog } from "../../entity_registry/show-dialog-entity-registry-detail";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
import { domainIcon } from "../../../../common/entity/domain_icon";
// tslint:disable-next-line
import { HaSwitch } from "../../../../components/ha-switch";
@customElement("ha-device-entities-card")
export class HaDeviceEntitiesCard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public deviceId!: string;
@property() public entities!: EntityRegistryEntry[];
@property() public narrow!: boolean;
@property() private _showDisabled = false;
private _entities = memoizeOne(
(
deviceId: string,
entities: EntityRegistryEntry[]
): EntityRegistryEntry[] =>
entities
.filter((entity) => entity.device_id === deviceId)
.sort((ent1, ent2) =>
compare(
this._computeEntityName(ent1) || `zzz${ent1.entity_id}`,
this._computeEntityName(ent2) || `zzz${ent2.entity_id}`
)
)
);
protected render(): TemplateResult {
const entities = this._entities(this.deviceId, this.entities);
return html`
<ha-card>
<paper-item>
<ha-switch
?checked=${this._showDisabled}
@change=${this._showDisabledChanged}
>${this.hass.localize(
"ui.panel.config.entity_registry.picker.show_disabled"
)}
</ha-switch>
</paper-item>
${entities.length
? entities.map((entry: EntityRegistryEntry) => {
if (!this._showDisabled && entry.disabled_by) {
return "";
}
const stateObj = this.hass.states[entry.entity_id];
return html`
<paper-icon-item
.entry=${entry}
class=${classMap({ "disabled-entry": !!entry.disabled_by })}
>
${stateObj
? html`
<state-badge
.stateObj=${stateObj}
slot="item-icon"
></state-badge>
`
: html`
<ha-icon
slot="item-icon"
.icon=${domainIcon(computeDomain(entry.entity_id))}
></ha-icon>
`}
<paper-item-body two-line>
<div class="name">${this._computeEntityName(entry)}</div>
<div class="secondary entity-id">${entry.entity_id}</div>
</paper-item-body>
<div class="buttons">
${stateObj
? html`
<paper-icon-button
@click=${this._openMoreInfo}
icon="hass:open-in-new"
></paper-icon-button>
`
: ""}
<paper-icon-button
@click=${this._openEditEntry}
icon="hass:settings"
></paper-icon-button>
</div>
</paper-icon-item>
`;
})
: html`
<div class="config-entry-row">
<paper-item-body two-line>
<div>
${this.hass.localize(
"ui.panel.config.devices.entities.none"
)}
</div>
</paper-item-body>
</div>
`}
</ha-card>
`;
}
private _showDisabledChanged(ev: Event) {
this._showDisabled = (ev.target as HaSwitch).checked;
}
private _openEditEntry(ev: MouseEvent): void {
const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry;
showEntityRegistryDetailDialog(this, {
entry,
});
}
private _openMoreInfo(ev: MouseEvent) {
const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry;
fireEvent(this, "hass-more-info", { entityId: entry.entity_id });
}
private _computeEntityName(entity) {
if (entity.name) {
return entity.name;
}
const state = this.hass.states[entity.entity_id];
return state ? computeStateName(state) : null;
}
static get styles(): CSSResult {
return css`
ha-icon {
width: 40px;
}
.entity-id {
color: var(--secondary-text-color);
}
.buttons {
text-align: right;
margin: 0 0 0 8px;
}
.disabled-entry {
color: var(--secondary-text-color);
}
`;
}
}

View File

@ -11,11 +11,12 @@ import memoizeOne from "memoize-one";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-error-screen";
import "../ha-config-section";
import "./device-detail/ha-device-card";
import "./device-detail/ha-device-triggers-card";
import "./device-detail/ha-device-conditions-card";
import "./device-detail/ha-device-actions-card";
import "./device-detail/ha-device-entities-card";
import { HomeAssistant } from "../../../types";
import { ConfigEntry } from "../../../data/config_entries";
import { EntityRegistryEntry } from "../../../data/entity_registry";
@ -37,6 +38,7 @@ export class HaConfigDevicePage extends LitElement {
@property() public entities!: EntityRegistryEntry[];
@property() public areas!: AreaRegistryEntry[];
@property() public deviceId!: string;
@property() public narrow!: boolean;
private _device = memoizeOne(
(
@ -67,7 +69,11 @@ export class HaConfigDevicePage extends LitElement {
icon="hass:settings"
@click=${this._showSettings}
></paper-icon-button>
<div class="content">
<ha-config-section .isWide=${!this.narrow}>
<span slot="header">Device info</span>
<span slot="introduction">
Here are all the details of your device.
</span>
<ha-device-card
.hass=${this.hass}
.areas=${this.areas}
@ -75,7 +81,18 @@ export class HaConfigDevicePage extends LitElement {
.device=${device}
.entities=${this.entities}
hide-settings
hide-entities
></ha-device-card>
<div class="header">Entities</div>
<ha-device-entities-card
.hass=${this.hass}
.deviceId=${this.deviceId}
.entities=${this.entities}
>
</ha-device-entities-card>
<div class="header">Automations</div>
<ha-device-triggers-card
.hass=${this.hass}
.deviceId=${this.deviceId}
@ -88,7 +105,7 @@ export class HaConfigDevicePage extends LitElement {
.hass=${this.hass}
.deviceId=${this.deviceId}
></ha-device-actions-card>
</div>
</ha-config-section>
</hass-subpage>
`;
}
@ -104,12 +121,20 @@ export class HaConfigDevicePage extends LitElement {
static get styles(): CSSResult {
return css`
.content {
padding: 16px;
.header {
font-family: var(--paper-font-display1_-_font-family);
-webkit-font-smoothing: var(
--paper-font-display1_-_-webkit-font-smoothing
);
font-size: var(--paper-font-display1_-_font-size);
font-weight: var(--paper-font-display1_-_font-weight);
letter-spacing: var(--paper-font-display1_-_letter-spacing);
line-height: var(--paper-font-display1_-_line-height);
opacity: var(--dark-primary-opacity);
}
.content > * {
display: block;
margin-bottom: 16px;
ha-config-section *:last-child {
padding-bottom: 24px;
}
`;
}

View File

@ -22,10 +22,15 @@ import { HaSwitch } from "../../../components/ha-switch";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import {
updateEntityRegistryEntry,
removeEntityRegistryEntry,
} from "../../../data/entity_registry";
class DialogEntityRegistryDetail extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _name!: string;
@property() private _platform!: string;
@property() private _entityId!: string;
@property() private _disabledBy!: string | null;
@property() private _error?: string;
@ -38,6 +43,7 @@ class DialogEntityRegistryDetail extends LitElement {
this._params = params;
this._error = undefined;
this._name = this._params.entry.name || "";
this._platform = this._params.entry.platform;
this._entityId = this._params.entry.entity_id;
this._disabledBy = this._params.entry.disabled_by;
await this.updateComplete;
@ -164,7 +170,7 @@ class DialogEntityRegistryDetail extends LitElement {
private async _updateEntry(): Promise<void> {
this._submitting = true;
try {
await this._params!.updateEntry({
await updateEntityRegistryEntry(this.hass!, this._entityId, {
name: this._name.trim() || null,
disabled_by: this._disabledBy,
new_entity_id: this._entityId.trim(),
@ -178,11 +184,27 @@ class DialogEntityRegistryDetail extends LitElement {
}
private async _deleteEntry(): Promise<void> {
if (
!confirm(
`${this.hass.localize(
"ui.panel.config.entity_registry.editor.confirm_delete"
)}
${this.hass.localize(
"ui.panel.config.entity_registry.editor.confirm_delete2",
"platform",
this._platform
)}`
)
) {
return;
}
this._submitting = true;
try {
if (await this._params!.removeEntry()) {
this._params = undefined;
}
await removeEntityRegistryEntry(this.hass!, this._entityId);
this._params = undefined;
} finally {
this._submitting = false;
}

View File

@ -13,8 +13,6 @@ import { HomeAssistant } from "../../../types";
import {
EntityRegistryEntry,
computeEntityRegistryName,
updateEntityRegistryEntry,
removeEntityRegistryEntry,
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import "../../../layouts/hass-subpage";
@ -143,35 +141,6 @@ class HaConfigEntityRegistry extends LitElement {
const entry = (ev.currentTarget! as any).entry;
showEntityRegistryDetailDialog(this, {
entry,
updateEntry: async (updates) => {
const updated = await updateEntityRegistryEntry(
this.hass!,
entry.entity_id,
updates
);
this._entities = this._entities!.map((ent) =>
ent === entry ? updated : ent
);
},
removeEntry: async () => {
if (
!confirm(`Are you sure you want to delete this entry?
Deleting an entry will not remove the entity from Home Assistant. To do this, you will need to remove the integration "${
entry.platform
}" from Home Assistant.`)
) {
return false;
}
try {
await removeEntityRegistryEntry(this.hass!, entry.entity_id);
this._entities = this._entities!.filter((ent) => ent !== entry);
return true;
} catch (err) {
return false;
}
},
});
}

View File

@ -1,15 +1,8 @@
import { fireEvent } from "../../../common/dom/fire_event";
import {
EntityRegistryEntry,
EntityRegistryEntryUpdateParams,
} from "../../../data/entity_registry";
import { EntityRegistryEntry } from "../../../data/entity_registry";
export interface EntityRegistryDetailDialogParams {
entry: EntityRegistryEntry;
updateEntry: (
updates: Partial<EntityRegistryEntryUpdateParams>
) => Promise<unknown>;
removeEntry: () => Promise<boolean>;
}
export const loadEntityRegistryDetailDialog = () =>

View File

@ -930,6 +930,8 @@
"enabled_cause": "Disabled by {cause}.",
"enabled_description": "Disabled entities will not be added to Home Assistant.",
"delete": "DELETE",
"confirm_delete": "Are you sure you want to delete this entry?",
"confirm_delete2": "Deleting an entry will not remove the entity from Home Assistant. To do this, you will need to remove the integration '{platform}' from Home Assistant.",
"update": "UPDATE"
}
},