mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 16:26:43 +00:00
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:
parent
ddb525f6cd
commit
f6dac98abd
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
118
src/panels/config/devices/device-detail/ha-device-info-card.ts
Normal file
118
src/panels/config/devices/device-detail/ha-device-info-card.ts
Normal 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);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user