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 {
[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 {
<ha-checkbox
class="mdc-data-table__row-checkbox"
@change=${this._handleRowCheckboxChange}
.disabled=${row.selectable === false}
.checked=${this._checkedRows.includes(
String(row[this.id])
)}
@ -298,9 +301,12 @@ export class HaDataTable extends BaseElement {
protected createAdapter(): MDCDataTableAdapter {
return {
addClassAtRowIndex: (rowIndex: number, cssClasses: string) => {
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 {

View File

@ -68,6 +68,13 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
if (!this._related) {
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`
${this._related.config_entry && this._entries
? 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 { 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}
></search-input>
<div style=${styleMap({ width: `${this._width}px` })}>
<div
style=${styleMap({ width: `${this._width}px` })}
class=${classMap({ advanced: Boolean(this.showAdvanced) })}
>
${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;
}

View File

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

View File

@ -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`
<ha-paper-dialog
@ -68,9 +69,7 @@ export class DialogEntityRegistryDetail extends LitElement {
dialog-dismiss
></paper-icon-button>
<div class="main-title" main-title>
${stateObj
? computeStateName(stateObj)
: entry.name || entry.entity_id}
${stateObj ? computeStateName(stateObj) : entry?.name || entityId}
</div>
${stateObj
? html`
@ -99,6 +98,7 @@ export class DialogEntityRegistryDetail extends LitElement {
</paper-tabs>
${cache(
this._curTab === "tab-settings"
? entry
? html`
<entity-registry-settings
.hass=${this.hass}
@ -107,12 +107,19 @@ export class DialogEntityRegistryDetail extends LitElement {
@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"
? html`
<paper-dialog-scrollable>
<ha-related-items
.hass=${this.hass}
.itemId=${entry.entity_id}
.itemId=${entityId}
itemType="entity"
@close-dialog=${this._closeDialog}
></ha-related-items>
@ -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;
}

View File

@ -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`
<div
tabindex="0"
@ -102,15 +117,21 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
})}
.icon=${entity.unavailable
? "hass:alert-circle"
: "hass:cancel"}
: entity.disabled_by
? "hass:cancel"
: "hass:pencil-off"}
></ha-icon>
<paper-tooltip position="left">
${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"
)}
</paper-tooltip>
</div>
@ -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);
}
);
@ -322,6 +366,15 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
"ui.panel.config.entities.picker.filter.show_unavailable"
)}
</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-menu-button>
`;
@ -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,
});
}

View File

@ -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;
};

View File

@ -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": {