mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-10 03:12:49 +00:00
Compare commits
1 Commits
dev
...
automation
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5706be3fb7 |
@@ -1,8 +1,23 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device/device_registry";
|
||||
import { getDeviceIntegrationLookup } from "../../../data/device/device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity/entity";
|
||||
import type { EntitySources } from "../../../data/entity/entity_sources";
|
||||
import { fetchEntitySourcesWithCache } from "../../../data/entity/entity_sources";
|
||||
import type { TargetSelector } from "../../../data/selector";
|
||||
import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
} from "../../../data/selector";
|
||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker";
|
||||
import "../../ha-dialog";
|
||||
import "../../ha-dialog-header";
|
||||
import "../../ha-icon-button";
|
||||
@@ -10,6 +25,7 @@ import "../../ha-icon-next";
|
||||
import "../../ha-md-list";
|
||||
import "../../ha-md-list-item";
|
||||
import "../../ha-svg-icon";
|
||||
import "../../list/ha-list-base";
|
||||
import "../ha-target-picker-item-row";
|
||||
import type { TargetDetailsDialogParams } from "./show-dialog-target-details";
|
||||
|
||||
@@ -21,6 +37,12 @@ class DialogTargetDetails extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _entitySourcesLoaded = false;
|
||||
|
||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||
|
||||
public showDialog(params: TargetDetailsDialogParams): void {
|
||||
this._params = params;
|
||||
this._opened = true;
|
||||
@@ -34,6 +56,72 @@ class DialogTargetDetails extends LitElement implements HassDialog {
|
||||
private _dialogClosed() {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
this._params = undefined;
|
||||
this._entitySources = undefined;
|
||||
this._entitySourcesLoaded = false;
|
||||
}
|
||||
|
||||
private _hasIntegration(selector: TargetSelector) {
|
||||
return (
|
||||
(selector.target?.entity &&
|
||||
ensureArray(selector.target.entity).some((e) => e.integration)) ||
|
||||
(selector.target?.device &&
|
||||
ensureArray(selector.target.device).some((d) => d.integration))
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (!changedProperties.has("_params")) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this._params?.selector &&
|
||||
this._hasIntegration(this._params.selector) &&
|
||||
!this._entitySourcesLoaded
|
||||
) {
|
||||
this._loadEntitySources();
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadEntitySources(): Promise<void> {
|
||||
try {
|
||||
this._entitySources = await fetchEntitySourcesWithCache(this.hass);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to load entity sources for target details", err);
|
||||
} finally {
|
||||
this._entitySourcesLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
private _filterEntities = (entity: HassEntity): boolean => {
|
||||
const target = this._selectorTarget();
|
||||
if (!target?.entity) {
|
||||
return true;
|
||||
}
|
||||
return ensureArray(target.entity).some((e) =>
|
||||
filterSelectorEntities(e, entity, this._entitySources)
|
||||
);
|
||||
};
|
||||
|
||||
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
|
||||
const target = this._selectorTarget();
|
||||
if (!target?.device) {
|
||||
return true;
|
||||
}
|
||||
const deviceIntegrations = this._entitySources
|
||||
? this._deviceIntegrationLookup(
|
||||
this._entitySources,
|
||||
Object.values(this.hass.entities)
|
||||
)
|
||||
: undefined;
|
||||
return ensureArray(target.device).some((d) =>
|
||||
filterSelectorDevices(d, device, deviceIntegrations)
|
||||
);
|
||||
};
|
||||
|
||||
private _selectorTarget() {
|
||||
return this._params?.selector?.target || null;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -41,6 +129,29 @@ class DialogTargetDetails extends LitElement implements HassDialog {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
let deviceFilter: HaDevicePickerDeviceFilterFunc | undefined;
|
||||
let entityFilter: HaEntityPickerEntityFilterFunc | undefined;
|
||||
let includeDomains: string[] | undefined;
|
||||
let includeDeviceClasses: string[] | undefined;
|
||||
let primaryEntitiesOnly: boolean | undefined;
|
||||
|
||||
if (this._params.selector) {
|
||||
deviceFilter = this._filterDevices;
|
||||
entityFilter = this._filterEntities;
|
||||
primaryEntitiesOnly = this._params.selector.target?.primary_entities_only;
|
||||
} else {
|
||||
deviceFilter = this._params.deviceFilter;
|
||||
entityFilter = this._params.entityFilter;
|
||||
includeDomains = this._params.includeDomains;
|
||||
includeDeviceClasses = this._params.includeDeviceClasses;
|
||||
primaryEntitiesOnly = this._params.primaryEntitiesOnly;
|
||||
}
|
||||
|
||||
const waitingForSources =
|
||||
this._params.selector &&
|
||||
this._hasIntegration(this._params.selector) &&
|
||||
!this._entitySourcesLoaded;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.hass=${this.hass}
|
||||
@@ -48,26 +159,57 @@ class DialogTargetDetails extends LitElement implements HassDialog {
|
||||
header-title=${this.hass.localize(
|
||||
"ui.components.target-picker.target_details"
|
||||
)}
|
||||
header-subtitle=${`${this.hass.localize(
|
||||
`ui.components.target-picker.type.${this._params.type}`
|
||||
)}:
|
||||
${this._params.title}`}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-target-picker-item-row
|
||||
.hass=${this.hass}
|
||||
.type=${this._params.type}
|
||||
.itemId=${this._params.itemId}
|
||||
.deviceFilter=${this._params.deviceFilter}
|
||||
.entityFilter=${this._params.entityFilter}
|
||||
.includeDomains=${this._params.includeDomains}
|
||||
.includeDeviceClasses=${this._params.includeDeviceClasses}
|
||||
.primaryEntitiesOnly=${this._params.primaryEntitiesOnly}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
<div class="type-wrapper">
|
||||
<div class="type-label">
|
||||
${this.hass.localize(
|
||||
`ui.components.target-picker.type.${this._params.type}`
|
||||
)}
|
||||
</div>
|
||||
<ha-list-base
|
||||
.ariaLabel=${`${this.hass.localize(`ui.components.target-picker.type.${this._params.type}`)}: ${this._params.title}`}
|
||||
wrap-focus
|
||||
>
|
||||
${waitingForSources
|
||||
? nothing
|
||||
: html`
|
||||
<ha-target-picker-item-row
|
||||
.hass=${this.hass}
|
||||
.type=${this._params.type}
|
||||
.itemId=${this._params.itemId}
|
||||
.deviceFilter=${deviceFilter}
|
||||
.entityFilter=${entityFilter}
|
||||
.includeDomains=${includeDomains}
|
||||
.includeDeviceClasses=${includeDeviceClasses}
|
||||
.primaryEntitiesOnly=${primaryEntitiesOnly}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`}
|
||||
</ha-list-base>
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.type-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--ha-border-radius-xl);
|
||||
border: var(--ha-border-width-sm) solid
|
||||
var(--ha-color-border-neutral-normal);
|
||||
overflow: hidden;
|
||||
}
|
||||
.type-label {
|
||||
background-color: var(--ha-color-surface-low);
|
||||
padding: var(--ha-space-1) var(--ha-space-3);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../../data/entity/entity";
|
||||
import type { TargetSelector } from "../../../data/selector";
|
||||
import type { TargetType } from "../../../data/target";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../../device/ha-device-picker";
|
||||
|
||||
export type NewBackupType = "automatic" | "manual";
|
||||
|
||||
export interface TargetDetailsDialogParams {
|
||||
title: string;
|
||||
type: TargetType;
|
||||
itemId: string;
|
||||
selector?: TargetSelector;
|
||||
deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||
includeDomains?: string[];
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { TargetType, TargetTypeFloorless } from "../../data/target";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker";
|
||||
import "../ha-expansion-panel";
|
||||
import "../ha-md-list";
|
||||
import "../list/ha-list-base";
|
||||
import "./ha-target-picker-item-row";
|
||||
|
||||
@customElement("ha-target-picker-item-group")
|
||||
@@ -66,23 +66,25 @@ export class HaTargetPickerItemGroup extends LitElement {
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
${Object.entries(this.items).map(([type, items]) =>
|
||||
items
|
||||
? items.map(
|
||||
(item) =>
|
||||
html`<ha-target-picker-item-row
|
||||
.hass=${this.hass}
|
||||
.type=${type as TargetTypeFloorless}
|
||||
.itemId=${item}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.primaryEntitiesOnly=${this.primaryEntitiesOnly}
|
||||
></ha-target-picker-item-row>`
|
||||
)
|
||||
: nothing
|
||||
)}
|
||||
<ha-list-base>
|
||||
${Object.entries(this.items).map(([type, items]) =>
|
||||
items
|
||||
? items.map(
|
||||
(item) =>
|
||||
html`<ha-target-picker-item-row
|
||||
.hass=${this.hass}
|
||||
.type=${type as TargetTypeFloorless}
|
||||
.itemId=${item}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.entityFilter=${this.entityFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.primaryEntitiesOnly=${this.primaryEntitiesOnly}
|
||||
></ha-target-picker-item-row>`
|
||||
)
|
||||
: nothing
|
||||
)}
|
||||
</ha-list-base>
|
||||
</ha-expansion-panel>`;
|
||||
}
|
||||
|
||||
@@ -96,7 +98,7 @@ export class HaTargetPickerItemGroup extends LitElement {
|
||||
--expansion-panel-content-padding: 0;
|
||||
}
|
||||
ha-expansion-panel::part(summary) {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
background-color: var(--ha-color-surface-low);
|
||||
padding: var(--ha-space-1) var(--ha-space-2);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--secondary-text-color);
|
||||
@@ -104,9 +106,6 @@ export class HaTargetPickerItemGroup extends LitElement {
|
||||
justify-content: space-between;
|
||||
min-height: unset;
|
||||
}
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiChevronLeft,
|
||||
mdiChevronRight,
|
||||
mdiClose,
|
||||
mdiDevices,
|
||||
mdiHome,
|
||||
mdiLabel,
|
||||
mdiMinusBox,
|
||||
mdiTextureBox,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
type PropertyValues,
|
||||
type TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -38,18 +48,17 @@ import {
|
||||
type ExtractFromTargetResultReferenced,
|
||||
type TargetType,
|
||||
} from "../../data/target";
|
||||
import { showMoreInfoDialog } from "../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import { buttonLinkStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../device/ha-device-picker";
|
||||
import { floorDefaultIconPath } from "../ha-floor-icon";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-md-list";
|
||||
import type { HaMdList } from "../ha-md-list";
|
||||
import "../ha-md-list-item";
|
||||
import type { HaMdListItem } from "../ha-md-list-item";
|
||||
import "../ha-state-icon";
|
||||
import "../ha-svg-icon";
|
||||
import "../item/ha-list-item-base";
|
||||
import "../item/ha-list-item-button";
|
||||
import { showTargetDetailsDialog } from "./dialog/show-dialog-target-details";
|
||||
|
||||
@customElement("ha-target-picker-item-row")
|
||||
@@ -65,6 +74,9 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sub-entry", reflect: true })
|
||||
public subEntry = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public subLevel = 0;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-context" })
|
||||
public hideContext = false;
|
||||
|
||||
@@ -106,12 +118,6 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
@consume({ context: labelsContext, subscribe: true })
|
||||
_labelRegistry!: LabelRegistryEntry[];
|
||||
|
||||
@query("ha-md-list-item") public item?: HaMdListItem;
|
||||
|
||||
@query("ha-md-list") public list?: HaMdList;
|
||||
|
||||
@query("ha-target-picker-item-row") public itemRow?: HaTargetPickerItemRow;
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues<this>) {
|
||||
if (!this.subEntry && changedProps.has("itemId")) {
|
||||
this._updateItemData();
|
||||
@@ -137,101 +143,128 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
|
||||
const replaceable = !this.subEntry && !this.expand;
|
||||
|
||||
return html`
|
||||
<ha-md-list-item
|
||||
type=${replaceable ? "button" : "text"}
|
||||
class=${classMap({
|
||||
error: notFound,
|
||||
replaceable,
|
||||
})}
|
||||
@click=${replaceable ? this._replaceItem : undefined}
|
||||
>
|
||||
<div class="icon" slot="start">
|
||||
${this.subEntry
|
||||
? html`
|
||||
<div class="horizontal-line-wrapper">
|
||||
<div class="horizontal-line"></div>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${iconPath
|
||||
? html`<ha-icon .icon=${iconPath}></ha-icon>`
|
||||
: this._iconImg
|
||||
? html`<img
|
||||
alt=${this._domainName || ""}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
src=${this._iconImg}
|
||||
/>`
|
||||
: fallbackIconPath
|
||||
? html`<ha-svg-icon .path=${fallbackIconPath}></ha-svg-icon>`
|
||||
: this.type === "entity"
|
||||
? html`
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObject ||
|
||||
({
|
||||
entity_id: this.itemId,
|
||||
attributes: {},
|
||||
} as HassEntity)}
|
||||
>
|
||||
</ha-state-icon>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
const content = html`
|
||||
<div class="icon" slot="start">
|
||||
${iconPath
|
||||
? html`<ha-icon .icon=${iconPath}></ha-icon>`
|
||||
: this._iconImg
|
||||
? html`<img
|
||||
alt=${this._domainName || ""}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
src=${this._iconImg}
|
||||
/>`
|
||||
: fallbackIconPath
|
||||
? html`<ha-svg-icon .path=${fallbackIconPath}></ha-svg-icon>`
|
||||
: this.type === "entity"
|
||||
? html`
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObject ||
|
||||
({
|
||||
entity_id: this.itemId,
|
||||
attributes: {},
|
||||
} as HassEntity)}
|
||||
>
|
||||
</ha-state-icon>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
|
||||
<div slot="headline">${name}</div>
|
||||
${notFound || (context && !this.hideContext)
|
||||
? html`<span slot="supporting-text"
|
||||
>${notFound
|
||||
? this.hass.localize(
|
||||
`ui.components.target-picker.${this.type}_not_found`
|
||||
)
|
||||
: context}</span
|
||||
>`
|
||||
: nothing}
|
||||
${this._domainName && this.subEntry
|
||||
? html`<span slot="supporting-text" class="domain"
|
||||
>${this._domainName}</span
|
||||
>`
|
||||
: nothing}
|
||||
${!this.subEntry && entries && showEntities
|
||||
? html`
|
||||
<div slot="end" class="summary">
|
||||
${showEntities &&
|
||||
!this.expand &&
|
||||
entries?.referenced_entities.length
|
||||
? html`<button class="main link" @click=${this._openDetails}>
|
||||
<div slot="headline">${name}</div>
|
||||
${notFound || (context && !this.hideContext)
|
||||
? html`<span slot="supporting-text"
|
||||
>${notFound
|
||||
? this.hass.localize(
|
||||
`ui.components.target-picker.${this.type}_not_found`
|
||||
)
|
||||
: context}</span
|
||||
>`
|
||||
: nothing}
|
||||
${stateObject && this.subEntry
|
||||
? html`<span slot="supporting-text" class="state"
|
||||
>${this.hass.formatEntityState(stateObject)}</span
|
||||
>`
|
||||
: nothing}
|
||||
${!this.subEntry && entries && showEntities
|
||||
? html`
|
||||
<div slot="end" class="summary">
|
||||
${showEntities &&
|
||||
!this.expand &&
|
||||
entries?.referenced_entities.length
|
||||
? html`<button class="main link" @click=${this._openDetails}>
|
||||
${this.hass.localize(
|
||||
"ui.components.target-picker.entities_count",
|
||||
{
|
||||
count: entries?.referenced_entities.length,
|
||||
}
|
||||
)}
|
||||
</button>`
|
||||
: showEntities
|
||||
? html`<span class="main">
|
||||
${this.hass.localize(
|
||||
"ui.components.target-picker.entities_count",
|
||||
{
|
||||
count: entries?.referenced_entities.length,
|
||||
}
|
||||
)}
|
||||
</button>`
|
||||
: showEntities
|
||||
? html`<span class="main">
|
||||
${this.hass.localize(
|
||||
"ui.components.target-picker.entities_count",
|
||||
{
|
||||
count: entries?.referenced_entities.length,
|
||||
}
|
||||
)}
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${!this.expand && !this.subEntry
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${!this.expand && !this.subEntry
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.path=${mdiClose}
|
||||
slot="end"
|
||||
@click=${this._removeItem}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: this.subEntry && this.type === "entity"
|
||||
? html`
|
||||
<ha-icon-button
|
||||
.path=${mdiClose}
|
||||
<ha-svg-icon
|
||||
.path=${computeRTL(this.hass)
|
||||
? mdiChevronLeft
|
||||
: mdiChevronRight}
|
||||
slot="end"
|
||||
@click=${this._removeItem}
|
||||
></ha-icon-button>
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
|
||||
let item: TemplateResult;
|
||||
|
||||
if (replaceable || (this.subEntry && this.type === "entity")) {
|
||||
item = html`
|
||||
<ha-list-item-button
|
||||
class=${classMap({
|
||||
error: notFound,
|
||||
replaceable,
|
||||
})}
|
||||
@click=${replaceable
|
||||
? this._replaceItem
|
||||
: this.subEntry && this.type === "entity"
|
||||
? this._openMoreInfo
|
||||
: undefined}
|
||||
>
|
||||
${content}
|
||||
</ha-list-item-button>
|
||||
`;
|
||||
} else {
|
||||
item = html`
|
||||
<ha-list-item-base
|
||||
class=${classMap({
|
||||
error: notFound,
|
||||
})}
|
||||
>
|
||||
${content}
|
||||
</ha-list-item-base>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${item}
|
||||
${this.expand && entries && entries.referenced_entities
|
||||
? this._renderEntries()
|
||||
: nothing}
|
||||
@@ -241,6 +274,10 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
private _renderEntries() {
|
||||
const entries = this.parentEntries || this._entries;
|
||||
|
||||
if (!entries || entries.referenced_entities.length === 0) {
|
||||
return this._renderEmptyEntries();
|
||||
}
|
||||
|
||||
let nextType: TargetType =
|
||||
this.type === "floor"
|
||||
? "area"
|
||||
@@ -350,54 +387,64 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
) || ([] as string[]),
|
||||
}));
|
||||
|
||||
const nextSubLevel = this.subLevel + 1;
|
||||
|
||||
return html`
|
||||
<div class="entries-tree">
|
||||
<div class="line-wrapper">
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<ha-md-list class="entries">
|
||||
${rows1.map(
|
||||
(itemId, index) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.hass=${this.hass}
|
||||
.type=${nextType}
|
||||
.itemId=${itemId}
|
||||
.parentEntries=${rows1Entries?.[index]}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
${deviceRows.map(
|
||||
(itemId, index) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.hass=${this.hass}
|
||||
type="device"
|
||||
.itemId=${itemId}
|
||||
.parentEntries=${deviceRowsEntries?.[index]}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
${entityRows.map(
|
||||
(itemId) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.hass=${this.hass}
|
||||
type="entity"
|
||||
.itemId=${itemId}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</div>
|
||||
${rows1.map(
|
||||
(itemId, index) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.subLevel=${nextSubLevel}
|
||||
style=${`--sub-entry-indent: calc(${nextSubLevel} * var(--ha-space-10));`}
|
||||
.hass=${this.hass}
|
||||
.type=${nextType}
|
||||
.itemId=${itemId}
|
||||
.parentEntries=${rows1Entries?.[index]}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
${deviceRows.map(
|
||||
(itemId, index) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.subLevel=${nextSubLevel}
|
||||
style=${`--sub-entry-indent: calc(${nextSubLevel} * var(--ha-space-10));`}
|
||||
.hass=${this.hass}
|
||||
type="device"
|
||||
.itemId=${itemId}
|
||||
.parentEntries=${deviceRowsEntries?.[index]}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
expand
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
${entityRows.map(
|
||||
(itemId) => html`
|
||||
<ha-target-picker-item-row
|
||||
sub-entry
|
||||
.subLevel=${nextSubLevel}
|
||||
style=${`--sub-entry-indent: calc(${nextSubLevel} * var(--ha-space-10));`}
|
||||
.hass=${this.hass}
|
||||
type="entity"
|
||||
.itemId=${itemId}
|
||||
.hideContext=${this.hideContext || this.type !== "label"}
|
||||
></ha-target-picker-item-row>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderEmptyEntries() {
|
||||
return html`<ha-list-item-base>
|
||||
<ha-svg-icon .path=${mdiMinusBox} slot="start" class="icon"></ha-svg-icon>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize("ui.components.target-picker.no_targets")}</span
|
||||
>
|
||||
</ha-list-item-base>`;
|
||||
}
|
||||
|
||||
private async _updateItemData() {
|
||||
if (this.type === "entity") {
|
||||
this._entries = undefined;
|
||||
@@ -640,6 +687,12 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _openMoreInfo = () => {
|
||||
showMoreInfoDialog(this, {
|
||||
entityId: this.itemId,
|
||||
});
|
||||
};
|
||||
|
||||
static styles = [
|
||||
buttonLinkStyle,
|
||||
css`
|
||||
@@ -651,12 +704,6 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
--md-list-item-two-line-container-height: 56px;
|
||||
}
|
||||
|
||||
:host([expand]:not([sub-entry])) ha-md-list-item {
|
||||
border: 2px solid var(--ha-color-border-neutral-loud);
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
}
|
||||
|
||||
.error {
|
||||
background: var(--ha-color-fill-warning-quiet-resting);
|
||||
}
|
||||
@@ -680,6 +727,7 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
.icon {
|
||||
width: 24px;
|
||||
display: flex;
|
||||
color: var(--ha-color-on-neutral-normal);
|
||||
}
|
||||
|
||||
img {
|
||||
@@ -697,53 +745,21 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
:host([sub-entry]) .summary {
|
||||
margin-right: var(--ha-space-12);
|
||||
margin-inline-start: var(--ha-space-12);
|
||||
}
|
||||
.summary .main {
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
:host([expand]) .summary .main {
|
||||
color: var(--ha-color-text-secondary);
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
}
|
||||
.summary .secondary {
|
||||
font-size: var(--ha-font-size-s);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.entries-tree {
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.entries-tree .line-wrapper {
|
||||
padding: var(--ha-space-5);
|
||||
}
|
||||
|
||||
.entries-tree .line-wrapper .line {
|
||||
border-left: 2px dashed var(--divider-color);
|
||||
height: calc(100% - 28px);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
:host([sub-entry]) .entries-tree .line-wrapper .line {
|
||||
height: calc(100% - 12px);
|
||||
top: -18px;
|
||||
}
|
||||
|
||||
.entries {
|
||||
padding: 0;
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
|
||||
.horizontal-line-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.horizontal-line-wrapper .horizontal-line {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
margin-inline-start: -28px;
|
||||
width: 29px;
|
||||
border-top: 2px dashed var(--divider-color);
|
||||
}
|
||||
|
||||
button.link {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
@@ -754,12 +770,19 @@ export class HaTargetPickerItemRow extends LitElement {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.domain {
|
||||
.state {
|
||||
width: fit-content;
|
||||
border-radius: var(--ha-border-radius-md);
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
padding: var(--ha-space-1);
|
||||
font-family: var(--ha-font-family-code);
|
||||
font-size: var(--ha-font-size-s);
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
|
||||
ha-list-item-button::part(end) {
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
|
||||
:host([sub-entry]) ha-list-item-button::part(base),
|
||||
:host([sub-entry]) ha-list-item-base::part(base) {
|
||||
padding-inline-start: var(--sub-entry-indent);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -76,6 +76,7 @@ import type {
|
||||
} from "../../../../data/script";
|
||||
import { getActionType, isAction } from "../../../../data/script";
|
||||
import { describeAction } from "../../../../data/script_i18n";
|
||||
import type { TargetSelector } from "../../../../data/selector";
|
||||
import { callExecuteScript } from "../../../../data/service";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -288,6 +289,12 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
? { device_id: (this.action as DeviceAction).device_id }
|
||||
: undefined;
|
||||
|
||||
const serviceTargetSpec =
|
||||
type === "service" && action
|
||||
? this.hass.services?.[computeDomain(action)]?.[computeObjectId(action)]
|
||||
?.target
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
${type === "service" && "action" in this.action && this.action.action
|
||||
? html`
|
||||
@@ -317,7 +324,11 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
)
|
||||
)}
|
||||
${target !== undefined || (actionHasTarget && !this._isNew)
|
||||
? this._renderTargets(target, actionHasTarget && !this._isNew)
|
||||
? this._renderTargets(
|
||||
target,
|
||||
actionHasTarget && !this._isNew,
|
||||
serviceTargetSpec
|
||||
)
|
||||
: nothing}
|
||||
${type !== "condition" &&
|
||||
(this.action as NonConditionAction).continue_on_error === true
|
||||
@@ -681,11 +692,16 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _renderTargets = memoizeOne(
|
||||
(target?: HassServiceTarget, targetRequired = false) =>
|
||||
(
|
||||
target?: HassServiceTarget,
|
||||
targetRequired = false,
|
||||
targetSpec?: TargetSelector["target"]
|
||||
) =>
|
||||
html`<ha-automation-row-targets
|
||||
.hass=${this.hass}
|
||||
.target=${target}
|
||||
.targetRequired=${targetRequired}
|
||||
.selector=${targetSpec ? { target: targetSpec } : undefined}
|
||||
></ha-automation-row-targets>`
|
||||
);
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
import type { DeviceCondition } from "../../../../data/device/device_automation";
|
||||
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import type { TargetSelector } from "../../../../data/selector";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showPromptDialog,
|
||||
@@ -180,6 +181,9 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
? { device_id: [(this.condition as DeviceCondition).device_id] }
|
||||
: undefined;
|
||||
|
||||
const conditionTargetSpec =
|
||||
this.conditionDescriptions[this.condition.condition]?.target;
|
||||
|
||||
return html`
|
||||
<ha-condition-icon
|
||||
slot="leading-icon"
|
||||
@@ -191,7 +195,11 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
describeCondition(this.condition, this.hass, this._entityReg)
|
||||
)}
|
||||
${target !== undefined || (descriptionHasTarget && !this._isNew)
|
||||
? this._renderTargets(target, descriptionHasTarget && !this._isNew)
|
||||
? this._renderTargets(
|
||||
target,
|
||||
descriptionHasTarget && !this._isNew,
|
||||
conditionTargetSpec
|
||||
)
|
||||
: nothing}
|
||||
</h3>
|
||||
<ha-automation-row-event-chip
|
||||
@@ -505,11 +513,16 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _renderTargets = memoizeOne(
|
||||
(target?: HassServiceTarget, targetRequired = false) =>
|
||||
(
|
||||
target?: HassServiceTarget,
|
||||
targetRequired = false,
|
||||
targetSpec?: TargetSelector["target"]
|
||||
) =>
|
||||
html`<ha-automation-row-targets
|
||||
.hass=${this.hass}
|
||||
.target=${target}
|
||||
.targetRequired=${targetRequired}
|
||||
.selector=${targetSpec ? { target: targetSpec } : undefined}
|
||||
></ha-automation-row-targets>`
|
||||
);
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ export const getTargetIcon = (
|
||||
targetType: string,
|
||||
targetId: string | undefined,
|
||||
configEntryLookup: Record<string, ConfigEntry>,
|
||||
getLabel?: (id: string) => LabelRegistryEntry | undefined
|
||||
getLabel?: (id: string) => LabelRegistryEntry | undefined,
|
||||
slot?: string
|
||||
): TemplateResult | typeof nothing => {
|
||||
if (!targetId) {
|
||||
return nothing;
|
||||
@@ -22,6 +23,7 @@ export const getTargetIcon = (
|
||||
|
||||
if (targetType === "floor" && hass.floors[targetId]) {
|
||||
return html`<ha-floor-icon
|
||||
.slot=${slot}
|
||||
.floor=${hass.floors[targetId]}
|
||||
></ha-floor-icon>`;
|
||||
}
|
||||
@@ -29,9 +31,12 @@ export const getTargetIcon = (
|
||||
if (targetType === "area") {
|
||||
const area = hass.areas[targetId];
|
||||
if (area?.icon) {
|
||||
return html`<ha-icon .icon=${area.icon}></ha-icon>`;
|
||||
return html`<ha-icon .slot=${slot} .icon=${area.icon}></ha-icon>`;
|
||||
}
|
||||
return html`<ha-svg-icon .path=${mdiTextureBox}></ha-svg-icon>`;
|
||||
return html`<ha-svg-icon
|
||||
.slot=${slot}
|
||||
.path=${mdiTextureBox}
|
||||
></ha-svg-icon>`;
|
||||
}
|
||||
|
||||
if (targetType === "device" && hass.devices[targetId]) {
|
||||
@@ -45,6 +50,7 @@ export const getTargetIcon = (
|
||||
return html`<ha-domain-icon
|
||||
.domain=${domain}
|
||||
brand-fallback
|
||||
.slot=${slot}
|
||||
></ha-domain-icon>`;
|
||||
}
|
||||
}
|
||||
@@ -53,15 +59,16 @@ export const getTargetIcon = (
|
||||
return html`<ha-state-icon
|
||||
.hass=${hass}
|
||||
.stateObj=${hass.states[targetId]}
|
||||
.slot=${slot}
|
||||
></ha-state-icon>`;
|
||||
}
|
||||
|
||||
if (targetType === "label" && getLabel) {
|
||||
const label = getLabel(targetId);
|
||||
if (label?.icon) {
|
||||
return html`<ha-icon .icon=${label.icon}></ha-icon>`;
|
||||
return html`<ha-icon .slot=${slot} .icon=${label.icon}></ha-icon>`;
|
||||
}
|
||||
return html`<ha-svg-icon .path=${mdiLabel}></ha-svg-icon>`;
|
||||
return html`<ha-svg-icon .slot=${slot} .path=${mdiLabel}></ha-svg-icon>`;
|
||||
}
|
||||
|
||||
return nothing;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import {
|
||||
mdiAlert,
|
||||
mdiAlertOctagon,
|
||||
mdiCodeBraces,
|
||||
mdiFormatListBulleted,
|
||||
mdiMenuDown,
|
||||
mdiShape,
|
||||
} from "@mdi/js";
|
||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
@@ -12,8 +14,13 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
import { transform } from "../../../../common/decorators/transform";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { isTemplate } from "../../../../common/string/has-template";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import type { HaDropdownSelectEvent } from "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { showTargetDetailsDialog } from "../../../../components/target-picker/dialog/show-dialog-target-details";
|
||||
import type { ConfigEntry } from "../../../../data/config_entries";
|
||||
import {
|
||||
configEntriesContext,
|
||||
@@ -23,6 +30,9 @@ import {
|
||||
statesContext,
|
||||
} from "../../../../data/context";
|
||||
import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
|
||||
import type { TargetSelector } from "../../../../data/selector";
|
||||
import type { TargetType } from "../../../../data/target";
|
||||
import { showMoreInfoDialog } from "../../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { getTargetIcon } from "./get_target_icon";
|
||||
import { getTargetText } from "./get_target_text";
|
||||
@@ -38,6 +48,9 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public targetRequired = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public selector?: TargetSelector;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
@@ -110,17 +123,67 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
return html`<span class="target">
|
||||
<ha-svg-icon .path=${mdiFormatListBulleted}></ha-svg-icon>
|
||||
<div class="label">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.automation.editor.target_summary.targets",
|
||||
{
|
||||
count: totalLength,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</span>`;
|
||||
const rows = Object.entries(this.target!)
|
||||
.reduce<["floor" | "area" | "device" | "entity" | "label", string][]>(
|
||||
(acc, [targetType, targetId]) => {
|
||||
const type = targetType.replace("_id", "") as
|
||||
| "floor"
|
||||
| "area"
|
||||
| "device"
|
||||
| "entity"
|
||||
| "label";
|
||||
return [
|
||||
...acc,
|
||||
...ensureArray(targetId).map((id): [typeof type, string] => [
|
||||
type,
|
||||
id,
|
||||
]),
|
||||
];
|
||||
},
|
||||
[]
|
||||
)
|
||||
.sort(([typeA], [typeB]) => {
|
||||
const order = ["entity", "device", "area", "floor", "label"];
|
||||
return order.indexOf(typeA) - order.indexOf(typeB);
|
||||
});
|
||||
|
||||
let lastTargetType: string | null = null;
|
||||
|
||||
return html`
|
||||
<ha-dropdown
|
||||
@wa-select=${this._handleTargetSelect}
|
||||
@click=${stopPropagation}
|
||||
>
|
||||
<span slot="trigger" class="target interactive">
|
||||
<ha-svg-icon .path=${mdiFormatListBulleted}></ha-svg-icon>
|
||||
<div class="label">
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.automation.editor.target_summary.targets",
|
||||
{
|
||||
count: totalLength,
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
<ha-svg-icon .path=${mdiMenuDown}></ha-svg-icon>
|
||||
</span>
|
||||
${rows.map(([targetType, targetId]) => {
|
||||
const content = html`${lastTargetType !== null &&
|
||||
lastTargetType !== targetType
|
||||
? html`<wa-divider></wa-divider>`
|
||||
: nothing}
|
||||
${!lastTargetType || lastTargetType !== targetType
|
||||
? html`<h3>
|
||||
${this._i18n.localize(
|
||||
`ui.panel.config.automation.editor.target_summary.types.${targetType}`
|
||||
)}
|
||||
</h3>`
|
||||
: nothing}
|
||||
${this._renderTarget(targetType, targetId, true)}`;
|
||||
lastTargetType = targetType;
|
||||
return content;
|
||||
})}
|
||||
</ha-dropdown>
|
||||
`;
|
||||
}
|
||||
|
||||
private _getLabel = (id: string) =>
|
||||
@@ -152,9 +215,22 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
icon: TemplateResult | typeof nothing,
|
||||
label: string,
|
||||
warning = false,
|
||||
error = false
|
||||
error = false,
|
||||
targetId?: string,
|
||||
targetType?: string
|
||||
) {
|
||||
return html`<div class=${classMap({ target: true, warning, error })}>
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
target: true,
|
||||
warning,
|
||||
error,
|
||||
interactive: targetId && targetType,
|
||||
})}
|
||||
.targetId=${targetId}
|
||||
.targetType=${targetType}
|
||||
.label=${label}
|
||||
@click=${this._handleTargetClick}
|
||||
>
|
||||
${icon}
|
||||
<div class="label">${label}</div>
|
||||
</div>`;
|
||||
@@ -162,48 +238,131 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
|
||||
private _renderTarget(
|
||||
targetType: "floor" | "area" | "device" | "entity" | "label",
|
||||
targetId: string
|
||||
targetId: string,
|
||||
dropdownOption = false
|
||||
) {
|
||||
let icon: string | undefined;
|
||||
let label: string;
|
||||
let warning = false;
|
||||
let badgeTargetId: string | undefined = targetId;
|
||||
let badgeTargetType: string | undefined = targetType;
|
||||
|
||||
if (targetType === "entity" && ["all", "none"].includes(targetId)) {
|
||||
return this._renderTargetBadge(
|
||||
html`<ha-svg-icon .path=${mdiShape}></ha-svg-icon>`,
|
||||
this._i18n.localize(
|
||||
`ui.panel.config.automation.editor.target_summary.${targetId as "all" | "none"}_entities`
|
||||
)
|
||||
icon = mdiShape;
|
||||
label = this._i18n.localize(
|
||||
`ui.panel.config.automation.editor.target_summary.${targetId as "all" | "none"}_entities`
|
||||
);
|
||||
badgeTargetId = undefined;
|
||||
badgeTargetType = undefined;
|
||||
} else if (isTemplate(targetId)) {
|
||||
// Check if the target is a template
|
||||
icon = mdiCodeBraces;
|
||||
label = this._i18n.localize(
|
||||
"ui.panel.config.automation.editor.target_summary.template"
|
||||
);
|
||||
badgeTargetId = undefined;
|
||||
badgeTargetType = undefined;
|
||||
} else {
|
||||
const exists = this._checkTargetExists(targetType, targetId);
|
||||
if (!exists) {
|
||||
icon = mdiAlert;
|
||||
label = getTargetText(this.hass, targetType, targetId, this._getLabel);
|
||||
warning = true;
|
||||
badgeTargetId = undefined;
|
||||
badgeTargetType = undefined;
|
||||
} else {
|
||||
label = getTargetText(this.hass, targetType, targetId, this._getLabel);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the target is a template
|
||||
if (isTemplate(targetId)) {
|
||||
return this._renderTargetBadge(
|
||||
html`<ha-svg-icon .path=${mdiCodeBraces}></ha-svg-icon>`,
|
||||
this._i18n.localize(
|
||||
"ui.panel.config.automation.editor.target_summary.template"
|
||||
)
|
||||
);
|
||||
}
|
||||
const iconTemplate = icon
|
||||
? html`<ha-svg-icon
|
||||
.slot=${dropdownOption ? "icon" : ""}
|
||||
.icon=${icon}
|
||||
></ha-svg-icon>`
|
||||
: getTargetIcon(
|
||||
this.hass,
|
||||
targetType,
|
||||
targetId,
|
||||
this._configEntryLookup || {},
|
||||
this._getLabel,
|
||||
dropdownOption ? "icon" : ""
|
||||
);
|
||||
|
||||
const exists = this._checkTargetExists(targetType, targetId);
|
||||
if (!exists) {
|
||||
return this._renderTargetBadge(
|
||||
html`<ha-svg-icon .path=${mdiAlert}></ha-svg-icon>`,
|
||||
getTargetText(this.hass, targetType, targetId, this._getLabel),
|
||||
true
|
||||
);
|
||||
if (dropdownOption) {
|
||||
return html`<ha-dropdown-item
|
||||
.value=${{
|
||||
targetId: badgeTargetId,
|
||||
targetType: badgeTargetType,
|
||||
label,
|
||||
}}
|
||||
class=${classMap({
|
||||
warning,
|
||||
})}
|
||||
>${iconTemplate} ${label}</ha-dropdown-item
|
||||
>`;
|
||||
}
|
||||
|
||||
return this._renderTargetBadge(
|
||||
getTargetIcon(
|
||||
this.hass,
|
||||
targetType,
|
||||
targetId,
|
||||
this._configEntryLookup || {},
|
||||
this._getLabel
|
||||
),
|
||||
getTargetText(this.hass, targetType, targetId, this._getLabel)
|
||||
iconTemplate,
|
||||
label,
|
||||
warning,
|
||||
false,
|
||||
badgeTargetId,
|
||||
badgeTargetType
|
||||
);
|
||||
}
|
||||
|
||||
private _handleTargetClick(ev: Event) {
|
||||
const target = ev.currentTarget as HTMLDivElement & {
|
||||
targetId: string;
|
||||
targetType: TargetType;
|
||||
label: string;
|
||||
};
|
||||
|
||||
this._showTargetInfo(target.targetId, target.targetType, target.label, ev);
|
||||
}
|
||||
|
||||
private _handleTargetSelect(
|
||||
ev: HaDropdownSelectEvent<{
|
||||
targetId?: string;
|
||||
targetType?: TargetType;
|
||||
label: string;
|
||||
}>
|
||||
) {
|
||||
const value = ev.detail.item.value;
|
||||
|
||||
if (!value.targetId || !value.targetType) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showTargetInfo(value.targetId, value.targetType, value.label);
|
||||
}
|
||||
|
||||
private _showTargetInfo(
|
||||
targetId: string,
|
||||
targetType: TargetType,
|
||||
label: string,
|
||||
ev?: Event
|
||||
) {
|
||||
if (!targetId || !targetType) {
|
||||
return;
|
||||
}
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (targetType === "entity") {
|
||||
showMoreInfoDialog(this, { entityId: targetId });
|
||||
return;
|
||||
}
|
||||
|
||||
showTargetDetailsDialog(this, {
|
||||
title: label,
|
||||
type: targetType,
|
||||
itemId: targetId,
|
||||
selector: this.selector,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: contents;
|
||||
@@ -255,6 +414,25 @@ export class HaAutomationRowTargets extends LitElement {
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.target.interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.target.interactive:hover {
|
||||
background: var(--ha-color-fill-neutral-normal-hover);
|
||||
}
|
||||
|
||||
ha-dropdown-item {
|
||||
padding: 0 var(--ha-space-2);
|
||||
}
|
||||
ha-dropdown-item.warning {
|
||||
background-color: var(--ha-color-fill-warning-quiet-resting);
|
||||
color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
ha-dropdown-item.warning:hover {
|
||||
background-color: var(--ha-color-fill-warning-quiet-hover);
|
||||
color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ import { validateConfig } from "../../../../data/config";
|
||||
import { fullEntitiesContext } from "../../../../data/context";
|
||||
import type { DeviceTrigger } from "../../../../data/device/device_automation";
|
||||
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import type { TargetSelector } from "../../../../data/selector";
|
||||
import type { TriggerDescriptions } from "../../../../data/trigger";
|
||||
import { isTriggerList } from "../../../../data/trigger";
|
||||
import {
|
||||
@@ -214,6 +215,12 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
? { device_id: (this.trigger as DeviceTrigger).device_id }
|
||||
: undefined;
|
||||
|
||||
const triggerTargetSpec =
|
||||
type === "platform"
|
||||
? this.triggerDescriptions[(this.trigger as PlatformTrigger).trigger]
|
||||
?.target
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
${type === "list"
|
||||
? html`<ha-svg-icon
|
||||
@@ -229,7 +236,11 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<h3 slot="header">
|
||||
${describeTrigger(this.trigger, this.hass, this._entityReg)}
|
||||
${target !== undefined || (descriptionHasTarget && !this._isNew)
|
||||
? this._renderTargets(target, descriptionHasTarget && !this._isNew)
|
||||
? this._renderTargets(
|
||||
target,
|
||||
descriptionHasTarget && !this._isNew,
|
||||
triggerTargetSpec
|
||||
)
|
||||
: nothing}
|
||||
</h3>
|
||||
<ha-automation-row-event-chip
|
||||
@@ -507,11 +518,16 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
}
|
||||
|
||||
private _renderTargets = memoizeOne(
|
||||
(target?: HassServiceTarget, targetRequired = false) =>
|
||||
(
|
||||
target?: HassServiceTarget,
|
||||
targetRequired = false,
|
||||
targetSpec?: TargetSelector["target"]
|
||||
) =>
|
||||
html`<ha-automation-row-targets
|
||||
.hass=${this.hass}
|
||||
.target=${target}
|
||||
.targetRequired=${targetRequired}
|
||||
.selector=${targetSpec ? { target: targetSpec } : undefined}
|
||||
></ha-automation-row-targets>`
|
||||
);
|
||||
|
||||
|
||||
@@ -5101,7 +5101,14 @@
|
||||
"invalid": "Invalid target",
|
||||
"all_entities": "All entities",
|
||||
"none_entities": "No entities",
|
||||
"template": "Template"
|
||||
"template": "Template",
|
||||
"types": {
|
||||
"entity": "[%key:ui::components::target-picker::type::entities%]",
|
||||
"device": "[%key:ui::components::target-picker::type::devices%]",
|
||||
"area": "[%key:ui::components::target-picker::type::areas%]",
|
||||
"floor": "Floors",
|
||||
"label": "[%key:ui::components::target-picker::type::labels%]"
|
||||
}
|
||||
},
|
||||
"generic": "Generic",
|
||||
"triggers": {
|
||||
|
||||
Reference in New Issue
Block a user