Add related items to device info (#4637)

* Add related items to device info

* Update ha-config-device-page.ts

* remove log

* Lint

* Fix dialog logic showing triggers on script dialog

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
Bram Kragten 2020-01-29 00:13:44 +01:00 committed by GitHub
parent 036eedc69d
commit 65994e7280
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 768 additions and 272 deletions

View File

@ -47,12 +47,9 @@ export class HaChips extends LitElement {
} }
private _handleClick(ev) { private _handleClick(ev) {
fireEvent( fireEvent(this, "chip-clicked", {
this, index: ev.target.closest("button").index,
"chip-clicked", });
{ index: ev.target.closest("button").index },
{ bubbles: false }
);
} }
static get styles(): CSSResult { static get styles(): CSSResult {

View File

@ -4,6 +4,7 @@ import {
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { HomeAssistant, ServiceCallResponse } from "../types"; import { HomeAssistant, ServiceCallResponse } from "../types";
import { navigate } from "../common/navigate";
export const SCENE_IGNORED_DOMAINS = [ export const SCENE_IGNORED_DOMAINS = [
"sensor", "sensor",
@ -18,6 +19,22 @@ export const SCENE_IGNORED_DOMAINS = [
"zone", "zone",
]; ];
let inititialSceneEditorData: Partial<SceneConfig> | undefined;
export const showSceneEditor = (
el: HTMLElement,
data?: Partial<SceneConfig>
) => {
inititialSceneEditorData = data;
navigate(el, "/config/scene/edit/new");
};
export const getSceneEditorInitData = () => {
const data = inititialSceneEditorData;
inititialSceneEditorData = undefined;
return data;
};
export interface SceneEntity extends HassEntityBase { export interface SceneEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & { id?: string }; attributes: HassEntityAttributeBase & { id?: string };
} }

View File

@ -5,6 +5,7 @@ import {
HassEntityBase, HassEntityBase,
HassEntityAttributeBase, HassEntityAttributeBase,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { navigate } from "../common/navigate";
export interface ScriptEntity extends HassEntityBase { export interface ScriptEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & { attributes: HassEntityAttributeBase & {
@ -65,3 +66,19 @@ export const triggerScript = (
export const deleteScript = (hass: HomeAssistant, objectId: string) => export const deleteScript = (hass: HomeAssistant, objectId: string) =>
hass.callApi("DELETE", `config/script/config/${objectId}`); hass.callApi("DELETE", `config/script/config/${objectId}`);
let inititialScriptEditorData: Partial<ScriptConfig> | undefined;
export const showScriptEditor = (
el: HTMLElement,
data?: Partial<ScriptConfig>
) => {
inititialScriptEditorData = data;
navigate(el, "/config/script/new");
};
export const getScriptEditorInitData = () => {
const data = inititialScriptEditorData;
inititialScriptEditorData = undefined;
return data;
};

View File

@ -5,12 +5,14 @@ import { DeviceAutomation } from "../../../../data/device_automation";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import "../../../../components/ha-chips"; import "../../../../components/ha-chips";
import { showAutomationEditor } from "../../../../data/automation"; import { showAutomationEditor } from "../../../../data/automation";
import { showScriptEditor } from "../../../../data/script";
export abstract class HaDeviceAutomationCard< export abstract class HaDeviceAutomationCard<
T extends DeviceAutomation T extends DeviceAutomation
> extends LitElement { > extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public deviceId?: string; @property() public deviceId?: string;
@property() public script = false;
@property() public automations: T[] = []; @property() public automations: T[] = [];
protected headerKey = ""; protected headerKey = "";
@ -46,20 +48,18 @@ export abstract class HaDeviceAutomationCard<
return html``; return html``;
} }
return html` return html`
<ha-card> <h3>
<div class="card-header"> ${this.hass.localize(this.headerKey)}
${this.hass.localize(this.headerKey)} </h3>
</div> <div class="content">
<div class="card-content"> <ha-chips
<ha-chips @chip-clicked=${this._handleAutomationClicked}
@chip-clicked=${this._handleAutomationClicked} .items=${this.automations.map((automation) =>
.items=${this.automations.map((automation) => this._localizeDeviceAutomation(this.hass, automation)
this._localizeDeviceAutomation(this.hass, automation) )}
)} >
> </ha-chips>
</ha-chips> </div>
</div>
</ha-card>
`; `;
} }
@ -68,6 +68,10 @@ export abstract class HaDeviceAutomationCard<
if (!automation) { if (!automation) {
return; return;
} }
if (this.script) {
showScriptEditor(this, { sequence: [automation] });
return;
}
const data = {}; const data = {};
data[this.type] = [automation]; data[this.type] = [automation];
showAutomationEditor(this, data); showAutomationEditor(this, data);

View File

@ -0,0 +1,184 @@
import {
LitElement,
html,
css,
CSSResult,
TemplateResult,
property,
customElement,
} from "lit-element";
import "../../../../components/ha-dialog";
import "./ha-device-triggers-card";
import "./ha-device-conditions-card";
import "./ha-device-actions-card";
import { DeviceAutomationDialogParams } from "./show-dialog-device-automation";
import { HomeAssistant } from "../../../../types";
import {
DeviceTrigger,
DeviceCondition,
DeviceAction,
fetchDeviceTriggers,
fetchDeviceConditions,
fetchDeviceActions,
} from "../../../../data/device_automation";
@customElement("dialog-device-automation")
export class DialogDeviceAutomation extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _triggers: DeviceTrigger[] = [];
@property() private _conditions: DeviceCondition[] = [];
@property() private _actions: DeviceAction[] = [];
@property() private _params?: DeviceAutomationDialogParams;
public async showDialog(params: DeviceAutomationDialogParams): Promise<void> {
this._params = params;
await this.updateComplete;
}
protected updated(changedProps): void {
super.updated(changedProps);
if (!changedProps.has("_params")) {
return;
}
this._triggers = [];
this._conditions = [];
this._actions = [];
if (!this._params) {
return;
}
const { deviceId, script } = this._params;
fetchDeviceActions(this.hass, deviceId).then(
(actions) => (this._actions = actions)
);
if (script) {
return;
}
fetchDeviceTriggers(this.hass, deviceId).then(
(triggers) => (this._triggers = triggers)
);
fetchDeviceConditions(this.hass, deviceId).then(
(conditions) => (this._conditions = conditions)
);
}
protected render(): TemplateResult | void {
if (!this._params) {
return html``;
}
return html`
<ha-dialog
open
@closing="${this._close}"
.title=${this.hass.localize(
`ui.panel.config.devices.${
this._params.script ? "script" : "automation"
}.create`
)}
>
<div @chip-clicked=${this._close}>
${this._triggers.length ||
this._conditions.length ||
this._actions.length
? html`
${this._triggers.length
? html`
<ha-device-triggers-card
.hass=${this.hass}
.automations=${this._triggers}
></ha-device-triggers-card>
`
: ""}
${this._conditions.length
? html`
<ha-device-conditions-card
.hass=${this.hass}
.automations=${this._conditions}
></ha-device-conditions-card>
`
: ""}
${this._actions.length
? html`
<ha-device-actions-card
.hass=${this.hass}
.automations=${this._actions}
.script=${this._params.script}
></ha-device-actions-card>
`
: ""}
`
: html``}
</div>
<mwc-button slot="primaryAction" @click="${this._close}">
Close
</mwc-button>
</ha-dialog>
`;
}
private _close(): void {
this._params = undefined;
}
static get styles(): CSSResult[] {
return [
css`
ha-dialog {
--mdc-dialog-title-ink-color: var(--primary-text-color);
--justify-action-buttons: space-between;
}
@media only screen and (min-width: 600px) {
ha-dialog {
--mdc-dialog-min-width: 600px;
}
}
.form {
padding-bottom: 24px;
}
.location {
display: flex;
}
.location > * {
flex-grow: 1;
min-width: 0;
}
.location > *:first-child {
margin-right: 4px;
}
.location > *:last-child {
margin-left: 4px;
}
ha-location-editor {
margin-top: 16px;
}
ha-user-picker {
margin-top: 16px;
}
mwc-button.warning {
--mdc-theme-primary: var(--google-red-500);
}
.error {
color: var(--google-red-500);
}
a {
color: var(--primary-color);
}
p {
color: var(--primary-text-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-device-automation": DialogDeviceAutomation;
}
}

View File

@ -1,5 +1,3 @@
import "../../../../components/ha-card";
import { import {
DeviceRegistryEntry, DeviceRegistryEntry,
computeDeviceName, computeDeviceName,
@ -27,63 +25,63 @@ export class HaDeviceCard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-card> <div class="info">
<div class="card-content"> ${this.device.model
<div class="info"> ? html`
<div class="model">${this.device.model}</div> <div class="model">${this.device.model}</div>
${this.device.manufacturer `
? html` : ""}
<div class="manuf"> ${this.device.manufacturer
${this.hass.localize( ? html`
"ui.panel.config.integrations.config_entry.manuf", <div class="manuf">
"manufacturer", ${this.hass.localize(
this.device.manufacturer "ui.panel.config.integrations.config_entry.manuf",
)} "manufacturer",
</div> this.device.manufacturer
` )}
: ""} </div>
${this.device.area_id `
? html` : ""}
<div class="area"> ${this.device.area_id
<div class="extra-info"> ? html`
${this.hass.localize( <div class="area">
"ui.panel.config.integrations.config_entry.area",
"area",
this._computeArea(this.areas, this.device)
)}
</div>
</div>
`
: ""}
</div>
${this.device.via_device_id
? html`
<div class="extra-info"> <div class="extra-info">
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.integrations.config_entry.via" "ui.panel.config.integrations.config_entry.area",
)} "area",
<span class="hub" this._computeArea(this.areas, this.device)
>${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>
` </div>
: ""} `
</div> : ""}
</ha-card> ${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>
`; `;
} }

View File

@ -6,8 +6,9 @@ import {
customElement, customElement,
css, css,
CSSResult, CSSResult,
queryAll,
PropertyValues,
} from "lit-element"; } from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
@ -19,15 +20,13 @@ import "@polymer/paper-item/paper-item-body";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import "../../../../components/ha-switch";
import { showEntityRegistryDetailDialog } from "../../entities/show-dialog-entity-registry-detail"; import { showEntityRegistryDetailDialog } from "../../entities/show-dialog-entity-registry-detail";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain"; import { computeDomain } from "../../../../common/entity/compute_domain";
import { domainIcon } from "../../../../common/entity/domain_icon"; import { domainIcon } from "../../../../common/entity/domain_icon";
// tslint:disable-next-line
import { HaSwitch } from "../../../../components/ha-switch";
import { EntityRegistryStateEntry } from "../ha-config-device-page"; import { EntityRegistryStateEntry } from "../ha-config-device-page";
import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view"; import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view";
import { createRowElement } from "../../../lovelace/create-element/create-row-element";
import { LovelaceRow } from "../../../lovelace/entity-rows/types";
@customElement("ha-device-entities-card") @customElement("ha-device-entities-card")
export class HaDeviceEntitiesCard extends LitElement { export class HaDeviceEntitiesCard extends LitElement {
@ -36,66 +35,61 @@ export class HaDeviceEntitiesCard extends LitElement {
@property() public entities!: EntityRegistryStateEntry[]; @property() public entities!: EntityRegistryStateEntry[];
@property() public narrow!: boolean; @property() public narrow!: boolean;
@property() private _showDisabled = false; @property() private _showDisabled = false;
@queryAll("#entities > *") private _entityRows?: LovelaceRow[];
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (!changedProps.has("hass")) {
return;
}
this._entityRows?.forEach((element) => {
element.hass = this.hass;
});
}
protected render(): TemplateResult { protected render(): TemplateResult {
const disabledEntities: EntityRegistryStateEntry[] = [];
return html` return html`
<ha-card> <ha-card
<paper-item> .header=${this.hass.localize(
<ha-switch "ui.panel.config.devices.entities.entities"
.checked=${this._showDisabled} )}
@change=${this._showDisabledChanged} >
>${this.hass.localize(
"ui.panel.config.entities.picker.filter.show_disabled"
)}
</ha-switch>
</paper-item>
${this.entities.length ${this.entities.length
? html` ? html`
${this.entities.map((entry: EntityRegistryStateEntry) => { <div id="entities">
if (!this._showDisabled && entry.disabled_by) { ${this.entities.map((entry: EntityRegistryStateEntry) => {
return ""; if (entry.disabled_by) {
} disabledEntities.push(entry);
const stateObj = this.hass.states[entry.entity_id]; return "";
return html` }
<paper-icon-item return this.hass.states[entry.entity_id]
.entry=${entry} ? this._renderEntity(entry)
class=${classMap({ "disabled-entry": !!entry.disabled_by })} : this._renderEntry(entry);
> })}
${stateObj </div>
? html` ${disabledEntities.length
<state-badge ? !this._showDisabled
@click=${this._openMoreInfo} ? html`
.stateObj=${stateObj} <button
slot="item-icon" class="show-more"
></state-badge> @click=${this._toggleShowDisabled}
` >
: html` +${disabledEntities.length} disabled entities
<ha-icon </button>
slot="item-icon" `
.icon=${domainIcon(computeDomain(entry.entity_id))} : html`
></ha-icon> ${disabledEntities.map((entry) =>
`} this._renderEntry(entry)
<paper-item-body two-line @click=${this._openMoreInfo}> )}
<div class="name">${entry.stateName}</div> <button
<div class="secondary entity-id">${entry.entity_id}</div> class="show-more"
</paper-item-body> @click=${this._toggleShowDisabled}
<div class="buttons"> >
${stateObj Hide disabled
? html` </button>
<paper-icon-button `
@click=${this._openMoreInfo} : ""}
icon="hass:information-outline"
></paper-icon-button>
`
: ""}
<paper-icon-button
@click=${this._openEditEntry}
icon="hass:settings"
></paper-icon-button>
</div>
</paper-icon-item>
`;
})}
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._addToLovelaceView}> <mwc-button @click=${this._addToLovelaceView}>
${this.hass.localize( ${this.hass.localize(
@ -119,22 +113,48 @@ export class HaDeviceEntitiesCard extends LitElement {
`; `;
} }
private _showDisabledChanged(ev: Event) { private _toggleShowDisabled() {
this._showDisabled = (ev.target as HaSwitch).checked; this._showDisabled = !this._showDisabled;
} }
private _openEditEntry(ev: MouseEvent): void { private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry; const element = createRowElement({ entity: entry.entity_id });
if (this.hass) {
element.hass = this.hass;
}
// @ts-ignore
element.entry = entry;
element.addEventListener("hass-more-info", (ev) => this._openEditEntry(ev));
return html`
<div>${element}</div>
`;
}
private _renderEntry(entry: EntityRegistryStateEntry): TemplateResult {
return html`
<paper-icon-item .entry=${entry} @click=${this._openEditEntry}>
<ha-icon
slot="item-icon"
.icon=${domainIcon(computeDomain(entry.entity_id))}
></ha-icon>
<paper-item-body>
<div class="name">
${entry.stateName || entry.entity_id}
</div>
</paper-item-body>
</paper-icon-item>
`;
}
private _openEditEntry(ev: Event): void {
ev.stopPropagation();
const entry = (ev.currentTarget! as any).entry;
showEntityRegistryDetailDialog(this, { showEntityRegistryDetailDialog(this, {
entry, entry,
}); });
} }
private _openMoreInfo(ev: MouseEvent) {
const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry;
fireEvent(this, "hass-more-info", { entityId: entry.entity_id });
}
private _addToLovelaceView(): void { private _addToLovelaceView(): void {
addEntitiesToLovelaceView( addEntitiesToLovelaceView(
this, this,
@ -158,11 +178,32 @@ export class HaDeviceEntitiesCard extends LitElement {
.disabled-entry { .disabled-entry {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
state-badge { #entities > * {
margin: 8px;
}
paper-icon-item {
min-height: 40px;
padding: 0 8px;
cursor: pointer; cursor: pointer;
} }
paper-icon-item:not(.disabled-entry) paper-item-body { .name {
font-size: 14px;
}
button.show-more {
color: var(--primary-color);
text-align: left;
cursor: pointer; cursor: pointer;
background: none;
border-width: initial;
border-style: none;
border-color: initial;
border-image: initial;
padding: 16px;
font: inherit;
}
button.show-more:focus {
outline: none;
text-decoration: underline;
} }
`; `;
} }

View File

@ -0,0 +1,22 @@
import { fireEvent } from "../../../../common/dom/fire_event";
export interface DeviceAutomationDialogParams {
deviceId: string;
script?: boolean;
}
export const loadDeviceAutomationDialog = () =>
import(
/* webpackChunkName: "device-automation-dialog" */ "./ha-device-automation-dialog"
);
export const showDeviceAutomationDialog = (
element: HTMLElement,
detailParams: DeviceAutomationDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-device-automation",
dialogImport: loadDeviceAutomationDialog,
dialogParams: detailParams,
});
};

View File

@ -9,14 +9,13 @@ import {
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import "@polymer/paper-tooltip/paper-tooltip";
import "../../../layouts/hass-tabs-subpage"; 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-card";
import "./device-detail/ha-device-triggers-card";
import "./device-detail/ha-device-conditions-card";
import "./device-detail/ha-device-actions-card";
import "./device-detail/ha-device-entities-card"; import "./device-detail/ha-device-entities-card";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { ConfigEntry } from "../../../data/config_entries"; import { ConfigEntry } from "../../../data/config_entries";
@ -33,19 +32,15 @@ import {
loadDeviceRegistryDetailDialog, loadDeviceRegistryDetailDialog,
showDeviceRegistryDetailDialog, showDeviceRegistryDetailDialog,
} from "../../../dialogs/device-registry-detail/show-dialog-device-registry-detail"; } from "../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
import "../../../components/ha-icon-next";
import {
DeviceTrigger,
DeviceAction,
DeviceCondition,
fetchDeviceTriggers,
fetchDeviceConditions,
fetchDeviceActions,
} from "../../../data/device_automation";
import { compare } from "../../../common/string/compare"; import { compare } from "../../../common/string/compare";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { createValidEntityId } from "../../../common/entity/valid_entity_id"; 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 { SceneEntities, showSceneEditor } from "../../../data/scene";
import { navigate } from "../../../common/navigate";
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
export interface EntityRegistryStateEntry extends EntityRegistryEntry { export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string; stateName?: string;
@ -59,12 +54,11 @@ export class HaConfigDevicePage extends LitElement {
@property() public entities!: EntityRegistryEntry[]; @property() public entities!: EntityRegistryEntry[];
@property() public areas!: AreaRegistryEntry[]; @property() public areas!: AreaRegistryEntry[];
@property() public deviceId!: string; @property() public deviceId!: string;
@property() public narrow!: boolean; @property({ type: Boolean, reflect: true }) public narrow!: boolean;
@property() public isWide!: boolean;
@property() public showAdvanced!: boolean; @property() public showAdvanced!: boolean;
@property() public route!: Route; @property() public route!: Route;
@property() private _triggers: DeviceTrigger[] = []; @property() private _related?: RelatedResult;
@property() private _conditions: DeviceCondition[] = [];
@property() private _actions: DeviceAction[] = [];
private _device = memoizeOne( private _device = memoizeOne(
( (
@ -97,25 +91,10 @@ export class HaConfigDevicePage extends LitElement {
loadDeviceRegistryDetailDialog(); loadDeviceRegistryDetailDialog();
} }
protected updated(changedProps): void { protected updated(changedProps) {
super.updated(changedProps); super.updated(changedProps);
if (changedProps.has("deviceId")) { if (changedProps.has("deviceId")) {
if (this.deviceId) { this._findRelated();
fetchDeviceTriggers(this.hass, this.deviceId).then(
(triggers) => (this._triggers = triggers)
);
fetchDeviceConditions(this.hass, this.deviceId).then(
(conditions) => (this._conditions = conditions)
);
fetchDeviceActions(this.hass, this.deviceId).then(
(actions) => (this._actions = actions)
);
} else {
this._triggers = [];
this._conditions = [];
this._actions = [];
}
} }
} }
@ -146,70 +125,176 @@ export class HaConfigDevicePage extends LitElement {
icon="hass:settings" icon="hass:settings"
@click=${this._showSettings} @click=${this._showSettings}
></paper-icon-button> ></paper-icon-button>
<ha-config-section .isWide=${!this.narrow}>
<span slot="header"
>${this.hass.localize("ui.panel.config.devices.info")}</span
>
<span slot="introduction">
${this.hass.localize("ui.panel.config.devices.details")}
</span>
<ha-device-card
.hass=${this.hass}
.areas=${this.areas}
.devices=${this.devices}
.device=${device}
></ha-device-card>
${entities.length <div class="container">
? html` <div class="left">
<div class="header"> <div class="device-info">
<h1>${device.name_by_user || device.name}</h1>
<ha-device-card
.hass=${this.hass}
.areas=${this.areas}
.devices=${this.devices}
.device=${device}
></ha-device-card>
</div>
${
entities.length
? html`
<ha-device-entities-card
.hass=${this.hass}
.entities=${entities}
>
</ha-device-entities-card>
`
: html``
}
</div>
<div class="right">
<div class="column">
<ha-card
.header=${this.hass.localize(
"ui.panel.config.devices.automation.automations"
)}
>${
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}
>
<paper-item-body>
${state.attributes.friendly_name ||
automation}
</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`
<paper-item class="no-link"
>${this.hass.localize(
"ui.panel.config.devices.automation.no_automations"
)}</paper-item
>
`
}
<div class="card-actions">
<mwc-button @click=${this._showAutomationDialog}>
${this.hass.localize( ${this.hass.localize(
"ui.panel.config.devices.entities.entities" "ui.panel.config.devices.automation.create"
)} )}
</mwc-button>
</div> </div>
<ha-device-entities-card </ha-card>
.hass=${this.hass} </div>
.entities=${entities} <div class="column">
> <ha-card
</ha-device-entities-card> .header=${this.hass.localize(
` "ui.panel.config.devices.scene.scenes"
: html``} )}
${this._triggers.length || >${
this._conditions.length || this._related?.scene?.length
this._actions.length ? this._related.scene.map((scene) => {
? html` const state = this.hass.states[scene];
<div class="header"> return state
${this.hass.localize("ui.panel.config.devices.automations")} ? 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>
`
: "";
})
: html`
<paper-item class="no-link"
>${this.hass.localize(
"ui.panel.config.devices.scene.no_scenes"
)}</paper-item
>
`
}
<div class="card-actions">
<mwc-button @click=${this._createScene}>
${this.hass.localize(
"ui.panel.config.devices.scene.create"
)} </mwc-button>
</div> </div>
${this._triggers.length </ha-card>
? html` <ha-card
<ha-device-triggers-card .header=${this.hass.localize(
.hass=${this.hass} "ui.panel.config.devices.script.scripts"
.automations=${this._triggers} )}
></ha-device-triggers-card> >${
` this._related?.script?.length
: ""} ? this._related.script.map((script) => {
${this._conditions.length const state = this.hass.states[script];
? html` return state
<ha-device-conditions-card ? html`
.hass=${this.hass} <paper-item
.automations=${this._conditions} .script=${script}
></ha-device-conditions-card> @click=${this._openScript}
` >
: ""} <paper-item-body>
${this._actions.length ${state.attributes.friendly_name || script}
? html` </paper-item-body>
<ha-device-actions-card <ha-icon-next></ha-icon-next>
.hass=${this.hass} </paper-item>
.automations=${this._actions} `
></ha-device-actions-card> : "";
` })
: ""} : html`
` <paper-item class="no-link">
: html``} ${this.hass.localize(
"ui.panel.config.devices.script.no_scripts"
)}</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> </ha-config-section>
</hass-tabs-subpage> </hass-tabs-subpage> `;
`;
} }
private _computeEntityName(entity) { private _computeEntityName(entity) {
@ -220,6 +305,50 @@ export class HaConfigDevicePage extends LitElement {
return state ? computeStateName(state) : null; return state ? computeStateName(state) : null;
} }
private async _findRelated() {
this._related = await findRelated(this.hass, "device", this.deviceId);
}
private _createScene() {
const entities: SceneEntities = {};
this._entities(this.deviceId, this.entities).forEach((entity) => {
entities[entity.entity_id] = "";
});
showSceneEditor(this, {
entities,
});
}
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 });
}
private _showAutomationDialog() {
showDeviceAutomationDialog(this, {
deviceId: this.deviceId,
script: false,
});
}
private async _showSettings() { private async _showSettings() {
const device = this._device(this.deviceId, this.devices)!; const device = this._device(this.deviceId, this.devices)!;
showDeviceRegistryDetailDialog(this, { showDeviceRegistryDetailDialog(this, {
@ -282,20 +411,81 @@ export class HaConfigDevicePage extends LitElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
.header { .container {
font-family: var(--paper-font-display1_-_font-family); display: flex;
flex-wrap: wrap;
margin: auto;
max-width: 1000px;
margin-top: 32px;
margin-bottom: 32px;
}
.device-info {
padding: 16px;
}
.show-more {
}
h1 {
margin-top: 0;
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var( -webkit-font-smoothing: var(
--paper-font-display1_-_-webkit-font-smoothing --paper-font-headline_-_-webkit-font-smoothing
); );
font-size: var(--paper-font-display1_-_font-size); font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-display1_-_font-weight); font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-display1_-_letter-spacing); letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-display1_-_line-height); line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity); opacity: var(--dark-primary-opacity);
} }
ha-config-section *:last-child { .left,
padding-bottom: 24px; .column,
.fullwidth {
padding: 8px;
box-sizing: border-box;
}
.left {
width: 33.33%;
padding-bottom: 0;
}
.right {
width: 66.66%;
display: flex;
flex-wrap: wrap;
}
.fullwidth {
width: 100%;
}
.column {
width: 50%;
}
.column > *:not(:first-child) {
margin-top: 16px;
}
:host([narrow]) .left,
:host([narrow]) .right,
:host([narrow]) .column {
width: 100%;
}
:host([narrow]) .container {
margin-top: 0;
}
paper-item {
cursor: pointer;
}
paper-item.no-link {
cursor: default;
} }
`; `;
} }

View File

@ -39,6 +39,7 @@ import {
SceneEntities, SceneEntities,
applyScene, applyScene,
activateScene, activateScene,
getSceneEditorInitData,
} from "../../../data/scene"; } from "../../../data/scene";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { import {
@ -391,6 +392,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
super.updated(changedProps); super.updated(changedProps);
const oldscene = changedProps.get("scene") as SceneEntity; const oldscene = changedProps.get("scene") as SceneEntity;
if ( if (
changedProps.has("scene") && changedProps.has("scene") &&
this.scene && this.scene &&
@ -403,10 +405,16 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
if (changedProps.has("creatingNew") && this.creatingNew && this.hass) { if (changedProps.has("creatingNew") && this.creatingNew && this.hass) {
this._dirty = false; this._dirty = false;
const initData = getSceneEditorInitData();
this._config = { this._config = {
name: this.hass.localize("ui.panel.config.scene.editor.default_name"), name: this.hass.localize("ui.panel.config.scene.editor.default_name"),
entities: {}, entities: {},
...initData,
}; };
if (initData) {
this._initEntities(this._config);
this._dirty = true;
}
} }
if (changedProps.has("_entityRegistryEntries")) { if (changedProps.has("_entityRegistryEntries")) {
@ -458,24 +466,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
config.entities = {}; config.entities = {};
} }
this._entities = Object.keys(config.entities); this._initEntities(config);
this._entities.forEach((entity) => {
this._storeState(entity);
});
const filteredEntityReg = this._entityRegistryEntries.filter((entityReg) =>
this._entities.includes(entityReg.entity_id)
);
for (const entityReg of filteredEntityReg) {
if (!entityReg.device_id) {
continue;
}
if (!this._devices.includes(entityReg.device_id)) {
this._devices = [...this._devices, entityReg.device_id];
}
}
const { context } = await activateScene(this.hass, this.scene!.entity_id); const { context } = await activateScene(this.hass, this.scene!.entity_id);
@ -489,6 +480,25 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
this._config = config; this._config = config;
} }
private _initEntities(config: SceneConfig) {
this._entities = Object.keys(config.entities);
this._entities.forEach((entity) => this._storeState(entity));
const filteredEntityReg = this._entityRegistryEntries.filter((entityReg) =>
this._entities.includes(entityReg.entity_id)
);
this._devices = [];
for (const entityReg of filteredEntityReg) {
if (!entityReg.device_id) {
continue;
}
if (!this._devices.includes(entityReg.device_id)) {
this._devices = [...this._devices, entityReg.device_id];
}
}
}
private _entityPicked(ev: CustomEvent) { private _entityPicked(ev: CustomEvent) {
const entityId = ev.detail.value; const entityId = ev.detail.value;
(ev.target as any).value = ""; (ev.target as any).value = "";

View File

@ -20,6 +20,7 @@ import {
ScriptEntity, ScriptEntity,
ScriptConfig, ScriptConfig,
deleteScript, deleteScript,
getScriptEditorInitData,
} from "../../../data/script"; } from "../../../data/script";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/ha-app-layout"; import "../../../layouts/ha-app-layout";
@ -188,10 +189,12 @@ export class HaScriptEditor extends LitElement {
} }
if (changedProps.has("creatingNew") && this.creatingNew && this.hass) { if (changedProps.has("creatingNew") && this.creatingNew && this.hass) {
this._dirty = false; const initData = getScriptEditorInitData();
this._dirty = initData ? true : false;
this._config = { this._config = {
alias: this.hass.localize("ui.panel.config.script.editor.default_name"), alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
sequence: [{ service: "" }], sequence: [{ service: "" }],
...initData,
}; };
} }
} }

View File

@ -39,11 +39,11 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
return { entities: [] }; return { entities: [] };
} }
@property() protected _config?: EntitiesCardConfig; @property() private _config?: EntitiesCardConfig;
protected _hass?: HomeAssistant; private _hass?: HomeAssistant;
protected _configEntities?: EntitiesCardEntityConfig[]; private _configEntities?: EntitiesCardEntityConfig[];
set hass(hass: HomeAssistant) { set hass(hass: HomeAssistant) {
this._hass = hass; this._hass = hass;

View File

@ -1271,6 +1271,9 @@
"name": "Name", "name": "Name",
"update": "Update", "update": "Update",
"automation": { "automation": {
"automations": "Automations",
"no_automations": "No automations",
"create": "Create automation with device",
"triggers": { "triggers": {
"caption": "Do something when..." "caption": "Do something when..."
}, },
@ -1281,15 +1284,25 @@
"caption": "When something is triggered..." "caption": "When something is triggered..."
} }
}, },
"script": {
"scripts": "Scripts",
"no_scripts": "No scripts",
"create": "Create script with device"
},
"scene": {
"scenes": "Scenes",
"no_scenes": "No scenes",
"create": "Create scene with device"
},
"cant_edit": "You can only edit items that are created in the UI.",
"device_not_found": "Device not found.", "device_not_found": "Device not found.",
"info": "Device info",
"details": "Here are all the details of your device.",
"entities": { "entities": {
"entities": "Entities", "entities": "Entities",
"add_entities_lovelace": "Add all device entities to Lovelace UI", "add_entities_lovelace": "Add to Lovelace",
"none": "This device has no entities" "none": "This device has no entities"
}, },
"automations": "Automations", "scripts": "Scripts",
"scenes": "Scenes",
"confirm_rename_entity_ids": "Do you also want to rename the entity id's of your entities?", "confirm_rename_entity_ids": "Do you also want to rename the entity id's of your entities?",
"data_table": { "data_table": {
"device": "Device", "device": "Device",