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:
Bram Kragten 2019-10-09 17:48:41 +02:00 committed by Paulus Schoutsen
parent fc3f7ca4b2
commit 320be2e5d9
7 changed files with 311 additions and 407 deletions

View File

@ -73,7 +73,7 @@ export interface DataTabelSortColumnData {
export interface DataTabelColumnData extends DataTabelSortColumnData { export interface DataTabelColumnData extends DataTabelSortColumnData {
title: string; title: string;
type?: "numeric"; type?: "numeric";
template?: (data: any) => TemplateResult; template?: (data: any, row: DataTabelRowData) => TemplateResult;
} }
export interface DataTabelRowData { export interface DataTabelRowData {
@ -254,7 +254,7 @@ export class HaDataTable extends BaseElement {
})}" })}"
> >
${column.template ${column.template
? column.template(row[key]) ? column.template(row[key], row)
: row[key]} : row[key]}
</td> </td>
`; `;

View File

@ -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 { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import "../../../../layouts/hass-subpage";
import { EventsMixin } from "../../../../mixins/events-mixin"; import { EventsMixin } from "../../../../mixins/events-mixin";
import LocalizeMixin from "../../../../mixins/localize-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 { compare } from "../../../../common/string/compare";
import { import { updateDeviceRegistryEntry } from "../../../../data/device_registry";
subscribeDeviceRegistry,
updateDeviceRegistryEntry,
} from "../../../../data/device_registry";
import { subscribeAreaRegistry } from "../../../../data/area_registry";
import { import {
loadDeviceRegistryDetailDialog, loadDeviceRegistryDetailDialog,
showDeviceRegistryDetailDialog, showDeviceRegistryDetailDialog,
} from "../../../../dialogs/device-registry-detail/show-dialog-device-registry-detail"; } 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 * @appliesMixin EventsMixin
*/ */
@ -37,10 +19,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() { static get template() {
return html` return html`
<style> <style>
:host(:not([narrow])) .device-entities {
max-height: 225px;
overflow: auto;
}
ha-card { ha-card {
flex: 1 0 100%; flex: 1 0 100%;
padding-bottom: 10px; padding-bottom: 10px;
@ -70,11 +48,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
.extra-info { .extra-info {
margin-top: 8px; margin-top: 8px;
} }
paper-icon-item {
cursor: pointer;
padding-top: 4px;
padding-bottom: 4px;
}
.manuf, .manuf,
.entity-id, .entity-id,
.area { .area {
@ -82,15 +55,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
} }
</style> </style>
<ha-card> <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="card-content">
<div class="info"> <div class="info">
<div class="model">[[device.model]]</div> <div class="model">[[device.model]]</div>
@ -122,27 +86,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
</div> </div>
</template> </template>
</div> </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> </ha-card>
`; `;
} }
@ -152,14 +95,11 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
device: Object, device: Object,
devices: Array, devices: Array,
areas: Array, areas: Array,
entities: Array,
hass: Object, hass: Object,
narrow: { narrow: {
type: Boolean, type: Boolean,
reflectToAttribute: true, reflectToAttribute: true,
}, },
hideSettings: { type: Boolean, value: false },
hideEntities: { type: Boolean, value: false },
_childDevices: { _childDevices: {
type: Array, type: Array,
computed: "_computeChildDevices(device, devices)", computed: "_computeChildDevices(device, devices)",
@ -172,30 +112,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
loadDeviceRegistryDetailDialog(); 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) { _computeArea(areas, device) {
if (!areas || !device || !device.area_id) { if (!areas || !device || !device.area_id) {
return "No Area"; return "No Area";
@ -210,30 +126,6 @@ class HaDeviceCard extends EventsMixin(LocalizeMixin(PolymerElement)) {
.sort((dev1, dev2) => compare(dev1.name, dev2.name)); .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) { _deviceName(device) {
return device.name_by_user || device.name; return device.name_by_user || device.name;
} }

View File

@ -13,6 +13,7 @@ import "../../../layouts/hass-subpage";
import "../../../layouts/hass-error-screen"; import "../../../layouts/hass-error-screen";
import "../ha-config-section"; import "../ha-config-section";
import "./device-detail/ha-device-card";
import "./device-detail/ha-device-triggers-card"; import "./device-detail/ha-device-triggers-card";
import "./device-detail/ha-device-conditions-card"; import "./device-detail/ha-device-conditions-card";
import "./device-detail/ha-device-actions-card"; import "./device-detail/ha-device-actions-card";
@ -144,9 +145,6 @@ export class HaConfigDevicePage extends LitElement {
.areas=${this.areas} .areas=${this.areas}
.devices=${this.devices} .devices=${this.devices}
.device=${device} .device=${device}
.entities=${this.entities}
hide-settings
hide-entities
></ha-device-card> ></ha-device-card>
${entities.length ${entities.length

View File

@ -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 "../../../layouts/hass-subpage";
import "../../../resources/ha-style"; import "./ha-devices-data-table";
import "../../../components/ha-icon-next";
import "../ha-config-section";
import memoizeOne from "memoize-one";
import { import {
LitElement, LitElement,
@ -21,33 +7,14 @@ import {
TemplateResult, TemplateResult,
property, property,
customElement, customElement,
CSSResult,
css,
} from "lit-element"; } from "lit-element";
import { HomeAssistant } from "../../../types"; 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 { DeviceRegistryEntry } from "../../../data/device_registry";
import { EntityRegistryEntry } from "../../../data/entity_registry"; import { EntityRegistryEntry } from "../../../data/entity_registry";
import { ConfigEntry } from "../../../data/config_entries"; import { ConfigEntry } from "../../../data/config_entries";
import { AreaRegistryEntry } from "../../../data/area_registry"; 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") @customElement("ha-config-devices-dashboard")
export class HaConfigDeviceDashboard extends LitElement { export class HaConfigDeviceDashboard extends LitElement {
@ -59,234 +26,35 @@ export class HaConfigDeviceDashboard extends LitElement {
@property() public areas!: AreaRegistryEntry[]; @property() public areas!: AreaRegistryEntry[];
@property() public domain!: string; @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 { protected render(): TemplateResult {
return html` return html`
<hass-subpage <hass-subpage
header=${this.hass.localize("ui.panel.config.devices.caption")} header=${this.hass.localize("ui.panel.config.devices.caption")}
> >
<ha-data-table <div class="content">
.columns=${this._columns(this.narrow)} <ha-devices-data-table
.data=${this._devices( .hass=${this.hass}
this.devices, .narrow=${this.narrow}
this.entries, .devices=${this.devices}
this.entities, .entries=${this.entries}
this.areas, .entities=${this.entities}
this.domain, .areas=${this.areas}
this.hass.localize .domain=${this.domain}
).map((device: DeviceRowData) => { ></ha-devices-data-table>
// We don't need a lot of this data for mobile view, but kept it for filtering... </div>
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>
</hass-subpage> </hass-subpage>
`; `;
} }
private _batteryEntity( static get styles(): CSSResult {
deviceId: string, return css`
deviceEntityLookup: DeviceEntityLookup .content {
): string | undefined { padding: 4px;
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);
} }
} ha-devices-data-table {
width: 100%;
return undefined; }
} `;
private _handleRowClicked(ev: CustomEvent) {
const deviceId = (ev.detail as RowClickedEvent).id;
navigate(this, `/config/devices/device/${deviceId}`);
} }
} }

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

View File

@ -20,7 +20,7 @@ class HaCeEntitiesCard extends LocalizeMixIn(EventsMixin(PolymerElement)) {
return html` return html`
<style> <style>
ha-card { ha-card {
flex: 1 0 100%; margin-top: 8px;
padding-bottom: 8px; padding-bottom: 8px;
} }
paper-icon-item { paper-icon-item {

View File

@ -2,10 +2,7 @@ import memoizeOne from "memoize-one";
import "../../../../layouts/hass-subpage"; import "../../../../layouts/hass-subpage";
import "../../../../layouts/hass-error-screen"; import "../../../../layouts/hass-error-screen";
import "../../../../components/entity/state-badge"; import "../../devices/ha-devices-data-table";
import { compare } from "../../../../common/string/compare";
import "../../devices/device-detail/ha-device-card";
import "./ha-ce-entities-card"; import "./ha-ce-entities-card";
import { showOptionsFlowDialog } from "../../../../dialogs/config-flow/show-dialog-options-flow"; import { showOptionsFlowDialog } from "../../../../dialogs/config-flow/show-dialog-options-flow";
import { property, LitElement, CSSResult, css, html } from "lit-element"; import { property, LitElement, CSSResult, css, html } from "lit-element";
@ -43,15 +40,9 @@ class HaConfigEntryPage extends LitElement {
if (!devices) { if (!devices) {
return []; return [];
} }
return devices return devices.filter((device) =>
.filter((device) => device.config_entries.includes(configEntry.entry_id)
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 || "")
);
} }
); );
@ -116,24 +107,19 @@ class HaConfigEntryPage extends LitElement {
)} )}
</p> </p>
` `
: ""} : html`
${configEntryDevices.map( <ha-devices-data-table
(device) => html` .hass=${this.hass}
<ha-device-card .narrow=${this.narrow}
class="card" .devices=${configEntryDevices}
.hass=${this.hass} .entries=${this.configEntries}
.areas=${this.areas} .entities=${this.entityRegistryEntries}
.devices=${this.deviceRegistryEntries} .areas=${this.areas}
.device=${device} ></ha-devices-data-table>
.entities=${this.entityRegistryEntries} `}
.narrow=${this.narrow}
></ha-device-card>
`
)}
${noDeviceEntities.length > 0 ${noDeviceEntities.length > 0
? html` ? html`
<ha-ce-entities-card <ha-ce-entities-card
class="card"
.heading=${this.hass.localize( .heading=${this.hass.localize(
"ui.panel.config.integrations.config_entry.no_device" "ui.panel.config.integrations.config_entry.no_device"
)} )}
@ -185,18 +171,13 @@ class HaConfigEntryPage extends LitElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
.content { .content {
display: flex;
flex-wrap: wrap;
padding: 4px; padding: 4px;
justify-content: center;
} }
.card { p {
box-sizing: border-box; text-align: center;
display: flex; }
flex: 1 0 300px; ha-devices-data-table {
min-width: 0; width: 100%;
max-width: 500px;
padding: 8px;
} }
`; `;
} }