mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 18:56:39 +00:00
20240404.0 (#20414)
This commit is contained in:
commit
e96aca90fe
@ -161,6 +161,7 @@ const createWebpackConfig = ({
|
|||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js", ".json"],
|
extensions: [".ts", ".js", ".json"],
|
||||||
alias: {
|
alias: {
|
||||||
|
"lit/static-html$": "lit/static-html.js",
|
||||||
"lit/decorators$": "lit/decorators.js",
|
"lit/decorators$": "lit/decorators.js",
|
||||||
"lit/directive$": "lit/directive.js",
|
"lit/directive$": "lit/directive.js",
|
||||||
"lit/directives/until$": "lit/directives/until.js",
|
"lit/directives/until$": "lit/directives/until.js",
|
||||||
|
@ -136,7 +136,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
<div class="action">
|
<div class="action">
|
||||||
<span>
|
<span>
|
||||||
${this._action
|
${this._action
|
||||||
? describeAction(this.hass, [], this._action)
|
? describeAction(this.hass, [], [], [], this._action)
|
||||||
: "<invalid YAML>"}
|
: "<invalid YAML>"}
|
||||||
</span>
|
</span>
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
@ -149,7 +149,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
${ACTIONS.map(
|
${ACTIONS.map(
|
||||||
(conf) => html`
|
(conf) => html`
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<span>${describeAction(this.hass, [], conf as any)}</span>
|
<span>${describeAction(this.hass, [], [], [], conf as any)}</span>
|
||||||
<pre>${dump(conf)}</pre>
|
<pre>${dump(conf)}</pre>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20240403.1"
|
version = "20240404.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { SelectedDetail } from "@material/mwc-list";
|
|
||||||
import { mdiFilterVariantRemove } from "@mdi/js";
|
import { mdiFilterVariantRemove } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@ -56,11 +55,7 @@ export class HaFilterIntegrations extends LitElement {
|
|||||||
@value-changed=${this._handleSearchChange}
|
@value-changed=${this._handleSearchChange}
|
||||||
>
|
>
|
||||||
</search-input-outlined>
|
</search-input-outlined>
|
||||||
<mwc-list
|
<mwc-list class="ha-scrollbar" @click=${this._handleItemClick}>
|
||||||
@selected=${this._integrationsSelected}
|
|
||||||
multi
|
|
||||||
class="ha-scrollbar"
|
|
||||||
>
|
|
||||||
${repeat(
|
${repeat(
|
||||||
this._integrations(this._manifests, this._filter, this.value),
|
this._integrations(this._manifests, this._filter, this.value),
|
||||||
(i) => i.domain,
|
(i) => i.domain,
|
||||||
@ -92,7 +87,7 @@ export class HaFilterIntegrations extends LitElement {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this.expanded) return;
|
if (!this.expanded) return;
|
||||||
this.renderRoot.querySelector("mwc-list")!.style.height =
|
this.renderRoot.querySelector("mwc-list")!.style.height =
|
||||||
`${this.clientHeight - 49}px`;
|
`${this.clientHeight - 49 - 32}px`; // 32px is the height of the search input
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,34 +126,21 @@ export class HaFilterIntegrations extends LitElement {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
private async _integrationsSelected(
|
private _handleItemClick(ev) {
|
||||||
ev: CustomEvent<SelectedDetail<Set<number>>>
|
const listItem = ev.target.closest("ha-check-list-item");
|
||||||
) {
|
const value = listItem?.value;
|
||||||
const integrations = this._integrations(
|
if (!value) {
|
||||||
this._manifests!,
|
|
||||||
this._filter,
|
|
||||||
this.value
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ev.detail.index.size) {
|
|
||||||
fireEvent(this, "data-table-filter-changed", {
|
|
||||||
value: [],
|
|
||||||
items: undefined,
|
|
||||||
});
|
|
||||||
this.value = [];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (this.value?.includes(value)) {
|
||||||
const value: string[] = [];
|
this.value = this.value?.filter((val) => val !== value);
|
||||||
|
} else {
|
||||||
for (const index of ev.detail.index) {
|
this.value = [...(this.value || []), value];
|
||||||
const domain = integrations[index].domain;
|
|
||||||
value.push(domain);
|
|
||||||
}
|
}
|
||||||
this.value = value;
|
listItem.selected = this.value?.includes(value);
|
||||||
|
|
||||||
fireEvent(this, "data-table-filter-changed", {
|
fireEvent(this, "data-table-filter-changed", {
|
||||||
value,
|
value: this.value,
|
||||||
items: undefined,
|
items: undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
40
src/components/ha-outlined-field.ts
Normal file
40
src/components/ha-outlined-field.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { MdOutlinedField } from "@material/web/field/outlined-field";
|
||||||
|
import "element-internals-polyfill";
|
||||||
|
import { css } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import { literal } from "lit/static-html";
|
||||||
|
|
||||||
|
@customElement("ha-outlined-field")
|
||||||
|
export class HaOutlinedField extends MdOutlinedField {
|
||||||
|
protected readonly fieldTag = literal`ha-outlined-field`;
|
||||||
|
|
||||||
|
static override styles = [
|
||||||
|
...super.styles,
|
||||||
|
css`
|
||||||
|
.container::before {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-color: var(--ha-outlined-field-container-color, transparent);
|
||||||
|
opacity: var(--ha-outlined-field-container-opacity, 1);
|
||||||
|
border-start-start-radius: var(--_container-shape-start-start);
|
||||||
|
border-start-end-radius: var(--_container-shape-start-end);
|
||||||
|
border-end-start-radius: var(--_container-shape-end-start);
|
||||||
|
border-end-end-radius: var(--_container-shape-end-end);
|
||||||
|
}
|
||||||
|
.with-start .start {
|
||||||
|
margin-inline-end: var(--ha-outlined-field-start-margin, 4px);
|
||||||
|
}
|
||||||
|
.with-end .end {
|
||||||
|
margin-inline-start: var(--ha-outlined-field-end-margin, 4px);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-outlined-field": HaOutlinedField;
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,13 @@ import { MdOutlinedTextField } from "@material/web/textfield/outlined-text-field
|
|||||||
import "element-internals-polyfill";
|
import "element-internals-polyfill";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
import { literal } from "lit/static-html";
|
||||||
|
import "./ha-outlined-field";
|
||||||
|
|
||||||
@customElement("ha-outlined-text-field")
|
@customElement("ha-outlined-text-field")
|
||||||
export class HaOutlinedTextField extends MdOutlinedTextField {
|
export class HaOutlinedTextField extends MdOutlinedTextField {
|
||||||
|
protected readonly fieldTag = literal`ha-outlined-field`;
|
||||||
|
|
||||||
static override styles = [
|
static override styles = [
|
||||||
...super.styles,
|
...super.styles,
|
||||||
css`
|
css`
|
||||||
@ -25,12 +29,10 @@ export class HaOutlinedTextField extends MdOutlinedTextField {
|
|||||||
--md-outlined-field-container-shape-end-end: 10px;
|
--md-outlined-field-container-shape-end-end: 10px;
|
||||||
--md-outlined-field-container-shape-end-start: 10px;
|
--md-outlined-field-container-shape-end-start: 10px;
|
||||||
--md-outlined-field-focus-outline-width: 1px;
|
--md-outlined-field-focus-outline-width: 1px;
|
||||||
|
--ha-outlined-field-start-margin: -4px;
|
||||||
|
--ha-outlined-field-end-margin: -4px;
|
||||||
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
|
--mdc-icon-size: var(--md-input-chip-icon-size, 18px);
|
||||||
}
|
}
|
||||||
md-outlined-field {
|
|
||||||
background: var(--ha-outlined-text-field-container-color, transparent);
|
|
||||||
opacity: var(--ha-outlined-text-field-container-opacity, 1);
|
|
||||||
}
|
|
||||||
.input {
|
.input {
|
||||||
font-family: Roboto, sans-serif;
|
font-family: Roboto, sans-serif;
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ class SearchInputOutlined extends LitElement {
|
|||||||
ha-outlined-text-field {
|
ha-outlined-text-field {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
--ha-outlined-text-field-container-color: var(--card-background-color);
|
--ha-outlined-field-container-color: var(--card-background-color);
|
||||||
}
|
}
|
||||||
ha-svg-icon,
|
ha-svg-icon,
|
||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { consume } from "@lit-labs/context";
|
||||||
import {
|
import {
|
||||||
mdiAlertCircle,
|
mdiAlertCircle,
|
||||||
mdiCircle,
|
mdiCircle,
|
||||||
@ -6,14 +7,13 @@ import {
|
|||||||
mdiProgressWrench,
|
mdiProgressWrench,
|
||||||
mdiRecordCircleOutline,
|
mdiRecordCircleOutline,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
html,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
|
css,
|
||||||
|
html,
|
||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@ -23,27 +23,31 @@ import { relativeTime } from "../../common/datetime/relative_time";
|
|||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { toggleAttribute } from "../../common/dom/toggle_attribute";
|
import { toggleAttribute } from "../../common/dom/toggle_attribute";
|
||||||
import {
|
import {
|
||||||
EntityRegistryEntry,
|
floorsContext,
|
||||||
subscribeEntityRegistry,
|
fullEntitiesContext,
|
||||||
} from "../../data/entity_registry";
|
labelsContext,
|
||||||
|
} from "../../data/context";
|
||||||
|
import { EntityRegistryEntry } from "../../data/entity_registry";
|
||||||
|
import { FloorRegistryEntry } from "../../data/floor_registry";
|
||||||
|
import { LabelRegistryEntry } from "../../data/label_registry";
|
||||||
import { LogbookEntry } from "../../data/logbook";
|
import { LogbookEntry } from "../../data/logbook";
|
||||||
import {
|
import {
|
||||||
ChooseAction,
|
ChooseAction,
|
||||||
ChooseActionChoice,
|
ChooseActionChoice,
|
||||||
getActionType,
|
|
||||||
IfAction,
|
IfAction,
|
||||||
ParallelAction,
|
ParallelAction,
|
||||||
RepeatAction,
|
RepeatAction,
|
||||||
|
getActionType,
|
||||||
} from "../../data/script";
|
} from "../../data/script";
|
||||||
import { describeAction } from "../../data/script_i18n";
|
import { describeAction } from "../../data/script_i18n";
|
||||||
import {
|
import {
|
||||||
ActionTraceStep,
|
ActionTraceStep,
|
||||||
AutomationTraceExtended,
|
AutomationTraceExtended,
|
||||||
ChooseActionTraceStep,
|
ChooseActionTraceStep,
|
||||||
getDataFromPath,
|
|
||||||
IfActionTraceStep,
|
IfActionTraceStep,
|
||||||
isTriggerPath,
|
|
||||||
TriggerTraceStep,
|
TriggerTraceStep,
|
||||||
|
getDataFromPath,
|
||||||
|
isTriggerPath,
|
||||||
} from "../../data/trace";
|
} from "../../data/trace";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "./ha-timeline";
|
import "./ha-timeline";
|
||||||
@ -200,6 +204,8 @@ class ActionRenderer {
|
|||||||
constructor(
|
constructor(
|
||||||
private hass: HomeAssistant,
|
private hass: HomeAssistant,
|
||||||
private entityReg: EntityRegistryEntry[],
|
private entityReg: EntityRegistryEntry[],
|
||||||
|
private labelReg: LabelRegistryEntry[],
|
||||||
|
private floorReg: FloorRegistryEntry[],
|
||||||
private entries: TemplateResult[],
|
private entries: TemplateResult[],
|
||||||
private trace: AutomationTraceExtended,
|
private trace: AutomationTraceExtended,
|
||||||
private logbookRenderer: LogbookRenderer,
|
private logbookRenderer: LogbookRenderer,
|
||||||
@ -310,7 +316,14 @@ class ActionRenderer {
|
|||||||
|
|
||||||
this._renderEntry(
|
this._renderEntry(
|
||||||
path,
|
path,
|
||||||
describeAction(this.hass, this.entityReg, data, actionType),
|
describeAction(
|
||||||
|
this.hass,
|
||||||
|
this.entityReg,
|
||||||
|
this.labelReg,
|
||||||
|
this.floorReg,
|
||||||
|
data,
|
||||||
|
actionType
|
||||||
|
),
|
||||||
undefined,
|
undefined,
|
||||||
data.enabled === false
|
data.enabled === false
|
||||||
);
|
);
|
||||||
@ -475,7 +488,13 @@ class ActionRenderer {
|
|||||||
|
|
||||||
const name =
|
const name =
|
||||||
repeatConfig.alias ||
|
repeatConfig.alias ||
|
||||||
describeAction(this.hass, this.entityReg, repeatConfig);
|
describeAction(
|
||||||
|
this.hass,
|
||||||
|
this.entityReg,
|
||||||
|
this.labelReg,
|
||||||
|
this.floorReg,
|
||||||
|
repeatConfig
|
||||||
|
);
|
||||||
|
|
||||||
this._renderEntry(repeatPath, name, undefined, disabled);
|
this._renderEntry(repeatPath, name, undefined, disabled);
|
||||||
|
|
||||||
@ -631,15 +650,17 @@ export class HaAutomationTracer extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public allowPick = false;
|
@property({ type: Boolean }) public allowPick = false;
|
||||||
|
|
||||||
@state() private _entityReg: EntityRegistryEntry[] = [];
|
@state()
|
||||||
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
|
_entityReg!: EntityRegistryEntry[];
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
@state()
|
||||||
return [
|
@consume({ context: labelsContext, subscribe: true })
|
||||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
_labelReg!: LabelRegistryEntry[];
|
||||||
this._entityReg = entities;
|
|
||||||
}),
|
@state()
|
||||||
];
|
@consume({ context: floorsContext, subscribe: true })
|
||||||
}
|
_floorReg!: FloorRegistryEntry[];
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.trace) {
|
if (!this.trace) {
|
||||||
@ -657,6 +678,8 @@ export class HaAutomationTracer extends LitElement {
|
|||||||
const actionRenderer = new ActionRenderer(
|
const actionRenderer = new ActionRenderer(
|
||||||
this.hass,
|
this.hass,
|
||||||
this._entityReg,
|
this._entityReg,
|
||||||
|
this._labelReg,
|
||||||
|
this._floorReg,
|
||||||
entries,
|
entries,
|
||||||
this.trace,
|
this.trace,
|
||||||
logbookRenderer,
|
logbookRenderer,
|
||||||
|
@ -2,6 +2,8 @@ import { createContext } from "@lit-labs/context";
|
|||||||
import { HassConfig } from "home-assistant-js-websocket";
|
import { HassConfig } from "home-assistant-js-websocket";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { EntityRegistryEntry } from "./entity_registry";
|
import { EntityRegistryEntry } from "./entity_registry";
|
||||||
|
import { FloorRegistryEntry } from "./floor_registry";
|
||||||
|
import { LabelRegistryEntry } from "./label_registry";
|
||||||
|
|
||||||
export const connectionContext =
|
export const connectionContext =
|
||||||
createContext<HomeAssistant["connection"]>("connection");
|
createContext<HomeAssistant["connection"]>("connection");
|
||||||
@ -25,3 +27,7 @@ export const panelsContext = createContext<HomeAssistant["panels"]>("panels");
|
|||||||
|
|
||||||
export const fullEntitiesContext =
|
export const fullEntitiesContext =
|
||||||
createContext<EntityRegistryEntry[]>("extendedEntities");
|
createContext<EntityRegistryEntry[]>("extendedEntities");
|
||||||
|
|
||||||
|
export const floorsContext = createContext<FloorRegistryEntry[]>("floors");
|
||||||
|
|
||||||
|
export const labelsContext = createContext<LabelRegistryEntry[]>("labels");
|
||||||
|
@ -14,7 +14,9 @@ import {
|
|||||||
computeEntityRegistryName,
|
computeEntityRegistryName,
|
||||||
entityRegistryById,
|
entityRegistryById,
|
||||||
} from "./entity_registry";
|
} from "./entity_registry";
|
||||||
|
import { FloorRegistryEntry } from "./floor_registry";
|
||||||
import { domainToName } from "./integration";
|
import { domainToName } from "./integration";
|
||||||
|
import { LabelRegistryEntry } from "./label_registry";
|
||||||
import {
|
import {
|
||||||
ActionType,
|
ActionType,
|
||||||
ActionTypes,
|
ActionTypes,
|
||||||
@ -40,6 +42,8 @@ const actionTranslationBaseKey =
|
|||||||
export const describeAction = <T extends ActionType>(
|
export const describeAction = <T extends ActionType>(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityRegistry: EntityRegistryEntry[],
|
entityRegistry: EntityRegistryEntry[],
|
||||||
|
labelRegistry: LabelRegistryEntry[],
|
||||||
|
floorRegistry: FloorRegistryEntry[],
|
||||||
action: ActionTypes[T],
|
action: ActionTypes[T],
|
||||||
actionType?: T,
|
actionType?: T,
|
||||||
ignoreAlias = false
|
ignoreAlias = false
|
||||||
@ -48,6 +52,8 @@ export const describeAction = <T extends ActionType>(
|
|||||||
return tryDescribeAction(
|
return tryDescribeAction(
|
||||||
hass,
|
hass,
|
||||||
entityRegistry,
|
entityRegistry,
|
||||||
|
labelRegistry,
|
||||||
|
floorRegistry,
|
||||||
action,
|
action,
|
||||||
actionType,
|
actionType,
|
||||||
ignoreAlias
|
ignoreAlias
|
||||||
@ -66,6 +72,8 @@ export const describeAction = <T extends ActionType>(
|
|||||||
const tryDescribeAction = <T extends ActionType>(
|
const tryDescribeAction = <T extends ActionType>(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityRegistry: EntityRegistryEntry[],
|
entityRegistry: EntityRegistryEntry[],
|
||||||
|
labelRegistry: LabelRegistryEntry[],
|
||||||
|
floorRegistry: FloorRegistryEntry[],
|
||||||
action: ActionTypes[T],
|
action: ActionTypes[T],
|
||||||
actionType?: T,
|
actionType?: T,
|
||||||
ignoreAlias = false
|
ignoreAlias = false
|
||||||
@ -82,10 +90,12 @@ const tryDescribeAction = <T extends ActionType>(
|
|||||||
|
|
||||||
const targets: string[] = [];
|
const targets: string[] = [];
|
||||||
if (config.target) {
|
if (config.target) {
|
||||||
for (const [key, label] of Object.entries({
|
for (const [key, name] of Object.entries({
|
||||||
area_id: "areas",
|
area_id: "areas",
|
||||||
device_id: "devices",
|
device_id: "devices",
|
||||||
entity_id: "entities",
|
entity_id: "entities",
|
||||||
|
floor_id: "floors",
|
||||||
|
label_id: "labels",
|
||||||
})) {
|
})) {
|
||||||
if (!(key in config.target)) {
|
if (!(key in config.target)) {
|
||||||
continue;
|
continue;
|
||||||
@ -99,7 +109,7 @@ const tryDescribeAction = <T extends ActionType>(
|
|||||||
targets.push(
|
targets.push(
|
||||||
hass.localize(
|
hass.localize(
|
||||||
`${actionTranslationBaseKey}.service.description.target_template`,
|
`${actionTranslationBaseKey}.service.description.target_template`,
|
||||||
{ name: label }
|
{ name }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@ -147,6 +157,32 @@ const tryDescribeAction = <T extends ActionType>(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if (key === "floor_id") {
|
||||||
|
const floor = floorRegistry.find(
|
||||||
|
(flr) => flr.floor_id === targetThing
|
||||||
|
);
|
||||||
|
if (floor?.name) {
|
||||||
|
targets.push(floor.name);
|
||||||
|
} else {
|
||||||
|
targets.push(
|
||||||
|
hass.localize(
|
||||||
|
`${actionTranslationBaseKey}.service.description.target_unknown_floor`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (key === "label_id") {
|
||||||
|
const label = labelRegistry.find(
|
||||||
|
(lbl) => lbl.label_id === targetThing
|
||||||
|
);
|
||||||
|
if (label?.name) {
|
||||||
|
targets.push(label.name);
|
||||||
|
} else {
|
||||||
|
targets.push(
|
||||||
|
hass.localize(
|
||||||
|
`${actionTranslationBaseKey}.service.description.target_unknown_label`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
targets.push(targetThing);
|
targets.push(targetThing);
|
||||||
}
|
}
|
||||||
|
@ -496,8 +496,9 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
${this.showFilters && !showPane
|
${this.showFilters && !showPane
|
||||||
? html`<ha-dialog
|
? html`<ha-dialog
|
||||||
open
|
open
|
||||||
hideActions
|
.heading=${localize("ui.components.subpage-data-table.filters", {
|
||||||
.heading=${localize("ui.components.subpage-data-table.filters")}
|
number: this.data.length,
|
||||||
|
})}
|
||||||
>
|
>
|
||||||
<ha-dialog-header slot="heading">
|
<ha-dialog-header slot="heading">
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
@ -509,7 +510,9 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
)}
|
)}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<span slot="title"
|
<span slot="title"
|
||||||
>${localize("ui.components.subpage-data-table.filters")}</span
|
>${localize("ui.components.subpage-data-table.filters", {
|
||||||
|
number: this.data.length,
|
||||||
|
})}</span
|
||||||
>
|
>
|
||||||
${this.filters
|
${this.filters
|
||||||
? html`<ha-icon-button
|
? html`<ha-icon-button
|
||||||
@ -523,8 +526,17 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
: nothing}
|
: nothing}
|
||||||
</ha-dialog-header>
|
</ha-dialog-header>
|
||||||
<div class="filter-dialog-content">
|
<div class="filter-dialog-content">
|
||||||
<slot name="filter-pane"></slot></div
|
<slot name="filter-pane"></slot>
|
||||||
></ha-dialog>`
|
</div>
|
||||||
|
<div slot="primaryAction">
|
||||||
|
<ha-button @click=${this._toggleFilters}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.subpage-data-table.show_results",
|
||||||
|
{ number: this.data.length }
|
||||||
|
)}
|
||||||
|
</ha-button>
|
||||||
|
</div>
|
||||||
|
</ha-dialog>`
|
||||||
: nothing}
|
: nothing}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -779,7 +791,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ha-dialog {
|
ha-dialog {
|
||||||
--dialog-z-index: 100;
|
|
||||||
--mdc-dialog-min-width: calc(
|
--mdc-dialog-min-width: calc(
|
||||||
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
|
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
|
||||||
);
|
);
|
||||||
@ -794,7 +805,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.filter-dialog-content {
|
.filter-dialog-content {
|
||||||
height: calc(100vh - 1px - var(--header-height));
|
height: calc(100vh - 1px - 61px - var(--header-height));
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,14 @@ import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
|||||||
import { ACTION_ICONS, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
|
import { ACTION_ICONS, YAML_ONLY_ACTION_TYPES } from "../../../../data/action";
|
||||||
import { AutomationClipboard } from "../../../../data/automation";
|
import { AutomationClipboard } from "../../../../data/automation";
|
||||||
import { validateConfig } from "../../../../data/config";
|
import { validateConfig } from "../../../../data/config";
|
||||||
import { fullEntitiesContext } from "../../../../data/context";
|
import {
|
||||||
|
floorsContext,
|
||||||
|
fullEntitiesContext,
|
||||||
|
labelsContext,
|
||||||
|
} from "../../../../data/context";
|
||||||
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../../data/entity_registry";
|
||||||
|
import { FloorRegistryEntry } from "../../../../data/floor_registry";
|
||||||
|
import { LabelRegistryEntry } from "../../../../data/label_registry";
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
NonConditionAction,
|
NonConditionAction,
|
||||||
@ -146,6 +152,14 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||||
_entityReg!: EntityRegistryEntry[];
|
_entityReg!: EntityRegistryEntry[];
|
||||||
|
|
||||||
|
@state()
|
||||||
|
@consume({ context: labelsContext, subscribe: true })
|
||||||
|
_labelReg!: LabelRegistryEntry[];
|
||||||
|
|
||||||
|
@state()
|
||||||
|
@consume({ context: floorsContext, subscribe: true })
|
||||||
|
_floorReg!: FloorRegistryEntry[];
|
||||||
|
|
||||||
@state() private _warnings?: string[];
|
@state() private _warnings?: string[];
|
||||||
|
|
||||||
@state() private _uiModeAvailable = true;
|
@state() private _uiModeAvailable = true;
|
||||||
@ -210,7 +224,13 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
.path=${ACTION_ICONS[type!]}
|
.path=${ACTION_ICONS[type!]}
|
||||||
></ha-svg-icon>`}
|
></ha-svg-icon>`}
|
||||||
${capitalizeFirstLetter(
|
${capitalizeFirstLetter(
|
||||||
describeAction(this.hass, this._entityReg, this.action)
|
describeAction(
|
||||||
|
this.hass,
|
||||||
|
this._entityReg,
|
||||||
|
this._labelReg,
|
||||||
|
this._floorReg,
|
||||||
|
this.action
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@ -573,7 +593,15 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
),
|
),
|
||||||
inputType: "string",
|
inputType: "string",
|
||||||
placeholder: capitalizeFirstLetter(
|
placeholder: capitalizeFirstLetter(
|
||||||
describeAction(this.hass, this._entityReg, this.action, undefined, true)
|
describeAction(
|
||||||
|
this.hass,
|
||||||
|
this._entityReg,
|
||||||
|
this._labelReg,
|
||||||
|
this._floorReg,
|
||||||
|
this.action,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
)
|
||||||
),
|
),
|
||||||
defaultValue: this.action.alias,
|
defaultValue: this.action.alias,
|
||||||
confirmText: this.hass.localize("ui.common.submit"),
|
confirmText: this.hass.localize("ui.common.submit"),
|
||||||
|
@ -422,6 +422,14 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
const labelsInOverflow =
|
const labelsInOverflow =
|
||||||
(this._sizeController.value && this._sizeController.value < 700) ||
|
(this._sizeController.value && this._sizeController.value < 700) ||
|
||||||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
||||||
|
const automations = this._automations(
|
||||||
|
this.automations,
|
||||||
|
this._entityReg,
|
||||||
|
this.hass.areas,
|
||||||
|
this._categories,
|
||||||
|
this._labels,
|
||||||
|
this._filteredAutomations
|
||||||
|
);
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage-data-table
|
<hass-tabs-subpage-data-table
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -432,13 +440,23 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
id="entity_id"
|
id="entity_id"
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.automations}
|
.tabs=${configSections.automations}
|
||||||
|
.searchLabel=${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.picker.search",
|
||||||
|
{ number: automations.length }
|
||||||
|
)}
|
||||||
selectable
|
selectable
|
||||||
.selected=${this._selected.length}
|
.selected=${this._selected.length}
|
||||||
@selection-changed=${this._handleSelectionChanged}
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
hasFilters
|
hasFilters
|
||||||
.filters=${
|
.filters=${
|
||||||
Object.values(this._filters).filter((filter) => filter.value?.length)
|
Object.values(this._filters).filter((filter) =>
|
||||||
.length
|
Array.isArray(filter.value)
|
||||||
|
? filter.value.length
|
||||||
|
: filter.value &&
|
||||||
|
Object.values(filter.value).some((val) =>
|
||||||
|
Array.isArray(val) ? val.length : val
|
||||||
|
)
|
||||||
|
).length
|
||||||
}
|
}
|
||||||
.columns=${this._columns(
|
.columns=${this._columns(
|
||||||
this.narrow,
|
this.narrow,
|
||||||
@ -446,14 +464,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
this.hass.locale
|
this.hass.locale
|
||||||
)}
|
)}
|
||||||
initialGroupColumn="category"
|
initialGroupColumn="category"
|
||||||
.data=${this._automations(
|
.data=${automations}
|
||||||
this.automations,
|
|
||||||
this._entityReg,
|
|
||||||
this.hass.areas,
|
|
||||||
this._categories,
|
|
||||||
this._labels,
|
|
||||||
this._filteredAutomations
|
|
||||||
)}
|
|
||||||
.empty=${!this.automations.length}
|
.empty=${!this.automations.length}
|
||||||
@row-click=${this._handleRowClicked}
|
@row-click=${this._handleRowClicked}
|
||||||
.noDataText=${this.hass.localize(
|
.noDataText=${this.hass.localize(
|
||||||
|
@ -591,7 +591,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
.tabs=${configSections.devices}
|
.tabs=${configSections.devices}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.searchLabel=${this.hass.localize(
|
.searchLabel=${this.hass.localize(
|
||||||
"ui.panel.config.devices.picker.search"
|
"ui.panel.config.devices.picker.search",
|
||||||
|
{ number: devicesOutput.length }
|
||||||
)}
|
)}
|
||||||
.columns=${this._columns(this.hass.localize, this.narrow)}
|
.columns=${this._columns(this.hass.localize, this.narrow)}
|
||||||
.data=${devicesOutput}
|
.data=${devicesOutput}
|
||||||
@ -600,8 +601,13 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
@selection-changed=${this._handleSelectionChanged}
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
hasFilters
|
hasFilters
|
||||||
.filters=${Object.values(this._filters).filter(
|
.filters=${Object.values(this._filters).filter((filter) =>
|
||||||
(filter) => filter.value?.length
|
Array.isArray(filter.value)
|
||||||
|
? filter.value.length
|
||||||
|
: filter.value &&
|
||||||
|
Object.values(filter.value).some((val) =>
|
||||||
|
Array.isArray(val) ? val.length : val
|
||||||
|
)
|
||||||
).length}
|
).length}
|
||||||
@clear-filter=${this._clearFilter}
|
@clear-filter=${this._clearFilter}
|
||||||
@search-changed=${this._handleSearchChange}
|
@search-changed=${this._handleSearchChange}
|
||||||
|
@ -27,7 +27,6 @@ import {
|
|||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { until } from "lit/directives/until";
|
|
||||||
import memoize from "memoize-one";
|
import memoize from "memoize-one";
|
||||||
import { computeCssColor } from "../../../common/color/compute-color";
|
import { computeCssColor } from "../../../common/color/compute-color";
|
||||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
@ -67,7 +66,6 @@ import {
|
|||||||
removeEntityRegistryEntry,
|
removeEntityRegistryEntry,
|
||||||
updateEntityRegistryEntry,
|
updateEntityRegistryEntry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import { entryIcon } from "../../../data/icons";
|
|
||||||
import {
|
import {
|
||||||
LabelRegistryEntry,
|
LabelRegistryEntry,
|
||||||
createLabelRegistryEntry,
|
createLabelRegistryEntry,
|
||||||
@ -207,6 +205,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
type: "icon",
|
type: "icon",
|
||||||
template: (entry) =>
|
template: (entry) =>
|
||||||
entry.icon
|
entry.icon
|
||||||
|
? html`<ha-icon .icon=${entry.icon}></ha-icon>`
|
||||||
|
: entry.entity
|
||||||
? html`
|
? html`
|
||||||
<ha-state-icon
|
<ha-state-icon
|
||||||
title=${ifDefined(entry.entity?.state)}
|
title=${ifDefined(entry.entity?.state)}
|
||||||
@ -215,13 +215,9 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
.stateObj=${entry.entity}
|
.stateObj=${entry.entity}
|
||||||
></ha-state-icon>
|
></ha-state-icon>
|
||||||
`
|
`
|
||||||
: html`
|
: html`<ha-domain-icon
|
||||||
<ha-icon
|
.domain=${computeDomain(entry.entity_id)}
|
||||||
icon=${until(
|
></ha-domain-icon>`,
|
||||||
entryIcon(this.hass, entry as EntityRegistryEntry)
|
|
||||||
)}
|
|
||||||
></ha-icon>
|
|
||||||
`,
|
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
main: true,
|
main: true,
|
||||||
@ -577,12 +573,19 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
)}
|
)}
|
||||||
.data=${filteredEntities}
|
.data=${filteredEntities}
|
||||||
.searchLabel=${this.hass.localize(
|
.searchLabel=${this.hass.localize(
|
||||||
"ui.panel.config.entities.picker.search"
|
"ui.panel.config.entities.picker.search",
|
||||||
|
{ number: filteredEntities.length }
|
||||||
)}
|
)}
|
||||||
hasFilters
|
hasFilters
|
||||||
.filters=${
|
.filters=${
|
||||||
Object.values(this._filters).filter((filter) => filter.value?.length)
|
Object.values(this._filters).filter((filter) =>
|
||||||
.length
|
Array.isArray(filter.value)
|
||||||
|
? filter.value.length
|
||||||
|
: filter.value &&
|
||||||
|
Object.values(filter.value).some((val) =>
|
||||||
|
Array.isArray(val) ? val.length : val
|
||||||
|
)
|
||||||
|
).length
|
||||||
}
|
}
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
selectable
|
selectable
|
||||||
|
@ -35,7 +35,11 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
import { listenMediaQuery } from "../../common/dom/media_query";
|
import { listenMediaQuery } from "../../common/dom/media_query";
|
||||||
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
|
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
|
||||||
import { fullEntitiesContext } from "../../data/context";
|
import {
|
||||||
|
floorsContext,
|
||||||
|
fullEntitiesContext,
|
||||||
|
labelsContext,
|
||||||
|
} from "../../data/context";
|
||||||
import {
|
import {
|
||||||
entityRegistryByEntityId,
|
entityRegistryByEntityId,
|
||||||
entityRegistryById,
|
entityRegistryById,
|
||||||
@ -45,6 +49,8 @@ import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
|
|||||||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant, Route } from "../../types";
|
import { HomeAssistant, Route } from "../../types";
|
||||||
|
import { subscribeLabelRegistry } from "../../data/label_registry";
|
||||||
|
import { subscribeFloorRegistry } from "../../data/floor_registry";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -379,11 +385,27 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
|
|||||||
initialValue: [],
|
initialValue: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private _labelsContext = new ContextProvider(this, {
|
||||||
|
context: labelsContext,
|
||||||
|
initialValue: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
private _floorsContext = new ContextProvider(this, {
|
||||||
|
context: floorsContext,
|
||||||
|
initialValue: [],
|
||||||
|
});
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
return [
|
return [
|
||||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||||
this._entitiesContext.setValue(entities);
|
this._entitiesContext.setValue(entities);
|
||||||
}),
|
}),
|
||||||
|
subscribeLabelRegistry(this.hass.connection!, (labels) => {
|
||||||
|
this._labelsContext.setValue(labels);
|
||||||
|
}),
|
||||||
|
subscribeFloorRegistry(this.hass.connection!, (floors) => {
|
||||||
|
this._floorsContext.setValue(floors);
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,22 +486,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
const labelsInOverflow =
|
const labelsInOverflow =
|
||||||
(this._sizeController.value && this._sizeController.value < 700) ||
|
(this._sizeController.value && this._sizeController.value < 700) ||
|
||||||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
||||||
return html`
|
const helpers = this._getItems(
|
||||||
<hass-tabs-subpage-data-table
|
|
||||||
.hass=${this.hass}
|
|
||||||
.narrow=${this.narrow}
|
|
||||||
back-path="/config"
|
|
||||||
.route=${this.route}
|
|
||||||
.tabs=${configSections.devices}
|
|
||||||
selectable
|
|
||||||
.selected=${this._selected.length}
|
|
||||||
@selection-changed=${this._handleSelectionChanged}
|
|
||||||
hasFilters
|
|
||||||
.filters=${Object.values(this._filters).filter(
|
|
||||||
(filter) => filter.value?.length
|
|
||||||
).length}
|
|
||||||
.columns=${this._columns(this.narrow, this.hass.localize)}
|
|
||||||
.data=${this._getItems(
|
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
this._stateItems,
|
this._stateItems,
|
||||||
this._entityEntries,
|
this._entityEntries,
|
||||||
@ -510,7 +495,32 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
this._categories,
|
this._categories,
|
||||||
this._labels,
|
this._labels,
|
||||||
this._filteredStateItems
|
this._filteredStateItems
|
||||||
|
);
|
||||||
|
return html`
|
||||||
|
<hass-tabs-subpage-data-table
|
||||||
|
.hass=${this.hass}
|
||||||
|
.narrow=${this.narrow}
|
||||||
|
back-path="/config"
|
||||||
|
.route=${this.route}
|
||||||
|
.tabs=${configSections.devices}
|
||||||
|
.searchLabel=${this.hass.localize(
|
||||||
|
"ui.panel.config.helpers.picker.search",
|
||||||
|
{ number: helpers.length }
|
||||||
)}
|
)}
|
||||||
|
selectable
|
||||||
|
.selected=${this._selected.length}
|
||||||
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
|
hasFilters
|
||||||
|
.filters=${Object.values(this._filters).filter((filter) =>
|
||||||
|
Array.isArray(filter.value)
|
||||||
|
? filter.value.length
|
||||||
|
: filter.value &&
|
||||||
|
Object.values(filter.value).some((val) =>
|
||||||
|
Array.isArray(val) ? val.length : val
|
||||||
|
)
|
||||||
|
).length}
|
||||||
|
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||||
|
.data=${helpers}
|
||||||
initialGroupColumn="category"
|
initialGroupColumn="category"
|
||||||
.activeFilters=${this._activeFilters}
|
.activeFilters=${this._activeFilters}
|
||||||
@clear-filter=${this._clearFilter}
|
@clear-filter=${this._clearFilter}
|
||||||
|
@ -425,6 +425,14 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
const labelsInOverflow =
|
const labelsInOverflow =
|
||||||
(this._sizeController.value && this._sizeController.value < 700) ||
|
(this._sizeController.value && this._sizeController.value < 700) ||
|
||||||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
||||||
|
const scenes = this._scenes(
|
||||||
|
this.scenes,
|
||||||
|
this._entityReg,
|
||||||
|
this.hass.areas,
|
||||||
|
this._categories,
|
||||||
|
this._labels,
|
||||||
|
this._filteredScenes
|
||||||
|
);
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage-data-table
|
<hass-tabs-subpage-data-table
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -432,24 +440,26 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
back-path="/config"
|
back-path="/config"
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.automations}
|
.tabs=${configSections.automations}
|
||||||
|
.searchLabel=${this.hass.localize(
|
||||||
|
"ui.panel.config.scene.picker.search",
|
||||||
|
{ number: scenes.length }
|
||||||
|
)}
|
||||||
selectable
|
selectable
|
||||||
.selected=${this._selected.length}
|
.selected=${this._selected.length}
|
||||||
@selection-changed=${this._handleSelectionChanged}
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
hasFilters
|
hasFilters
|
||||||
.filters=${Object.values(this._filters).filter(
|
.filters=${Object.values(this._filters).filter((filter) =>
|
||||||
(filter) => filter.value?.length
|
Array.isArray(filter.value)
|
||||||
|
? filter.value.length
|
||||||
|
: filter.value &&
|
||||||
|
Object.values(filter.value).some((val) =>
|
||||||
|
Array.isArray(val) ? val.length : val
|
||||||
|
)
|
||||||
).length}
|
).length}
|
||||||
.columns=${this._columns(this.narrow, this.hass.localize)}
|
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||||
id="entity_id"
|
id="entity_id"
|
||||||
initialGroupColumn="category"
|
initialGroupColumn="category"
|
||||||
.data=${this._scenes(
|
.data=${scenes}
|
||||||
this.scenes,
|
|
||||||
this._entityReg,
|
|
||||||
this.hass.areas,
|
|
||||||
this._categories,
|
|
||||||
this._labels,
|
|
||||||
this._filteredScenes
|
|
||||||
)}
|
|
||||||
.empty=${!this.scenes.length}
|
.empty=${!this.scenes.length}
|
||||||
.activeFilters=${this._activeFilters}
|
.activeFilters=${this._activeFilters}
|
||||||
.noDataText=${this.hass.localize(
|
.noDataText=${this.hass.localize(
|
||||||
|
@ -437,6 +437,14 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
const labelsInOverflow =
|
const labelsInOverflow =
|
||||||
(this._sizeController.value && this._sizeController.value < 700) ||
|
(this._sizeController.value && this._sizeController.value < 700) ||
|
||||||
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
(!this._sizeController.value && this.hass.dockedSidebar === "docked");
|
||||||
|
const scripts = this._scripts(
|
||||||
|
this.scripts,
|
||||||
|
this._entityReg,
|
||||||
|
this.hass.areas,
|
||||||
|
this._categories,
|
||||||
|
this._labels,
|
||||||
|
this._filteredScripts
|
||||||
|
);
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage-data-table
|
<hass-tabs-subpage-data-table
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@ -444,27 +452,29 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
back-path="/config"
|
back-path="/config"
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.automations}
|
.tabs=${configSections.automations}
|
||||||
|
.searchLabel=${this.hass.localize(
|
||||||
|
"ui.panel.config.script.picker.search",
|
||||||
|
{ number: scripts.length }
|
||||||
|
)}
|
||||||
hasFilters
|
hasFilters
|
||||||
initialGroupColumn="category"
|
initialGroupColumn="category"
|
||||||
selectable
|
selectable
|
||||||
.selected=${this._selected.length}
|
.selected=${this._selected.length}
|
||||||
@selection-changed=${this._handleSelectionChanged}
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
.filters=${Object.values(this._filters).filter(
|
.filters=${Object.values(this._filters).filter((filter) =>
|
||||||
(filter) => filter.value?.length
|
Array.isArray(filter.value)
|
||||||
|
? filter.value.length
|
||||||
|
: filter.value &&
|
||||||
|
Object.values(filter.value).some((val) =>
|
||||||
|
Array.isArray(val) ? val.length : val
|
||||||
|
)
|
||||||
).length}
|
).length}
|
||||||
.columns=${this._columns(
|
.columns=${this._columns(
|
||||||
this.narrow,
|
this.narrow,
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
this.hass.locale
|
this.hass.locale
|
||||||
)}
|
)}
|
||||||
.data=${this._scripts(
|
.data=${scripts}
|
||||||
this.scripts,
|
|
||||||
this._entityReg,
|
|
||||||
this.hass.areas,
|
|
||||||
this._categories,
|
|
||||||
this._labels,
|
|
||||||
this._filteredScripts
|
|
||||||
)}
|
|
||||||
.empty=${!this.scripts.length}
|
.empty=${!this.scripts.length}
|
||||||
.activeFilters=${this._activeFilters}
|
.activeFilters=${this._activeFilters}
|
||||||
id="entity_id"
|
id="entity_id"
|
||||||
|
@ -9,6 +9,7 @@ import { property, query, state } from "lit/decorators";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { ensureArray } from "../../common/array/ensure-array";
|
import { ensureArray } from "../../common/array/ensure-array";
|
||||||
import { storage } from "../../common/decorators/storage";
|
import { storage } from "../../common/decorators/storage";
|
||||||
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { navigate } from "../../common/navigate";
|
import { navigate } from "../../common/navigate";
|
||||||
import { constructUrlCurrentPath } from "../../common/url/construct-url";
|
import { constructUrlCurrentPath } from "../../common/url/construct-url";
|
||||||
import {
|
import {
|
||||||
@ -27,37 +28,29 @@ import "../../components/ha-menu-button";
|
|||||||
import "../../components/ha-target-picker";
|
import "../../components/ha-target-picker";
|
||||||
import "../../components/ha-top-app-bar-fixed";
|
import "../../components/ha-top-app-bar-fixed";
|
||||||
import {
|
import {
|
||||||
AreaDeviceLookup,
|
|
||||||
AreaEntityLookup,
|
|
||||||
getAreaDeviceLookup,
|
|
||||||
getAreaEntityLookup,
|
|
||||||
} from "../../data/area_registry";
|
|
||||||
import {
|
|
||||||
DeviceEntityLookup,
|
|
||||||
getDeviceEntityLookup,
|
|
||||||
subscribeDeviceRegistry,
|
|
||||||
} from "../../data/device_registry";
|
|
||||||
import { subscribeEntityRegistry } from "../../data/entity_registry";
|
|
||||||
import {
|
|
||||||
HistoryResult,
|
|
||||||
computeHistory,
|
|
||||||
subscribeHistory,
|
|
||||||
HistoryStates,
|
|
||||||
EntityHistoryState,
|
EntityHistoryState,
|
||||||
|
HistoryResult,
|
||||||
|
HistoryStates,
|
||||||
|
LineChartState,
|
||||||
LineChartUnit,
|
LineChartUnit,
|
||||||
computeGroupKey,
|
computeGroupKey,
|
||||||
LineChartState,
|
computeHistory,
|
||||||
|
subscribeHistory,
|
||||||
} from "../../data/history";
|
} from "../../data/history";
|
||||||
import { fetchStatistics, Statistics } from "../../data/recorder";
|
import { Statistics, fetchStatistics } from "../../data/recorder";
|
||||||
|
import {
|
||||||
|
expandAreaTarget,
|
||||||
|
expandDeviceTarget,
|
||||||
|
expandFloorTarget,
|
||||||
|
expandLabelTarget,
|
||||||
|
} from "../../data/selector";
|
||||||
import { getSensorNumericDeviceClasses } from "../../data/sensor";
|
import { getSensorNumericDeviceClasses } from "../../data/sensor";
|
||||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { fileDownload } from "../../util/file_download";
|
import { fileDownload } from "../../util/file_download";
|
||||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
|
||||||
|
|
||||||
class HaPanelHistory extends SubscribeMixin(LitElement) {
|
class HaPanelHistory extends LitElement {
|
||||||
@property({ attribute: false }) hass!: HomeAssistant;
|
@property({ attribute: false }) hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ reflect: true, type: Boolean }) public narrow = false;
|
@property({ reflect: true, type: Boolean }) public narrow = false;
|
||||||
@ -83,12 +76,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _statisticsHistory?: HistoryResult;
|
@state() private _statisticsHistory?: HistoryResult;
|
||||||
|
|
||||||
@state() private _deviceEntityLookup?: DeviceEntityLookup;
|
|
||||||
|
|
||||||
@state() private _areaEntityLookup?: AreaEntityLookup;
|
|
||||||
|
|
||||||
@state() private _areaDeviceLookup?: AreaDeviceLookup;
|
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
private _showBack?: boolean;
|
private _showBack?: boolean;
|
||||||
|
|
||||||
@ -123,18 +110,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||||||
this._unsubscribeHistory();
|
this._unsubscribeHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public hassSubscribe(): UnsubscribeFunc[] {
|
|
||||||
return [
|
|
||||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
|
||||||
this._deviceEntityLookup = getDeviceEntityLookup(entities);
|
|
||||||
this._areaEntityLookup = getAreaEntityLookup(entities);
|
|
||||||
}),
|
|
||||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
|
||||||
this._areaDeviceLookup = getAreaDeviceLookup(devices);
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _goBack(): void {
|
private _goBack(): void {
|
||||||
history.back();
|
history.back();
|
||||||
}
|
}
|
||||||
@ -332,7 +307,9 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||||||
const entityIds = searchParams.entity_id;
|
const entityIds = searchParams.entity_id;
|
||||||
const deviceIds = searchParams.device_id;
|
const deviceIds = searchParams.device_id;
|
||||||
const areaIds = searchParams.area_id;
|
const areaIds = searchParams.area_id;
|
||||||
if (entityIds || deviceIds || areaIds) {
|
const floorIds = searchParams.floor_id;
|
||||||
|
const labelsIds = searchParams.label_id;
|
||||||
|
if (entityIds || deviceIds || areaIds || floorIds || labelsIds) {
|
||||||
this._targetPickerValue = {};
|
this._targetPickerValue = {};
|
||||||
}
|
}
|
||||||
if (entityIds) {
|
if (entityIds) {
|
||||||
@ -347,6 +324,14 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||||||
const splitIds = areaIds.split(",");
|
const splitIds = areaIds.split(",");
|
||||||
this._targetPickerValue!.area_id = splitIds;
|
this._targetPickerValue!.area_id = splitIds;
|
||||||
}
|
}
|
||||||
|
if (floorIds) {
|
||||||
|
const splitIds = floorIds.split(",");
|
||||||
|
this._targetPickerValue!.floor_id = splitIds;
|
||||||
|
}
|
||||||
|
if (labelsIds) {
|
||||||
|
const splitIds = labelsIds.split(",");
|
||||||
|
this._targetPickerValue!.label_id = splitIds;
|
||||||
|
}
|
||||||
|
|
||||||
const startDate = searchParams.start_date;
|
const startDate = searchParams.start_date;
|
||||||
if (startDate) {
|
if (startDate) {
|
||||||
@ -522,95 +507,77 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||||||
private _getEntityIds(): string[] {
|
private _getEntityIds(): string[] {
|
||||||
return this.__getEntityIds(
|
return this.__getEntityIds(
|
||||||
this._targetPickerValue,
|
this._targetPickerValue,
|
||||||
this._deviceEntityLookup,
|
this.hass.entities,
|
||||||
this._areaEntityLookup,
|
this.hass.devices,
|
||||||
this._areaDeviceLookup
|
this.hass.areas
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private __getEntityIds = memoizeOne(
|
private __getEntityIds = memoizeOne(
|
||||||
(
|
(
|
||||||
targetPickerValue: HassServiceTarget,
|
targetPickerValue: HassServiceTarget,
|
||||||
deviceEntityLookup: DeviceEntityLookup | undefined,
|
entities: HomeAssistant["entities"],
|
||||||
areaEntityLookup: AreaEntityLookup | undefined,
|
devices: HomeAssistant["devices"],
|
||||||
areaDeviceLookup: AreaDeviceLookup | undefined
|
areas: HomeAssistant["areas"]
|
||||||
): string[] => {
|
): string[] => {
|
||||||
if (
|
if (!targetPickerValue) {
|
||||||
!targetPickerValue ||
|
|
||||||
deviceEntityLookup === undefined ||
|
|
||||||
areaEntityLookup === undefined ||
|
|
||||||
areaDeviceLookup === undefined
|
|
||||||
) {
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const entityIds = new Set<string>();
|
const targetSelector = { target: {} };
|
||||||
let {
|
const targetEntities = new Set(ensureArray(targetPickerValue.entity_id));
|
||||||
area_id: searchingAreaId,
|
const targetDevices = new Set(ensureArray(targetPickerValue.device_id));
|
||||||
device_id: searchingDeviceId,
|
const targetAreas = new Set(ensureArray(targetPickerValue.area_id));
|
||||||
entity_id: searchingEntityId,
|
const targetFloors = new Set(ensureArray(targetPickerValue.floor_id));
|
||||||
} = targetPickerValue;
|
const targetLabels = new Set(ensureArray(targetPickerValue.label_id));
|
||||||
|
|
||||||
if (searchingAreaId) {
|
targetLabels.forEach((labelId) => {
|
||||||
searchingAreaId = ensureArray(searchingAreaId);
|
const expanded = expandLabelTarget(
|
||||||
for (const singleSearchingAreaId of searchingAreaId) {
|
this.hass,
|
||||||
const foundEntities = areaEntityLookup[singleSearchingAreaId];
|
labelId,
|
||||||
if (foundEntities?.length) {
|
areas,
|
||||||
for (const foundEntity of foundEntities) {
|
devices,
|
||||||
if (foundEntity.entity_category === null) {
|
entities,
|
||||||
entityIds.add(foundEntity.entity_id);
|
targetSelector
|
||||||
}
|
);
|
||||||
}
|
expanded.devices.forEach((id) => targetDevices.add(id));
|
||||||
}
|
expanded.entities.forEach((id) => targetEntities.add(id));
|
||||||
|
expanded.areas.forEach((id) => targetAreas.add(id));
|
||||||
|
});
|
||||||
|
|
||||||
const foundDevices = areaDeviceLookup[singleSearchingAreaId];
|
targetFloors.forEach((floorId) => {
|
||||||
if (!foundDevices?.length) {
|
const expanded = expandFloorTarget(
|
||||||
continue;
|
this.hass,
|
||||||
}
|
floorId,
|
||||||
|
areas,
|
||||||
|
targetSelector
|
||||||
|
);
|
||||||
|
expanded.areas.forEach((id) => targetAreas.add(id));
|
||||||
|
});
|
||||||
|
|
||||||
for (const foundDevice of foundDevices) {
|
targetAreas.forEach((areaId) => {
|
||||||
const foundDeviceEntities = deviceEntityLookup[foundDevice.id];
|
const expanded = expandAreaTarget(
|
||||||
if (!foundDeviceEntities?.length) {
|
this.hass,
|
||||||
continue;
|
areaId,
|
||||||
}
|
devices,
|
||||||
|
entities,
|
||||||
|
targetSelector
|
||||||
|
);
|
||||||
|
expanded.devices.forEach((id) => targetDevices.add(id));
|
||||||
|
expanded.entities.forEach((id) => targetEntities.add(id));
|
||||||
|
});
|
||||||
|
|
||||||
for (const foundDeviceEntity of foundDeviceEntities) {
|
targetDevices.forEach((deviceId) => {
|
||||||
if (
|
const expanded = expandDeviceTarget(
|
||||||
(!foundDeviceEntity.area_id ||
|
this.hass,
|
||||||
foundDeviceEntity.area_id === singleSearchingAreaId) &&
|
deviceId,
|
||||||
foundDeviceEntity.entity_category === null
|
entities,
|
||||||
) {
|
targetSelector
|
||||||
entityIds.add(foundDeviceEntity.entity_id);
|
);
|
||||||
}
|
expanded.entities.forEach((id) => targetEntities.add(id));
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchingDeviceId) {
|
return Array.from(targetEntities);
|
||||||
searchingDeviceId = ensureArray(searchingDeviceId);
|
|
||||||
for (const singleSearchingDeviceId of searchingDeviceId) {
|
|
||||||
const foundEntities = deviceEntityLookup[singleSearchingDeviceId];
|
|
||||||
if (!foundEntities?.length) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const foundEntity of foundEntities) {
|
|
||||||
if (foundEntity.entity_category === null) {
|
|
||||||
entityIds.add(foundEntity.entity_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchingEntityId) {
|
|
||||||
searchingEntityId = ensureArray(searchingEntityId);
|
|
||||||
for (const singleSearchingEntityId of searchingEntityId) {
|
|
||||||
entityIds.add(singleSearchingEntityId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [...entityIds];
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -639,6 +606,12 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
|
|||||||
","
|
","
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (this._targetPickerValue.label_id) {
|
||||||
|
params.label_id = ensureArray(this._targetPickerValue.label_id).join(",");
|
||||||
|
}
|
||||||
|
if (this._targetPickerValue.floor_id) {
|
||||||
|
params.floor_id = ensureArray(this._targetPickerValue.floor_id).join(",");
|
||||||
|
}
|
||||||
if (this._targetPickerValue.area_id) {
|
if (this._targetPickerValue.area_id) {
|
||||||
params.area_id = ensureArray(this._targetPickerValue.area_id).join(",");
|
params.area_id = ensureArray(this._targetPickerValue.area_id).join(",");
|
||||||
}
|
}
|
||||||
|
@ -501,6 +501,7 @@
|
|||||||
},
|
},
|
||||||
"subpage-data-table": {
|
"subpage-data-table": {
|
||||||
"filters": "Filters",
|
"filters": "Filters",
|
||||||
|
"show_results": "show {number} results",
|
||||||
"clear_filter": "Clear filter",
|
"clear_filter": "Clear filter",
|
||||||
"close_filter": "Close filters",
|
"close_filter": "Close filters",
|
||||||
"exit_selection_mode": "Exit selection mode",
|
"exit_selection_mode": "Exit selection mode",
|
||||||
@ -2270,7 +2271,8 @@
|
|||||||
"category": "Category"
|
"category": "Category"
|
||||||
},
|
},
|
||||||
"create_helper": "Create helper",
|
"create_helper": "Create helper",
|
||||||
"no_helpers": "Looks like you don't have any helpers yet!"
|
"no_helpers": "Looks like you don't have any helpers yet!",
|
||||||
|
"search": "Search {number} helpers"
|
||||||
},
|
},
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
@ -2684,6 +2686,7 @@
|
|||||||
"assign_category": "Assign category",
|
"assign_category": "Assign category",
|
||||||
"no_category_support": "You can't assign an category to this automation",
|
"no_category_support": "You can't assign an category to this automation",
|
||||||
"no_category_entity_reg": "To assign an category to an automation it needs to have a unique ID.",
|
"no_category_entity_reg": "To assign an category to an automation it needs to have a unique ID.",
|
||||||
|
"search": "Search {number} automations",
|
||||||
"headers": {
|
"headers": {
|
||||||
"toggle": "Enable/disable",
|
"toggle": "Enable/disable",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@ -3241,7 +3244,9 @@
|
|||||||
"target_template": "templated {name}",
|
"target_template": "templated {name}",
|
||||||
"target_unknown_entity": "unknown entity",
|
"target_unknown_entity": "unknown entity",
|
||||||
"target_unknown_device": "unknown device",
|
"target_unknown_device": "unknown device",
|
||||||
"target_unknown_area": "unknown area"
|
"target_unknown_area": "unknown area",
|
||||||
|
"target_unknown_floor": "unknown floor",
|
||||||
|
"target_unknown_label": "unknown label"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"play_media": {
|
"play_media": {
|
||||||
@ -3575,7 +3580,8 @@
|
|||||||
"delete": "[%key:ui::common::delete%]",
|
"delete": "[%key:ui::common::delete%]",
|
||||||
"duplicate": "[%key:ui::common::duplicate%]",
|
"duplicate": "[%key:ui::common::duplicate%]",
|
||||||
"empty_header": "Create your first script",
|
"empty_header": "Create your first script",
|
||||||
"empty_text": "A script is a sequence of actions that can be run from a dashboard, an automation, or be triggered by voice. For example, a ''Wake-up routine''' script that gradually turns on the light in the bedroom and opens the blinds after a delay."
|
"empty_text": "A script is a sequence of actions that can be run from a dashboard, an automation, or be triggered by voice. For example, a ''Wake-up routine''' script that gradually turns on the light in the bedroom and opens the blinds after a delay.",
|
||||||
|
"search": "Search {number} scripts"
|
||||||
},
|
},
|
||||||
"dialog_new": {
|
"dialog_new": {
|
||||||
"header": "Create script",
|
"header": "Create script",
|
||||||
@ -3683,7 +3689,8 @@
|
|||||||
"no_category_support": "You can't assign an category to this scene",
|
"no_category_support": "You can't assign an category to this scene",
|
||||||
"no_category_entity_reg": "To assign an category to an scene it needs to have a unique ID.",
|
"no_category_entity_reg": "To assign an category to an scene it needs to have a unique ID.",
|
||||||
"empty_header": "Create your first scene",
|
"empty_header": "Create your first scene",
|
||||||
"empty_text": "Scenes capture entities' states, so you can re-experience the same scene later on. For example, a ''Watching TV'' scene that dims the living room lights, sets a warm white color and turns on the TV."
|
"empty_text": "Scenes capture entities' states, so you can re-experience the same scene later on. For example, a ''Watching TV'' scene that dims the living room lights, sets a warm white color and turns on the TV.",
|
||||||
|
"search": "Search {number} scenes"
|
||||||
},
|
},
|
||||||
"editor": {
|
"editor": {
|
||||||
"default_name": "New scene",
|
"default_name": "New scene",
|
||||||
@ -4008,7 +4015,7 @@
|
|||||||
"confirm_delete": "Are you sure you want to delete this device?",
|
"confirm_delete": "Are you sure you want to delete this device?",
|
||||||
"confirm_delete_integration": "Are you sure you want to remove this device from {integration}?",
|
"confirm_delete_integration": "Are you sure you want to remove this device from {integration}?",
|
||||||
"picker": {
|
"picker": {
|
||||||
"search": "Search devices",
|
"search": "Search {number} devices",
|
||||||
"state": "State"
|
"state": "State"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -4019,7 +4026,7 @@
|
|||||||
"header": "Entities",
|
"header": "Entities",
|
||||||
"introduction": "Home Assistant keeps a registry of every entity it has ever seen that can be uniquely identified. Each of these entities will have an entity ID assigned which will be reserved for just this entity.",
|
"introduction": "Home Assistant keeps a registry of every entity it has ever seen that can be uniquely identified. Each of these entities will have an entity ID assigned which will be reserved for just this entity.",
|
||||||
"introduction2": "Use the entity registry to override the name, change the entity ID or remove the entry from Home Assistant.",
|
"introduction2": "Use the entity registry to override the name, change the entity ID or remove the entry from Home Assistant.",
|
||||||
"search": "Search entities",
|
"search": "Search {number} entities",
|
||||||
"unnamed_entity": "Unnamed entity",
|
"unnamed_entity": "Unnamed entity",
|
||||||
"status": {
|
"status": {
|
||||||
"restored": "Restored",
|
"restored": "Restored",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user