mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 11:46:42 +00:00
Convert integration entry page to data table (#3963)
* Convert integration entry page to data table * Simplify device-card In a future PR this has to be changed further * Center no devices text * Review comments
This commit is contained in:
parent
fc3f7ca4b2
commit
320be2e5d9
@ -73,7 +73,7 @@ export interface DataTabelSortColumnData {
|
||||
export interface DataTabelColumnData extends DataTabelSortColumnData {
|
||||
title: string;
|
||||
type?: "numeric";
|
||||
template?: (data: any) => TemplateResult;
|
||||
template?: (data: any, row: DataTabelRowData) => TemplateResult;
|
||||
}
|
||||
|
||||
export interface DataTabelRowData {
|
||||
@ -254,7 +254,7 @@ export class HaDataTable extends BaseElement {
|
||||
})}"
|
||||
>
|
||||
${column.template
|
||||
? column.template(row[key])
|
||||
? column.template(row[key], row)
|
||||
: row[key]}
|
||||
</td>
|
||||
`;
|
||||
|
@ -1,35 +1,17 @@
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import "../../../../components/entity/state-badge";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import {
|
||||
subscribeDeviceRegistry,
|
||||
updateDeviceRegistryEntry,
|
||||
} from "../../../../data/device_registry";
|
||||
import { subscribeAreaRegistry } from "../../../../data/area_registry";
|
||||
import { updateDeviceRegistryEntry } from "../../../../data/device_registry";
|
||||
import {
|
||||
loadDeviceRegistryDetailDialog,
|
||||
showDeviceRegistryDetailDialog,
|
||||
} from "../../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
|
||||
|
||||
function computeEntityName(hass, entity) {
|
||||
if (entity.name) return entity.name;
|
||||
const state = hass.states[entity.entity_id];
|
||||
return state ? computeStateName(state) : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
@ -37,10 +19,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host(:not([narrow])) .device-entities {
|
||||
max-height: 225px;
|
||||
overflow: auto;
|
||||
}
|
||||
ha-card {
|
||||
flex: 1 0 100%;
|
||||
padding-bottom: 10px;
|
||||
@ -70,11 +48,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
.extra-info {
|
||||
margin-top: 8px;
|
||||
}
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.manuf,
|
||||
.entity-id,
|
||||
.area {
|
||||
@ -82,15 +55,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
}
|
||||
</style>
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
<template is="dom-if" if="[[!hideSettings]]">
|
||||
<div class="name">[[_deviceName(device)]]</div>
|
||||
<paper-icon-button
|
||||
icon="hass:settings"
|
||||
on-click="_gotoSettings"
|
||||
></paper-icon-button>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="info">
|
||||
<div class="model">[[device.model]]</div>
|
||||
@ -122,27 +86,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template is="dom-if" if="[[!hideEntities]]">
|
||||
<div class="device-entities">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[_computeDeviceEntities(hass, device, entities)]]"
|
||||
as="entity"
|
||||
>
|
||||
<paper-icon-item on-click="_openMoreInfo">
|
||||
<state-badge
|
||||
state-obj="[[_computeStateObj(entity, hass)]]"
|
||||
slot="item-icon"
|
||||
></state-badge>
|
||||
<paper-item-body>
|
||||
<div class="name">[[_computeEntityName(entity, hass)]]</div>
|
||||
<div class="secondary entity-id">[[entity.entity_id]]</div>
|
||||
</paper-item-body>
|
||||
</paper-icon-item>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
@ -152,14 +95,11 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
device: Object,
|
||||
devices: Array,
|
||||
areas: Array,
|
||||
entities: Array,
|
||||
hass: Object,
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
reflectToAttribute: true,
|
||||
},
|
||||
hideSettings: { type: Boolean, value: false },
|
||||
hideEntities: { type: Boolean, value: false },
|
||||
_childDevices: {
|
||||
type: Array,
|
||||
computed: "_computeChildDevices(device, devices)",
|
||||
@ -172,30 +112,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
loadDeviceRegistryDetailDialog();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubAreas = subscribeAreaRegistry(this.hass.connection, (areas) => {
|
||||
this._areas = areas;
|
||||
});
|
||||
this._unsubDevices = subscribeDeviceRegistry(
|
||||
this.hass.connection,
|
||||
(devices) => {
|
||||
this.devices = devices;
|
||||
this.device = devices.find((device) => device.id === this.device.id);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubAreas) {
|
||||
this._unsubAreas();
|
||||
}
|
||||
if (this._unsubDevices) {
|
||||
this._unsubDevices();
|
||||
}
|
||||
}
|
||||
|
||||
_computeArea(areas, device) {
|
||||
if (!areas || !device || !device.area_id) {
|
||||
return "No Area";
|
||||
@ -210,30 +126,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
.sort((dev1, dev2) => compare(dev1.name, dev2.name));
|
||||
}
|
||||
|
||||
_computeDeviceEntities(hass, device, entities) {
|
||||
return entities
|
||||
.filter((entity) => entity.device_id === device.id)
|
||||
.sort((ent1, ent2) =>
|
||||
compare(
|
||||
computeEntityName(hass, ent1) || `zzz${ent1.entity_id}`,
|
||||
computeEntityName(hass, ent2) || `zzz${ent2.entity_id}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_computeStateObj(entity, hass) {
|
||||
return hass.states[entity.entity_id];
|
||||
}
|
||||
|
||||
_computeEntityName(entity, hass) {
|
||||
return (
|
||||
computeEntityName(hass, entity) ||
|
||||
`(${this.localize(
|
||||
"ui.panel.config.integrations.config_entry.entity_unavailable"
|
||||
)})`
|
||||
);
|
||||
}
|
||||
|
||||
_deviceName(device) {
|
||||
return device.name_by_user || device.name;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import "../../../layouts/hass-subpage";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../ha-config-section";
|
||||
|
||||
import "./device-detail/ha-device-card";
|
||||
import "./device-detail/ha-device-triggers-card";
|
||||
import "./device-detail/ha-device-conditions-card";
|
||||
import "./device-detail/ha-device-actions-card";
|
||||
@ -144,9 +145,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.areas=${this.areas}
|
||||
.devices=${this.devices}
|
||||
.device=${device}
|
||||
.entities=${this.entities}
|
||||
hide-settings
|
||||
hide-entities
|
||||
></ha-device-card>
|
||||
|
||||
${entities.length
|
||||
|
@ -1,19 +1,5 @@
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../resources/ha-style";
|
||||
import "../../../components/ha-icon-next";
|
||||
|
||||
import "../ha-config-section";
|
||||
|
||||
import memoizeOne from "memoize-one";
|
||||
import "./ha-devices-data-table";
|
||||
|
||||
import {
|
||||
LitElement,
|
||||
@ -21,33 +7,14 @@ import {
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
// tslint:disable-next-line
|
||||
import {
|
||||
DataTabelColumnContainer,
|
||||
RowClickedEvent,
|
||||
DataTabelRowData,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
// tslint:disable-next-line
|
||||
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
|
||||
interface DeviceRowData extends DeviceRegistryEntry {
|
||||
device?: DeviceRowData;
|
||||
area?: string;
|
||||
integration?: string;
|
||||
battery_entity?: string;
|
||||
}
|
||||
|
||||
interface DeviceEntityLookup {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
}
|
||||
|
||||
@customElement("ha-config-devices-dashboard")
|
||||
export class HaConfigDeviceDashboard extends LitElement {
|
||||
@ -59,234 +26,35 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
@property() public areas!: AreaRegistryEntry[];
|
||||
@property() public domain!: string;
|
||||
|
||||
private _devices = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
entries: ConfigEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
domain: string,
|
||||
localize: LocalizeFunc
|
||||
) => {
|
||||
// Some older installations might have devices pointing at invalid entryIDs
|
||||
// So we guard for that.
|
||||
|
||||
let outputDevices: DeviceRowData[] = devices;
|
||||
|
||||
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
|
||||
for (const device of devices) {
|
||||
deviceLookup[device.id] = device;
|
||||
}
|
||||
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
|
||||
const entryLookup: { [entryId: string]: ConfigEntry } = {};
|
||||
for (const entry of entries) {
|
||||
entryLookup[entry.entry_id] = entry;
|
||||
}
|
||||
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
for (const area of areas) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
outputDevices = outputDevices.filter((device) =>
|
||||
device.config_entries.find(
|
||||
(entryId) =>
|
||||
entryId in entryLookup && entryLookup[entryId].domain === domain
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
outputDevices = outputDevices.map((device) => {
|
||||
return {
|
||||
...device,
|
||||
name:
|
||||
device.name_by_user ||
|
||||
device.name ||
|
||||
this._fallbackDeviceName(device.id, deviceEntityLookup) ||
|
||||
"No name",
|
||||
model: device.model || "<unknown>",
|
||||
manufacturer: device.manufacturer || "<unknown>",
|
||||
area: device.area_id ? areaLookup[device.area_id].name : "No area",
|
||||
integration: device.config_entries.length
|
||||
? device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
.map(
|
||||
(entId) =>
|
||||
localize(
|
||||
`component.${entryLookup[entId].domain}.config.title`
|
||||
) || entryLookup[entId].domain
|
||||
)
|
||||
.join(", ")
|
||||
: "No integration",
|
||||
battery_entity: this._batteryEntity(device.id, deviceEntityLookup),
|
||||
};
|
||||
});
|
||||
|
||||
return outputDevices;
|
||||
}
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTabelColumnContainer =>
|
||||
narrow
|
||||
? {
|
||||
device: {
|
||||
title: "Device",
|
||||
sortable: true,
|
||||
filterKey: "name",
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
template: (device: DeviceRowData) => {
|
||||
const battery = device.battery_entity
|
||||
? this.hass.states[device.battery_entity]
|
||||
: undefined;
|
||||
// Have to work on a nice layout for mobile
|
||||
return html`
|
||||
${device.name_by_user || device.name}<br />
|
||||
${device.area} | ${device.integration}<br />
|
||||
${battery
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-state-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${battery}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
device_name: {
|
||||
title: "Device",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
},
|
||||
manufacturer: {
|
||||
title: "Manufacturer",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
model: {
|
||||
title: "Model",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
area: {
|
||||
title: "Area",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
integration: {
|
||||
title: "Integration",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
battery: {
|
||||
title: "Battery",
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
template: (batteryEntity: string) => {
|
||||
const battery = batteryEntity
|
||||
? this.hass.states[batteryEntity]
|
||||
: undefined;
|
||||
return battery
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-state-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${battery}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: html`
|
||||
-
|
||||
`;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
header=${this.hass.localize("ui.panel.config.devices.caption")}
|
||||
>
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._devices(
|
||||
this.devices,
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this.domain,
|
||||
this.hass.localize
|
||||
).map((device: DeviceRowData) => {
|
||||
// We don't need a lot of this data for mobile view, but kept it for filtering...
|
||||
const data: DataTabelRowData = {
|
||||
device_name: device.name,
|
||||
id: device.id,
|
||||
manufacturer: device.manufacturer,
|
||||
model: device.model,
|
||||
area: device.area,
|
||||
integration: device.integration,
|
||||
};
|
||||
if (this.narrow) {
|
||||
data.device = device;
|
||||
return data;
|
||||
}
|
||||
data.battery = device.battery_entity;
|
||||
return data;
|
||||
})}
|
||||
@row-click=${this._handleRowClicked}
|
||||
></ha-data-table>
|
||||
<div class="content">
|
||||
<ha-devices-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.devices=${this.devices}
|
||||
.entries=${this.entries}
|
||||
.entities=${this.entities}
|
||||
.areas=${this.areas}
|
||||
.domain=${this.domain}
|
||||
></ha-devices-data-table>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _batteryEntity(
|
||||
deviceId: string,
|
||||
deviceEntityLookup: DeviceEntityLookup
|
||||
): string | undefined {
|
||||
const batteryEntity = (deviceEntityLookup[deviceId] || []).find(
|
||||
(entity) =>
|
||||
this.hass.states[entity.entity_id] &&
|
||||
this.hass.states[entity.entity_id].attributes.device_class === "battery"
|
||||
);
|
||||
|
||||
return batteryEntity ? batteryEntity.entity_id : undefined;
|
||||
}
|
||||
|
||||
private _fallbackDeviceName(
|
||||
deviceId: string,
|
||||
deviceEntityLookup: DeviceEntityLookup
|
||||
): string | undefined {
|
||||
for (const entity of deviceEntityLookup[deviceId] || []) {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.content {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: CustomEvent) {
|
||||
const deviceId = (ev.detail as RowClickedEvent).id;
|
||||
navigate(this, `/config/devices/device/${deviceId}`);
|
||||
ha-devices-data-table {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
265
src/panels/config/devices/ha-devices-data-table.ts
Normal file
265
src/panels/config/devices/ha-devices-data-table.ts
Normal file
@ -0,0 +1,265 @@
|
||||
import "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
// tslint:disable-next-line
|
||||
import {
|
||||
DataTabelColumnContainer,
|
||||
RowClickedEvent,
|
||||
DataTabelRowData,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
// tslint:disable-next-line
|
||||
import { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
|
||||
export interface DeviceRowData extends DeviceRegistryEntry {
|
||||
device?: DeviceRowData;
|
||||
area?: string;
|
||||
integration?: string;
|
||||
battery_entity?: string;
|
||||
}
|
||||
|
||||
export interface DeviceEntityLookup {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
}
|
||||
|
||||
@customElement("ha-devices-data-table")
|
||||
export class HaDevicesDataTable extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public narrow = false;
|
||||
@property() public devices!: DeviceRegistryEntry[];
|
||||
@property() public entries!: ConfigEntry[];
|
||||
@property() public entities!: EntityRegistryEntry[];
|
||||
@property() public areas!: AreaRegistryEntry[];
|
||||
@property() public domain!: string;
|
||||
|
||||
private _devices = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
entries: ConfigEntry[],
|
||||
entities: EntityRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
domain: string,
|
||||
localize: LocalizeFunc
|
||||
) => {
|
||||
// Some older installations might have devices pointing at invalid entryIDs
|
||||
// So we guard for that.
|
||||
|
||||
let outputDevices: DeviceRowData[] = devices;
|
||||
|
||||
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
|
||||
for (const device of devices) {
|
||||
deviceLookup[device.id] = device;
|
||||
}
|
||||
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
deviceEntityLookup[entity.device_id] = [];
|
||||
}
|
||||
deviceEntityLookup[entity.device_id].push(entity);
|
||||
}
|
||||
|
||||
const entryLookup: { [entryId: string]: ConfigEntry } = {};
|
||||
for (const entry of entries) {
|
||||
entryLookup[entry.entry_id] = entry;
|
||||
}
|
||||
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
for (const area of areas) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
if (domain) {
|
||||
outputDevices = outputDevices.filter((device) =>
|
||||
device.config_entries.find(
|
||||
(entryId) =>
|
||||
entryId in entryLookup && entryLookup[entryId].domain === domain
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
outputDevices = outputDevices.map((device) => {
|
||||
return {
|
||||
...device,
|
||||
name:
|
||||
device.name_by_user ||
|
||||
device.name ||
|
||||
this._fallbackDeviceName(device.id, deviceEntityLookup) ||
|
||||
"No name",
|
||||
model: device.model || "<unknown>",
|
||||
manufacturer: device.manufacturer || "<unknown>",
|
||||
area: device.area_id ? areaLookup[device.area_id].name : "No area",
|
||||
integration: device.config_entries.length
|
||||
? device.config_entries
|
||||
.filter((entId) => entId in entryLookup)
|
||||
.map(
|
||||
(entId) =>
|
||||
localize(
|
||||
`component.${entryLookup[entId].domain}.config.title`
|
||||
) || entryLookup[entId].domain
|
||||
)
|
||||
.join(", ")
|
||||
: "No integration",
|
||||
battery_entity: this._batteryEntity(device.id, deviceEntityLookup),
|
||||
};
|
||||
});
|
||||
|
||||
return outputDevices;
|
||||
}
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTabelColumnContainer =>
|
||||
narrow
|
||||
? {
|
||||
name: {
|
||||
title: "Device",
|
||||
sortable: true,
|
||||
filterKey: "name",
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
template: (name, device: DataTabelRowData) => {
|
||||
const battery = device.battery_entity
|
||||
? this.hass.states[device.battery_entity]
|
||||
: undefined;
|
||||
// Have to work on a nice layout for mobile
|
||||
return html`
|
||||
${name}<br />
|
||||
${device.area} | ${device.integration}<br />
|
||||
${battery
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-state-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${battery}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: {
|
||||
title: "Device",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
},
|
||||
manufacturer: {
|
||||
title: "Manufacturer",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
model: {
|
||||
title: "Model",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
area: {
|
||||
title: "Area",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
integration: {
|
||||
title: "Integration",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
},
|
||||
battery_entity: {
|
||||
title: "Battery",
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
template: (batteryEntity: string) => {
|
||||
const battery = batteryEntity
|
||||
? this.hass.states[batteryEntity]
|
||||
: undefined;
|
||||
return battery
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-state-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${battery}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: html`
|
||||
-
|
||||
`;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-data-table
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._devices(
|
||||
this.devices,
|
||||
this.entries,
|
||||
this.entities,
|
||||
this.areas,
|
||||
this.domain,
|
||||
this.hass.localize
|
||||
)}
|
||||
@row-click=${this._handleRowClicked}
|
||||
></ha-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
private _batteryEntity(
|
||||
deviceId: string,
|
||||
deviceEntityLookup: DeviceEntityLookup
|
||||
): string | undefined {
|
||||
const batteryEntity = (deviceEntityLookup[deviceId] || []).find(
|
||||
(entity) =>
|
||||
this.hass.states[entity.entity_id] &&
|
||||
this.hass.states[entity.entity_id].attributes.device_class === "battery"
|
||||
);
|
||||
|
||||
return batteryEntity ? batteryEntity.entity_id : undefined;
|
||||
}
|
||||
|
||||
private _fallbackDeviceName(
|
||||
deviceId: string,
|
||||
deviceEntityLookup: DeviceEntityLookup
|
||||
): string | undefined {
|
||||
for (const entity of deviceEntityLookup[deviceId] || []) {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (stateObj) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: CustomEvent) {
|
||||
const deviceId = (ev.detail as RowClickedEvent).id;
|
||||
navigate(this, `/config/devices/device/${deviceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-devices-data-table": HaDevicesDataTable;
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ class HaCeEntitiesCard extends LocalizeMixIn(EventsMixin(PolymerElement)) {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
flex: 1 0 100%;
|
||||
margin-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
paper-icon-item {
|
||||
|
@ -2,10 +2,7 @@ import memoizeOne from "memoize-one";
|
||||
import "../../../../layouts/hass-subpage";
|
||||
import "../../../../layouts/hass-error-screen";
|
||||
|
||||
import "../../../../components/entity/state-badge";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
|
||||
import "../../devices/device-detail/ha-device-card";
|
||||
import "../../devices/ha-devices-data-table";
|
||||
import "./ha-ce-entities-card";
|
||||
import { showOptionsFlowDialog } from "../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import { property, LitElement, CSSResult, css, html } from "lit-element";
|
||||
@ -43,15 +40,9 @@ class HaConfigEntryPage extends LitElement {
|
||||
if (!devices) {
|
||||
return [];
|
||||
}
|
||||
return devices
|
||||
.filter((device) =>
|
||||
device.config_entries.includes(configEntry.entry_id)
|
||||
)
|
||||
.sort(
|
||||
(dev1, dev2) =>
|
||||
Number(!!dev1.via_device_id) - Number(!!dev2.via_device_id) ||
|
||||
compare(dev1.name || "", dev2.name || "")
|
||||
);
|
||||
return devices.filter((device) =>
|
||||
device.config_entries.includes(configEntry.entry_id)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@ -116,24 +107,19 @@ class HaConfigEntryPage extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
${configEntryDevices.map(
|
||||
(device) => html`
|
||||
<ha-device-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.areas=${this.areas}
|
||||
.devices=${this.deviceRegistryEntries}
|
||||
.device=${device}
|
||||
.entities=${this.entityRegistryEntries}
|
||||
.narrow=${this.narrow}
|
||||
></ha-device-card>
|
||||
`
|
||||
)}
|
||||
: html`
|
||||
<ha-devices-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.devices=${configEntryDevices}
|
||||
.entries=${this.configEntries}
|
||||
.entities=${this.entityRegistryEntries}
|
||||
.areas=${this.areas}
|
||||
></ha-devices-data-table>
|
||||
`}
|
||||
${noDeviceEntities.length > 0
|
||||
? html`
|
||||
<ha-ce-entities-card
|
||||
class="card"
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.no_device"
|
||||
)}
|
||||
@ -185,18 +171,13 @@ class HaConfigEntryPage extends LitElement {
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 4px;
|
||||
justify-content: center;
|
||||
}
|
||||
.card {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1 0 300px;
|
||||
min-width: 0;
|
||||
max-width: 500px;
|
||||
padding: 8px;
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
ha-devices-data-table {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user