include entities not in entity registry in config entities (#4867)

* include entities not in entity registry in config entities

* Update ha-data-table.ts

* Comments

* Update ha-device-entities-card.ts

* Comments
This commit is contained in:
Bram Kragten 2020-02-17 15:02:23 +01:00 committed by GitHub
parent 8f9a6bd544
commit 49b0c8d549
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 142 additions and 42 deletions

View File

@ -80,6 +80,7 @@ export interface DataTableColumnData extends DataTableSortColumnData {
export interface DataTableRowData { export interface DataTableRowData {
[key: string]: any; [key: string]: any;
selectable?: boolean;
} }
@customElement("ha-data-table") @customElement("ha-data-table")
@ -249,6 +250,7 @@ export class HaDataTable extends BaseElement {
data-row-id="${row[this.id]}" data-row-id="${row[this.id]}"
@click=${this._handleRowClick} @click=${this._handleRowClick}
class="mdc-data-table__row" class="mdc-data-table__row"
.selectable=${row.selectable !== false}
> >
${this.selectable ${this.selectable
? html` ? html`
@ -258,6 +260,7 @@ export class HaDataTable extends BaseElement {
<ha-checkbox <ha-checkbox
class="mdc-data-table__row-checkbox" class="mdc-data-table__row-checkbox"
@change=${this._handleRowCheckboxChange} @change=${this._handleRowCheckboxChange}
.disabled=${row.selectable === false}
.checked=${this._checkedRows.includes( .checked=${this._checkedRows.includes(
String(row[this.id]) String(row[this.id])
)} )}
@ -298,9 +301,12 @@ export class HaDataTable extends BaseElement {
protected createAdapter(): MDCDataTableAdapter { protected createAdapter(): MDCDataTableAdapter {
return { return {
addClassAtRowIndex: (rowIndex: number, cssClasses: string) => { addClassAtRowIndex: (rowIndex: number, cssClasses: string) => {
if (!(this.rowElements[rowIndex] as any).selectable) {
return;
}
this.rowElements[rowIndex].classList.add(cssClasses); this.rowElements[rowIndex].classList.add(cssClasses);
}, },
getRowCount: () => this.data.length, getRowCount: () => this.rowElements.length,
getRowElements: () => this.rowElements, getRowElements: () => this.rowElements,
getRowIdAtIndex: (rowIndex: number) => this._getRowIdAtIndex(rowIndex), getRowIdAtIndex: (rowIndex: number) => this._getRowIdAtIndex(rowIndex),
getRowIndexByChildElement: (el: Element) => getRowIndexByChildElement: (el: Element) =>
@ -309,7 +315,7 @@ export class HaDataTable extends BaseElement {
isCheckboxAtRowIndexChecked: (rowIndex: number) => isCheckboxAtRowIndexChecked: (rowIndex: number) =>
this._checkedRows.includes(this._getRowIdAtIndex(rowIndex)), this._checkedRows.includes(this._getRowIdAtIndex(rowIndex)),
isHeaderRowCheckboxChecked: () => this._headerChecked, isHeaderRowCheckboxChecked: () => this._headerChecked,
isRowsSelectable: () => true, isRowsSelectable: () => this.selectable,
notifyRowSelectionChanged: () => undefined, notifyRowSelectionChanged: () => undefined,
notifySelectedAll: () => undefined, notifySelectedAll: () => undefined,
notifyUnselectedAll: () => undefined, notifyUnselectedAll: () => undefined,
@ -332,6 +338,9 @@ export class HaDataTable extends BaseElement {
this._headerIndeterminate = indeterminate; this._headerIndeterminate = indeterminate;
}, },
setRowCheckboxCheckedAtIndex: (rowIndex: number, checked: boolean) => { setRowCheckboxCheckedAtIndex: (rowIndex: number, checked: boolean) => {
if (!(this.rowElements[rowIndex] as any).selectable) {
return;
}
this._setRowChecked(this._getRowIdAtIndex(rowIndex), checked); this._setRowChecked(this._getRowIdAtIndex(rowIndex), checked);
}, },
}; };
@ -516,6 +525,7 @@ export class HaDataTable extends BaseElement {
padding-left: 16px; padding-left: 16px;
/* @noflip */ /* @noflip */
padding-right: 0; padding-right: 0;
width: 40px;
} }
[dir="rtl"] .mdc-data-table__header-cell--checkbox, [dir="rtl"] .mdc-data-table__header-cell--checkbox,
.mdc-data-table__header-cell--checkbox[dir="rtl"], .mdc-data-table__header-cell--checkbox[dir="rtl"],
@ -558,6 +568,7 @@ export class HaDataTable extends BaseElement {
.mdc-data-table__cell--icon { .mdc-data-table__cell--icon {
color: var(--secondary-text-color); color: var(--secondary-text-color);
text-align: center; text-align: center;
width: 24px;
} }
.mdc-data-table__header-cell { .mdc-data-table__header-cell {

View File

@ -68,6 +68,13 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
if (!this._related) { if (!this._related) {
return html``; return html``;
} }
if (Object.keys(this._related).length === 0) {
return html`
<p>
${this.hass.localize("ui.components.related-items.no_related_found")}
</p>
`;
}
return html` return html`
${this._related.config_entry && this._entries ${this._related.config_entry && this._entries
? this._related.config_entry.map((relatedConfigEntryId) => { ? this._related.config_entry.map((relatedConfigEntryId) => {

View File

@ -20,6 +20,7 @@ import "../../common/search/search-input";
import { styleMap } from "lit-html/directives/style-map"; import { styleMap } from "lit-html/directives/style-map";
import { FlowConfig } from "./show-dialog-data-entry-flow"; import { FlowConfig } from "./show-dialog-data-entry-flow";
import { configFlowContentStyles } from "./styles"; import { configFlowContentStyles } from "./styles";
import { classMap } from "lit-html/directives/class-map";
interface HandlerObj { interface HandlerObj {
name: string; name: string;
@ -69,7 +70,10 @@ class StepFlowPickHandler extends LitElement {
.filter=${this.filter} .filter=${this.filter}
@value-changed=${this._filterChanged} @value-changed=${this._filterChanged}
></search-input> ></search-input>
<div style=${styleMap({ width: `${this._width}px` })}> <div
style=${styleMap({ width: `${this._width}px` })}
class=${classMap({ advanced: Boolean(this.showAdvanced) })}
>
${handlers.map( ${handlers.map(
(handler: HandlerObj) => (handler: HandlerObj) =>
html` html`
@ -143,6 +147,14 @@ class StepFlowPickHandler extends LitElement {
overflow: auto; overflow: auto;
max-height: 600px; max-height: 600px;
} }
@media all and (max-height: 1px) {
div {
max-height: calc(100vh - 205px);
}
div.advanced {
max-height: calc(100vh - 300px);
}
}
paper-item { paper-item {
cursor: pointer; cursor: pointer;
} }

View File

@ -152,6 +152,7 @@ export class HaDeviceEntitiesCard extends LitElement {
const entry = (ev.currentTarget! as any).entry; const entry = (ev.currentTarget! as any).entry;
showEntityRegistryDetailDialog(this, { showEntityRegistryDetailDialog(this, {
entry, entry,
entity_id: entry.entity_id,
}); });
} }

View File

@ -51,7 +51,8 @@ export class DialogEntityRegistryDetail extends LitElement {
return html``; return html``;
} }
const entry = this._params.entry; const entry = this._params.entry;
const stateObj: HassEntity | undefined = this.hass.states[entry.entity_id]; const entityId = this._params.entity_id;
const stateObj: HassEntity | undefined = this.hass.states[entityId];
return html` return html`
<ha-paper-dialog <ha-paper-dialog
@ -68,9 +69,7 @@ export class DialogEntityRegistryDetail extends LitElement {
dialog-dismiss dialog-dismiss
></paper-icon-button> ></paper-icon-button>
<div class="main-title" main-title> <div class="main-title" main-title>
${stateObj ${stateObj ? computeStateName(stateObj) : entry?.name || entityId}
? computeStateName(stateObj)
: entry.name || entry.entity_id}
</div> </div>
${stateObj ${stateObj
? html` ? html`
@ -99,20 +98,28 @@ export class DialogEntityRegistryDetail extends LitElement {
</paper-tabs> </paper-tabs>
${cache( ${cache(
this._curTab === "tab-settings" this._curTab === "tab-settings"
? html` ? entry
<entity-registry-settings ? html`
.hass=${this.hass} <entity-registry-settings
.entry=${entry} .hass=${this.hass}
.dialogElement=${this._dialog} .entry=${entry}
@close-dialog=${this._closeDialog} .dialogElement=${this._dialog}
></entity-registry-settings> @close-dialog=${this._closeDialog}
` ></entity-registry-settings>
`
: html`
<paper-dialog-scrollable>
${this.hass.localize(
"ui.dialogs.entity_registry.no_unique_id"
)}
</paper-dialog-scrollable>
`
: this._curTab === "tab-related" : this._curTab === "tab-related"
? html` ? html`
<paper-dialog-scrollable> <paper-dialog-scrollable>
<ha-related-items <ha-related-items
.hass=${this.hass} .hass=${this.hass}
.itemId=${entry.entity_id} .itemId=${entityId}
itemType="entity" itemType="entity"
@close-dialog=${this._closeDialog} @close-dialog=${this._closeDialog}
></ha-related-items> ></ha-related-items>
@ -139,7 +146,7 @@ export class DialogEntityRegistryDetail extends LitElement {
private _openMoreInfo(): void { private _openMoreInfo(): void {
fireEvent(this, "hass-more-info", { fireEvent(this, "hass-more-info", {
entityId: this._params!.entry.entity_id, entityId: this._params!.entity_id,
}); });
this._params = undefined; this._params = undefined;
} }

View File

@ -3,7 +3,7 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-tooltip/paper-tooltip"; import "@polymer/paper-tooltip/paper-tooltip";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc, HassEntities } from "home-assistant-js-websocket";
import { import {
css, css,
CSSResult, CSSResult,
@ -22,7 +22,6 @@ import { stateIcon } from "../../../common/entity/state_icon";
import { import {
DataTableColumnContainer, DataTableColumnContainer,
DataTableColumnData, DataTableColumnData,
HaDataTable,
RowClickedEvent, RowClickedEvent,
SelectionChangedEvent, SelectionChangedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
@ -47,6 +46,20 @@ import {
} from "./show-dialog-entity-registry-detail"; } from "./show-dialog-entity-registry-detail";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import { computeStateName } from "../../../common/entity/compute_state_name";
// tslint:disable-next-line: no-duplicate-imports
import { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
export interface StateEntity extends EntityRegistryEntry {
readonly?: boolean;
selectable?: boolean;
}
export interface EntityRow extends StateEntity {
icon: string;
unavailable: boolean;
status: string;
}
@customElement("ha-config-entities") @customElement("ha-config-entities")
export class HaConfigEntities extends SubscribeMixin(LitElement) { export class HaConfigEntities extends SubscribeMixin(LitElement) {
@ -57,9 +70,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
@property() private _entities?: EntityRegistryEntry[]; @property() private _entities?: EntityRegistryEntry[];
@property() private _showDisabled = false; @property() private _showDisabled = false;
@property() private _showUnavailable = true; @property() private _showUnavailable = true;
@property() private _showReadOnly = true;
@property() private _filter = ""; @property() private _filter = "";
@property() private _selectedEntities: string[] = []; @property() private _selectedEntities: string[] = [];
@query("ha-data-table") private _dataTable!: HaDataTable; @query("hass-tabs-subpage-data-table")
private _dataTable!: HaTabsSubpageDataTable;
private getDialog?: () => DialogEntityRegistryDetail | undefined; private getDialog?: () => DialogEntityRegistryDetail | undefined;
private _columns = memoize( private _columns = memoize(
@ -90,7 +105,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
sortable: true, sortable: true,
filterable: true, filterable: true,
template: (_status, entity: any) => template: (_status, entity: any) =>
entity.unavailable || entity.disabled_by entity.unavailable || entity.disabled_by || entity.readonly
? html` ? html`
<div <div
tabindex="0" tabindex="0"
@ -102,15 +117,21 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
})} })}
.icon=${entity.unavailable .icon=${entity.unavailable
? "hass:alert-circle" ? "hass:alert-circle"
: "hass:cancel"} : entity.disabled_by
? "hass:cancel"
: "hass:pencil-off"}
></ha-icon> ></ha-icon>
<paper-tooltip position="left"> <paper-tooltip position="left">
${entity.unavailable ${entity.unavailable
? this.hass.localize( ? this.hass.localize(
"ui.panel.config.entities.picker.status.unavailable" "ui.panel.config.entities.picker.status.unavailable"
) )
: this.hass.localize( : entity.disabled_by
? this.hass.localize(
"ui.panel.config.entities.picker.status.disabled" "ui.panel.config.entities.picker.status.disabled"
)
: this.hass.localize(
"ui.panel.config.entities.picker.status.readonly"
)} )}
</paper-tooltip> </paper-tooltip>
</div> </div>
@ -156,21 +177,43 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
private _filteredEntities = memoize( private _filteredEntities = memoize(
( (
entities: EntityRegistryEntry[], entities: EntityRegistryEntry[],
states: HassEntities,
showDisabled: boolean, showDisabled: boolean,
showUnavailable: boolean showUnavailable: boolean,
) => { showReadOnly: boolean
): EntityRow[] => {
const stateEntities: StateEntity[] = [];
if (showReadOnly) {
const regEntityIds = new Set(
entities.map((entity) => entity.entity_id)
);
for (const entityId of Object.keys(states)) {
if (regEntityIds.has(entityId)) {
continue;
}
stateEntities.push({
name: computeStateName(states[entityId]),
entity_id: entityId,
platform: computeDomain(entityId),
disabled_by: null,
readonly: true,
selectable: false,
});
}
}
if (!showDisabled) { if (!showDisabled) {
entities = entities.filter((entity) => !Boolean(entity.disabled_by)); entities = entities.filter((entity) => !Boolean(entity.disabled_by));
} }
return entities.reduce((result, entry) => { const result: EntityRow[] = [];
const state = this.hass!.states[entry.entity_id];
const unavailable = for (const entry of entities.concat(stateEntities)) {
state && (state.state === "unavailable" || state.attributes.restored); // if there is not state it is disabled const state = states[entry.entity_id];
const unavailable = state?.state === "unavailable";
if (!showUnavailable && unavailable) { if (!showUnavailable && unavailable) {
return result; continue;
} }
result.push({ result.push({
@ -192,8 +235,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
) )
: this.hass.localize("ui.panel.config.entities.picker.status.ok"), : this.hass.localize("ui.panel.config.entities.picker.status.ok"),
}); });
return result; }
}, [] as any);
return result;
} }
); );
@ -322,6 +366,15 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
"ui.panel.config.entities.picker.filter.show_unavailable" "ui.panel.config.entities.picker.filter.show_unavailable"
)} )}
</paper-icon-item> </paper-icon-item>
<paper-icon-item @tap="${this._showReadOnlyChanged}">
<paper-checkbox
.checked=${this._showReadOnly}
slot="item-icon"
></paper-checkbox>
${this.hass!.localize(
"ui.panel.config.entities.picker.filter.show_readonly"
)}
</paper-icon-item>
</paper-listbox> </paper-listbox>
</paper-menu-button> </paper-menu-button>
`; `;
@ -336,8 +389,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
.columns=${this._columns(this.narrow, this.hass.language)} .columns=${this._columns(this.narrow, this.hass.language)}
.data=${this._filteredEntities( .data=${this._filteredEntities(
this._entities, this._entities,
this.hass.states,
this._showDisabled, this._showDisabled,
this._showUnavailable this._showUnavailable,
this._showReadOnly
)} )}
.filter=${this._filter} .filter=${this._filter}
selectable selectable
@ -369,6 +424,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
this._showUnavailable = !this._showUnavailable; this._showUnavailable = !this._showUnavailable;
} }
private _showReadOnlyChanged() {
this._showReadOnly = !this._showReadOnly;
}
private _handleSearchChange(ev: CustomEvent) { private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value; this._filter = ev.detail.value;
} }
@ -461,15 +520,13 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
} }
private _openEditEntry(ev: CustomEvent): void { private _openEditEntry(ev: CustomEvent): void {
const entryId = (ev.detail as RowClickedEvent).id; const entityId = (ev.detail as RowClickedEvent).id;
const entry = this._entities!.find( const entry = this._entities!.find(
(entity) => entity.entity_id === entryId (entity) => entity.entity_id === entityId
); );
if (!entry) {
return;
}
this.getDialog = showEntityRegistryDetailDialog(this, { this.getDialog = showEntityRegistryDetailDialog(this, {
entry, entry,
entity_id: entityId,
}); });
} }

View File

@ -3,7 +3,8 @@ import { EntityRegistryEntry } from "../../../data/entity_registry";
import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail"; import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail";
export interface EntityRegistryDetailDialogParams { export interface EntityRegistryDetailDialogParams {
entry: EntityRegistryEntry; entry?: EntityRegistryEntry;
entity_id: string;
} }
export const loadEntityRegistryDetailDialog = () => export const loadEntityRegistryDetailDialog = () =>
@ -21,12 +22,12 @@ const getDialog = () => {
export const showEntityRegistryDetailDialog = ( export const showEntityRegistryDetailDialog = (
element: HTMLElement, element: HTMLElement,
systemLogDetailParams: EntityRegistryDetailDialogParams entityDetailParams: EntityRegistryDetailDialogParams
): (() => DialogEntityRegistryDetail | undefined) => { ): (() => DialogEntityRegistryDetail | undefined) => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-entity-registry-detail", dialogTag: "dialog-entity-registry-detail",
dialogImport: loadEntityRegistryDetailDialog, dialogImport: loadEntityRegistryDetailDialog,
dialogParams: systemLogDetailParams, dialogParams: entityDetailParams,
}); });
return getDialog; return getDialog;
}; };

View File

@ -572,6 +572,7 @@
"service": "Service" "service": "Service"
}, },
"related-items": { "related-items": {
"no_related_found": "No related items found.",
"integration": "Integration", "integration": "Integration",
"device": "Device", "device": "Device",
"area": "Area", "area": "Area",
@ -640,6 +641,7 @@
"control": "Control", "control": "Control",
"related": "Related", "related": "Related",
"dismiss": "Dismiss", "dismiss": "Dismiss",
"no_unique_id": "This entity does not have a unique ID, therefore it's settings can not be managed from the UI.",
"editor": { "editor": {
"name": "Name Override", "name": "Name Override",
"entity_id": "Entity ID", "entity_id": "Entity ID",
@ -1345,11 +1347,13 @@
"filter": { "filter": {
"filter": "Filter", "filter": "Filter",
"show_disabled": "Show disabled entities", "show_disabled": "Show disabled entities",
"show_unavailable": "Show unavailable entities" "show_unavailable": "Show unavailable entities",
"show_readonly": "Show read-only entities"
}, },
"status": { "status": {
"unavailable": "Unavailable", "unavailable": "Unavailable",
"disabled": "Disabled", "disabled": "Disabled",
"readonly": "Read-only",
"ok": "Ok" "ok": "Ok"
}, },
"headers": { "headers": {