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) {
fireEvent(
this,
"chip-clicked",
{ index: ev.target.closest("button").index },
{ bubbles: false }
);
fireEvent(this, "chip-clicked", {
index: ev.target.closest("button").index,
});
}
static get styles(): CSSResult {

View File

@ -4,6 +4,7 @@ import {
} from "home-assistant-js-websocket";
import { HomeAssistant, ServiceCallResponse } from "../types";
import { navigate } from "../common/navigate";
export const SCENE_IGNORED_DOMAINS = [
"sensor",
@ -18,6 +19,22 @@ export const SCENE_IGNORED_DOMAINS = [
"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 {
attributes: HassEntityAttributeBase & { id?: string };
}

View File

@ -5,6 +5,7 @@ import {
HassEntityBase,
HassEntityAttributeBase,
} from "home-assistant-js-websocket";
import { navigate } from "../common/navigate";
export interface ScriptEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
@ -65,3 +66,19 @@ export const triggerScript = (
export const deleteScript = (hass: HomeAssistant, objectId: string) =>
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-chips";
import { showAutomationEditor } from "../../../../data/automation";
import { showScriptEditor } from "../../../../data/script";
export abstract class HaDeviceAutomationCard<
T extends DeviceAutomation
> extends LitElement {
@property() public hass!: HomeAssistant;
@property() public deviceId?: string;
@property() public script = false;
@property() public automations: T[] = [];
protected headerKey = "";
@ -46,11 +48,10 @@ export abstract class HaDeviceAutomationCard<
return html``;
}
return html`
<ha-card>
<div class="card-header">
<h3>
${this.hass.localize(this.headerKey)}
</div>
<div class="card-content">
</h3>
<div class="content">
<ha-chips
@chip-clicked=${this._handleAutomationClicked}
.items=${this.automations.map((automation) =>
@ -59,7 +60,6 @@ export abstract class HaDeviceAutomationCard<
>
</ha-chips>
</div>
</ha-card>
`;
}
@ -68,6 +68,10 @@ export abstract class HaDeviceAutomationCard<
if (!automation) {
return;
}
if (this.script) {
showScriptEditor(this, { sequence: [automation] });
return;
}
const data = {};
data[this.type] = [automation];
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 {
DeviceRegistryEntry,
computeDeviceName,
@ -27,10 +25,12 @@ export class HaDeviceCard extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card>
<div class="card-content">
<div class="info">
${this.device.model
? html`
<div class="model">${this.device.model}</div>
`
: ""}
${this.device.manufacturer
? html`
<div class="manuf">
@ -55,7 +55,6 @@ export class HaDeviceCard extends LitElement {
</div>
`
: ""}
</div>
${this.device.via_device_id
? html`
<div class="extra-info">
@ -83,7 +82,6 @@ export class HaDeviceCard extends LitElement {
`
: ""}
</div>
</ha-card>
`;
}

View File

@ -6,8 +6,9 @@ import {
customElement,
css,
CSSResult,
queryAll,
PropertyValues,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { HomeAssistant } from "../../../../types";
@ -19,15 +20,13 @@ import "@polymer/paper-item/paper-item-body";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
import "../../../../components/ha-switch";
import { showEntityRegistryDetailDialog } from "../../entities/show-dialog-entity-registry-detail";
import { fireEvent } from "../../../../common/dom/fire_event";
import { computeDomain } from "../../../../common/entity/compute_domain";
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 { 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")
export class HaDeviceEntitiesCard extends LitElement {
@ -36,66 +35,61 @@ export class HaDeviceEntitiesCard extends LitElement {
@property() public entities!: EntityRegistryStateEntry[];
@property() public narrow!: boolean;
@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 {
const disabledEntities: EntityRegistryStateEntry[] = [];
return html`
<ha-card>
<paper-item>
<ha-switch
.checked=${this._showDisabled}
@change=${this._showDisabledChanged}
>${this.hass.localize(
"ui.panel.config.entities.picker.filter.show_disabled"
<ha-card
.header=${this.hass.localize(
"ui.panel.config.devices.entities.entities"
)}
</ha-switch>
</paper-item>
>
${this.entities.length
? html`
<div id="entities">
${this.entities.map((entry: EntityRegistryStateEntry) => {
if (!this._showDisabled && entry.disabled_by) {
if (entry.disabled_by) {
disabledEntities.push(entry);
return "";
}
const stateObj = this.hass.states[entry.entity_id];
return html`
<paper-icon-item
.entry=${entry}
class=${classMap({ "disabled-entry": !!entry.disabled_by })}
>
${stateObj
return this.hass.states[entry.entity_id]
? this._renderEntity(entry)
: this._renderEntry(entry);
})}
</div>
${disabledEntities.length
? !this._showDisabled
? html`
<state-badge
@click=${this._openMoreInfo}
.stateObj=${stateObj}
slot="item-icon"
></state-badge>
<button
class="show-more"
@click=${this._toggleShowDisabled}
>
+${disabledEntities.length} disabled entities
</button>
`
: html`
<ha-icon
slot="item-icon"
.icon=${domainIcon(computeDomain(entry.entity_id))}
></ha-icon>
`}
<paper-item-body two-line @click=${this._openMoreInfo}>
<div class="name">${entry.stateName}</div>
<div class="secondary entity-id">${entry.entity_id}</div>
</paper-item-body>
<div class="buttons">
${stateObj
? html`
<paper-icon-button
@click=${this._openMoreInfo}
icon="hass:information-outline"
></paper-icon-button>
${disabledEntities.map((entry) =>
this._renderEntry(entry)
)}
<button
class="show-more"
@click=${this._toggleShowDisabled}
>
Hide disabled
</button>
`
: ""}
<paper-icon-button
@click=${this._openEditEntry}
icon="hass:settings"
></paper-icon-button>
</div>
</paper-icon-item>
`;
})}
<div class="card-actions">
<mwc-button @click=${this._addToLovelaceView}>
${this.hass.localize(
@ -119,22 +113,48 @@ export class HaDeviceEntitiesCard extends LitElement {
`;
}
private _showDisabledChanged(ev: Event) {
this._showDisabled = (ev.target as HaSwitch).checked;
private _toggleShowDisabled() {
this._showDisabled = !this._showDisabled;
}
private _openEditEntry(ev: MouseEvent): void {
const entry = (ev.currentTarget! as any).closest("paper-icon-item").entry;
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
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, {
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 {
addEntitiesToLovelaceView(
this,
@ -158,11 +178,32 @@ export class HaDeviceEntitiesCard extends LitElement {
.disabled-entry {
color: var(--secondary-text-color);
}
state-badge {
#entities > * {
margin: 8px;
}
paper-icon-item {
min-height: 40px;
padding: 0 8px;
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;
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 "@polymer/paper-tooltip/paper-tooltip";
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-triggers-card";
import "./device-detail/ha-device-conditions-card";
import "./device-detail/ha-device-actions-card";
import "./device-detail/ha-device-entities-card";
import { HomeAssistant, Route } from "../../../types";
import { ConfigEntry } from "../../../data/config_entries";
@ -33,19 +32,15 @@ import {
loadDeviceRegistryDetailDialog,
showDeviceRegistryDetailDialog,
} from "../../../dialogs/device-registry-detail/show-dialog-device-registry-detail";
import {
DeviceTrigger,
DeviceAction,
DeviceCondition,
fetchDeviceTriggers,
fetchDeviceConditions,
fetchDeviceActions,
} from "../../../data/device_automation";
import "../../../components/ha-icon-next";
import { compare } from "../../../common/string/compare";
import { computeStateName } from "../../../common/entity/compute_state_name";
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";
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string;
@ -59,12 +54,11 @@ export class HaConfigDevicePage extends LitElement {
@property() public entities!: EntityRegistryEntry[];
@property() public areas!: AreaRegistryEntry[];
@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 route!: Route;
@property() private _triggers: DeviceTrigger[] = [];
@property() private _conditions: DeviceCondition[] = [];
@property() private _actions: DeviceAction[] = [];
@property() private _related?: RelatedResult;
private _device = memoizeOne(
(
@ -97,25 +91,10 @@ export class HaConfigDevicePage extends LitElement {
loadDeviceRegistryDetailDialog();
}
protected updated(changedProps): void {
protected updated(changedProps) {
super.updated(changedProps);
if (changedProps.has("deviceId")) {
if (this.deviceId) {
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 = [];
}
this._findRelated();
}
}
@ -146,70 +125,176 @@ export class HaConfigDevicePage extends LitElement {
icon="hass:settings"
@click=${this._showSettings}
></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>
<div class="container">
<div class="left">
<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>
${entities.length
? html`
<div class="header">
${this.hass.localize(
"ui.panel.config.devices.entities.entities"
)}
</div>
${
entities.length
? html`
<ha-device-entities-card
.hass=${this.hass}
.entities=${entities}
>
</ha-device-entities-card>
`
: html``}
${this._triggers.length ||
this._conditions.length ||
this._actions.length
? html`
<div class="header">
${this.hass.localize("ui.panel.config.devices.automations")}
: html``
}
</div>
${this._triggers.length
<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`
<ha-device-triggers-card
.hass=${this.hass}
.automations=${this._triggers}
></ha-device-triggers-card>
<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>
`
: ""}
${this._conditions.length
</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(
"ui.panel.config.devices.automation.create"
)}
</mwc-button>
</div>
</ha-card>
</div>
<div class="column">
<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
? html`
<ha-device-conditions-card
.hass=${this.hass}
.automations=${this._conditions}
></ha-device-conditions-card>
<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>
`
: ""}
${this._actions.length
</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>
</ha-card>
<ha-card
.header=${this.hass.localize(
"ui.panel.config.devices.script.scripts"
)}
>${
this._related?.script?.length
? this._related.script.map((script) => {
const state = this.hass.states[script];
return state
? html`
<ha-device-actions-card
.hass=${this.hass}
.automations=${this._actions}
></ha-device-actions-card>
<paper-item
.script=${script}
@click=${this._openScript}
>
<paper-item-body>
${state.attributes.friendly_name || script}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
`
: ""}
: "";
})
: html`
<paper-item class="no-link">
${this.hass.localize(
"ui.panel.config.devices.script.no_scripts"
)}</paper-item
>
`
: html``}
}
<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>
`;
</hass-tabs-subpage> `;
}
private _computeEntityName(entity) {
@ -220,6 +305,50 @@ export class HaConfigDevicePage extends LitElement {
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() {
const device = this._device(this.deviceId, this.devices)!;
showDeviceRegistryDetailDialog(this, {
@ -282,20 +411,81 @@ export class HaConfigDevicePage extends LitElement {
static get styles(): CSSResult {
return css`
.header {
font-family: var(--paper-font-display1_-_font-family);
.container {
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(
--paper-font-display1_-_-webkit-font-smoothing
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-display1_-_font-size);
font-weight: var(--paper-font-display1_-_font-weight);
letter-spacing: var(--paper-font-display1_-_letter-spacing);
line-height: var(--paper-font-display1_-_line-height);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
ha-config-section *:last-child {
padding-bottom: 24px;
.left,
.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,
applyScene,
activateScene,
getSceneEditorInitData,
} from "../../../data/scene";
import { fireEvent } from "../../../common/dom/fire_event";
import {
@ -391,6 +392,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
super.updated(changedProps);
const oldscene = changedProps.get("scene") as SceneEntity;
if (
changedProps.has("scene") &&
this.scene &&
@ -403,10 +405,16 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
if (changedProps.has("creatingNew") && this.creatingNew && this.hass) {
this._dirty = false;
const initData = getSceneEditorInitData();
this._config = {
name: this.hass.localize("ui.panel.config.scene.editor.default_name"),
entities: {},
...initData,
};
if (initData) {
this._initEntities(this._config);
this._dirty = true;
}
}
if (changedProps.has("_entityRegistryEntries")) {
@ -458,24 +466,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
config.entities = {};
}
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)
);
for (const entityReg of filteredEntityReg) {
if (!entityReg.device_id) {
continue;
}
if (!this._devices.includes(entityReg.device_id)) {
this._devices = [...this._devices, entityReg.device_id];
}
}
this._initEntities(config);
const { context } = await activateScene(this.hass, this.scene!.entity_id);
@ -489,6 +480,25 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
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) {
const entityId = ev.detail.value;
(ev.target as any).value = "";

View File

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

View File

@ -39,11 +39,11 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
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) {
this._hass = hass;

View File

@ -1271,6 +1271,9 @@
"name": "Name",
"update": "Update",
"automation": {
"automations": "Automations",
"no_automations": "No automations",
"create": "Create automation with device",
"triggers": {
"caption": "Do something when..."
},
@ -1281,15 +1284,25 @@
"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.",
"info": "Device info",
"details": "Here are all the details of your device.",
"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"
},
"automations": "Automations",
"scripts": "Scripts",
"scenes": "Scenes",
"confirm_rename_entity_ids": "Do you also want to rename the entity id's of your entities?",
"data_table": {
"device": "Device",