ha-frontend/src/panels/config/devices/device-detail/ha-device-entities-card.ts

314 lines
8.8 KiB
TypeScript

import "@material/mwc-list/mwc-list";
import {
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { until } from "lit/directives/until";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { stripPrefixFromEntityName } from "../../../../common/entity/strip_prefix_from_entity_name";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
import "../../../../components/ha-list-item";
import {
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntry,
} from "../../../../data/entity_registry";
import { entryIcon } from "../../../../data/icons";
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 { EntityRegistryStateEntry } from "../ha-config-device-page";
import {
computeCards,
computeSection,
} from "../../../lovelace/common/generate-lovelace-config";
@customElement("ha-device-entities-card")
export class HaDeviceEntitiesCard extends LitElement {
@property() public header!: string;
@property() public deviceName!: string;
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public entities!: EntityRegistryStateEntry[];
@property({ type: Boolean }) public showHidden = false;
@state() private _extDisabledEntityEntries?: Record<
string,
ExtEntityRegistryEntry
>;
private _entityRows: Array<LovelaceRow | HuiErrorCard> = [];
protected shouldUpdate(changedProps: PropertyValues) {
if (changedProps.has("hass") && changedProps.size === 1) {
this._entityRows.forEach((element) => {
element.hass = this.hass;
});
return false;
}
return true;
}
protected render(): TemplateResult {
if (!this.entities.length) {
return html`
<ha-card outlined .header=${this.header}>
<div class="empty card-content">
${this.hass.localize("ui.panel.config.devices.entities.none")}
</div>
</ha-card>
`;
}
const shownEntities: EntityRegistryStateEntry[] = [];
const hiddenEntities: EntityRegistryStateEntry[] = [];
this._entityRows = [];
this.entities.forEach((entry) => {
if (entry.disabled_by) {
if (this._extDisabledEntityEntries) {
hiddenEntities.push(
this._extDisabledEntityEntries[entry.entity_id] || entry
);
} else {
hiddenEntities.push(entry);
}
} else {
shownEntities.push(entry);
}
});
return html`
<ha-card outlined .header=${this.header}>
<div id="entities">
<mwc-list>
${shownEntities.map((entry) =>
this.hass.states[entry.entity_id]
? this._renderEntity(entry)
: this._renderEntry(entry)
)}
</mwc-list>
</div>
${hiddenEntities.length
? !this.showHidden
? html`
<button class="show-more" @click=${this._toggleShowHidden}>
${this.hass.localize(
"ui.panel.config.devices.entities.hidden_entities",
{ count: hiddenEntities.length }
)}
</button>
`
: html`
<mwc-list>
${hiddenEntities.map((entry) => this._renderEntry(entry))}
</mwc-list>
<button class="show-more" @click=${this._toggleShowHidden}>
${this.hass.localize(
"ui.panel.config.devices.entities.show_less"
)}
</button>
`
: ""}
<div class="card-actions">
<mwc-button @click=${this._addToLovelaceView}>
${this.hass.localize(
"ui.panel.config.devices.entities.add_entities_lovelace"
)}
</mwc-button>
</div>
</ha-card>
`;
}
private _toggleShowHidden() {
this.showHidden = !this.showHidden;
if (!this.showHidden || this._extDisabledEntityEntries !== undefined) {
return;
}
this._extDisabledEntityEntries = {};
const toFetch = this.entities.filter((entry) => entry.disabled_by);
const worker = async () => {
if (toFetch.length === 0) {
return;
}
const entityId = toFetch.pop()!.entity_id;
const entry = await getExtendedEntityRegistryEntry(this.hass, entityId);
this._extDisabledEntityEntries![entityId] = entry;
this.requestUpdate("_extDisabledEntityEntries");
worker();
};
// Fetch 3 in parallel
worker();
worker();
worker();
}
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
const config: LovelaceRowConfig = {
entity: entry.entity_id,
};
const element = createRowElement(config);
if (this.hass) {
element.hass = this.hass;
const stateObj = this.hass.states[entry.entity_id];
let name = entry.name
? stripPrefixFromEntityName(entry.name, this.deviceName.toLowerCase())
: entry.has_entity_name
? entry.original_name || this.deviceName
: stripPrefixFromEntityName(
computeStateName(stateObj),
this.deviceName.toLowerCase()
);
if (!name) {
name = computeStateName(stateObj);
}
if (entry.hidden_by) {
name += ` (${this.hass.localize(
"ui.panel.config.devices.entities.hidden"
)})`;
}
config.name = name;
}
// @ts-ignore
element.entry = entry;
this._entityRows.push(element);
return html` <div>${element}</div> `;
}
private _renderEntry(entry: EntityRegistryStateEntry): TemplateResult {
const name =
entry.stateName ||
entry.name ||
(entry as ExtEntityRegistryEntry).original_name;
const icon = until(entryIcon(this.hass, entry));
return html`
<ha-list-item
graphic="icon"
class="disabled-entry"
.entry=${entry}
@click=${this._openEditEntry}
>
<ha-icon slot="graphic" .icon=${icon}></ha-icon>
<div class="name">
${name
? stripPrefixFromEntityName(name, this.deviceName.toLowerCase()) ||
name
: entry.entity_id}
</div>
</ha-list-item>
`;
}
private _openEditEntry(ev: Event): void {
const entry = (ev.currentTarget! as any).entry;
showMoreInfoDialog(this, { entityId: entry.entity_id });
}
private _addToLovelaceView(): void {
const entities = this.entities
.filter((entity) => !entity.disabled_by)
.map((entity) => entity.entity_id);
addEntitiesToLovelaceView(
this,
this.hass,
computeCards(this.hass.states, entities, {
title: this.deviceName,
}),
computeSection(entities, {
title: this.deviceName,
}),
entities
);
}
static get styles(): CSSResultGroup {
return css`
:host {
display: block;
}
ha-icon {
margin-left: 8px;
margin-inline-start: 8px;
margin-inline-end: initial;
}
.entity-id {
color: var(--secondary-text-color);
}
.buttons {
text-align: right;
margin: 0 0 0 8px;
}
.disabled-entry {
color: var(--secondary-text-color);
}
#entities {
margin-top: -24px; /* match the spacing between card title and content of the device info card above it */
}
#entities > * {
margin: 8px 16px 8px 8px;
}
#entities > paper-icon-item {
margin: 0;
}
paper-icon-item {
min-height: 40px;
padding: 0 16px;
cursor: pointer;
--paper-item-icon-width: 48px;
}
.name {
font-size: 14px;
}
.empty {
text-align: center;
}
button.show-more {
color: var(--primary-color);
text-align: left;
cursor: pointer;
background: none;
border-width: initial;
border-style: none;
border-color: initial;
border-image: initial;
padding: 16px;
font: inherit;
}
button.show-more:focus {
outline: none;
text-decoration: underline;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-device-entities-card": HaDeviceEntitiesCard;
}
}