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

View File

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

View File

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

View File

@ -6,9 +6,6 @@ import {
CSSResult,
property,
} 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 {
@ -18,7 +15,7 @@ import {
} from "../../../data/entity_registry";
import "../../../layouts/hass-subpage";
import "../../../layouts/hass-loading-screen";
import "../../../components/ha-card";
import "../../../components/data-table/ha-data-table";
import "../../../components/ha-icon";
import "../../../components/ha-switch";
import { domainIcon } from "../../../common/entity/domain_icon";
@ -30,11 +27,14 @@ import {
loadEntityRegistryDetailDialog,
} from "./show-dialog-entity-registry-detail";
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
import { HaSwitch } from "../../../components/ha-switch";
import memoize from "memoize-one";
// tslint:disable-next-line
import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
class HaConfigEntityRegistry extends LitElement {
@property() public hass!: HomeAssistant;
@ -43,11 +43,76 @@ class HaConfigEntityRegistry extends LitElement {
@property() private _showDisabled = false;
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(
(entities: EntityRegistryEntry[], showDisabled: boolean) =>
showDisabled
(showDisabled
? entities
: 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() {
@ -89,56 +154,21 @@ class HaConfigEntityRegistry extends LitElement {
"ui.panel.config.entity_registry.picker.integrations_page"
)}
</a>
</span>
<ha-card>
<paper-item>
<ha-switch
?checked=${this._showDisabled}
@change=${this._showDisabledChanged}
>${this.hass.localize(
"ui.panel.config.entity_registry.picker.show_disabled"
)}</ha-switch
></paper-item
<ha-switch
?checked=${this._showDisabled}
@change=${this._showDisabledChanged}
>${this.hass.localize(
"ui.panel.config.entity_registry.picker.show_disabled"
)}</ha-switch
>
${this._filteredEntities(this._entities, this._showDisabled).map(
(entry) => {
const state = this.hass!.states[entry.entity_id];
return html`
<paper-icon-item
@click=${this._openEditEntry}
.entry=${entry}
class=${classMap({ "disabled-entry": !!entry.disabled_by })}
>
<ha-icon
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>
</span>
<ha-data-table
.columns=${this._columns(this.hass.language)}
.data=${this._filteredEntities(this._entities, this._showDisabled)}
@row-click=${this._openEditEntry}
id="entity_id"
>
</ha-data-table>
</ha-config-section>
</hass-subpage>
`;
@ -155,9 +185,7 @@ class HaConfigEntityRegistry extends LitElement {
this._unsubEntities = subscribeEntityRegistry(
this.hass.connection,
(entities) => {
this._entities = entities.sort((ent1, ent2) =>
compare(ent1.entity_id, ent2.entity_id)
);
this._entities = entities;
}
);
}
@ -167,8 +195,14 @@ class HaConfigEntityRegistry extends LitElement {
this._showDisabled = (ev.target as HaSwitch).checked;
}
private _openEditEntry(ev: MouseEvent): void {
const entry = (ev.currentTarget! as any).entry;
private _openEditEntry(ev: CustomEvent): void {
const entryId = (ev.detail as RowClickedEvent).id;
const entry = this._entities!.find(
(entity) => entity.entity_id === entryId
);
if (!entry) {
return;
}
showEntityRegistryDetailDialog(this, {
entry,
});
@ -179,23 +213,12 @@ class HaConfigEntityRegistry extends LitElement {
a {
color: var(--primary-color);
}
ha-card {
ha-data-table {
margin-bottom: 24px;
direction: ltr;
margin-top: 0px;
}
paper-icon-item {
cursor: pointer;
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);
ha-switch {
margin-top: 16px;
}
`;
}

View File

@ -22,7 +22,7 @@ import "../../../../components/data-table/ha-data-table";
// tslint:disable-next-line
import {
SelectionChangedEvent,
DataTabelColumnContainer,
DataTableColumnContainer,
} from "../../../../components/data-table/ha-data-table";
import { computeStateName } from "../../../../common/entity/compute_state_name";
@ -55,7 +55,7 @@ export class HuiUnusedEntities extends LitElement {
}
private _columns = memoizeOne((narrow: boolean) => {
const columns: DataTabelColumnContainer = {
const columns: DataTableColumnContainer = {
entity: {
title: "Entity",
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.",
"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",
"show_disabled": "Show disabled entities"
"show_disabled": "Show disabled entities",
"headers": {
"name": "Name",
"entity_id": "Entity ID",
"integration": "Integration",
"enabled": "Enabled"
}
},
"editor": {
"unavailable": "This entity is not currently available.",