Update layout of device info page (#5342)

* Update layout of device info page

* Comments

* <a> and simplify layout

* Update ha-config-device-page.ts
This commit is contained in:
Bram Kragten 2020-03-30 14:21:21 +02:00 committed by GitHub
parent ddb525f6cd
commit f6dac98abd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 442 additions and 316 deletions

View File

@ -72,6 +72,7 @@ export interface DataTableColumnData extends DataTableSortColumnData {
type?: "numeric" | "icon"; type?: "numeric" | "icon";
template?: <T>(data: any, row: T) => TemplateResult | string; template?: <T>(data: any, row: T) => TemplateResult | string;
width?: string; width?: string;
maxWidth?: string;
grows?: boolean; grows?: boolean;
} }
@ -241,9 +242,8 @@ export class HaDataTable extends LitElement {
class="mdc-data-table__header-cell ${classMap(classes)}" class="mdc-data-table__header-cell ${classMap(classes)}"
style=${column.width style=${column.width
? styleMap({ ? styleMap({
[column.grows ? "minWidth" : "width"]: String( [column.grows ? "minWidth" : "width"]: column.width,
column.width maxWidth: column.maxWidth || "",
),
}) })
: ""} : ""}
role="columnheader" role="columnheader"
@ -329,7 +329,10 @@ export class HaDataTable extends LitElement {
? styleMap({ ? styleMap({
[column.grows [column.grows
? "minWidth" ? "minWidth"
: "width"]: String(column.width), : "width"]: column.width,
maxWidth: column.maxWidth
? column.maxWidth
: "",
}) })
: ""} : ""}
> >
@ -532,6 +535,7 @@ export class HaDataTable extends LitElement {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
flex-shrink: 0; flex-shrink: 0;
box-sizing: border-box;
} }
.mdc-data-table__cell.mdc-data-table__cell--icon { .mdc-data-table__cell.mdc-data-table__cell--icon {
@ -544,7 +548,7 @@ export class HaDataTable extends LitElement {
padding-left: 16px; padding-left: 16px;
/* @noflip */ /* @noflip */
padding-right: 0; padding-right: 0;
width: 40px; width: 56px;
} }
[dir="rtl"] .mdc-data-table__header-cell--checkbox, [dir="rtl"] .mdc-data-table__header-cell--checkbox,
.mdc-data-table__header-cell--checkbox[dir="rtl"], .mdc-data-table__header-cell--checkbox[dir="rtl"],
@ -591,7 +595,7 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell--icon, .mdc-data-table__header-cell--icon,
.mdc-data-table__cell--icon { .mdc-data-table__cell--icon {
width: 24px; width: 54px;
} }
.mdc-data-table__header-cell.mdc-data-table__header-cell--icon { .mdc-data-table__header-cell.mdc-data-table__header-cell--icon {
@ -695,6 +699,9 @@ export class HaDataTable extends LitElement {
.center { .center {
text-align: center; text-align: center;
} }
.secondary {
color: var(--secondary-text-color);
}
.scroller { .scroller {
display: flex; display: flex;
position: relative; position: relative;

View File

@ -530,6 +530,7 @@ class HaSidebar extends LitElement {
overflow-x: hidden; overflow-x: hidden;
scrollbar-color: var(--scrollbar-thumb-color) transparent; scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin; scrollbar-width: thin;
background: none;
} }
a { a {

View File

@ -27,6 +27,16 @@ export interface EntityRegistryEntryUpdateParams {
new_entity_id?: string; new_entity_id?: string;
} }
export const findBatteryEntity = (
hass: HomeAssistant,
entities: EntityRegistryEntry[]
): EntityRegistryEntry | undefined =>
entities.find(
(entity) =>
hass.states[entity.entity_id] &&
hass.states[entity.entity_id].attributes.device_class === "battery"
);
export const computeEntityRegistryName = ( export const computeEntityRegistryName = (
hass: HomeAssistant, hass: HomeAssistant,
entry: EntityRegistryEntry entry: EntityRegistryEntry

View File

@ -1,133 +0,0 @@
import {
DeviceRegistryEntry,
computeDeviceName,
} from "../../../../data/device_registry";
import { loadDeviceRegistryDetailDialog } from "../../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
import {
LitElement,
html,
customElement,
property,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import { HomeAssistant } from "../../../../types";
import { AreaRegistryEntry } from "../../../../data/area_registry";
@customElement("ha-device-card")
export class HaDeviceCard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public device!: DeviceRegistryEntry;
@property() public devices!: DeviceRegistryEntry[];
@property() public areas!: AreaRegistryEntry[];
@property() public narrow!: boolean;
protected render(): TemplateResult {
return html`
<div class="info">
${this.device.model
? html`
<div class="model">${this.device.model}</div>
`
: ""}
${this.device.manufacturer
? html`
<div class="manuf">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.manuf",
"manufacturer",
this.device.manufacturer
)}
</div>
`
: ""}
${this.device.area_id
? html`
<div class="area">
<div class="extra-info">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.area",
"area",
this._computeArea(this.areas, this.device)
)}
</div>
</div>
`
: ""}
${this.device.via_device_id
? html`
<div class="extra-info">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.via"
)}
<span class="hub"
>${this._computeDeviceName(
this.devices,
this.device.via_device_id
)}</span
>
</div>
`
: ""}
${this.device.sw_version
? html`
<div class="extra-info">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.firmware",
"version",
this.device.sw_version
)}
</div>
`
: ""}
</div>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
loadDeviceRegistryDetailDialog();
}
private _computeArea(areas, device) {
if (!areas || !device || !device.area_id) {
return "No Area";
}
// +1 because of "No Area" entry
return areas.find((area) => area.area_id === device.area_id).name;
}
private _computeDeviceName(devices, deviceId) {
const device = devices.find((dev) => dev.id === deviceId);
return device
? computeDeviceName(device, this.hass)
: `(${this.hass.localize(
"ui.panel.config.integrations.config_entry.device_unavailable"
)})`;
}
static get styles(): CSSResult {
return css`
ha-card {
flex: 1 0 100%;
padding-bottom: 10px;
min-width: 0;
}
.device {
width: 30%;
}
.area {
color: var(--primary-text-color);
}
.extra-info {
margin-top: 8px;
}
.manuf,
.entity-id,
.model {
color: var(--secondary-text-color);
}
`;
}
}

View File

@ -166,6 +166,9 @@ export class HaDeviceEntitiesCard extends LitElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
:host {
display: block;
}
ha-icon { ha-icon {
width: 40px; width: 40px;
} }
@ -182,6 +185,9 @@ export class HaDeviceEntitiesCard extends LitElement {
#entities > * { #entities > * {
margin: 8px 16px 8px 8px; margin: 8px 16px 8px 8px;
} }
#entities > paper-icon-item {
margin: 0;
}
paper-icon-item { paper-icon-item {
min-height: 40px; min-height: 40px;
padding: 0 8px; padding: 0 8px;

View File

@ -0,0 +1,118 @@
import {
DeviceRegistryEntry,
computeDeviceName,
} from "../../../../data/device_registry";
import { loadDeviceRegistryDetailDialog } from "../../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
import {
LitElement,
html,
customElement,
property,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import { HomeAssistant } from "../../../../types";
import { AreaRegistryEntry } from "../../../../data/area_registry";
@customElement("ha-device-info-card")
export class HaDeviceCard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public device!: DeviceRegistryEntry;
@property() public devices!: DeviceRegistryEntry[];
@property() public areas!: AreaRegistryEntry[];
@property() public narrow!: boolean;
protected render(): TemplateResult {
return html`
<ha-card header="Device info">
<div class="card-content">
${this.device.model
? html`
<div class="model">${this.device.model}</div>
`
: ""}
${this.device.manufacturer
? html`
<div class="manuf">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.manuf",
"manufacturer",
this.device.manufacturer
)}
</div>
`
: ""}
${this.device.via_device_id
? html`
<div class="extra-info">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.via"
)}
<span class="hub"
>${this._computeDeviceName(
this.devices,
this.device.via_device_id
)}</span
>
</div>
`
: ""}
${this.device.sw_version
? html`
<div class="extra-info">
${this.hass.localize(
"ui.panel.config.integrations.config_entry.firmware",
"version",
this.device.sw_version
)}
</div>
`
: ""}
<slot></slot>
</div>
</ha-card>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
loadDeviceRegistryDetailDialog();
}
private _computeDeviceName(devices, deviceId) {
const device = devices.find((dev) => dev.id === deviceId);
return device
? computeDeviceName(device, this.hass)
: `(${this.hass.localize(
"ui.panel.config.integrations.config_entry.device_unavailable"
)})`;
}
static get styles(): CSSResult {
return css`
:host {
display: block;
}
ha-card {
flex: 1 0 100%;
padding-bottom: 10px;
min-width: 0;
}
.device {
width: 30%;
}
.area {
color: var(--primary-text-color);
}
.extra-info {
margin-top: 8px;
}
.manuf,
.entity-id,
.model {
color: var(--secondary-text-color);
}
`;
}
}

View File

@ -15,7 +15,7 @@ import "../../../layouts/hass-tabs-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-info-card";
import "./device-detail/ha-device-card-mqtt"; import "./device-detail/ha-device-card-mqtt";
import "./device-detail/ha-device-entities-card"; import "./device-detail/ha-device-entities-card";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
@ -23,6 +23,7 @@ import { ConfigEntry } from "../../../data/config_entries";
import { import {
EntityRegistryEntry, EntityRegistryEntry,
updateEntityRegistryEntry, updateEntityRegistryEntry,
findBatteryEntity,
} from "../../../data/entity_registry"; } from "../../../data/entity_registry";
import { import {
DeviceRegistryEntry, DeviceRegistryEntry,
@ -41,9 +42,9 @@ import { createValidEntityId } from "../../../common/entity/valid_entity_id";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { RelatedResult, findRelated } from "../../../data/search"; import { RelatedResult, findRelated } from "../../../data/search";
import { SceneEntities, showSceneEditor } from "../../../data/scene"; import { SceneEntities, showSceneEditor } from "../../../data/scene";
import { navigate } from "../../../common/navigate";
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation"; import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { ifDefined } from "lit-html/directives/if-defined";
export interface EntityRegistryStateEntry extends EntityRegistryEntry { export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string; stateName?: string;
@ -96,6 +97,10 @@ export class HaConfigDevicePage extends LitElement {
) )
); );
private _batteryEntity = memoizeOne((entities: EntityRegistryEntry[]):
| EntityRegistryEntry
| undefined => findBatteryEntity(this.hass, entities));
protected firstUpdated(changedProps) { protected firstUpdated(changedProps) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
loadDeviceRegistryDetailDialog(); loadDeviceRegistryDetailDialog();
@ -123,6 +128,11 @@ export class HaConfigDevicePage extends LitElement {
const integrations = this._integrations(device, this.entries); const integrations = this._integrations(device, this.entries);
const entities = this._entities(this.deviceId, this.entities); const entities = this._entities(this.deviceId, this.entities);
const batteryEntity = this._batteryEntity(entities);
const batteryState = batteryEntity
? this.hass.states[batteryEntity.entity_id]
: undefined;
const areaName = this._computeAreaName(this.areas, device);
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage
@ -148,21 +158,58 @@ export class HaConfigDevicePage extends LitElement {
></paper-icon-button> ></paper-icon-button>
<div class="container"> <div class="container">
<div class="left"> <div class="header fullwidth">
<div class="device-info"> ${
${ this.narrow
this.narrow ? ""
? "" : html`
: html` <div>
<h1>${computeDeviceName(device, this.hass)}</h1> <h1>${computeDeviceName(device, this.hass)}</h1>
` ${areaName
} ? this.hass.localize(
<ha-device-card "ui.panel.config.integrations.config_entry.area",
"area",
areaName
)
: ""}
</div>
`
}
<div class="header-right">
${
batteryState
? html`
<div class="battery">
${batteryState.state}%
<ha-state-icon
.hass=${this.hass!}
.stateObj=${batteryState}
></ha-state-icon>
</div>
`
: ""
}
<img
src="https://brands.home-assistant.io/${
integrations[0]
}/logo.png"
srcset="
https://brands.home-assistant.io/${
integrations[0]
}/logo@2x.png 2x
"
@load=${this._onImageLoad}
@error=${this._onImageError}
/>
</div>
</div>
<div class="column">
<ha-device-info-card
.hass=${this.hass} .hass=${this.hass}
.areas=${this.areas} .areas=${this.areas}
.devices=${this.devices} .devices=${this.devices}
.device=${device} .device=${device}
></ha-device-card> >
${ ${
integrations.includes("mqtt") integrations.includes("mqtt")
? html` ? html`
@ -173,7 +220,7 @@ export class HaConfigDevicePage extends LitElement {
` `
: html`` : html``
} }
</div> </ha-device-info-card>
${ ${
entities.length entities.length
@ -187,32 +234,46 @@ export class HaConfigDevicePage extends LitElement {
: html`` : html``
} }
</div> </div>
<div class="right">
<div class="column"> <div class="column">
${ ${
isComponentLoaded(this.hass, "automation") isComponentLoaded(this.hass, "automation")
? html` ? html`
<ha-card <ha-card>
.header=${this.hass.localize( <div class="card-header">
"ui.panel.config.devices.automation.automations" ${this.hass.localize(
)} "ui.panel.config.devices.automation.automations"
>${this._related?.automation?.length )}
<paper-icon-button
@click=${this._showAutomationDialog}
title=${this.hass.localize(
"ui.panel.config.devices.automation.create"
)}
icon="hass:plus-circle"
></paper-icon-button>
</div>
${this._related?.automation?.length
? this._related.automation.map((automation) => { ? this._related.automation.map((automation) => {
const state = this.hass.states[automation]; const state = this.hass.states[automation];
return state return state
? html` ? html`
<div> <div>
<paper-item <a
.automation=${state} href=${ifDefined(
@click=${this._openAutomation} state.attributes.id
.disabled=${!state.attributes.id} ? `/config/automation/edit/${state.attributes.id}`
: undefined
)}
> >
<paper-item-body> <paper-item
${state.attributes.friendly_name || .automation=${state}
automation} .disabled=${!state.attributes.id}
</paper-item-body> >
<ha-icon-next></ha-icon-next> <paper-item-body>
</paper-item> ${computeStateName(state)}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
${!state.attributes.id ${!state.attributes.id
? html` ? html`
<paper-tooltip <paper-tooltip
@ -233,13 +294,6 @@ export class HaConfigDevicePage extends LitElement {
)}</paper-item )}</paper-item
> >
`} `}
<div class="card-actions">
<mwc-button @click=${this._showAutomationDialog}>
${this.hass.localize(
"ui.panel.config.devices.automation.create"
)}
</mwc-button>
</div>
</ha-card> </ha-card>
` `
: "" : ""
@ -249,58 +303,72 @@ export class HaConfigDevicePage extends LitElement {
${ ${
isComponentLoaded(this.hass, "scene") isComponentLoaded(this.hass, "scene")
? html` ? html`
<ha-card <ha-card>
.header=${this.hass.localize( <div class="card-header">
"ui.panel.config.devices.scene.scenes" ${this.hass.localize(
)} "ui.panel.config.devices.scene.scenes"
>${this._related?.scene?.length )}
? this._related.scene.map((scene) => { ${
const state = this.hass.states[scene]; entities.length
return state
? html` ? html`
<div> <paper-icon-button
<paper-item @click=${this._createScene}
.scene=${state} title=${this.hass.localize(
@click=${this._openScene} "ui.panel.config.devices.scene.create"
.disabled=${!state.attributes.id} )}
> icon="hass:plus-circle"
<paper-item-body> ></paper-icon-button>
${state.attributes.friendly_name ||
scene}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
${!state.attributes.id
? html`
<paper-tooltip
>${this.hass.localize(
"ui.panel.config.devices.cant_edit"
)}
</paper-tooltip>
`
: ""}
</div>
` `
: ""; : ""
}) }
: html` </div>
<paper-item class="no-link"
>${this.hass.localize( ${
"ui.panel.config.devices.scene.no_scenes" this._related?.scene?.length
)}</paper-item ? this._related.scene.map((scene) => {
> const state = this.hass.states[scene];
`} return state
${entities.length ? html`
? html` <div>
<div class="card-actions"> <a
<mwc-button @click=${this._createScene}> href=${ifDefined(
${this.hass.localize( state.attributes.id
"ui.panel.config.devices.scene.create" ? `/config/scene/edit/${state.attributes.id}`
)} : undefined
</mwc-button> )}
</div> >
` <paper-item
: ""} .scene=${state}
.disabled=${!state.attributes.id}
>
<paper-item-body>
${computeStateName(state)}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
${!state.attributes.id
? html`
<paper-tooltip
>${this.hass.localize(
"ui.panel.config.devices.cant_edit"
)}
</paper-tooltip>
`
: ""}
</div>
`
: "";
})
: html`
<paper-item class="no-link"
>${this.hass.localize(
"ui.panel.config.devices.scene.no_scenes"
)}</paper-item
>
`
}
</ha-card>
</ha-card> </ha-card>
` `
: "" : ""
@ -308,25 +376,38 @@ export class HaConfigDevicePage extends LitElement {
${ ${
isComponentLoaded(this.hass, "script") isComponentLoaded(this.hass, "script")
? html` ? html`
<ha-card <ha-card>
.header=${this.hass.localize( <div class="card-header">
"ui.panel.config.devices.script.scripts" ${this.hass.localize(
)} "ui.panel.config.devices.script.scripts"
>${this._related?.script?.length )}
<paper-icon-button
@click=${this._showScriptDialog}
title=${this.hass.localize(
"ui.panel.config.devices.script.create"
)}
icon="hass:plus-circle"
></paper-icon-button>
</div>
${this._related?.script?.length
? this._related.script.map((script) => { ? this._related.script.map((script) => {
const state = this.hass.states[script]; const state = this.hass.states[script];
return state return state
? html` ? html`
<paper-item <a
.script=${script} href=${ifDefined(
@click=${this._openScript} state.attributes.id
? `/config/script/edit/${state.attributes.id}`
: undefined
)}
> >
<paper-item-body> <paper-item .script=${script}>
${state.attributes.friendly_name || <paper-item-body>
script} ${computeStateName(state)}
</paper-item-body> </paper-item-body>
<ha-icon-next></ha-icon-next> <ha-icon-next></ha-icon-next>
</paper-item> </paper-item>
</a>
` `
: ""; : "";
}) })
@ -337,19 +418,11 @@ export class HaConfigDevicePage extends LitElement {
)}</paper-item )}</paper-item
> >
`} `}
<div class="card-actions">
<mwc-button @click=${this._showScriptDialog}>
${this.hass.localize(
"ui.panel.config.devices.script.create"
)}
</mwc-button>
</div>
</ha-card> </ha-card>
` `
: "" : ""
} }
</div> </div>
</div>
</div> </div>
</ha-config-section> </ha-config-section>
</hass-tabs-subpage> `; </hass-tabs-subpage> `;
@ -363,6 +436,21 @@ export class HaConfigDevicePage extends LitElement {
return state ? computeStateName(state) : null; return state ? computeStateName(state) : null;
} }
private _computeAreaName(areas, device): string | undefined {
if (!areas || !device || !device.area_id) {
return undefined;
}
return areas.find((area) => area.area_id === device.area_id).name;
}
private _onImageLoad(ev) {
ev.target.style.display = "inline-block";
}
private _onImageError(ev) {
ev.target.style.display = "none";
}
private async _findRelated() { private async _findRelated() {
this._related = await findRelated(this.hass, "device", this.deviceId); this._related = await findRelated(this.hass, "device", this.deviceId);
} }
@ -377,25 +465,6 @@ export class HaConfigDevicePage extends LitElement {
}); });
} }
private _openScene(ev: Event) {
const state = (ev.currentTarget as any).scene;
if (state.attributes.id) {
navigate(this, `/config/scene/edit/${state.attributes.id}`);
}
}
private _openScript(ev: Event) {
const script = (ev.currentTarget as any).script;
navigate(this, `/config/script/edit/${script}`);
}
private _openAutomation(ev: Event) {
const state = (ev.currentTarget as any).automation;
if (state.attributes.id) {
navigate(this, `/config/automation/edit/${state.attributes.id}`);
}
}
private _showScriptDialog() { private _showScriptDialog() {
showDeviceAutomationDialog(this, { deviceId: this.deviceId, script: true }); showDeviceAutomationDialog(this, { deviceId: this.deviceId, script: true });
} }
@ -478,6 +547,18 @@ export class HaConfigDevicePage extends LitElement {
margin-bottom: 32px; margin-bottom: 32px;
} }
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-header paper-icon-button {
margin-right: -8px;
color: var(--primary-color);
height: auto;
}
.device-info { .device-info {
padding: 16px; padding: 16px;
} }
@ -486,7 +567,7 @@ export class HaConfigDevicePage extends LitElement {
} }
h1 { h1 {
margin-top: 0; margin: 0;
font-family: var(--paper-font-headline_-_font-family); font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var( -webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing --paper-font-headline_-_-webkit-font-smoothing
@ -498,46 +579,60 @@ export class HaConfigDevicePage extends LitElement {
opacity: var(--dark-primary-opacity); opacity: var(--dark-primary-opacity);
} }
.left, .header {
display: flex;
justify-content: space-between;
}
.column, .column,
.fullwidth { .fullwidth {
padding: 8px; padding: 8px;
box-sizing: border-box; box-sizing: border-box;
} }
.column {
.left { width: 33%;
width: 33.33%; flex-grow: 1;
padding-bottom: 0;
} }
.right {
width: 66.66%;
display: flex;
flex-wrap: wrap;
}
.fullwidth { .fullwidth {
width: 100%; width: 100%;
flex-grow: 1;
} }
.column { .header-right {
width: 50%; align-self: center;
}
.header-right img {
height: 30px;
}
.header-right {
display: flex;
}
.header-right:first-child {
width: 100%;
justify-content: flex-end;
}
.header-right > *:not(:first-child) {
margin-left: 16px;
}
.battery {
align-self: center;
align-items: center;
display: flex;
} }
.column > *:not(:first-child) { .column > *:not(:first-child) {
margin-top: 16px; margin-top: 16px;
} }
:host([narrow]) .left,
:host([narrow]) .right,
:host([narrow]) .column { :host([narrow]) .column {
width: 100%; width: 100%;
} }
:host([narrow]) .container > *:first-child {
padding-top: 0;
}
:host([narrow]) .container { :host([narrow]) .container {
margin-top: 0; margin-top: 0;
} }
@ -549,6 +644,11 @@ export class HaConfigDevicePage extends LitElement {
paper-item.no-link { paper-item.no-link {
cursor: default; cursor: default;
} }
a {
text-decoration: none;
color: var(--primary-text-color);
}
`; `;
} }
} }

View File

@ -13,7 +13,10 @@ import {
computeDeviceName, computeDeviceName,
DeviceEntityLookup, DeviceEntityLookup,
} from "../../../data/device_registry"; } from "../../../data/device_registry";
import { EntityRegistryEntry } from "../../../data/entity_registry"; import {
EntityRegistryEntry,
findBatteryEntity,
} 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 { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
@ -130,25 +133,38 @@ export class HaConfigDeviceDashboard extends LitElement {
direction: "asc", direction: "asc",
grows: true, grows: true,
template: (name, device: DataTableRowData) => { template: (name, device: DataTableRowData) => {
const battery = device.battery_entity
? this.hass.states[device.battery_entity]
: undefined;
// Have to work on a nice layout for mobile
return html` return html`
${name}<br /> ${name}
${device.area} | ${device.integration}<br /> <div class="secondary">
${battery && !isNaN(battery.state as any) ${device.area} | ${device.integration}
? html` </div>
${battery.state}%
<ha-state-icon
.hass=${this.hass!}
.stateObj=${battery}
></ha-state-icon>
`
: ""}
`; `;
}, },
}, },
battery_entity: {
title: this.hass.localize(
"ui.panel.config.devices.data_table.battery"
),
sortable: true,
type: "numeric",
width: "90px",
template: (batteryEntity: string) => {
const battery = batteryEntity
? this.hass.states[batteryEntity]
: undefined;
return battery
? html`
${isNaN(battery.state as any) ? "-" : battery.state}%
<ha-state-icon
.hass=${this.hass!}
.stateObj=${battery}
></ha-state-icon>
`
: html`
-
`;
},
},
} }
: { : {
name: { name: {
@ -198,7 +214,8 @@ export class HaConfigDeviceDashboard extends LitElement {
), ),
sortable: true, sortable: true,
type: "numeric", type: "numeric",
width: "60px", width: "15%",
maxWidth: "90px",
template: (batteryEntity: string) => { template: (batteryEntity: string) => {
const battery = batteryEntity const battery = batteryEntity
? this.hass.states[batteryEntity] ? this.hass.states[batteryEntity]
@ -246,12 +263,10 @@ export class HaConfigDeviceDashboard extends LitElement {
deviceId: string, deviceId: string,
deviceEntityLookup: DeviceEntityLookup deviceEntityLookup: DeviceEntityLookup
): string | undefined { ): string | undefined {
const batteryEntity = (deviceEntityLookup[deviceId] || []).find( const batteryEntity = findBatteryEntity(
(entity) => this.hass,
this.hass.states[entity.entity_id] && deviceEntityLookup[deviceId] || []
this.hass.states[entity.entity_id].attributes.device_class === "battery"
); );
return batteryEntity ? batteryEntity.entity_id : undefined; return batteryEntity ? batteryEntity.entity_id : undefined;
} }

View File

@ -23,7 +23,10 @@ import {
computeDeviceName, computeDeviceName,
DeviceEntityLookup, DeviceEntityLookup,
} from "../../../data/device_registry"; } from "../../../data/device_registry";
import { EntityRegistryEntry } from "../../../data/entity_registry"; import {
EntityRegistryEntry,
findBatteryEntity,
} 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 { navigate } from "../../../common/navigate";
@ -204,7 +207,8 @@ export class HaDevicesDataTable extends LitElement {
), ),
sortable: true, sortable: true,
type: "numeric", type: "numeric",
width: "60px", width: "15%",
maxWidth: "90px",
template: (batteryEntity: string) => { template: (batteryEntity: string) => {
const battery = batteryEntity const battery = batteryEntity
? this.hass.states[batteryEntity] ? this.hass.states[batteryEntity]
@ -250,12 +254,10 @@ export class HaDevicesDataTable extends LitElement {
deviceId: string, deviceId: string,
deviceEntityLookup: DeviceEntityLookup deviceEntityLookup: DeviceEntityLookup
): string | undefined { ): string | undefined {
const batteryEntity = (deviceEntityLookup[deviceId] || []).find( const batteryEntity = findBatteryEntity(
(entity) => this.hass,
this.hass.states[entity.entity_id] && deviceEntityLookup[deviceId] || []
this.hass.states[entity.entity_id].attributes.device_class === "battery"
); );
return batteryEntity ? batteryEntity.entity_id : undefined; return batteryEntity ? batteryEntity.entity_id : undefined;
} }