From 49b0c8d5497589efd749b5533258de5b669f1e25 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 17 Feb 2020 15:02:23 +0100 Subject: [PATCH] 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 --- src/components/data-table/ha-data-table.ts | 15 ++- src/components/ha-related-items.ts | 7 ++ .../config-flow/step-flow-pick-handler.ts | 14 ++- .../device-detail/ha-device-entities-card.ts | 1 + .../entities/dialog-entity-registry-detail.ts | 35 ++++--- .../config/entities/ha-config-entities.ts | 99 +++++++++++++++---- .../show-dialog-entity-registry-detail.ts | 7 +- src/translations/en.json | 6 +- 8 files changed, 142 insertions(+), 42 deletions(-) diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index a6a9e3a7e2..28b657cd1c 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -80,6 +80,7 @@ export interface DataTableColumnData extends DataTableSortColumnData { export interface DataTableRowData { [key: string]: any; + selectable?: boolean; } @customElement("ha-data-table") @@ -249,6 +250,7 @@ export class HaDataTable extends BaseElement { data-row-id="${row[this.id]}" @click=${this._handleRowClick} class="mdc-data-table__row" + .selectable=${row.selectable !== false} > ${this.selectable ? html` @@ -258,6 +260,7 @@ export class HaDataTable extends BaseElement { { + if (!(this.rowElements[rowIndex] as any).selectable) { + return; + } this.rowElements[rowIndex].classList.add(cssClasses); }, - getRowCount: () => this.data.length, + getRowCount: () => this.rowElements.length, getRowElements: () => this.rowElements, getRowIdAtIndex: (rowIndex: number) => this._getRowIdAtIndex(rowIndex), getRowIndexByChildElement: (el: Element) => @@ -309,7 +315,7 @@ export class HaDataTable extends BaseElement { isCheckboxAtRowIndexChecked: (rowIndex: number) => this._checkedRows.includes(this._getRowIdAtIndex(rowIndex)), isHeaderRowCheckboxChecked: () => this._headerChecked, - isRowsSelectable: () => true, + isRowsSelectable: () => this.selectable, notifyRowSelectionChanged: () => undefined, notifySelectedAll: () => undefined, notifyUnselectedAll: () => undefined, @@ -332,6 +338,9 @@ export class HaDataTable extends BaseElement { this._headerIndeterminate = indeterminate; }, setRowCheckboxCheckedAtIndex: (rowIndex: number, checked: boolean) => { + if (!(this.rowElements[rowIndex] as any).selectable) { + return; + } this._setRowChecked(this._getRowIdAtIndex(rowIndex), checked); }, }; @@ -516,6 +525,7 @@ export class HaDataTable extends BaseElement { padding-left: 16px; /* @noflip */ padding-right: 0; + width: 40px; } [dir="rtl"] .mdc-data-table__header-cell--checkbox, .mdc-data-table__header-cell--checkbox[dir="rtl"], @@ -558,6 +568,7 @@ export class HaDataTable extends BaseElement { .mdc-data-table__cell--icon { color: var(--secondary-text-color); text-align: center; + width: 24px; } .mdc-data-table__header-cell { diff --git a/src/components/ha-related-items.ts b/src/components/ha-related-items.ts index ab459ce0c4..d1b900578b 100644 --- a/src/components/ha-related-items.ts +++ b/src/components/ha-related-items.ts @@ -68,6 +68,13 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) { if (!this._related) { return html``; } + if (Object.keys(this._related).length === 0) { + return html` +

+ ${this.hass.localize("ui.components.related-items.no_related_found")} +

+ `; + } return html` ${this._related.config_entry && this._entries ? this._related.config_entry.map((relatedConfigEntryId) => { diff --git a/src/dialogs/config-flow/step-flow-pick-handler.ts b/src/dialogs/config-flow/step-flow-pick-handler.ts index 8b09967cc7..a20e8de4c8 100644 --- a/src/dialogs/config-flow/step-flow-pick-handler.ts +++ b/src/dialogs/config-flow/step-flow-pick-handler.ts @@ -20,6 +20,7 @@ import "../../common/search/search-input"; import { styleMap } from "lit-html/directives/style-map"; import { FlowConfig } from "./show-dialog-data-entry-flow"; import { configFlowContentStyles } from "./styles"; +import { classMap } from "lit-html/directives/class-map"; interface HandlerObj { name: string; @@ -69,7 +70,10 @@ class StepFlowPickHandler extends LitElement { .filter=${this.filter} @value-changed=${this._filterChanged} > -
+
${handlers.map( (handler: HandlerObj) => html` @@ -143,6 +147,14 @@ class StepFlowPickHandler extends LitElement { overflow: auto; max-height: 600px; } + @media all and (max-height: 1px) { + div { + max-height: calc(100vh - 205px); + } + div.advanced { + max-height: calc(100vh - 300px); + } + } paper-item { cursor: pointer; } diff --git a/src/panels/config/devices/device-detail/ha-device-entities-card.ts b/src/panels/config/devices/device-detail/ha-device-entities-card.ts index 60c9301c51..1a35d70e35 100644 --- a/src/panels/config/devices/device-detail/ha-device-entities-card.ts +++ b/src/panels/config/devices/device-detail/ha-device-entities-card.ts @@ -152,6 +152,7 @@ export class HaDeviceEntitiesCard extends LitElement { const entry = (ev.currentTarget! as any).entry; showEntityRegistryDetailDialog(this, { entry, + entity_id: entry.entity_id, }); } diff --git a/src/panels/config/entities/dialog-entity-registry-detail.ts b/src/panels/config/entities/dialog-entity-registry-detail.ts index 440d557417..fbd9a94543 100644 --- a/src/panels/config/entities/dialog-entity-registry-detail.ts +++ b/src/panels/config/entities/dialog-entity-registry-detail.ts @@ -51,7 +51,8 @@ export class DialogEntityRegistryDetail extends LitElement { return html``; } 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`
- ${stateObj - ? computeStateName(stateObj) - : entry.name || entry.entity_id} + ${stateObj ? computeStateName(stateObj) : entry?.name || entityId}
${stateObj ? html` @@ -99,20 +98,28 @@ export class DialogEntityRegistryDetail extends LitElement { ${cache( this._curTab === "tab-settings" - ? html` - - ` + ? entry + ? html` + + ` + : html` + + ${this.hass.localize( + "ui.dialogs.entity_registry.no_unique_id" + )} + + ` : this._curTab === "tab-related" ? html` @@ -139,7 +146,7 @@ export class DialogEntityRegistryDetail extends LitElement { private _openMoreInfo(): void { fireEvent(this, "hass-more-info", { - entityId: this._params!.entry.entity_id, + entityId: this._params!.entity_id, }); this._params = undefined; } diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 77282d6f94..7cc43a7fe1 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -3,7 +3,7 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-tooltip/paper-tooltip"; -import { UnsubscribeFunc } from "home-assistant-js-websocket"; +import { UnsubscribeFunc, HassEntities } from "home-assistant-js-websocket"; import { css, CSSResult, @@ -22,7 +22,6 @@ import { stateIcon } from "../../../common/entity/state_icon"; import { DataTableColumnContainer, DataTableColumnData, - HaDataTable, RowClickedEvent, SelectionChangedEvent, } from "../../../components/data-table/ha-data-table"; @@ -47,6 +46,20 @@ import { } from "./show-dialog-entity-registry-detail"; import { configSections } from "../ha-panel-config"; 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") export class HaConfigEntities extends SubscribeMixin(LitElement) { @@ -57,9 +70,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { @property() private _entities?: EntityRegistryEntry[]; @property() private _showDisabled = false; @property() private _showUnavailable = true; + @property() private _showReadOnly = true; @property() private _filter = ""; @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 _columns = memoize( @@ -90,7 +105,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { sortable: true, filterable: true, template: (_status, entity: any) => - entity.unavailable || entity.disabled_by + entity.unavailable || entity.disabled_by || entity.readonly ? html`
${entity.unavailable ? this.hass.localize( "ui.panel.config.entities.picker.status.unavailable" ) - : this.hass.localize( + : entity.disabled_by + ? this.hass.localize( "ui.panel.config.entities.picker.status.disabled" + ) + : this.hass.localize( + "ui.panel.config.entities.picker.status.readonly" )}
@@ -156,21 +177,43 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { private _filteredEntities = memoize( ( entities: EntityRegistryEntry[], + states: HassEntities, 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) { entities = entities.filter((entity) => !Boolean(entity.disabled_by)); } - return entities.reduce((result, entry) => { - const state = this.hass!.states[entry.entity_id]; + const result: EntityRow[] = []; - const unavailable = - state && (state.state === "unavailable" || state.attributes.restored); // if there is not state it is disabled + for (const entry of entities.concat(stateEntities)) { + const state = states[entry.entity_id]; + const unavailable = state?.state === "unavailable"; if (!showUnavailable && unavailable) { - return result; + continue; } result.push({ @@ -192,8 +235,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { ) : 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" )} + + + ${this.hass!.localize( + "ui.panel.config.entities.picker.filter.show_readonly" + )} + `; @@ -336,8 +389,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { .columns=${this._columns(this.narrow, this.hass.language)} .data=${this._filteredEntities( this._entities, + this.hass.states, this._showDisabled, - this._showUnavailable + this._showUnavailable, + this._showReadOnly )} .filter=${this._filter} selectable @@ -369,6 +424,10 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { this._showUnavailable = !this._showUnavailable; } + private _showReadOnlyChanged() { + this._showReadOnly = !this._showReadOnly; + } + private _handleSearchChange(ev: CustomEvent) { this._filter = ev.detail.value; } @@ -461,15 +520,13 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) { } private _openEditEntry(ev: CustomEvent): void { - const entryId = (ev.detail as RowClickedEvent).id; + const entityId = (ev.detail as RowClickedEvent).id; const entry = this._entities!.find( - (entity) => entity.entity_id === entryId + (entity) => entity.entity_id === entityId ); - if (!entry) { - return; - } this.getDialog = showEntityRegistryDetailDialog(this, { entry, + entity_id: entityId, }); } diff --git a/src/panels/config/entities/show-dialog-entity-registry-detail.ts b/src/panels/config/entities/show-dialog-entity-registry-detail.ts index 4549579af4..b4b47b26f0 100644 --- a/src/panels/config/entities/show-dialog-entity-registry-detail.ts +++ b/src/panels/config/entities/show-dialog-entity-registry-detail.ts @@ -3,7 +3,8 @@ import { EntityRegistryEntry } from "../../../data/entity_registry"; import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail"; export interface EntityRegistryDetailDialogParams { - entry: EntityRegistryEntry; + entry?: EntityRegistryEntry; + entity_id: string; } export const loadEntityRegistryDetailDialog = () => @@ -21,12 +22,12 @@ const getDialog = () => { export const showEntityRegistryDetailDialog = ( element: HTMLElement, - systemLogDetailParams: EntityRegistryDetailDialogParams + entityDetailParams: EntityRegistryDetailDialogParams ): (() => DialogEntityRegistryDetail | undefined) => { fireEvent(element, "show-dialog", { dialogTag: "dialog-entity-registry-detail", dialogImport: loadEntityRegistryDetailDialog, - dialogParams: systemLogDetailParams, + dialogParams: entityDetailParams, }); return getDialog; }; diff --git a/src/translations/en.json b/src/translations/en.json index 771ac55b29..3ffe6d3d53 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -572,6 +572,7 @@ "service": "Service" }, "related-items": { + "no_related_found": "No related items found.", "integration": "Integration", "device": "Device", "area": "Area", @@ -640,6 +641,7 @@ "control": "Control", "related": "Related", "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": { "name": "Name Override", "entity_id": "Entity ID", @@ -1345,11 +1347,13 @@ "filter": { "filter": "Filter", "show_disabled": "Show disabled entities", - "show_unavailable": "Show unavailable entities" + "show_unavailable": "Show unavailable entities", + "show_readonly": "Show read-only entities" }, "status": { "unavailable": "Unavailable", "disabled": "Disabled", + "readonly": "Read-only", "ok": "Ok" }, "headers": {