mirror of
https://github.com/home-assistant/frontend.git
synced 2026-01-14 19:27:25 +00:00
Compare commits
10 Commits
fix-reclos
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5be7bad176 | ||
|
|
0a54a93a39 | ||
|
|
156583aff1 | ||
|
|
7572257821 | ||
|
|
4703cf802f | ||
|
|
55c2315329 | ||
|
|
7d7e95ac55 | ||
|
|
6d7694caff | ||
|
|
d7b6243698 | ||
|
|
73feef9e92 |
@@ -1,8 +1,9 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { fullEntitiesContext } from "../../data/context";
|
||||
import type { DeviceAutomation } from "../../data/device/device_automation";
|
||||
import {
|
||||
@@ -11,11 +12,12 @@ import {
|
||||
} from "../../data/device/device_automation";
|
||||
import type { EntityRegistryEntry } from "../../data/entity/entity_registry";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-generic-picker";
|
||||
import "../ha-md-select";
|
||||
import "../ha-md-select-option";
|
||||
import type { PickerValueRenderer } from "../ha-picker-field";
|
||||
|
||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
||||
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
||||
|
||||
export abstract class HaDeviceAutomationPicker<
|
||||
T extends DeviceAutomation,
|
||||
@@ -28,7 +30,7 @@ export abstract class HaDeviceAutomationPicker<
|
||||
|
||||
@property({ type: Object }) public value?: T;
|
||||
|
||||
@state() private _automations: T[] = [];
|
||||
@state() private _automations?: T[];
|
||||
|
||||
// Trigger an empty render so we start with a clean DOM.
|
||||
// paper-listbox does not like changing things around.
|
||||
@@ -44,12 +46,6 @@ export abstract class HaDeviceAutomationPicker<
|
||||
);
|
||||
}
|
||||
|
||||
protected get UNKNOWN_AUTOMATION_TEXT() {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.devices.automation.actions.unknown_action"
|
||||
);
|
||||
}
|
||||
|
||||
private _localizeDeviceAutomation: (
|
||||
hass: HomeAssistant,
|
||||
entityRegistry: EntityRegistryEntry[],
|
||||
@@ -75,7 +71,7 @@ export abstract class HaDeviceAutomationPicker<
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
if (!this.value) {
|
||||
if (!this.value || !this._automations) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -88,7 +84,7 @@ export abstract class HaDeviceAutomationPicker<
|
||||
);
|
||||
|
||||
if (idx === -1) {
|
||||
return UNKNOWN_AUTOMATION_KEY;
|
||||
return this.value.alias || this.value.type || "unknown";
|
||||
}
|
||||
|
||||
return `${this._automations[idx].device_id}_${idx}`;
|
||||
@@ -99,37 +95,21 @@ export abstract class HaDeviceAutomationPicker<
|
||||
return nothing;
|
||||
}
|
||||
const value = this._value;
|
||||
return html`
|
||||
<ha-md-select
|
||||
.label=${this.label}
|
||||
.value=${value}
|
||||
@change=${this._automationChanged}
|
||||
@closed=${stopPropagation}
|
||||
.disabled=${this._automations.length === 0}
|
||||
>
|
||||
${value === NO_AUTOMATION_KEY
|
||||
? html`<ha-md-select-option .value=${NO_AUTOMATION_KEY}>
|
||||
${this.NO_AUTOMATION_TEXT}
|
||||
</ha-md-select-option>`
|
||||
: nothing}
|
||||
${value === UNKNOWN_AUTOMATION_KEY
|
||||
? html`<ha-md-select-option .value=${UNKNOWN_AUTOMATION_KEY}>
|
||||
${this.UNKNOWN_AUTOMATION_TEXT}
|
||||
</ha-md-select-option>`
|
||||
: nothing}
|
||||
${this._automations.map(
|
||||
(automation, idx) => html`
|
||||
<ha-md-select-option .value=${`${automation.device_id}_${idx}`}>
|
||||
${this._localizeDeviceAutomation(
|
||||
this.hass,
|
||||
this._entityReg,
|
||||
automation
|
||||
)}
|
||||
</ha-md-select-option>
|
||||
`
|
||||
)}
|
||||
</ha-md-select>
|
||||
`;
|
||||
|
||||
return html`<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.value=${value}
|
||||
.disabled=${!this._automations || this._automations.length === 0}
|
||||
.getItems=${this._getItems(value, this._automations)}
|
||||
@value-changed=${this._automationChanged}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.actions.unknown_action"
|
||||
)}
|
||||
hide-clear-icon
|
||||
>
|
||||
</ha-generic-picker>`;
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
@@ -140,6 +120,57 @@ export abstract class HaDeviceAutomationPicker<
|
||||
}
|
||||
}
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
(value: string, automations: T[] | undefined) => {
|
||||
if (!automations) {
|
||||
return () => undefined;
|
||||
}
|
||||
|
||||
const automationListItems = automations.map((automation, idx) => {
|
||||
const primary = this._localizeDeviceAutomation(
|
||||
this.hass,
|
||||
this._entityReg,
|
||||
automation
|
||||
);
|
||||
return {
|
||||
id: `${automation.device_id}_${idx}`,
|
||||
primary,
|
||||
};
|
||||
});
|
||||
|
||||
automationListItems.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(
|
||||
a.primary,
|
||||
b.primary,
|
||||
this.hass.locale.language
|
||||
)
|
||||
);
|
||||
|
||||
if (value === NO_AUTOMATION_KEY) {
|
||||
automationListItems.unshift({
|
||||
id: NO_AUTOMATION_KEY,
|
||||
primary: this.NO_AUTOMATION_TEXT,
|
||||
});
|
||||
}
|
||||
|
||||
return () => automationListItems;
|
||||
}
|
||||
);
|
||||
|
||||
private _valueRenderer: PickerValueRenderer = (value: string) => {
|
||||
const automation = this._automations?.find(
|
||||
(a, idx) => value === `${a.device_id}_${idx}`
|
||||
);
|
||||
|
||||
const text = automation
|
||||
? this._localizeDeviceAutomation(this.hass, this._entityReg, automation)
|
||||
: value === NO_AUTOMATION_KEY
|
||||
? this.NO_AUTOMATION_TEXT
|
||||
: value;
|
||||
|
||||
return html`<span slot="headline">${text}</span>`;
|
||||
};
|
||||
|
||||
private async _updateDeviceInfo() {
|
||||
this._automations = this.deviceId
|
||||
? (await this._fetchDeviceAutomations(this.hass, this.deviceId)).sort(
|
||||
@@ -161,13 +192,14 @@ export abstract class HaDeviceAutomationPicker<
|
||||
this._renderEmpty = false;
|
||||
}
|
||||
|
||||
private _automationChanged(ev) {
|
||||
const value = ev.target.value;
|
||||
if (!value || [UNKNOWN_AUTOMATION_KEY, NO_AUTOMATION_KEY].includes(value)) {
|
||||
private _automationChanged(ev: CustomEvent<{ value: string }>) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
if (!value || NO_AUTOMATION_KEY === value) {
|
||||
return;
|
||||
}
|
||||
const [deviceId, idx] = value.split("_");
|
||||
const automation = this._automations[idx];
|
||||
const automation = this._automations![idx];
|
||||
if (automation.device_id !== deviceId) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { getEntityContext } from "../../common/entity/context/get_entity_context";
|
||||
import {
|
||||
STATE_DISPLAY_SPECIAL_CONTENT,
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS,
|
||||
@@ -95,6 +96,9 @@ export class HaStateContentPicker extends LitElement {
|
||||
@property({ type: Boolean, attribute: "allow-name" }) public allowName =
|
||||
false;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-context" }) public allowContext =
|
||||
false;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string[] | string;
|
||||
@@ -106,7 +110,12 @@ export class HaStateContentPicker extends LitElement {
|
||||
private _editIndex?: number;
|
||||
|
||||
private _getItems = memoizeOne(
|
||||
(entityId?: string, stateObj?: HassEntity, allowName?: boolean) => {
|
||||
(
|
||||
entityId?: string,
|
||||
stateObj?: HassEntity,
|
||||
allowName?: boolean,
|
||||
allowContext?: boolean
|
||||
) => {
|
||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||
const items: PickerComboBoxItem[] = [
|
||||
{
|
||||
@@ -149,6 +158,52 @@ export class HaStateContentPicker extends LitElement {
|
||||
"ui.components.state-content-picker.last_updated"
|
||||
),
|
||||
},
|
||||
...(allowContext && stateObj
|
||||
? (() => {
|
||||
const context = getEntityContext(
|
||||
stateObj,
|
||||
this.hass.entities,
|
||||
this.hass.devices,
|
||||
this.hass.areas,
|
||||
this.hass.floors
|
||||
);
|
||||
const contextItems: PickerComboBoxItem[] = [];
|
||||
if (context.device) {
|
||||
contextItems.push({
|
||||
id: "device_name",
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.device_name"
|
||||
),
|
||||
sorting_label: this.hass.localize(
|
||||
"ui.components.state-content-picker.device_name"
|
||||
),
|
||||
});
|
||||
}
|
||||
if (context.area) {
|
||||
contextItems.push({
|
||||
id: "area_name",
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.area_name"
|
||||
),
|
||||
sorting_label: this.hass.localize(
|
||||
"ui.components.state-content-picker.area_name"
|
||||
),
|
||||
});
|
||||
}
|
||||
if (context.floor) {
|
||||
contextItems.push({
|
||||
id: "floor_name",
|
||||
primary: this.hass.localize(
|
||||
"ui.components.state-content-picker.floor_name"
|
||||
),
|
||||
sorting_label: this.hass.localize(
|
||||
"ui.components.state-content-picker.floor_name"
|
||||
),
|
||||
});
|
||||
}
|
||||
return contextItems;
|
||||
})()
|
||||
: []),
|
||||
...(domain
|
||||
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
|
||||
@@ -300,7 +355,8 @@ export class HaStateContentPicker extends LitElement {
|
||||
const items = this._getItems(
|
||||
this.entityId,
|
||||
stateObjForItems,
|
||||
this.allowName
|
||||
this.allowName,
|
||||
this.allowContext
|
||||
);
|
||||
return items.find((item) => item.id === value)?.primary;
|
||||
}
|
||||
@@ -343,7 +399,12 @@ export class HaStateContentPicker extends LitElement {
|
||||
const stateObj = this.entityId
|
||||
? this.hass.states[this.entityId]
|
||||
: undefined;
|
||||
const items = this._getItems(this.entityId, stateObj, this.allowName);
|
||||
const items = this._getItems(
|
||||
this.entityId,
|
||||
stateObj,
|
||||
this.allowName,
|
||||
this.allowContext
|
||||
);
|
||||
const currentValue =
|
||||
this._editIndex != null ? this._value[this._editIndex] : undefined;
|
||||
|
||||
@@ -367,7 +428,12 @@ export class HaStateContentPicker extends LitElement {
|
||||
const stateObj = this.entityId
|
||||
? this.hass.states[this.entityId]
|
||||
: undefined;
|
||||
const items = this._getItems(this.entityId, stateObj, this.allowName);
|
||||
const items = this._getItems(
|
||||
this.entityId,
|
||||
stateObj,
|
||||
this.allowName,
|
||||
this.allowContext
|
||||
);
|
||||
|
||||
// If the search string does not match with the id of any of the items,
|
||||
// offer to add it as a custom attribute
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mdiLabel, mdiPlus } from "@mdi/js";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import type { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, html } from "lit";
|
||||
@@ -25,11 +25,9 @@ import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-generic-picker";
|
||||
import type { HaGenericPicker } from "./ha-generic-picker";
|
||||
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
|
||||
import type { PickerValueRenderer } from "./ha-picker-field";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
const NO_LABELS = "___NO_LABELS___";
|
||||
|
||||
@customElement("ha-label-picker")
|
||||
export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
@@ -108,52 +106,10 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
];
|
||||
}
|
||||
|
||||
private _labelMap = memoizeOne(
|
||||
(
|
||||
labels: LabelRegistryEntry[] | undefined
|
||||
): Map<string, LabelRegistryEntry> => {
|
||||
if (!labels) {
|
||||
return new Map();
|
||||
}
|
||||
return new Map(labels.map((label) => [label.label_id, label]));
|
||||
}
|
||||
);
|
||||
|
||||
private _computeValueRenderer = memoizeOne(
|
||||
(labels: LabelRegistryEntry[] | undefined): PickerValueRenderer =>
|
||||
(value) => {
|
||||
const label = this._labelMap(labels).get(value);
|
||||
|
||||
if (!label) {
|
||||
return html`
|
||||
<ha-svg-icon slot="start" .path=${mdiLabel}></ha-svg-icon>
|
||||
<span slot="headline">${value}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
${label.icon
|
||||
? html`<ha-icon slot="start" .icon=${label.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon slot="start" .path=${mdiLabel}></ha-svg-icon>`}
|
||||
<span slot="headline">${label.name}</span>
|
||||
`;
|
||||
}
|
||||
);
|
||||
|
||||
private _getLabelsMemoized = memoizeOne(getLabels);
|
||||
|
||||
private _getItems = () => {
|
||||
if (!this._labels || this._labels.length === 0) {
|
||||
return [
|
||||
{
|
||||
id: NO_LABELS,
|
||||
primary: this.hass.localize("ui.components.label-picker.no_labels"),
|
||||
icon_path: mdiLabel,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return this._getLabelsMemoized(
|
||||
private _getItems = () =>
|
||||
this._getLabelsMemoized(
|
||||
this.hass.states,
|
||||
this.hass.areas,
|
||||
this.hass.devices,
|
||||
@@ -166,7 +122,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
this.entityFilter,
|
||||
this.excludeLabels
|
||||
);
|
||||
};
|
||||
|
||||
private _allLabelNames = memoizeOne((labels?: LabelRegistryEntry[]) => {
|
||||
if (!labels) {
|
||||
@@ -219,8 +174,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
this.placeholder ??
|
||||
this.hass.localize("ui.components.label-picker.label");
|
||||
|
||||
const valueRenderer = this._computeValueRenderer(this._labels);
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
.disabled=${this.disabled}
|
||||
@@ -237,7 +190,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
.value=${this.value}
|
||||
.getItems=${this._getItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.searchKeys=${labelComboBoxKeys}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
@@ -251,10 +203,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (value === NO_LABELS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
this._setValue(undefined);
|
||||
return;
|
||||
|
||||
@@ -37,6 +37,7 @@ export class HaSelectorUiStateContent extends SubscribeMixin(LitElement) {
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.allowName=${this.selector.ui_state_content?.allow_name || false}
|
||||
.allowContext=${this.selector.ui_state_content?.allow_context || false}
|
||||
></ha-entity-state-content-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -501,6 +501,7 @@ export interface UiStateContentSelector {
|
||||
ui_state_content: {
|
||||
entity_id?: string;
|
||||
allow_name?: boolean;
|
||||
allow_context?: boolean;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ import { fullEntitiesContext } from "../../../../data/context";
|
||||
import type { EntityRegistryEntry } from "../../../../data/entity/entity_registry";
|
||||
import type {
|
||||
Action,
|
||||
DeviceAction,
|
||||
NonConditionAction,
|
||||
RepeatAction,
|
||||
ServiceAction,
|
||||
@@ -233,6 +234,13 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
private _renderRow() {
|
||||
const type = getAutomationActionType(this.action);
|
||||
|
||||
const target =
|
||||
type === "service" && "target" in this.action
|
||||
? (this.action as ServiceAction).target
|
||||
: type === "device_id" && (this.action as DeviceAction).device_id
|
||||
? { device_id: (this.action as DeviceAction).device_id }
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
${type === "service" && "action" in this.action && this.action.action
|
||||
? html`
|
||||
@@ -254,9 +262,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
${capitalizeFirstLetter(
|
||||
describeAction(this.hass, this._entityReg, this.action)
|
||||
)}
|
||||
${type === "service" && "target" in this.action
|
||||
? this._renderTargets((this.action as ServiceAction).target)
|
||||
: nothing}
|
||||
${target ? this._renderTargets(target) : nothing}
|
||||
</h3>
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
@@ -1504,14 +1504,7 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
box-shadow: inset var(--ha-shadow-offset-x-lg)
|
||||
calc(var(--ha-shadow-offset-y-lg) * -1) var(--ha-shadow-blur-lg)
|
||||
var(--ha-shadow-spread-lg) var(--ha-color-shadow-light);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.targets-show-more {
|
||||
box-shadow: inset var(--ha-shadow-offset-x-lg)
|
||||
calc(var(--ha-shadow-offset-y-lg) * -1) var(--ha-shadow-blur-lg)
|
||||
var(--ha-shadow-spread-lg) var(--ha-color-shadow-dark);
|
||||
}
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px), all and (max-height: 500px) {
|
||||
|
||||
@@ -76,6 +76,7 @@ import "./types/ha-automation-condition-template";
|
||||
import "./types/ha-automation-condition-time";
|
||||
import "./types/ha-automation-condition-trigger";
|
||||
import "./types/ha-automation-condition-zone";
|
||||
import type { DeviceCondition } from "../../../../data/device/device_automation";
|
||||
|
||||
export interface ConditionElement extends LitElement {
|
||||
condition: Condition;
|
||||
@@ -184,6 +185,14 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _renderRow() {
|
||||
const target =
|
||||
"target" in (this.conditionDescriptions[this.condition.condition] || {})
|
||||
? (this.condition as PlatformCondition).target
|
||||
: "device_id" in this.condition &&
|
||||
(this.condition as DeviceCondition).device_id
|
||||
? { device_id: [(this.condition as DeviceCondition).device_id] }
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<ha-condition-icon
|
||||
slot="leading-icon"
|
||||
@@ -194,10 +203,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
${capitalizeFirstLetter(
|
||||
describeCondition(this.condition, this.hass, this._entityReg)
|
||||
)}
|
||||
${"target" in
|
||||
(this.conditionDescriptions[this.condition.condition] || {})
|
||||
? this._renderTargets((this.condition as PlatformCondition).target)
|
||||
: nothing}
|
||||
${target ? this._renderTargets(target) : nothing}
|
||||
</h3>
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
@@ -381,12 +381,12 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.automation.picker.headers.voice_assistants"
|
||||
"ui.panel.config.voice_assistants.expose.headers.assistants"
|
||||
),
|
||||
type: "icon",
|
||||
type: "flex",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
minWidth: "160px",
|
||||
maxWidth: "160px",
|
||||
template: (automation) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entityReg,
|
||||
|
||||
@@ -56,6 +56,7 @@ import { isTrigger, subscribeTrigger } from "../../../../data/automation";
|
||||
import { describeTrigger } from "../../../../data/automation_i18n";
|
||||
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 { TriggerDescriptions } from "../../../../data/trigger";
|
||||
import { isTriggerList } from "../../../../data/trigger";
|
||||
@@ -196,6 +197,15 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
const yamlMode = this._yamlMode || !supported;
|
||||
|
||||
const target =
|
||||
type === "platform" &&
|
||||
"target" in
|
||||
this.triggerDescriptions[(this.trigger as PlatformTrigger).trigger]
|
||||
? (this.trigger as PlatformTrigger).target
|
||||
: type === "device" && (this.trigger as DeviceTrigger).device_id
|
||||
? { device_id: (this.trigger as DeviceTrigger).device_id }
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
${type === "list"
|
||||
? html`<ha-svg-icon
|
||||
@@ -210,11 +220,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
></ha-trigger-icon>`}
|
||||
<h3 slot="header">
|
||||
${describeTrigger(this.trigger, this.hass, this._entityReg)}
|
||||
${type === "platform" &&
|
||||
"target" in
|
||||
this.triggerDescriptions[(this.trigger as PlatformTrigger).trigger]
|
||||
? this._renderTargets((this.trigger as PlatformTrigger).target)
|
||||
: nothing}
|
||||
${target ? this._renderTargets(target) : nothing}
|
||||
</h3>
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
@@ -20,7 +20,6 @@ import type { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
import { showCategoryRegistryDetailDialog } from "./show-dialog-category-registry-detail";
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
const NO_CATEGORIES_ID = "___NO_CATEGORIES___";
|
||||
|
||||
@customElement("ha-category-picker")
|
||||
export class HaCategoryPicker extends SubscribeMixin(LitElement) {
|
||||
@@ -108,18 +107,6 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!categories || categories.length === 0) {
|
||||
return [
|
||||
{
|
||||
id: NO_CATEGORIES_ID,
|
||||
primary: this.hass.localize(
|
||||
"ui.components.category-picker.no_categories"
|
||||
),
|
||||
icon_path: mdiTag,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const items = categories.map<PickerComboBoxItem>((category) => ({
|
||||
id: category.category_id,
|
||||
primary: category.name,
|
||||
@@ -216,10 +203,6 @@ export class HaCategoryPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (value === NO_CATEGORIES_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
this._setValue(undefined);
|
||||
return;
|
||||
|
||||
@@ -498,12 +498,12 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.entities.picker.headers.voice_assistants"
|
||||
"ui.panel.config.voice_assistants.expose.headers.assistants"
|
||||
),
|
||||
type: "icon",
|
||||
type: "flex",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
minWidth: "160px",
|
||||
maxWidth: "160px",
|
||||
template: (entry) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entities,
|
||||
|
||||
@@ -487,12 +487,12 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.helpers.picker.headers.voice_assistants"
|
||||
"ui.panel.config.voice_assistants.expose.headers.assistants"
|
||||
),
|
||||
type: "icon",
|
||||
type: "flex",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
minWidth: "160px",
|
||||
maxWidth: "160px",
|
||||
template: (helper) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entityReg,
|
||||
|
||||
@@ -23,23 +23,12 @@ import {
|
||||
subscribeBluetoothScannersDetails,
|
||||
} from "../../../../../data/bluetooth";
|
||||
import type { DeviceRegistryEntry } from "../../../../../data/device/device_registry";
|
||||
import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
|
||||
import "../../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import { bluetoothTabs } from "./bluetooth-config-dashboard";
|
||||
import { showBluetoothDeviceInfoDialog } from "./show-dialog-bluetooth-device-info";
|
||||
|
||||
export const bluetoothAdvertisementMonitorTabs: PageNavigation[] = [
|
||||
{
|
||||
translationKey: "ui.panel.config.bluetooth.advertisement_monitor",
|
||||
path: "advertisement-monitor",
|
||||
},
|
||||
{
|
||||
translationKey: "ui.panel.config.bluetooth.visualization",
|
||||
path: "visualization",
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("bluetooth-advertisement-monitor")
|
||||
export class BluetoothAdvertisementMonitorPanel extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -232,7 +221,7 @@ export class BluetoothAdvertisementMonitorPanel extends LitElement {
|
||||
@collapsed-changed=${this._handleCollapseChanged}
|
||||
filter=${this.address || ""}
|
||||
clickable
|
||||
.tabs=${bluetoothAdvertisementMonitorTabs}
|
||||
.tabs=${bluetoothTabs}
|
||||
></hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { mdiCogOutline } from "@mdi/js";
|
||||
import {
|
||||
mdiBroadcast,
|
||||
mdiCogOutline,
|
||||
mdiLan,
|
||||
mdiLinkVariant,
|
||||
mdiNetwork,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -8,7 +14,6 @@ import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import "../../../../../components/ha-list";
|
||||
import "../../../../../components/ha-list-item";
|
||||
|
||||
import type {
|
||||
BluetoothAllocationsData,
|
||||
BluetoothScannerState,
|
||||
@@ -24,16 +29,44 @@ import type { ConfigEntry } from "../../../../../data/config_entries";
|
||||
import { getConfigEntries } from "../../../../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../../../../data/device/device_registry";
|
||||
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import "../../../../../layouts/hass-tabs-subpage";
|
||||
import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
|
||||
export const bluetoothTabs: PageNavigation[] = [
|
||||
{
|
||||
translationKey: "ui.panel.config.bluetooth.tabs.overview",
|
||||
path: `/config/bluetooth/dashboard`,
|
||||
iconPath: mdiNetwork,
|
||||
},
|
||||
{
|
||||
translationKey: "ui.panel.config.bluetooth.tabs.advertisements",
|
||||
path: `/config/bluetooth/advertisement-monitor`,
|
||||
iconPath: mdiBroadcast,
|
||||
},
|
||||
{
|
||||
translationKey: "ui.panel.config.bluetooth.tabs.visualization",
|
||||
path: `/config/bluetooth/visualization`,
|
||||
iconPath: mdiLan,
|
||||
},
|
||||
{
|
||||
translationKey: "ui.panel.config.bluetooth.tabs.connections",
|
||||
path: `/config/bluetooth/connection-monitor`,
|
||||
iconPath: mdiLinkVariant,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("bluetooth-config-dashboard")
|
||||
export class BluetoothConfigDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||
|
||||
@state() private _configEntries: ConfigEntry[] = [];
|
||||
|
||||
@state() private _connectionAllocationData: BluetoothAllocationsData[] = [];
|
||||
@@ -122,10 +155,12 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
<hass-tabs-subpage
|
||||
header=${this.hass.localize("ui.panel.config.bluetooth.title")}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.route=${this.route}
|
||||
.tabs=${bluetoothTabs}
|
||||
>
|
||||
<div class="content">
|
||||
<ha-card
|
||||
@@ -135,60 +170,8 @@ export class BluetoothConfigDashboard extends LitElement {
|
||||
>
|
||||
<ha-list>${this._renderAdaptersList()}</ha-list>
|
||||
</ha-card>
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.advertisement_monitor"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.advertisement_monitor_details"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
href="/config/bluetooth/advertisement-monitor"
|
||||
appearance="plain"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.advertisement_monitor"
|
||||
)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
href="/config/bluetooth/visualization"
|
||||
appearance="plain"
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.bluetooth.visualization")}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.connection_slot_allocations_monitor"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.connection_slot_allocations_monitor_description"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
href="/config/bluetooth/connection-monitor"
|
||||
appearance="plain"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.bluetooth.connection_monitor"
|
||||
)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import type { DeviceRegistryEntry } from "../../../../../data/device/device_regi
|
||||
import "../../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import { bluetoothTabs } from "./bluetooth-config-dashboard";
|
||||
|
||||
@customElement("bluetooth-connection-monitor")
|
||||
export class BluetoothConnectionMonitorPanel extends LitElement {
|
||||
@@ -214,6 +215,7 @@ export class BluetoothConnectionMonitorPanel extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.tabs=${bluetoothTabs}
|
||||
.columns=${this._columns(this.hass.localize)}
|
||||
.data=${this._dataWithNamedSourceAndIds(this._data)}
|
||||
.initialGroupColumn=${this._activeGrouping}
|
||||
|
||||
@@ -26,9 +26,9 @@ import {
|
||||
subscribeBluetoothScannersDetails,
|
||||
} from "../../../../../data/bluetooth";
|
||||
import type { DeviceRegistryEntry } from "../../../../../data/device/device_registry";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import "../../../../../layouts/hass-tabs-subpage";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import { bluetoothAdvertisementMonitorTabs } from "./bluetooth-advertisement-monitor";
|
||||
import { bluetoothTabs } from "./bluetooth-config-dashboard";
|
||||
|
||||
const UPDATE_THROTTLE_TIME = 10000;
|
||||
|
||||
@@ -123,8 +123,7 @@ export class BluetoothNetworkVisualization extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
header=${this.hass.localize("ui.panel.config.bluetooth.visualization")}
|
||||
.tabs=${bluetoothAdvertisementMonitorTabs}
|
||||
.tabs=${bluetoothTabs}
|
||||
>
|
||||
<ha-network-graph
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -415,12 +415,12 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.scene.picker.headers.voice_assistants"
|
||||
"ui.panel.config.voice_assistants.expose.headers.assistants"
|
||||
),
|
||||
type: "icon",
|
||||
type: "flex",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
minWidth: "160px",
|
||||
maxWidth: "160px",
|
||||
template: (scene) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entityReg,
|
||||
@@ -1189,13 +1189,19 @@ ${rejected
|
||||
private async _duplicate(scene) {
|
||||
if (scene.attributes.id) {
|
||||
const config = await getSceneConfig(this.hass, scene.attributes.id);
|
||||
showSceneEditor({
|
||||
...config,
|
||||
id: undefined,
|
||||
name: `${config?.name} (${this.hass.localize(
|
||||
"ui.panel.config.scene.picker.duplicate"
|
||||
)})`,
|
||||
});
|
||||
const entityRegEntry = this._entityReg.find(
|
||||
(reg) => reg.entity_id === scene.entity_id
|
||||
);
|
||||
showSceneEditor(
|
||||
{
|
||||
...config,
|
||||
id: undefined,
|
||||
name: `${config?.name} (${this.hass.localize(
|
||||
"ui.panel.config.scene.picker.duplicate"
|
||||
)})`,
|
||||
},
|
||||
entityRegEntry?.area_id || undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -403,12 +403,12 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
voice_assistants: {
|
||||
title: localize(
|
||||
"ui.panel.config.script.picker.headers.voice_assistants"
|
||||
"ui.panel.config.voice_assistants.expose.headers.assistants"
|
||||
),
|
||||
type: "icon",
|
||||
type: "flex",
|
||||
defaultHidden: true,
|
||||
minWidth: "100px",
|
||||
maxWidth: "100px",
|
||||
minWidth: "160px",
|
||||
maxWidth: "160px",
|
||||
template: (script) => {
|
||||
const exposedToVoiceAssistantIds = getEntityVoiceAssistantsIds(
|
||||
this._entityReg,
|
||||
|
||||
@@ -144,7 +144,9 @@ export class HuiTileCardEditor
|
||||
{
|
||||
name: "state_content",
|
||||
selector: {
|
||||
ui_state_content: {},
|
||||
ui_state_content: {
|
||||
allow_context: true,
|
||||
},
|
||||
},
|
||||
context: {
|
||||
filter_entity: "entity",
|
||||
|
||||
@@ -83,6 +83,15 @@ export class CommonControlsSectionStrategy extends ReactiveElement {
|
||||
({
|
||||
type: "tile",
|
||||
entity: entityId,
|
||||
name: [
|
||||
{
|
||||
type: "device",
|
||||
},
|
||||
{
|
||||
type: "entity",
|
||||
},
|
||||
],
|
||||
state_content: ["state", "area_name"],
|
||||
show_entity_picture: true,
|
||||
}) satisfies TileCardConfig
|
||||
)
|
||||
|
||||
@@ -15,27 +15,27 @@ export const semanticColorStyles = css`
|
||||
--ha-color-text-disabled: var(--ha-color-neutral-60);
|
||||
--ha-color-text-link: var(--ha-color-primary-40);
|
||||
/* border primary */
|
||||
--ha-color-border-primary-quiet: var(--ha-color-primary-80);
|
||||
--ha-color-border-primary-quiet: var(--ha-color-primary-90);
|
||||
--ha-color-border-primary-normal: var(--ha-color-primary-70);
|
||||
--ha-color-border-primary-loud: var(--ha-color-primary-40);
|
||||
|
||||
/* border neutral */
|
||||
--ha-color-border-neutral-quiet: var(--ha-color-neutral-80);
|
||||
--ha-color-border-neutral-quiet: var(--ha-color-neutral-90);
|
||||
--ha-color-border-neutral-normal: var(--ha-color-neutral-60);
|
||||
--ha-color-border-neutral-loud: var(--ha-color-neutral-40);
|
||||
|
||||
/* border danger */
|
||||
--ha-color-border-danger-quiet: var(--ha-color-red-80);
|
||||
--ha-color-border-danger-quiet: var(--ha-color-red-90);
|
||||
--ha-color-border-danger-normal: var(--ha-color-red-70);
|
||||
--ha-color-border-danger-loud: var(--ha-color-red-40);
|
||||
|
||||
/* border warning */
|
||||
--ha-color-border-warning-quiet: var(--ha-color-orange-80);
|
||||
--ha-color-border-warning-quiet: var(--ha-color-orange-90);
|
||||
--ha-color-border-warning-normal: var(--ha-color-orange-70);
|
||||
--ha-color-border-warning-loud: var(--ha-color-orange-40);
|
||||
|
||||
/* border success */
|
||||
--ha-color-border-success-quiet: var(--ha-color-green-80);
|
||||
--ha-color-border-success-quiet: var(--ha-color-green-90);
|
||||
--ha-color-border-success-normal: var(--ha-color-green-70);
|
||||
--ha-color-border-success-loud: var(--ha-color-green-40);
|
||||
|
||||
|
||||
@@ -103,6 +103,15 @@ class StateDisplay extends LitElement {
|
||||
return html`${this.name}`;
|
||||
}
|
||||
|
||||
if (
|
||||
content === "device_name" ||
|
||||
content === "area_name" ||
|
||||
content === "floor_name"
|
||||
) {
|
||||
const type = content.replace("_name", "") as "device" | "area" | "floor";
|
||||
return this.hass.formatEntityName(stateObj, { type }) || undefined;
|
||||
}
|
||||
|
||||
let relativeDateTime: string | Date | undefined;
|
||||
|
||||
// Check last-changed for backwards compatibility
|
||||
|
||||
@@ -1298,7 +1298,10 @@
|
||||
"last_changed": "Last changed",
|
||||
"last_updated": "Last updated",
|
||||
"remaining_time": "Remaining time",
|
||||
"install_status": "Install status"
|
||||
"install_status": "Install status",
|
||||
"device_name": "Device name",
|
||||
"area_name": "Area name",
|
||||
"floor_name": "Floor name"
|
||||
},
|
||||
"multi-textfield": {
|
||||
"add_item": "Add {item}"
|
||||
@@ -3341,8 +3344,7 @@
|
||||
"type": "Type",
|
||||
"editable": "Editable",
|
||||
"category": "Category",
|
||||
"area": "Area",
|
||||
"voice_assistants": "Voice assistants"
|
||||
"area": "Area"
|
||||
},
|
||||
"create_helper": "Create helper",
|
||||
"no_helpers": "Looks like you don't have any helpers yet!",
|
||||
@@ -6018,15 +6020,14 @@
|
||||
},
|
||||
"bluetooth": {
|
||||
"title": "Bluetooth",
|
||||
"tabs": {
|
||||
"overview": "Overview",
|
||||
"advertisements": "Advertisements",
|
||||
"visualization": "Visualization",
|
||||
"connections": "Connections"
|
||||
},
|
||||
"settings_title": "Bluetooth adapters",
|
||||
"option_flow": "Configure Bluetooth options",
|
||||
"advertisement_monitor": "Advertisement monitor",
|
||||
"advertisement_monitor_details": "The advertisement monitor listens for Bluetooth advertisements and displays the data in a structured format.",
|
||||
"connection_slot_allocations_monitor": "Connection slot allocations monitor",
|
||||
"connection_slot_allocations_monitor_description": "The connection slot allocations monitor displays the (GATT) connection slot allocations for each adapter. Each remote Bluetooth device that requires an active connection will use one connection slot while the Bluetooth device is connecting or connected.",
|
||||
"connection_monitor": "Connection monitor",
|
||||
"visualization": "Visualization",
|
||||
"used_connection_slot_allocations": "Used connection slot allocations",
|
||||
"no_connections": "No active connections",
|
||||
"active_connections": "connections",
|
||||
"no_advertisements_found": "No matching Bluetooth advertisements found",
|
||||
@@ -6034,8 +6035,6 @@
|
||||
"no_connection_slots": "No connection slots",
|
||||
"no_scanner_state_available": "No scanner state available",
|
||||
"scanner_state_unknown": "State unknown",
|
||||
"current_scanning_mode": "Current scanning mode",
|
||||
"requested_scanning_mode": "Requested scanning mode",
|
||||
"scanning_mode_none": "none",
|
||||
"scanning_mode_active": "active",
|
||||
"scanning_mode_passive": "passive",
|
||||
@@ -6060,7 +6059,6 @@
|
||||
"service_uuids": "Service UUIDs",
|
||||
"copy_to_clipboard": "[%key:ui::panel::config::automation::editor::copy_to_clipboard%]",
|
||||
"area": "Area",
|
||||
"core": "Home Assistant",
|
||||
"scanners": "Scanners",
|
||||
"known_devices": "Known devices",
|
||||
"unknown_devices": "Unknown devices"
|
||||
|
||||
Reference in New Issue
Block a user