Migrate entity registry to data-table (#3965)

* Migrate entity registry to data-table

* icons

* Styling

* Review comments

* fix not selector

* typos + move columns out of class

* Localize + comments

* Fucked up the rebase
This commit is contained in:
Bram Kragten 2019-10-11 14:55:45 +02:00 committed by GitHub
parent 6f7ea03e35
commit 90526ac563
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 158 additions and 114 deletions

View File

@ -59,31 +59,31 @@ export interface SortingChangedEvent {
export type SortingDirection = "desc" | "asc" | null; export type SortingDirection = "desc" | "asc" | null;
export interface DataTabelColumnContainer { export interface DataTableColumnContainer {
[key: string]: DataTabelColumnData; [key: string]: DataTableColumnData;
} }
export interface DataTabelSortColumnData { export interface DataTableSortColumnData {
sortable?: boolean; sortable?: boolean;
filterable?: boolean; filterable?: boolean;
filterKey?: string; filterKey?: string;
direction?: SortingDirection; direction?: SortingDirection;
} }
export interface DataTabelColumnData extends DataTabelSortColumnData { export interface DataTableColumnData extends DataTableSortColumnData {
title: string; title: string;
type?: "numeric"; type?: "numeric" | "icon";
template?: (data: any, row: DataTabelRowData) => TemplateResult; template?: <T>(data: any, row: T) => TemplateResult;
} }
export interface DataTabelRowData { export interface DataTableRowData {
[key: string]: any; [key: string]: any;
} }
@customElement("ha-data-table") @customElement("ha-data-table")
export class HaDataTable extends BaseElement { export class HaDataTable extends BaseElement {
@property({ type: Object }) public columns: DataTabelColumnContainer = {}; @property({ type: Object }) public columns: DataTableColumnContainer = {};
@property({ type: Array }) public data: DataTabelRowData[] = []; @property({ type: Array }) public data: DataTableRowData[] = [];
@property({ type: Boolean }) public selectable = false; @property({ type: Boolean }) public selectable = false;
@property({ type: String }) public id = "id"; @property({ type: String }) public id = "id";
protected mdcFoundation!: MDCDataTableFoundation; protected mdcFoundation!: MDCDataTableFoundation;
@ -98,9 +98,9 @@ export class HaDataTable extends BaseElement {
@property({ type: String }) private _filter = ""; @property({ type: String }) private _filter = "";
@property({ type: String }) private _sortColumn?: string; @property({ type: String }) private _sortColumn?: string;
@property({ type: String }) private _sortDirection: SortingDirection = null; @property({ type: String }) private _sortDirection: SortingDirection = null;
@property({ type: Array }) private _filteredData: DataTabelRowData[] = []; @property({ type: Array }) private _filteredData: DataTableRowData[] = [];
private _sortColumns: { private _sortColumns: {
[key: string]: DataTabelSortColumnData; [key: string]: DataTableSortColumnData;
} = {}; } = {};
private curRequest = 0; private curRequest = 0;
private _worker: any | undefined; private _worker: any | undefined;
@ -134,8 +134,8 @@ export class HaDataTable extends BaseElement {
} }
} }
const clonedColumns: DataTabelColumnContainer = deepClone(this.columns); const clonedColumns: DataTableColumnContainer = deepClone(this.columns);
Object.values(clonedColumns).forEach((column: DataTabelColumnData) => { Object.values(clonedColumns).forEach((column: DataTableColumnData) => {
delete column.title; delete column.title;
delete column.type; delete column.type;
delete column.template; delete column.template;
@ -190,9 +190,12 @@ export class HaDataTable extends BaseElement {
const [key, column] = columnEntry; const [key, column] = columnEntry;
const sorted = key === this._sortColumn; const sorted = key === this._sortColumn;
const classes = { const classes = {
"mdc-data-table__cell--numeric": Boolean( "mdc-data-table__header-cell--numeric": Boolean(
column.type && column.type === "numeric" column.type && column.type === "numeric"
), ),
"mdc-data-table__header-cell--icon": Boolean(
column.type && column.type === "icon"
),
sortable: Boolean(column.sortable), sortable: Boolean(column.sortable),
"not-sorted": Boolean(column.sortable && !sorted), "not-sorted": Boolean(column.sortable && !sorted),
}; };
@ -222,8 +225,8 @@ export class HaDataTable extends BaseElement {
<tbody class="mdc-data-table__content"> <tbody class="mdc-data-table__content">
${repeat( ${repeat(
this._filteredData!, this._filteredData!,
(row: DataTabelRowData) => row[this.id], (row: DataTableRowData) => row[this.id],
(row: DataTabelRowData) => html` (row: DataTableRowData) => html`
<tr <tr
data-row-id="${row[this.id]}" data-row-id="${row[this.id]}"
@click=${this._handleRowClick} @click=${this._handleRowClick}
@ -251,6 +254,9 @@ export class HaDataTable extends BaseElement {
"mdc-data-table__cell--numeric": Boolean( "mdc-data-table__cell--numeric": Boolean(
column.type && column.type === "numeric" column.type && column.type === "numeric"
), ),
"mdc-data-table__cell--icon": Boolean(
column.type && column.type === "icon"
),
})}" })}"
> >
${column.template ${column.template
@ -516,6 +522,11 @@ export class HaDataTable extends BaseElement {
text-align: left; text-align: left;
} }
.mdc-data-table__cell--icon {
color: var(--secondary-text-color);
text-align: center;
}
.mdc-data-table__header-cell { .mdc-data-table__header-cell {
font-family: Roboto, sans-serif; font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@ -543,6 +554,10 @@ export class HaDataTable extends BaseElement {
text-align: left; text-align: left;
} }
.mdc-data-table__header-cell--icon {
text-align: center;
}
/* custom from here */ /* custom from here */
.mdc-data-table { .mdc-data-table {
@ -554,7 +569,7 @@ export class HaDataTable extends BaseElement {
.mdc-data-table__header-cell.sortable { .mdc-data-table__header-cell.sortable {
cursor: pointer; cursor: pointer;
} }
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__cell--numeric) .mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon)
span { span {
position: relative; position: relative;
left: -24px; left: -24px;
@ -565,7 +580,7 @@ export class HaDataTable extends BaseElement {
.mdc-data-table__header-cell.not-sorted ha-icon { .mdc-data-table__header-cell.not-sorted ha-icon {
left: -36px; left: -36px;
} }
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__cell--numeric):hover .mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon):hover
span { span {
left: 0px; left: 0px;
} }

View File

@ -1,7 +1,7 @@
import { import {
DataTabelColumnContainer, DataTableColumnContainer,
DataTabelColumnData, DataTableColumnData,
DataTabelRowData, DataTableRowData,
SortingDirection, SortingDirection,
} from "./ha-data-table"; } from "./ha-data-table";
@ -9,8 +9,8 @@ import memoizeOne from "memoize-one";
export const filterSortData = memoizeOne( export const filterSortData = memoizeOne(
async ( async (
data: DataTabelRowData[], data: DataTableRowData[],
columns: DataTabelColumnContainer, columns: DataTableColumnContainer,
filter: string, filter: string,
direction: SortingDirection, direction: SortingDirection,
sortColumn?: string sortColumn?: string
@ -27,8 +27,8 @@ export const filterSortData = memoizeOne(
const _memFilterData = memoizeOne( const _memFilterData = memoizeOne(
async ( async (
data: DataTabelRowData[], data: DataTableRowData[],
columns: DataTabelColumnContainer, columns: DataTableColumnContainer,
filter: string filter: string
) => { ) => {
if (!filter) { if (!filter) {
@ -40,8 +40,8 @@ const _memFilterData = memoizeOne(
const _memSortData = memoizeOne( const _memSortData = memoizeOne(
( (
data: DataTabelRowData[], data: DataTableRowData[],
columns: DataTabelColumnContainer, columns: DataTableColumnContainer,
direction: SortingDirection, direction: SortingDirection,
sortColumn: string sortColumn: string
) => { ) => {
@ -50,8 +50,8 @@ const _memSortData = memoizeOne(
); );
export const filterData = ( export const filterData = (
data: DataTabelRowData[], data: DataTableRowData[],
columns: DataTabelColumnContainer, columns: DataTableColumnContainer,
filter: string filter: string
) => ) =>
data.filter((row) => { data.filter((row) => {
@ -71,8 +71,8 @@ export const filterData = (
}); });
export const sortData = ( export const sortData = (
data: DataTabelRowData[], data: DataTableRowData[],
column: DataTabelColumnData, column: DataTableColumnData,
direction: SortingDirection, direction: SortingDirection,
sortColumn: string sortColumn: string
) => ) =>

View File

@ -13,9 +13,9 @@ import {
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
// tslint:disable-next-line // tslint:disable-next-line
import { import {
DataTabelColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
DataTabelRowData, DataTableRowData,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
// tslint:disable-next-line // tslint:disable-next-line
import { DeviceRegistryEntry } from "../../../data/device_registry"; import { DeviceRegistryEntry } from "../../../data/device_registry";
@ -127,7 +127,7 @@ export class HaDevicesDataTable extends LitElement {
); );
private _columns = memoizeOne( private _columns = memoizeOne(
(narrow: boolean): DataTabelColumnContainer => (narrow: boolean): DataTableColumnContainer =>
narrow narrow
? { ? {
name: { name: {
@ -136,7 +136,7 @@ export class HaDevicesDataTable extends LitElement {
filterKey: "name", filterKey: "name",
filterable: true, filterable: true,
direction: "asc", direction: "asc",
template: (name, device: DataTabelRowData) => { template: (name, device: DataTableRowData) => {
const battery = device.battery_entity const battery = device.battery_entity
? this.hass.states[device.battery_entity] ? this.hass.states[device.battery_entity]
: undefined; : undefined;

View File

@ -6,9 +6,6 @@ import {
CSSResult, CSSResult,
property, property,
} from "lit-element"; } from "lit-element";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import { HomeAssistant } from "../../../types"; import { HomeAssistant } from "../../../types";
import { import {
@ -18,7 +15,7 @@ import {
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../components/ha-card"; import "../../../components/data-table/ha-data-table";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import "../../../components/ha-switch"; import "../../../components/ha-switch";
import { domainIcon } from "../../../common/entity/domain_icon"; import { domainIcon } from "../../../common/entity/domain_icon";
@ -30,11 +27,14 @@ import {
loadEntityRegistryDetailDialog, loadEntityRegistryDetailDialog,
} from "./show-dialog-entity-registry-detail"; } from "./show-dialog-entity-registry-detail";
import { UnsubscribeFunc } from "home-assistant-js-websocket"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { compare } from "../../../common/string/compare";
import { classMap } from "lit-html/directives/class-map";
// tslint:disable-next-line // tslint:disable-next-line
import { HaSwitch } from "../../../components/ha-switch"; import { HaSwitch } from "../../../components/ha-switch";
import memoize from "memoize-one"; import memoize from "memoize-one";
// tslint:disable-next-line
import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
class HaConfigEntityRegistry extends LitElement { class HaConfigEntityRegistry extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@ -43,11 +43,76 @@ class HaConfigEntityRegistry extends LitElement {
@property() private _showDisabled = false; @property() private _showDisabled = false;
private _unsubEntities?: UnsubscribeFunc; private _unsubEntities?: UnsubscribeFunc;
private _columns = memoize(
(_language): DataTableColumnContainer => {
return {
icon: {
title: "",
type: "icon",
template: (icon) => html`
<ha-icon slot="item-icon" .icon=${icon}></ha-icon>
`,
},
name: {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.headers.name"
),
sortable: true,
filterable: true,
direction: "asc",
},
entity_id: {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.headers.entity_id"
),
sortable: true,
filterable: true,
},
platform: {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.headers.integration"
),
sortable: true,
filterable: true,
template: (platform) =>
html`
${this.hass.localize(`component.${platform}.config.title`) ||
platform}
`,
},
disabled_by: {
title: this.hass.localize(
"ui.panel.config.entity_registry.picker.headers.enabled"
),
type: "icon",
template: (disabledBy) => html`
<ha-icon
slot="item-icon"
.icon=${disabledBy ? "hass:cancel" : "hass:check-circle"}
></ha-icon>
`,
},
};
}
);
private _filteredEntities = memoize( private _filteredEntities = memoize(
(entities: EntityRegistryEntry[], showDisabled: boolean) => (entities: EntityRegistryEntry[], showDisabled: boolean) =>
showDisabled (showDisabled
? entities ? entities
: entities.filter((entity) => !Boolean(entity.disabled_by)) : entities.filter((entity) => !Boolean(entity.disabled_by))
).map((entry) => {
const state = this.hass!.states[entry.entity_id];
return {
...entry,
icon: state
? stateIcon(state)
: domainIcon(computeDomain(entry.entity_id)),
name:
computeEntityRegistryName(this.hass!, entry) ||
this.hass!.localize("state.default.unavailable"),
};
})
); );
public disconnectedCallback() { public disconnectedCallback() {
@ -89,56 +154,21 @@ class HaConfigEntityRegistry extends LitElement {
"ui.panel.config.entity_registry.picker.integrations_page" "ui.panel.config.entity_registry.picker.integrations_page"
)} )}
</a> </a>
</span>
<ha-card>
<paper-item>
<ha-switch <ha-switch
?checked=${this._showDisabled} ?checked=${this._showDisabled}
@change=${this._showDisabledChanged} @change=${this._showDisabledChanged}
>${this.hass.localize( >${this.hass.localize(
"ui.panel.config.entity_registry.picker.show_disabled" "ui.panel.config.entity_registry.picker.show_disabled"
)}</ha-switch )}</ha-switch
></paper-item
> >
${this._filteredEntities(this._entities, this._showDisabled).map( </span>
(entry) => { <ha-data-table
const state = this.hass!.states[entry.entity_id]; .columns=${this._columns(this.hass.language)}
return html` .data=${this._filteredEntities(this._entities, this._showDisabled)}
<paper-icon-item @row-click=${this._openEditEntry}
@click=${this._openEditEntry} id="entity_id"
.entry=${entry}
class=${classMap({ "disabled-entry": !!entry.disabled_by })}
> >
<ha-icon </ha-data-table>
slot="item-icon"
.icon=${state
? stateIcon(state)
: domainIcon(computeDomain(entry.entity_id))}
></ha-icon>
<paper-item-body two-line>
<div class="name">
${computeEntityRegistryName(this.hass!, entry) ||
`(${this.hass!.localize(
"state.default.unavailable"
)})`}
</div>
<div class="secondary entity-id">
${entry.entity_id}
</div>
</paper-item-body>
<div class="platform">
${entry.platform}
${entry.disabled_by
? html`
<br />(disabled)
`
: ""}
</div>
</paper-icon-item>
`;
}
)}
</ha-card>
</ha-config-section> </ha-config-section>
</hass-subpage> </hass-subpage>
`; `;
@ -155,9 +185,7 @@ class HaConfigEntityRegistry extends LitElement {
this._unsubEntities = subscribeEntityRegistry( this._unsubEntities = subscribeEntityRegistry(
this.hass.connection, this.hass.connection,
(entities) => { (entities) => {
this._entities = entities.sort((ent1, ent2) => this._entities = entities;
compare(ent1.entity_id, ent2.entity_id)
);
} }
); );
} }
@ -167,8 +195,14 @@ class HaConfigEntityRegistry extends LitElement {
this._showDisabled = (ev.target as HaSwitch).checked; this._showDisabled = (ev.target as HaSwitch).checked;
} }
private _openEditEntry(ev: MouseEvent): void { private _openEditEntry(ev: CustomEvent): void {
const entry = (ev.currentTarget! as any).entry; const entryId = (ev.detail as RowClickedEvent).id;
const entry = this._entities!.find(
(entity) => entity.entity_id === entryId
);
if (!entry) {
return;
}
showEntityRegistryDetailDialog(this, { showEntityRegistryDetailDialog(this, {
entry, entry,
}); });
@ -179,23 +213,12 @@ class HaConfigEntityRegistry extends LitElement {
a { a {
color: var(--primary-color); color: var(--primary-color);
} }
ha-card { ha-data-table {
margin-bottom: 24px; margin-bottom: 24px;
direction: ltr; margin-top: 0px;
} }
paper-icon-item { ha-switch {
cursor: pointer; margin-top: 16px;
color: var(--primary-text-color);
}
ha-icon {
margin-left: 8px;
}
.platform {
text-align: right;
margin: 0 0 0 8px;
}
.disabled-entry {
color: var(--secondary-text-color);
} }
`; `;
} }

View File

@ -22,7 +22,7 @@ import "../../../../components/data-table/ha-data-table";
// tslint:disable-next-line // tslint:disable-next-line
import { import {
SelectionChangedEvent, SelectionChangedEvent,
DataTabelColumnContainer, DataTableColumnContainer,
} from "../../../../components/data-table/ha-data-table"; } from "../../../../components/data-table/ha-data-table";
import { computeStateName } from "../../../../common/entity/compute_state_name"; import { computeStateName } from "../../../../common/entity/compute_state_name";
@ -55,7 +55,7 @@ export class HuiUnusedEntities extends LitElement {
} }
private _columns = memoizeOne((narrow: boolean) => { private _columns = memoizeOne((narrow: boolean) => {
const columns: DataTabelColumnContainer = { const columns: DataTableColumnContainer = {
entity: { entity: {
title: "Entity", title: "Entity",
sortable: true, sortable: true,

View File

@ -1110,7 +1110,13 @@
"introduction": "Home Assistant keeps a registry of every entity it has ever seen that can be uniquely identified. Each of these entities will have an entity ID assigned which will be reserved for just this entity.", "introduction": "Home Assistant keeps a registry of every entity it has ever seen that can be uniquely identified. Each of these entities will have an entity ID assigned which will be reserved for just this entity.",
"introduction2": "Use the entity registry to override the name, change the entity ID or remove the entry from Home Assistant. Note, removing the entity registry entry won't remove the entity. To do that, follow the link below and remove it from the integrations page.", "introduction2": "Use the entity registry to override the name, change the entity ID or remove the entry from Home Assistant. Note, removing the entity registry entry won't remove the entity. To do that, follow the link below and remove it from the integrations page.",
"integrations_page": "Integrations page", "integrations_page": "Integrations page",
"show_disabled": "Show disabled entities" "show_disabled": "Show disabled entities",
"headers": {
"name": "Name",
"entity_id": "Entity ID",
"integration": "Integration",
"enabled": "Enabled"
}
}, },
"editor": { "editor": {
"unavailable": "This entity is not currently available.", "unavailable": "This entity is not currently available.",