314 lines
8.8 KiB
TypeScript
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;
|
|
}
|
|
}
|