mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-21 00:06:35 +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";
|
||||
template?: <T>(data: any, row: T) => TemplateResult | string;
|
||||
width?: string;
|
||||
maxWidth?: string;
|
||||
grows?: boolean;
|
||||
}
|
||||
|
||||
@ -241,9 +242,8 @@ export class HaDataTable extends LitElement {
|
||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||
style=${column.width
|
||||
? styleMap({
|
||||
[column.grows ? "minWidth" : "width"]: String(
|
||||
column.width
|
||||
),
|
||||
[column.grows ? "minWidth" : "width"]: column.width,
|
||||
maxWidth: column.maxWidth || "",
|
||||
})
|
||||
: ""}
|
||||
role="columnheader"
|
||||
@ -329,7 +329,10 @@ export class HaDataTable extends LitElement {
|
||||
? styleMap({
|
||||
[column.grows
|
||||
? "minWidth"
|
||||
: "width"]: String(column.width),
|
||||
: "width"]: column.width,
|
||||
maxWidth: column.maxWidth
|
||||
? column.maxWidth
|
||||
: "",
|
||||
})
|
||||
: ""}
|
||||
>
|
||||
@ -532,6 +535,7 @@ export class HaDataTable extends LitElement {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell.mdc-data-table__cell--icon {
|
||||
@ -544,7 +548,7 @@ export class HaDataTable extends LitElement {
|
||||
padding-left: 16px;
|
||||
/* @noflip */
|
||||
padding-right: 0;
|
||||
width: 40px;
|
||||
width: 56px;
|
||||
}
|
||||
[dir="rtl"] .mdc-data-table__header-cell--checkbox,
|
||||
.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__cell--icon {
|
||||
width: 24px;
|
||||
width: 54px;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell.mdc-data-table__header-cell--icon {
|
||||
@ -695,6 +699,9 @@ export class HaDataTable extends LitElement {
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.scroller {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
@ -530,6 +530,7 @@ class HaSidebar extends LitElement {
|
||||
overflow-x: hidden;
|
||||
scrollbar-color: var(--scrollbar-thumb-color) transparent;
|
||||
scrollbar-width: thin;
|
||||
background: none;
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -27,6 +27,16 @@ export interface EntityRegistryEntryUpdateParams {
|
||||
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 = (
|
||||
hass: HomeAssistant,
|
||||
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 {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-icon {
|
||||
width: 40px;
|
||||
}
|
||||
@ -182,6 +185,9 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
#entities > * {
|
||||
margin: 8px 16px 8px 8px;
|
||||
}
|
||||
#entities > paper-icon-item {
|
||||
margin: 0;
|
||||
}
|
||||
paper-icon-item {
|
||||
min-height: 40px;
|
||||
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 "../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-entities-card";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@ -23,6 +23,7 @@ import { ConfigEntry } from "../../../data/config_entries";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
findBatteryEntity,
|
||||
} from "../../../data/entity_registry";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
@ -41,9 +42,9 @@ import { createValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { RelatedResult, findRelated } from "../../../data/search";
|
||||
import { SceneEntities, showSceneEditor } from "../../../data/scene";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
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) {
|
||||
super.firstUpdated(changedProps);
|
||||
loadDeviceRegistryDetailDialog();
|
||||
@ -123,6 +128,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
const integrations = this._integrations(device, this.entries);
|
||||
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`
|
||||
<hass-tabs-subpage
|
||||
@ -148,21 +158,58 @@ export class HaConfigDevicePage extends LitElement {
|
||||
></paper-icon-button>
|
||||
|
||||
<div class="container">
|
||||
<div class="left">
|
||||
<div class="device-info">
|
||||
${
|
||||
this.narrow
|
||||
? ""
|
||||
: html`
|
||||
<div class="header fullwidth">
|
||||
${
|
||||
this.narrow
|
||||
? ""
|
||||
: html`
|
||||
<div>
|
||||
<h1>${computeDeviceName(device, this.hass)}</h1>
|
||||
`
|
||||
}
|
||||
<ha-device-card
|
||||
${areaName
|
||||
? this.hass.localize(
|
||||
"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}
|
||||
.areas=${this.areas}
|
||||
.devices=${this.devices}
|
||||
.device=${device}
|
||||
></ha-device-card>
|
||||
>
|
||||
${
|
||||
integrations.includes("mqtt")
|
||||
? html`
|
||||
@ -173,7 +220,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
`
|
||||
: html``
|
||||
}
|
||||
</div>
|
||||
</ha-device-info-card>
|
||||
|
||||
${
|
||||
entities.length
|
||||
@ -187,32 +234,46 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: html``
|
||||
}
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="column">
|
||||
${
|
||||
isComponentLoaded(this.hass, "automation")
|
||||
? html`
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.automations"
|
||||
)}
|
||||
>${this._related?.automation?.length
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.automations"
|
||||
)}
|
||||
<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) => {
|
||||
const state = this.hass.states[automation];
|
||||
return state
|
||||
? html`
|
||||
<div>
|
||||
<paper-item
|
||||
.automation=${state}
|
||||
@click=${this._openAutomation}
|
||||
.disabled=${!state.attributes.id}
|
||||
<a
|
||||
href=${ifDefined(
|
||||
state.attributes.id
|
||||
? `/config/automation/edit/${state.attributes.id}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<paper-item-body>
|
||||
${state.attributes.friendly_name ||
|
||||
automation}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
<paper-item
|
||||
.automation=${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
|
||||
@ -233,13 +294,6 @@ export class HaConfigDevicePage extends LitElement {
|
||||
)}</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>
|
||||
`
|
||||
: ""
|
||||
@ -249,58 +303,72 @@ export class HaConfigDevicePage extends LitElement {
|
||||
${
|
||||
isComponentLoaded(this.hass, "scene")
|
||||
? html`
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.scenes"
|
||||
)}
|
||||
>${this._related?.scene?.length
|
||||
? this._related.scene.map((scene) => {
|
||||
const state = this.hass.states[scene];
|
||||
return state
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.scenes"
|
||||
)}
|
||||
${
|
||||
entities.length
|
||||
? html`
|
||||
<div>
|
||||
<paper-item
|
||||
.scene=${state}
|
||||
@click=${this._openScene}
|
||||
.disabled=${!state.attributes.id}
|
||||
>
|
||||
<paper-item-body>
|
||||
${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>
|
||||
<paper-icon-button
|
||||
@click=${this._createScene}
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.create"
|
||||
)}
|
||||
icon="hass:plus-circle"
|
||||
></paper-icon-button>
|
||||
`
|
||||
: "";
|
||||
})
|
||||
: html`
|
||||
<paper-item class="no-link"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.no_scenes"
|
||||
)}</paper-item
|
||||
>
|
||||
`}
|
||||
${entities.length
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._createScene}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.create"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
|
||||
${
|
||||
this._related?.scene?.length
|
||||
? this._related.scene.map((scene) => {
|
||||
const state = this.hass.states[scene];
|
||||
return state
|
||||
? html`
|
||||
<div>
|
||||
<a
|
||||
href=${ifDefined(
|
||||
state.attributes.id
|
||||
? `/config/scene/edit/${state.attributes.id}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<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>
|
||||
`
|
||||
: ""
|
||||
@ -308,25 +376,38 @@ export class HaConfigDevicePage extends LitElement {
|
||||
${
|
||||
isComponentLoaded(this.hass, "script")
|
||||
? html`
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.devices.script.scripts"
|
||||
)}
|
||||
>${this._related?.script?.length
|
||||
<ha-card>
|
||||
<div class="card-header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.script.scripts"
|
||||
)}
|
||||
<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) => {
|
||||
const state = this.hass.states[script];
|
||||
return state
|
||||
? html`
|
||||
<paper-item
|
||||
.script=${script}
|
||||
@click=${this._openScript}
|
||||
<a
|
||||
href=${ifDefined(
|
||||
state.attributes.id
|
||||
? `/config/script/edit/${state.attributes.id}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<paper-item-body>
|
||||
${state.attributes.friendly_name ||
|
||||
script}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
<paper-item .script=${script}>
|
||||
<paper-item-body>
|
||||
${computeStateName(state)}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</a>
|
||||
`
|
||||
: "";
|
||||
})
|
||||
@ -337,19 +418,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
)}</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>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ha-config-section>
|
||||
</hass-tabs-subpage> `;
|
||||
@ -363,6 +436,21 @@ export class HaConfigDevicePage extends LitElement {
|
||||
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() {
|
||||
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() {
|
||||
showDeviceAutomationDialog(this, { deviceId: this.deviceId, script: true });
|
||||
}
|
||||
@ -478,6 +547,18 @@ export class HaConfigDevicePage extends LitElement {
|
||||
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 {
|
||||
padding: 16px;
|
||||
}
|
||||
@ -486,7 +567,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
margin: 0;
|
||||
font-family: var(--paper-font-headline_-_font-family);
|
||||
-webkit-font-smoothing: var(
|
||||
--paper-font-headline_-_-webkit-font-smoothing
|
||||
@ -498,46 +579,60 @@ export class HaConfigDevicePage extends LitElement {
|
||||
opacity: var(--dark-primary-opacity);
|
||||
}
|
||||
|
||||
.left,
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.column,
|
||||
.fullwidth {
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.left {
|
||||
width: 33.33%;
|
||||
padding-bottom: 0;
|
||||
.column {
|
||||
width: 33%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 66.66%;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.fullwidth {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.column {
|
||||
width: 50%;
|
||||
.header-right {
|
||||
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) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
:host([narrow]) .left,
|
||||
:host([narrow]) .right,
|
||||
:host([narrow]) .column {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host([narrow]) .container > *:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .container {
|
||||
margin-top: 0;
|
||||
}
|
||||
@ -549,6 +644,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
paper-item.no-link {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,10 @@ import {
|
||||
computeDeviceName,
|
||||
DeviceEntityLookup,
|
||||
} from "../../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
findBatteryEntity,
|
||||
} from "../../../data/entity_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
@ -130,25 +133,38 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
direction: "asc",
|
||||
grows: true,
|
||||
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`
|
||||
${name}<br />
|
||||
${device.area} | ${device.integration}<br />
|
||||
${battery && !isNaN(battery.state as any)
|
||||
? html`
|
||||
${battery.state}%
|
||||
<ha-state-icon
|
||||
.hass=${this.hass!}
|
||||
.stateObj=${battery}
|
||||
></ha-state-icon>
|
||||
`
|
||||
: ""}
|
||||
${name}
|
||||
<div class="secondary">
|
||||
${device.area} | ${device.integration}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
},
|
||||
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: {
|
||||
@ -198,7 +214,8 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
),
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
width: "60px",
|
||||
width: "15%",
|
||||
maxWidth: "90px",
|
||||
template: (batteryEntity: string) => {
|
||||
const battery = batteryEntity
|
||||
? this.hass.states[batteryEntity]
|
||||
@ -246,12 +263,10 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
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"
|
||||
const batteryEntity = findBatteryEntity(
|
||||
this.hass,
|
||||
deviceEntityLookup[deviceId] || []
|
||||
);
|
||||
|
||||
return batteryEntity ? batteryEntity.entity_id : undefined;
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,10 @@ import {
|
||||
computeDeviceName,
|
||||
DeviceEntityLookup,
|
||||
} from "../../../data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
findBatteryEntity,
|
||||
} from "../../../data/entity_registry";
|
||||
import { ConfigEntry } from "../../../data/config_entries";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
@ -204,7 +207,8 @@ export class HaDevicesDataTable extends LitElement {
|
||||
),
|
||||
sortable: true,
|
||||
type: "numeric",
|
||||
width: "60px",
|
||||
width: "15%",
|
||||
maxWidth: "90px",
|
||||
template: (batteryEntity: string) => {
|
||||
const battery = batteryEntity
|
||||
? this.hass.states[batteryEntity]
|
||||
@ -250,12 +254,10 @@ export class HaDevicesDataTable extends LitElement {
|
||||
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"
|
||||
const batteryEntity = findBatteryEntity(
|
||||
this.hass,
|
||||
deviceEntityLookup[deviceId] || []
|
||||
);
|
||||
|
||||
return batteryEntity ? batteryEntity.entity_id : undefined;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user