diff --git a/hassio/src/dashboard/hassio-dashboard.ts b/hassio/src/dashboard/hassio-dashboard.ts
index b0316ed0ee..f1bfb7a329 100644
--- a/hassio/src/dashboard/hassio-dashboard.ts
+++ b/hassio/src/dashboard/hassio-dashboard.ts
@@ -9,7 +9,6 @@ import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-tabs";
import "./hassio-addons";
-import "./hassio-update";
import "../../../src/layouts/hass-subpage";
@customElement("hassio-dashboard")
@@ -22,6 +21,12 @@ class HassioDashboard extends LitElement {
@property({ attribute: false }) public route!: Route;
+ firstUpdated() {
+ if (!atLeastVersion(this.hass.config.version, 2022, 5)) {
+ import("./hassio-update");
+ }
+ }
+
protected render(): TemplateResult {
if (atLeastVersion(this.hass.config.version, 2022, 5)) {
return html`
`;
}
diff --git a/hassio/src/entrypoint.ts b/hassio/src/entrypoint.ts
index 09e73ecf4d..d844ef976e 100644
--- a/hassio/src/entrypoint.ts
+++ b/hassio/src/entrypoint.ts
@@ -2,6 +2,7 @@
import "../../src/resources/compatibility";
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
import "../../src/resources/roboto";
+import "../../src/resources/ha-style";
import "../../src/resources/safari-14-attachshadow-patch";
import "./hassio-main";
diff --git a/hassio/src/hassio-main.ts b/hassio/src/hassio-main.ts
index 68fe3e7482..e9769e807c 100644
--- a/hassio/src/hassio-main.ts
+++ b/hassio/src/hassio-main.ts
@@ -9,7 +9,6 @@ import { navigate } from "../../src/common/navigate";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
-import "../../src/layouts/hass-loading-screen";
import { HomeAssistant } from "../../src/types";
import "./hassio-router";
import { SupervisorBaseElement } from "./supervisor-base-element";
diff --git a/hassio/src/hassio-panel-router.ts b/hassio/src/hassio-panel-router.ts
index bceab08108..201f3a6c71 100644
--- a/hassio/src/hassio-panel-router.ts
+++ b/hassio/src/hassio-panel-router.ts
@@ -5,12 +5,8 @@ import {
RouterOptions,
} from "../../src/layouts/hass-router-page";
import { HomeAssistant, Route } from "../../src/types";
-import "./addon-store/hassio-addon-store";
// Don't codesplit it, that way the dashboard always loads fast.
import "./dashboard/hassio-dashboard";
-// Don't codesplit the others, because it breaks the UI when pushed to a Pi
-import "./backups/hassio-backups";
-import "./system/hassio-system";
@customElement("hassio-panel-router")
class HassioPanelRouter extends HassRouterPage {
@@ -31,12 +27,15 @@ class HassioPanelRouter extends HassRouterPage {
},
store: {
tag: "hassio-addon-store",
+ load: () => import("./addon-store/hassio-addon-store"),
},
backups: {
tag: "hassio-backups",
+ load: () => import("./backups/hassio-backups"),
},
system: {
tag: "hassio-system",
+ load: () => import("./system/hassio-system"),
},
},
};
diff --git a/hassio/src/hassio-panel.ts b/hassio/src/hassio-panel.ts
index 6c6dc54c7c..c2d3295759 100644
--- a/hassio/src/hassio-panel.ts
+++ b/hassio/src/hassio-panel.ts
@@ -4,6 +4,7 @@ import {
Supervisor,
supervisorCollection,
} from "../../src/data/supervisor/supervisor";
+import "../../src/layouts/hass-loading-screen";
import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router";
diff --git a/hassio/src/hassio-router.ts b/hassio/src/hassio-router.ts
index b40c075524..23a6e73a37 100644
--- a/hassio/src/hassio-router.ts
+++ b/hassio/src/hassio-router.ts
@@ -5,7 +5,6 @@ import {
HassRouterPage,
RouterOptions,
} from "../../src/layouts/hass-router-page";
-import "../../src/resources/ha-style";
import { HomeAssistant } from "../../src/types";
// Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-panel";
diff --git a/hassio/src/update-available/update-available-card.ts b/hassio/src/update-available/update-available-card.ts
index 84b01d1fdd..c9fcb74d5e 100644
--- a/hassio/src/update-available/update-available-card.ts
+++ b/hassio/src/update-available/update-available-card.ts
@@ -42,9 +42,6 @@ import { updateCore } from "../../../src/data/supervisor/core";
import { StoreAddon } from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
-import "../../../src/layouts/hass-loading-screen";
-import "../../../src/layouts/hass-subpage";
-import "../../../src/layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../../src/types";
import { addonArchIsSupported, extractChangelog } from "../util/addon";
diff --git a/pyproject.toml b/pyproject.toml
index 38974e1400..8966ef67eb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
-version = "20230501.0"
+version = "20230502.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
diff --git a/src/components/ha-aliases-editor.ts b/src/components/ha-aliases-editor.ts
index ec79e59968..4f3d7aad5c 100644
--- a/src/components/ha-aliases-editor.ts
+++ b/src/components/ha-aliases-editor.ts
@@ -15,6 +15,8 @@ class AliasesEditor extends LitElement {
@property() public aliases!: string[];
+ @property({ type: Boolean }) public disabled = false;
+
protected render() {
if (!this.aliases) {
return nothing;
@@ -25,6 +27,7 @@ class AliasesEditor extends LitElement {
(alias, index) => html`
-
+
${this.hass!.localize("ui.dialogs.aliases.add_alias")}
diff --git a/src/components/ha-tts-picker.ts b/src/components/ha-tts-picker.ts
index d9dfed597b..8d3fcb6b07 100644
--- a/src/components/ha-tts-picker.ts
+++ b/src/components/ha-tts-picker.ts
@@ -19,7 +19,10 @@ import type { HaSelect } from "./ha-select";
const NONE = "__NONE_OPTION__";
-const NAME_MAP = { cloud: "Home Assistant Cloud" };
+const NAME_MAP = {
+ cloud: "Home Assistant Cloud",
+ google_translate: "Google Translate",
+};
@customElement("ha-tts-picker")
export class HaTTSPicker extends LitElement {
diff --git a/src/components/media-player/ha-browse-media-tts.ts b/src/components/media-player/ha-browse-media-tts.ts
index 152da17ac6..1755b4e6fd 100644
--- a/src/components/media-player/ha-browse-media-tts.ts
+++ b/src/components/media-player/ha-browse-media-tts.ts
@@ -21,6 +21,7 @@ import { buttonLinkStyle } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import "../ha-select";
import "../ha-textarea";
+import "../ha-language-picker";
export interface TtsMediaPickedEvent {
item: MediaPlayerItem;
@@ -103,21 +104,17 @@ class BrowseMediaTTS extends LitElement {
return html`
-
- ${languages.map(
- ([key, label]) =>
- html`${label}`
- )}
-
+
hass.callWS({ type: "cloud/tts/info" });
export const getCloudTtsLanguages = (info?: CloudTTSInfo) => {
- const languages: Array<[string, string]> = [];
+ const languages: string[] = [];
if (!info) {
return languages;
@@ -23,25 +22,9 @@ export const getCloudTtsLanguages = (info?: CloudTTSInfo) => {
continue;
}
seen.add(lang);
-
- let label = lang;
-
- if (lang in translationMetadata.translations) {
- label = translationMetadata.translations[lang].nativeName;
- } else {
- const [langFamily, dialect] = lang.split("-");
- if (langFamily in translationMetadata.translations) {
- label = `${translationMetadata.translations[langFamily].nativeName}`;
-
- if (langFamily.toLowerCase() !== dialect.toLowerCase()) {
- label += ` (${dialect})`;
- }
- }
- }
-
- languages.push([lang, label]);
+ languages.push(lang);
}
- return languages.sort((a, b) => caseInsensitiveStringCompare(a[1], b[1]));
+ return languages;
};
export const getCloudTtsSupportedGenders = (
diff --git a/src/data/voice.ts b/src/data/expose.ts
similarity index 75%
rename from src/data/voice.ts
rename to src/data/expose.ts
index c2bef3982b..d8cea10b34 100644
--- a/src/data/voice.ts
+++ b/src/data/expose.ts
@@ -12,6 +12,12 @@ export const voiceAssistants = {
},
} as const;
+export interface ExposeEntitySettings {
+ conversation?: boolean;
+ "cloud.alexa"?: boolean;
+ "cloud.google_assistant"?: boolean;
+}
+
export const setExposeNewEntities = (
hass: HomeAssistant,
assistant: string,
@@ -41,3 +47,8 @@ export const exposeEntities = (
entity_ids,
should_expose,
});
+
+export const listExposedEntities = (hass: HomeAssistant) =>
+ hass.callWS<{ exposed_entities: Record }>({
+ type: "homeassistant/expose_entity/list",
+ });
diff --git a/src/dialogs/more-info/components/voice/ha-more-info-view-voice-assistants.ts b/src/dialogs/more-info/components/voice/ha-more-info-view-voice-assistants.ts
index f43b7fc14e..0fe5b790c1 100644
--- a/src/dialogs/more-info/components/voice/ha-more-info-view-voice-assistants.ts
+++ b/src/dialogs/more-info/components/voice/ha-more-info-view-voice-assistants.ts
@@ -1,6 +1,8 @@
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
+import memoizeOne from "memoize-one";
import { ExtEntityRegistryEntry } from "../../../../data/entity_registry";
+import { ExposeEntitySettings, voiceAssistants } from "../../../../data/expose";
import "../../../../panels/config/voice-assistants/entity-voice-settings";
import { HomeAssistant } from "../../../../types";
@@ -12,13 +14,23 @@ class MoreInfoViewVoiceAssistants extends LitElement {
@property() public params?;
+ private _calculateExposed = memoizeOne((entry: ExtEntityRegistryEntry) => {
+ const exposed: ExposeEntitySettings = {};
+ Object.keys(voiceAssistants).forEach((key) => {
+ exposed[key] = entry.options?.[key]?.should_expose;
+ });
+ return exposed;
+ });
+
protected render() {
if (!this.params) {
return nothing;
}
return html``;
}
diff --git a/src/dialogs/more-info/ha-more-info-settings.ts b/src/dialogs/more-info/ha-more-info-settings.ts
index a10545c651..b3a4c2eed1 100644
--- a/src/dialogs/more-info/ha-more-info-settings.ts
+++ b/src/dialogs/more-info/ha-more-info-settings.ts
@@ -5,11 +5,12 @@ import {
CSSResultGroup,
html,
LitElement,
- PropertyValues,
nothing,
+ PropertyValues,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
+import "../../components/ha-alert";
import {
EntityRegistryEntry,
ExtEntityRegistryEntry,
@@ -39,18 +40,17 @@ export class HaMoreInfoSettings extends LitElement {
if (this.entry === null) {
return html`
- ${this.hass.localize(
- "ui.dialogs.entity_registry.no_unique_id",
- "entity_id",
- this.entityId,
- "faq_link",
- html`
${this.hass.localize("ui.dialogs.entity_registry.faq")}`
- )}
+
+ ${this.hass.localize("ui.dialogs.entity_registry.no_unique_id", {
+ entity_id: this.entityId,
+ faq_link: html`${this.hass.localize("ui.dialogs.entity_registry.faq")}`,
+ })}
+
`;
}
diff --git a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts
index 87ea70b4e5..aabdb1e7c4 100644
--- a/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts
+++ b/src/dialogs/voice-command-dialog/ha-voice-command-dialog.ts
@@ -1,5 +1,6 @@
import "@material/mwc-button/mwc-button";
import {
+ mdiAlertCircle,
mdiChevronDown,
mdiClose,
mdiHelpCircleOutline,
@@ -14,6 +15,7 @@ import {
LitElement,
nothing,
PropertyValues,
+ TemplateResult,
} from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { LocalStorage } from "../../common/decorators/local-storage";
@@ -42,7 +44,7 @@ import { showAlertDialog } from "../generic/show-dialog-box";
interface Message {
who: string;
- text?: string;
+ text?: string | TemplateResult;
error?: boolean;
}
@@ -109,7 +111,10 @@ export class HaVoiceCommandDialog extends LitElement {
if (!this._opened) {
return nothing;
}
- const supportsSTT = this._pipeline?.stt_engine && AudioRecorder.isSupported;
+
+ const supportsMicrophone = AudioRecorder.isSupported;
+ const supportsSTT = this._pipeline?.stt_engine;
+
return html`
${pipeline.name}${pipeline.id === this._preferredPipeline
- ? html``
+ ? html`
+
+ `
: nothing}
`
)}
@@ -203,7 +210,7 @@ export class HaVoiceCommandDialog extends LitElement {
iconTrailing
>
- ${this._showSendButton
+ ${this._showSendButton || !supportsSTT
? html`
`
- : supportsSTT
- ? html`
+ : html`
${this._audioRecorder?.active
? html`
@@ -224,18 +230,27 @@ export class HaVoiceCommandDialog extends LitElement {
`
- : ""}
-
-
- `
- : ""}
+ : nothing}
+
+
+
+
+ ${!supportsMicrophone
+ ? html`
+
+ `
+ : null}
+
+ `}
${this._agentInfo && this._agentInfo.attribution
@@ -382,7 +397,14 @@ export class HaVoiceCommandDialog extends LitElement {
}
}
- private _toggleListening() {
+ private _toggleListening(ev) {
+ ev.stopPropagation();
+ ev.preventDefault();
+ const supportsMicrophone = AudioRecorder.isSupported;
+ if (!supportsMicrophone) {
+ this._showNotSupportedMessage();
+ return;
+ }
if (!this._audioRecorder?.active) {
this._startListening();
} else {
@@ -390,6 +412,40 @@ export class HaVoiceCommandDialog extends LitElement {
}
}
+ private async _showNotSupportedMessage() {
+ this._addMessage({
+ who: "hass",
+ text: html`
+
+ ${this.hass.localize(
+ "ui.dialogs.voice_command.not_supported_microphone"
+ )}
+
+
+ ${this.hass.localize(
+ "ui.dialogs.voice_command.not_supported_microphone_documentation",
+ {
+ documentation_link: html`
+
+ ${this.hass.localize(
+ "ui.dialogs.voice_command.not_supported_microphone_documentation_link"
+ )}
+
+ `,
+ }
+ )}
+
+ `,
+ });
+ }
+
private async _startListening() {
this._audio?.pause();
if (!this._audioRecorder) {
@@ -561,7 +617,8 @@ export class HaVoiceCommandDialog extends LitElement {
return [
haStyleDialog,
css`
- ha-icon-button.listening-icon {
+ .listening-icon {
+ position: relative;
color: var(--secondary-text-color);
margin-right: -24px;
margin-inline-end: -24px;
@@ -569,10 +626,18 @@ export class HaVoiceCommandDialog extends LitElement {
direction: var(--direction);
}
- ha-icon-button.listening-icon[active] {
+ .listening-icon[active] {
color: var(--primary-color);
}
+ .unsupported {
+ color: var(--error-color);
+ position: absolute;
+ --mdc-icon-size: 16px;
+ right: 5px;
+ top: 0px;
+ }
+
ha-dialog {
--primary-action-button-flex: 1;
--secondary-action-button-flex: 0;
@@ -616,6 +681,19 @@ export class HaVoiceCommandDialog extends LitElement {
ha-button-menu ha-button ha-svg-icon {
height: 28px;
margin-left: 4px;
+ margin-inline-start: 4px;
+ margin-inline-end: 4px;
+ direction: var(--direction);
+ }
+ ha-list-item {
+ --mdc-list-item-meta-size: 16px;
+ }
+ ha-list-item ha-svg-icon {
+ margin-left: 4px;
+ margin-inline-start: 4px;
+ margin-inline-end: 4px;
+ direction: var(--direction);
+ display: block;
}
ha-button-menu a {
text-decoration: none;
@@ -648,6 +726,7 @@ export class HaVoiceCommandDialog extends LitElement {
display: block;
height: 400px;
box-sizing: border-box;
+ position: relative;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-dialog {
@@ -655,6 +734,7 @@ export class HaVoiceCommandDialog extends LitElement {
}
.messages {
height: 100%;
+ flex: 1;
}
}
.messages-container {
@@ -674,6 +754,12 @@ export class HaVoiceCommandDialog extends LitElement {
padding: 8px;
border-radius: 15px;
}
+ .message p {
+ margin: 0;
+ }
+ .message p:not(:last-child) {
+ margin-bottom: 8px;
+ }
.message.user {
margin-left: 24px;
diff --git a/src/layouts/supervisor-error-screen.ts b/src/layouts/supervisor-error-screen.ts
index eabb00e439..6d415a7c43 100644
--- a/src/layouts/supervisor-error-screen.ts
+++ b/src/layouts/supervisor-error-screen.ts
@@ -11,7 +11,6 @@ import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../common/config/version";
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
import "../components/ha-card";
-import "../resources/ha-style";
import { haStyle } from "../resources/styles";
import { HomeAssistant } from "../types";
import "./hass-subpage";
diff --git a/src/panels/config/areas/ha-config-areas-dashboard.ts b/src/panels/config/areas/ha-config-areas-dashboard.ts
index d4e6c1703d..5b03220dec 100644
--- a/src/panels/config/areas/ha-config-areas-dashboard.ts
+++ b/src/panels/config/areas/ha-config-areas-dashboard.ts
@@ -21,7 +21,6 @@ import {
subscribeEntityRegistry,
} from "../../../data/entity_registry";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
-import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { HomeAssistant, Route } from "../../../types";
@@ -222,10 +221,6 @@ export class HaConfigAreasDashboard extends SubscribeMixin(LitElement) {
static get styles(): CSSResultGroup {
return css`
- hass-loading-screen {
- --app-header-background-color: var(--sidebar-background-color);
- --app-header-text-color: var(--sidebar-text-color);
- }
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
diff --git a/src/panels/config/cloud/account/cloud-tts-pref.ts b/src/panels/config/cloud/account/cloud-tts-pref.ts
index 43d421fda6..0245c3a134 100644
--- a/src/panels/config/cloud/account/cloud-tts-pref.ts
+++ b/src/panels/config/cloud/account/cloud-tts-pref.ts
@@ -8,6 +8,7 @@ import "../../../../components/ha-card";
import "../../../../components/ha-select";
import "../../../../components/ha-svg-icon";
import "../../../../components/ha-switch";
+import "../../../../components/ha-language-picker";
import { CloudStatusLoggedIn, updateCloudPref } from "../../../../data/cloud";
import {
CloudTTSInfo,
@@ -54,34 +55,33 @@ export class CloudTTSPref extends LitElement {
'"tts.cloud_say"'
)}
+
+
+
-
- ${languages.map(
- ([key, label]) =>
- html`${label}`
- )}
-
-
-
- ${genders.map(
- ([key, label]) =>
- html`${label}`
- )}
-
+
+ ${genders.map(
+ ([key, label]) =>
+ html`${label}`
+ )}
+
+
@@ -115,11 +115,11 @@ export class CloudTTSPref extends LitElement {
}
async _handleLanguageChange(ev) {
- if (ev.target.value === this.cloudStatus!.prefs.tts_default_voice[0]) {
+ if (ev.detail.value === this.cloudStatus!.prefs.tts_default_voice[0]) {
return;
}
this.savingPreferences = true;
- const language = ev.target.value;
+ const language = ev.detail.value;
const curGender = this.cloudStatus!.prefs.tts_default_voice[1];
const genders = this.getSupportedGenders(
@@ -185,6 +185,18 @@ export class CloudTTSPref extends LitElement {
right: auto;
left: 24px;
}
+ .row {
+ display: flex;
+ }
+ .row > * {
+ flex: 1;
+ }
+ .row > *:first-child {
+ margin-right: 8px;
+ }
+ .row > *:last-child {
+ margin-left: 8px;
+ }
.card-actions {
display: flex;
flex-direction: row-reverse;
diff --git a/src/panels/config/devices/ha-config-device-page.ts b/src/panels/config/devices/ha-config-device-page.ts
index 4bc94fbd4c..afd74a41b1 100644
--- a/src/panels/config/devices/ha-config-device-page.ts
+++ b/src/panels/config/devices/ha-config-device-page.ts
@@ -62,7 +62,6 @@ import {
} from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-error-screen";
import "../../../layouts/hass-subpage";
-import "../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts
index 57011cf0e4..3a3daa702d 100644
--- a/src/panels/config/ha-panel-config.ts
+++ b/src/panels/config/ha-panel-config.ts
@@ -33,7 +33,6 @@ import { customElement, property, state } from "lit/decorators";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { listenMediaQuery } from "../../common/dom/media_query";
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
-import "../../layouts/hass-loading-screen";
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../types";
diff --git a/src/panels/config/integrations/ha-integration-card.ts b/src/panels/config/integrations/ha-integration-card.ts
index f9c56194a1..12a13a6cc5 100644
--- a/src/panels/config/integrations/ha-integration-card.ts
+++ b/src/panels/config/integrations/ha-integration-card.ts
@@ -174,9 +174,11 @@ export class HaIntegrationCard extends LitElement {
${this.items.map(
(item) =>
html`${item.title ||
this.hass.localize(
"ui.panel.config.integrations.config_entry.unnamed_entry"
@@ -1026,6 +1028,9 @@ export class HaIntegrationCard extends LitElement {
ha-list-item ha-svg-icon {
color: var(--secondary-text-color);
}
+ .config-entry {
+ height: 36px;
+ }
ha-icon-next {
width: 24px;
}
diff --git a/src/panels/config/integrations/integration-panels/zha/zha-add-group-page.ts b/src/panels/config/integrations/integration-panels/zha/zha-add-group-page.ts
index 4c18a4e2b9..64882051b2 100644
--- a/src/panels/config/integrations/integration-panels/zha/zha-add-group-page.ts
+++ b/src/panels/config/integrations/integration-panels/zha/zha-add-group-page.ts
@@ -13,7 +13,6 @@ import {
ZHADeviceEndpoint,
ZHAGroup,
} from "../../../../../data/zha";
-import "../../../../../layouts/hass-error-screen";
import "../../../../../layouts/hass-subpage";
import type { PolymerChangedEvent } from "../../../../../polymer-types";
import type { HomeAssistant } from "../../../../../types";
diff --git a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts
index a47c26bbe5..0de6edb5a4 100644
--- a/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts
+++ b/src/panels/config/integrations/integration-panels/zwave_js/zwave_js-node-config.ts
@@ -39,6 +39,8 @@ import {
ZwaveJSNodeMetadata,
ZWaveJSSetConfigParamResult,
} from "../../../../../data/zwave_js";
+import "../../../../../layouts/hass-error-screen";
+import "../../../../../layouts/hass-loading-screen";
import "../../../../../layouts/hass-tabs-subpage";
import { SubscribeMixin } from "../../../../../mixins/subscribe-mixin";
import { haStyle } from "../../../../../resources/styles";
diff --git a/src/panels/config/logs/ha-config-logs.ts b/src/panels/config/logs/ha-config-logs.ts
index 6788c9b639..6d176fca55 100644
--- a/src/panels/config/logs/ha-config-logs.ts
+++ b/src/panels/config/logs/ha-config-logs.ts
@@ -9,7 +9,6 @@ import "../../../components/search-input";
import { LogProvider } from "../../../data/error_log";
import { fetchHassioAddonsInfo } from "../../../data/hassio/addon";
import "../../../layouts/hass-subpage";
-import "../../../layouts/hass-tabs-subpage";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import "./error-log-card";
diff --git a/src/panels/config/repairs/ha-config-repairs.ts b/src/panels/config/repairs/ha-config-repairs.ts
index 7265f2f837..1643de6b06 100644
--- a/src/panels/config/repairs/ha-config-repairs.ts
+++ b/src/panels/config/repairs/ha-config-repairs.ts
@@ -9,7 +9,6 @@ import "../../../components/ha-list-item";
import "../../../components/ha-svg-icon";
import { domainToName } from "../../../data/integration";
import type { RepairsIssue } from "../../../data/repairs";
-import "../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { showRepairsFlowDialog } from "./show-dialog-repair-flow";
diff --git a/src/panels/config/voice-assistants/assist-pipeline-detail/assist-pipeline-detail-config.ts b/src/panels/config/voice-assistants/assist-pipeline-detail/assist-pipeline-detail-config.ts
index 19d78a6527..856af04c39 100644
--- a/src/panels/config/voice-assistants/assist-pipeline-detail/assist-pipeline-detail-config.ts
+++ b/src/panels/config/voice-assistants/assist-pipeline-detail/assist-pipeline-detail-config.ts
@@ -1,9 +1,10 @@
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
-import { SchemaUnion } from "../../../../components/ha-form/types";
+import { LocalizeKeys } from "../../../../common/translations/localize";
import { AssistPipeline } from "../../../../data/assist_pipeline";
import { HomeAssistant } from "../../../../types";
+import "../../../../components/ha-form/ha-form";
@customElement("assist-pipeline-detail-config")
export class AssistPipelineDetailConfig extends LitElement {
@@ -33,26 +34,28 @@ export class AssistPipelineDetailConfig extends LitElement {
text: {},
},
},
- {
- name: "language",
- required: true,
- selector: {
- language: {
- languages: supportedLanguages ?? [],
- },
- },
- },
+ supportedLanguages
+ ? {
+ name: "language",
+ required: true,
+ selector: {
+ language: {
+ languages: supportedLanguages,
+ },
+ },
+ }
+ : { name: "", type: "constant" },
] as const,
},
] as const
);
- private _computeLabel = (
- schema: SchemaUnion>
- ): string =>
- this.hass.localize(
- `ui.panel.config.voice_assistants.assistants.pipeline.detail.form.${schema.name}`
- );
+ private _computeLabel = (schema): string =>
+ schema.name
+ ? this.hass.localize(
+ `ui.panel.config.voice_assistants.assistants.pipeline.detail.form.${schema.name}` as LocalizeKeys
+ )
+ : "";
protected render() {
return html`
diff --git a/src/panels/config/voice-assistants/assist-pref.ts b/src/panels/config/voice-assistants/assist-pref.ts
index 80034e3bd1..f16d13d11d 100644
--- a/src/panels/config/voice-assistants/assist-pref.ts
+++ b/src/panels/config/voice-assistants/assist-pref.ts
@@ -20,7 +20,7 @@ import {
updateAssistPipeline,
} from "../../../data/assist_pipeline";
import { CloudStatus } from "../../../data/cloud";
-import { ExtEntityRegistryEntry } from "../../../data/entity_registry";
+import { ExposeEntitySettings } from "../../../data/expose";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
@@ -29,7 +29,10 @@ import { showVoiceAssistantPipelineDetailDialog } from "./show-dialog-voice-assi
export class AssistPref extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
- @property() private extEntities?: Record;
+ @property({ attribute: false }) public exposedEntities?: Record<
+ string,
+ ExposeEntitySettings
+ >;
@state() private _pipelines: AssistPipeline[] = [];
@@ -46,11 +49,10 @@ export class AssistPref extends LitElement {
});
}
- private _exposedEntities = memoizeOne(
- (extEntities: Record) =>
- Object.values(extEntities).filter(
- (entity) => entity.options?.conversation?.should_expose
- ).length
+ private _exposedEntitiesCount = memoizeOne(
+ (exposedEntities: Record) =>
+ Object.values(exposedEntities).filter((expose) => expose.conversation)
+ .length
);
protected render() {
@@ -119,8 +121,8 @@ export class AssistPref extends LitElement {
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.exposed_entities",
{
- number: this.extEntities
- ? this._exposedEntities(this.extEntities)
+ number: this.exposedEntities
+ ? this._exposedEntitiesCount(this.exposedEntities)
: 0,
}
)}
diff --git a/src/panels/config/voice-assistants/cloud-alexa-pref.ts b/src/panels/config/voice-assistants/cloud-alexa-pref.ts
index b4106304bf..a071eba07f 100644
--- a/src/panels/config/voice-assistants/cloud-alexa-pref.ts
+++ b/src/panels/config/voice-assistants/cloud-alexa-pref.ts
@@ -11,28 +11,30 @@ import "../../../components/ha-settings-row";
import "../../../components/ha-switch";
import type { HaSwitch } from "../../../components/ha-switch";
import { CloudStatusLoggedIn, updateCloudPref } from "../../../data/cloud";
-import { ExtEntityRegistryEntry } from "../../../data/entity_registry";
import {
+ ExposeEntitySettings,
getExposeNewEntities,
setExposeNewEntities,
-} from "../../../data/voice";
+} from "../../../data/expose";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
export class CloudAlexaPref extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
- @property() private extEntities?: Record;
+ @property({ attribute: false }) public exposedEntities?: Record<
+ string,
+ ExposeEntitySettings
+ >;
@property() public cloudStatus?: CloudStatusLoggedIn;
@state() private _exposeNew?: boolean;
- private _exposedEntities = memoizeOne(
- (extEntities: Record) =>
- Object.values(extEntities).filter(
- (entity) => entity.options?.["cloud.alexa"]?.should_expose
- ).length
+ private _exposedEntitiesCount = memoizeOne(
+ (exposedEntities: Record) =>
+ Object.values(exposedEntities).filter((expose) => expose["cloud.alexa"])
+ .length
);
protected willUpdate() {
@@ -183,8 +185,8 @@ export class CloudAlexaPref extends LitElement {
: this.hass.localize(
"ui.panel.config.cloud.account.alexa.exposed_entities",
{
- number: this.extEntities
- ? this._exposedEntities(this.extEntities)
+ number: this.exposedEntities
+ ? this._exposedEntitiesCount(this.exposedEntities)
: 0,
}
)}
diff --git a/src/panels/config/voice-assistants/cloud-google-pref.ts b/src/panels/config/voice-assistants/cloud-google-pref.ts
index 3c28d1b636..409319ffc9 100644
--- a/src/panels/config/voice-assistants/cloud-google-pref.ts
+++ b/src/panels/config/voice-assistants/cloud-google-pref.ts
@@ -4,6 +4,7 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
+import { isEmptyFilter } from "../../../common/entity/entity_filter";
import "../../../components/ha-alert";
import "../../../components/ha-card";
import "../../../components/ha-settings-row";
@@ -11,20 +12,22 @@ import type { HaSwitch } from "../../../components/ha-switch";
import "../../../components/ha-textfield";
import type { HaTextField } from "../../../components/ha-textfield";
import { CloudStatusLoggedIn, updateCloudPref } from "../../../data/cloud";
-import { showSaveSuccessToast } from "../../../util/toast-saved-success";
-import { HomeAssistant } from "../../../types";
-import { brandsUrl } from "../../../util/brands-url";
-import { isEmptyFilter } from "../../../common/entity/entity_filter";
import {
+ ExposeEntitySettings,
getExposeNewEntities,
setExposeNewEntities,
-} from "../../../data/voice";
-import { ExtEntityRegistryEntry } from "../../../data/entity_registry";
+} from "../../../data/expose";
+import { HomeAssistant } from "../../../types";
+import { brandsUrl } from "../../../util/brands-url";
+import { showSaveSuccessToast } from "../../../util/toast-saved-success";
export class CloudGooglePref extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
- @property() private extEntities?: Record;
+ @property({ attribute: false }) public exposedEntities?: Record<
+ string,
+ ExposeEntitySettings
+ >;
@property({ attribute: false }) public cloudStatus?: CloudStatusLoggedIn;
@@ -40,10 +43,10 @@ export class CloudGooglePref extends LitElement {
}
}
- private _exposedEntities = memoizeOne(
- (extEntities: Record) =>
- Object.values(extEntities).filter(
- (entity) => entity.options?.["cloud.google_assistant"]?.should_expose
+ private _exposedEntitiesCount = memoizeOne(
+ (exposedEntities: Record) =>
+ Object.values(exposedEntities).filter(
+ (expose) => expose["cloud.google_assistant"]
).length
);
@@ -239,8 +242,8 @@ export class CloudGooglePref extends LitElement {
: this.hass.localize(
"ui.panel.config.cloud.account.google.exposed_entities",
{
- number: this.extEntities
- ? this._exposedEntities(this.extEntities)
+ number: this.exposedEntities
+ ? this._exposedEntitiesCount(this.exposedEntities)
: 0,
}
)}
diff --git a/src/panels/config/voice-assistants/dialog-expose-entity.ts b/src/panels/config/voice-assistants/dialog-expose-entity.ts
index 6e6da8088e..2f41470b49 100644
--- a/src/panels/config/voice-assistants/dialog-expose-entity.ts
+++ b/src/panels/config/voice-assistants/dialog-expose-entity.ts
@@ -1,18 +1,16 @@
import "@material/mwc-button";
import "@material/mwc-list";
import { mdiClose } from "@mdi/js";
+import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ifDefined } from "lit/directives/if-defined";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../common/dom/fire_event";
+import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/ha-check-list-item";
import "../../../components/search-input";
-import {
- computeEntityRegistryName,
- ExtEntityRegistryEntry,
-} from "../../../data/entity_registry";
-import { voiceAssistants } from "../../../data/voice";
+import { ExposeEntitySettings, voiceAssistants } from "../../../data/expose";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "./entity-voice-settings";
@@ -49,7 +47,7 @@ class DialogExposeEntity extends LitElement {
);
const entities = this._filterEntities(
- this._params.extendedEntities,
+ this._params.exposedEntities,
this._filter
);
@@ -126,42 +124,40 @@ class DialogExposeEntity extends LitElement {
}
private _filterEntities = memoizeOne(
- (RegEntries: Record, filter?: string) => {
+ (
+ exposedEntities: Record,
+ filter?: string
+ ) => {
const lowerFilter = filter?.toLowerCase();
- return Object.values(RegEntries).filter(
+ return Object.values(this.hass.states).filter(
(entity) =>
this._params!.filterAssistants.some(
- (ass) => !entity.options?.[ass]?.should_expose
+ (ass) => !exposedEntities[entity.entity_id]?.[ass]
) &&
(!lowerFilter ||
entity.entity_id.toLowerCase().includes(lowerFilter) ||
- computeEntityRegistryName(this.hass!, entity)
- ?.toLowerCase()
- .includes(lowerFilter))
+ computeStateName(entity)?.toLowerCase().includes(lowerFilter))
);
}
);
- private _renderItem = (entity: ExtEntityRegistryEntry) => {
- const entityState = this.hass.states[entity.entity_id];
- return html`
-
-
- ${computeEntityRegistryName(this.hass!, entity)}
- ${entity.entity_id}
-
- `;
- };
+ private _renderItem = (entityState: HassEntity) => html`
+
+
+ ${computeStateName(entityState)}
+ ${entityState.entity_id}
+
+ `;
private _expose() {
this._params!.exposeEntities(this._selected);
diff --git a/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts b/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts
index aa221e8b66..6048cfc0ac 100644
--- a/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts
+++ b/src/panels/config/voice-assistants/dialog-voice-assistant-pipeline-detail.ts
@@ -44,7 +44,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
@state() private _submitting = false;
- @state() private _supportedLanguages: string[] = [];
+ @state() private _supportedLanguages?: string[];
public showDialog(params: VoiceAssistantPipelineDetailsDialogParams): void {
this._params = params;
diff --git a/src/panels/config/voice-assistants/dialog-voice-settings.ts b/src/panels/config/voice-assistants/dialog-voice-settings.ts
index 74ae26d75b..1e3aac8da0 100644
--- a/src/panels/config/voice-assistants/dialog-voice-settings.ts
+++ b/src/panels/config/voice-assistants/dialog-voice-settings.ts
@@ -1,38 +1,31 @@
import "@material/mwc-button/mwc-button";
-import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
+import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
-import {
- ExtEntityRegistryEntry,
- computeEntityRegistryName,
- getExtendedEntityRegistryEntry,
-} from "../../../data/entity_registry";
+import { computeStateName } from "../../../common/entity/compute_state_name";
+import { createCloseHeading } from "../../../components/ha-dialog";
import { haStyle, haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
-import { VoiceSettingsDialogParams } from "./show-dialog-voice-settings";
import "./entity-voice-settings";
-import { createCloseHeading } from "../../../components/ha-dialog";
+import { VoiceSettingsDialogParams } from "./show-dialog-voice-settings";
@customElement("dialog-voice-settings")
class DialogVoiceSettings extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
- @state() private _extEntityReg?: ExtEntityRegistryEntry;
+ @state() private _params?: VoiceSettingsDialogParams;
- public async showDialog(params: VoiceSettingsDialogParams): Promise {
- this._extEntityReg = await getExtendedEntityRegistryEntry(
- this.hass,
- params.entityId
- );
+ public showDialog(params: VoiceSettingsDialogParams): void {
+ this._params = params;
}
public closeDialog(): void {
- this._extEntityReg = undefined;
+ this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render() {
- if (!this._extEntityReg) {
+ if (!this._params) {
return nothing;
}
@@ -43,15 +36,18 @@ class DialogVoiceSettings extends LitElement {
hideActions
.heading=${createCloseHeading(
this.hass,
- computeEntityRegistryName(this.hass, this._extEntityReg) ||
+ computeStateName(this.hass.states[this._params.entityId]) ||
this.hass.localize("ui.panel.config.entities.picker.unnamed_entity")
)}
>
@@ -59,7 +55,11 @@ class DialogVoiceSettings extends LitElement {
}
private _entityEntryUpdated(ev: CustomEvent) {
- this._extEntityReg = ev.detail;
+ this._params!.extEntityReg = ev.detail;
+ }
+
+ private _exposedEntitiesChanged() {
+ this._params!.exposedEntitiesChanged?.();
}
static get styles(): CSSResultGroup {
diff --git a/src/panels/config/voice-assistants/entity-voice-settings.ts b/src/panels/config/voice-assistants/entity-voice-settings.ts
index b985447260..9c92bfebd0 100644
--- a/src/panels/config/voice-assistants/entity-voice-settings.ts
+++ b/src/panels/config/voice-assistants/entity-voice-settings.ts
@@ -36,18 +36,27 @@ import {
fetchCloudGoogleEntity,
GoogleEntity,
} from "../../../data/google_assistant";
-import { exposeEntities, voiceAssistants } from "../../../data/voice";
+import {
+ exposeEntities,
+ ExposeEntitySettings,
+ voiceAssistants,
+} from "../../../data/expose";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
import { brandsUrl } from "../../../util/brands-url";
import { EntityRegistrySettings } from "../entities/entity-registry-settings";
+import { documentationUrl } from "../../../util/documentation-url";
@customElement("entity-voice-settings")
export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
@property({ attribute: false }) public hass!: HomeAssistant;
- @property({ type: Object }) public entry!: ExtEntityRegistryEntry;
+ @property() public entityId!: string;
+
+ @property({ attribute: false }) public exposed!: ExposeEntitySettings;
+
+ @property({ attribute: false }) public entry?: ExtEntityRegistryEntry;
@state() private _cloudStatus?: CloudStatus;
@@ -63,7 +72,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
if (!isComponentLoaded(this.hass, "cloud")) {
return;
}
- if (changedProps.has("entry") && this.entry) {
+ if (changedProps.has("entityId") && this.entityId) {
this._fetchEntities();
}
if (!this.hasUpdated) {
@@ -77,7 +86,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
try {
const googleEntity = await fetchCloudGoogleEntity(
this.hass,
- this.entry.entity_id
+ this.entityId
);
this._googleEntity = googleEntity;
this.requestUpdate("_googleEntity");
@@ -89,7 +98,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
}
try {
- await fetchCloudAlexaEntity(this.hass, this.entry.entity_id);
+ await fetchCloudAlexaEntity(this.hass, this.entityId);
} catch (err: any) {
if (err.code === "not_supported") {
this._unsupported["cloud.alexa"] = true;
@@ -153,9 +162,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
uiAssistants.splice(uiAssistants.indexOf("cloud.alexa"), 1);
}
- const uiExposed = uiAssistants.some(
- (key) => this.entry.options?.[key]?.should_expose
- );
+ const uiExposed = uiAssistants.some((key) => this.exposed[key]);
let manFilterFuncs:
| {
@@ -171,10 +178,9 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
);
}
- const manExposedAlexa =
- alexaManual && manFilterFuncs!.alexa(this.entry.entity_id);
+ const manExposedAlexa = alexaManual && manFilterFuncs!.alexa(this.entityId);
const manExposedGoogle =
- googleManual && manFilterFuncs!.google(this.entry.entity_id);
+ googleManual && manFilterFuncs!.google(this.entityId);
const anyExposed = uiExposed || manExposedAlexa || manExposedGoogle;
@@ -198,13 +204,14 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
? manExposedAlexa
: googleManual && key === "cloud.google_assistant"
? manExposedGoogle
- : this.entry.options?.[key]?.should_expose;
+ : this.exposed[key];
const manualConfig =
(alexaManual && key === "cloud.alexa") ||
(googleManual && key === "cloud.google_assistant");
const support2fa =
+ this.entry &&
key === "cloud.google_assistant" &&
!googleManual &&
supported &&
@@ -249,7 +256,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
)}
>
@@ -274,12 +281,26 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
${this.hass.localize("ui.dialogs.voice-settings.aliases_description")}
-
+ ${!this.entry
+ ? html`
+ ${this.hass.localize(
+ "ui.dialogs.voice-settings.aliases_no_unique_id",
+ {
+ faq_link: html`${this.hass.localize("ui.dialogs.entity_registry.faq")}`,
+ }
+ )}
+ `
+ : html``}
`;
}
@@ -291,7 +312,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
try {
await updateCloudGoogleEntityConfig(
this.hass,
- this.entry.entity_id,
+ this.entityId,
!ev.target.checked
);
} catch (_err) {
@@ -303,15 +324,11 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
if (!this._aliases) {
return;
}
- const result = await updateEntityRegistryEntry(
- this.hass,
- this.entry.entity_id,
- {
- aliases: this._aliases
- .map((alias) => alias.trim())
- .filter((alias) => alias),
- }
- );
+ const result = await updateEntityRegistryEntry(this.hass, this.entityId, {
+ aliases: this._aliases
+ .map((alias) => alias.trim())
+ .filter((alias) => alias),
+ });
fireEvent(this, "entity-entry-updated", result.entity_entry);
}
@@ -319,14 +336,17 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
exposeEntities(
this.hass,
[ev.target.assistant],
- [this.entry.entity_id],
+ [this.entityId],
ev.target.checked
);
- const entry = await getExtendedEntityRegistryEntry(
- this.hass,
- this.entry.entity_id
- );
- fireEvent(this, "entity-entry-updated", entry);
+ if (this.entry) {
+ const entry = await getExtendedEntityRegistryEntry(
+ this.hass,
+ this.entityId
+ );
+ fireEvent(this, "entity-entry-updated", entry);
+ }
+ fireEvent(this, "exposed-entities-changed");
}
private async _toggleAll(ev) {
@@ -336,17 +356,15 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
? ev.target.assistants.filter((key) => !this._unsupported[key])
: ev.target.assistants;
- exposeEntities(
- this.hass,
- assistants,
- [this.entry.entity_id],
- ev.target.checked
- );
- const entry = await getExtendedEntityRegistryEntry(
- this.hass,
- this.entry.entity_id
- );
- fireEvent(this, "entity-entry-updated", entry);
+ exposeEntities(this.hass, assistants, [this.entityId], ev.target.checked);
+ if (this.entry) {
+ const entry = await getExtendedEntityRegistryEntry(
+ this.hass,
+ this.entityId
+ );
+ fireEvent(this, "entity-entry-updated", entry);
+ }
+ fireEvent(this, "exposed-entities-changed");
}
static get styles(): CSSResultGroup {
diff --git a/src/panels/config/voice-assistants/expose/expose-assistant-icon.ts b/src/panels/config/voice-assistants/expose/expose-assistant-icon.ts
index ed4daf7086..5fe28881aa 100644
--- a/src/panels/config/voice-assistants/expose/expose-assistant-icon.ts
+++ b/src/panels/config/voice-assistants/expose/expose-assistant-icon.ts
@@ -3,7 +3,7 @@ import { mdiAlertCircle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
-import { voiceAssistants } from "../../../../data/voice";
+import { voiceAssistants } from "../../../../data/expose";
import { HomeAssistant } from "../../../../types";
import { brandsUrl } from "../../../../util/brands-url";
import "../../../../components/ha-svg-icon";
diff --git a/src/panels/config/voice-assistants/ha-config-voice-assistants-assistants.ts b/src/panels/config/voice-assistants/ha-config-voice-assistants-assistants.ts
index 137f57a76a..f1cfc8b24e 100644
--- a/src/panels/config/voice-assistants/ha-config-voice-assistants-assistants.ts
+++ b/src/panels/config/voice-assistants/ha-config-voice-assistants-assistants.ts
@@ -1,16 +1,13 @@
-import { consume } from "@lit-labs/context";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
-import { css, html, LitElement, nothing, PropertyValues } from "lit";
-import { customElement, property, state } from "lit/decorators";
+import { css, html, LitElement, nothing } from "lit";
+import { customElement, property } from "lit/decorators";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { computeRTLDirection } from "../../../common/util/compute_rtl";
import { CloudStatus } from "../../../data/cloud";
-import { entitiesContext } from "../../../data/context";
-import {
- ExtEntityRegistryEntry,
- getExtendedEntityRegistryEntries,
-} from "../../../data/entity_registry";
+import { ExposeEntitySettings } from "../../../data/expose";
+
+import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../../types";
import "./assist-pref";
@@ -25,18 +22,17 @@ export class HaConfigVoiceAssistantsAssistants extends LitElement {
@property({ attribute: false }) public cloudStatus?: CloudStatus;
+ @property({ attribute: false }) public exposedEntities?: Record<
+ string,
+ ExposeEntitySettings
+ >;
+
@property() public isWide!: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
- @state()
- @consume({ context: entitiesContext, subscribe: true })
- _entities!: HomeAssistant["entities"];
-
- @state() private _extEntities?: Record;
-
protected render() {
if (!this.hass) {
return html``;
@@ -56,7 +52,7 @@ export class HaConfigVoiceAssistantsAssistants extends LitElement {
`
: nothing}
@@ -64,13 +60,13 @@ export class HaConfigVoiceAssistantsAssistants extends LitElement {
? html`
@@ -81,19 +77,6 @@ export class HaConfigVoiceAssistantsAssistants extends LitElement {
`;
}
- public willUpdate(changedProperties: PropertyValues): void {
- if (changedProperties.has("_entities")) {
- this._fetchExtendedEntities();
- }
- }
-
- private async _fetchExtendedEntities() {
- this._extEntities = await getExtendedEntityRegistryEntries(
- this.hass,
- Object.keys(this._entities)
- );
- }
-
static styles = css`
.content {
padding: 28px 20px 0;
diff --git a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts
index 707e1af95f..23f337086c 100644
--- a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts
+++ b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts
@@ -19,7 +19,8 @@ import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import memoize from "memoize-one";
-import { HASSDomEvent } from "../../../common/dom/fire_event";
+import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
+import { computeStateName } from "../../../common/entity/compute_state_name";
import {
EntityFilter,
generateFilter,
@@ -38,26 +39,28 @@ import { AlexaEntity, fetchCloudAlexaEntities } from "../../../data/alexa";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
import { entitiesContext } from "../../../data/context";
import {
- computeEntityRegistryName,
- EntityRegistryEntry,
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntries,
} from "../../../data/entity_registry";
+import {
+ exposeEntities,
+ ExposeEntitySettings,
+ voiceAssistants,
+} from "../../../data/expose";
import {
fetchCloudGoogleEntities,
GoogleEntity,
} from "../../../data/google_assistant";
-import { exposeEntities, voiceAssistants } from "../../../data/voice";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
+import "./expose/expose-assistant-icon";
import { voiceAssistantTabs } from "./ha-config-voice-assistants";
import { showExposeEntityDialog } from "./show-dialog-expose-entity";
import { showVoiceSettingsDialog } from "./show-dialog-voice-settings";
-import "./expose/expose-assistant-icon";
@customElement("ha-config-voice-assistants-expose")
export class VoiceAssistantsExpose extends LitElement {
@@ -71,6 +74,11 @@ export class VoiceAssistantsExpose extends LitElement {
@property({ attribute: false }) public route!: Route;
+ @property({ attribute: false }) public exposedEntities?: Record<
+ string,
+ ExposeEntitySettings
+ >;
+
@state()
@consume({ context: entitiesContext, subscribe: true })
_entities!: HomeAssistant["entities"];
@@ -256,8 +264,8 @@ export class VoiceAssistantsExpose extends LitElement {
private _filteredEntities = memoize(
(
- entities: HomeAssistant["entities"],
- extEntities: Record | undefined,
+ entities: Record,
+ exposedEntities: Record,
devices: HomeAssistant["devices"],
areas: HomeAssistant["areas"],
cloudStatus: CloudStatus | undefined,
@@ -296,12 +304,11 @@ export class VoiceAssistantsExpose extends LitElement {
const result: Record = {};
- let filteredEntities = Object.values(entities);
+ let filteredEntities = Object.values(this.hass.states);
filteredEntities = filteredEntities.filter((entity) =>
showAssistants.some(
- (assis) =>
- extEntities?.[entity.entity_id].options?.[assis]?.should_expose
+ (assis) => exposedEntities?.[entity.entity_id]?.[assis]
)
);
@@ -317,37 +324,38 @@ export class VoiceAssistantsExpose extends LitElement {
filteredAssistants.some(
(assis) =>
!(assis === "cloud.alexa" && alexaManual) &&
- extEntities?.[entity.entity_id].options?.[assis]?.should_expose
+ exposedEntities?.[entity.entity_id]?.[assis]
)
);
}
});
- for (const entry of filteredEntities) {
- const entity = this.hass.states[entry.entity_id];
- const areaId = entry.area_id ?? devices[entry.device_id!]?.area_id;
+ for (const entityState of filteredEntities) {
+ const entry: ExtEntityRegistryEntry | undefined =
+ entities[entityState.entity_id];
+ const areaId =
+ entry?.area_id ?? entry?.device_id
+ ? devices[entry.device_id!]?.area_id
+ : undefined;
const area = areaId ? areas[areaId] : undefined;
- result[entry.entity_id] = {
- entity_id: entry.entity_id,
- entity,
+ result[entityState.entity_id] = {
+ entity_id: entityState.entity_id,
+ entity: entityState,
name:
- computeEntityRegistryName(
- this.hass!,
- entry as EntityRegistryEntry
- ) ||
+ computeStateName(entityState) ||
this.hass.localize(
"ui.panel.config.entities.picker.unnamed_entity"
),
area: area ? area.name : "—",
assistants: Object.keys(
- extEntities![entry.entity_id].options!
+ exposedEntities?.[entityState.entity_id]
).filter(
(key) =>
showAssistants.includes(key) &&
- extEntities![entry.entity_id].options![key]?.should_expose
+ exposedEntities?.[entityState.entity_id]?.[key]
),
- aliases: extEntities?.[entry.entity_id].aliases,
+ aliases: entry?.aliases || [],
};
}
@@ -358,7 +366,7 @@ export class VoiceAssistantsExpose extends LitElement {
(this.cloudStatus as CloudStatusLoggedIn).google_entities,
(this.cloudStatus as CloudStatusLoggedIn).alexa_entities
);
- Object.keys(entities).forEach((entityId) => {
+ Object.keys(this.hass.states).forEach((entityId) => {
const assistants: string[] = [];
if (
alexaManual &&
@@ -383,30 +391,33 @@ export class VoiceAssistantsExpose extends LitElement {
result[entityId].assistants.push(...assistants);
result[entityId].manAssistants = assistants;
} else {
- const entry = entities[entityId];
- const areaId = entry.area_id ?? devices[entry.device_id!]?.area_id;
+ const entityState = this.hass.states[entityId];
+ const entry: ExtEntityRegistryEntry | undefined =
+ entities[entityId];
+ const areaId =
+ entry?.area_id ?? entry?.device_id
+ ? devices[entry.device_id!]?.area_id
+ : undefined;
const area = areaId ? areas[areaId] : undefined;
result[entityId] = {
- entity_id: entry.entity_id,
- entity: this.hass.states[entityId],
- name: computeEntityRegistryName(
- this.hass!,
- entry as EntityRegistryEntry
- ),
+ entity_id: entityState.entity_id,
+ entity: entityState,
+ name: computeStateName(entityState),
area: area ? area.name : "—",
assistants: [
- ...(extEntities
- ? Object.keys(extEntities[entry.entity_id].options!).filter(
+ ...(exposedEntities
+ ? Object.keys(
+ exposedEntities?.[entityState.entity_id]
+ ).filter(
(key) =>
showAssistants.includes(key) &&
- extEntities[entry.entity_id].options![key]
- ?.should_expose
+ exposedEntities?.[entityState.entity_id]?.[key]
)
: []),
...assistants,
],
manAssistants: assistants,
- aliases: extEntities?.[entityId].aliases,
+ aliases: entry?.aliases || [],
};
}
});
@@ -468,14 +479,14 @@ export class VoiceAssistantsExpose extends LitElement {
}
protected render() {
- if (!this.hass || this.hass.entities === undefined) {
+ if (!this.hass || !this.exposedEntities || !this._extEntities) {
return html``;
}
const activeFilters = this._activeFilters(this._searchParms);
const filteredEntities = this._filteredEntities(
- this._entities,
this._extEntities,
+ this.exposedEntities,
this.hass.devices,
this.hass.areas,
this.cloudStatus,
@@ -621,9 +632,11 @@ export class VoiceAssistantsExpose extends LitElement {
: this._availableAssistants(this.cloudStatus);
showExposeEntityDialog(this, {
filterAssistants: assistants,
- extendedEntities: this._extEntities!,
+ exposedEntities: this.exposedEntities!,
exposeEntities: (entities) => {
- exposeEntities(this.hass, assistants, entities, true);
+ exposeEntities(this.hass, assistants, entities, true).then(() =>
+ fireEvent(this, "exposed-entities-changed")
+ );
},
});
}
@@ -645,7 +658,9 @@ export class VoiceAssistantsExpose extends LitElement {
const assistants = this._searchParms.has("assistants")
? this._searchParms.get("assistants")!.split(",")
: this._availableAssistants(this.cloudStatus);
- exposeEntities(this.hass, assistants, [entityId], false);
+ exposeEntities(this.hass, assistants, [entityId], false).then(() =>
+ fireEvent(this, "exposed-entities-changed")
+ );
};
private _unexposeSelected() {
@@ -670,7 +685,12 @@ export class VoiceAssistantsExpose extends LitElement {
),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: () => {
- exposeEntities(this.hass, assistants, this._selectedEntities, false);
+ exposeEntities(
+ this.hass,
+ assistants,
+ this._selectedEntities,
+ false
+ ).then(() => fireEvent(this, "exposed-entities-changed"));
this._clearSelection();
},
});
@@ -698,7 +718,12 @@ export class VoiceAssistantsExpose extends LitElement {
),
dismissText: this.hass.localize("ui.common.cancel"),
confirm: () => {
- exposeEntities(this.hass, assistants, this._selectedEntities, true);
+ exposeEntities(
+ this.hass,
+ assistants,
+ this._selectedEntities,
+ true
+ ).then(() => fireEvent(this, "exposed-entities-changed"));
this._clearSelection();
},
});
@@ -710,7 +735,14 @@ export class VoiceAssistantsExpose extends LitElement {
private _openEditEntry(ev: CustomEvent): void {
const entityId = (ev.detail as RowClickedEvent).id;
- showVoiceSettingsDialog(this, { entityId });
+ showVoiceSettingsDialog(this, {
+ entityId,
+ exposed: this.exposedEntities![entityId],
+ extEntityReg: this._extEntities?.[entityId],
+ exposedEntitiesChanged: () => {
+ fireEvent(this, "exposed-entities-changed");
+ },
+ });
}
private _clearFilter() {
diff --git a/src/panels/config/voice-assistants/ha-config-voice-assistants.ts b/src/panels/config/voice-assistants/ha-config-voice-assistants.ts
index fe35b6dd78..2ea1fc334c 100644
--- a/src/panels/config/voice-assistants/ha-config-voice-assistants.ts
+++ b/src/panels/config/voice-assistants/ha-config-voice-assistants.ts
@@ -1,11 +1,18 @@
+import { consume } from "@lit-labs/context";
import { mdiDevices, mdiMicrophone } from "@mdi/js";
-import { customElement, property } from "lit/decorators";
+import { PropertyValues } from "lit";
+import { customElement, property, state } from "lit/decorators";
+import { CloudStatus } from "../../../data/cloud";
+import { entitiesContext } from "../../../data/context";
+import {
+ ExposeEntitySettings,
+ listExposedEntities,
+} from "../../../data/expose";
import {
HassRouterPage,
RouterOptions,
} from "../../../layouts/hass-router-page";
import { HomeAssistant } from "../../../types";
-import { CloudStatus } from "../../../data/cloud";
export const voiceAssistantTabs = [
{
@@ -30,6 +37,28 @@ class HaConfigVoiceAssistants extends HassRouterPage {
@property() public isWide!: boolean;
+ @state()
+ @consume({ context: entitiesContext, subscribe: true })
+ _entities!: HomeAssistant["entities"];
+
+ @state() private _exposedEntities?: Record;
+
+ public connectedCallback(): void {
+ super.connectedCallback();
+ this.addEventListener(
+ "exposed-entities-changed",
+ this._fetchExposedEntities
+ );
+ }
+
+ public disconnectedCallback(): void {
+ super.connectedCallback();
+ this.removeEventListener(
+ "exposed-entities-changed",
+ this._fetchExposedEntities
+ );
+ }
+
protected routerOptions: RouterOptions = {
defaultPage: "assistants",
routes: {
@@ -55,11 +84,30 @@ class HaConfigVoiceAssistants extends HassRouterPage {
pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.route = this.routeTail;
+ pageEl.exposedEntities = this._exposedEntities;
}
+
+ public willUpdate(changedProperties: PropertyValues): void {
+ if (changedProperties.has("_entities")) {
+ this._fetchExposedEntities();
+ }
+ }
+
+ private _fetchExposedEntities = async () => {
+ this._exposedEntities = (
+ await listExposedEntities(this.hass)
+ ).exposed_entities;
+ if (this.lastChild) {
+ (this.lastChild as any).exposedEntities = this._exposedEntities;
+ }
+ };
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-voice-assistants": HaConfigVoiceAssistants;
}
+ interface HASSDomEvents {
+ "exposed-entities-changed": undefined;
+ }
}
diff --git a/src/panels/config/voice-assistants/show-dialog-expose-entity.ts b/src/panels/config/voice-assistants/show-dialog-expose-entity.ts
index acb406e711..4a9a992363 100644
--- a/src/panels/config/voice-assistants/show-dialog-expose-entity.ts
+++ b/src/panels/config/voice-assistants/show-dialog-expose-entity.ts
@@ -1,9 +1,9 @@
import { fireEvent } from "../../../common/dom/fire_event";
-import { ExtEntityRegistryEntry } from "../../../data/entity_registry";
+import { ExposeEntitySettings } from "../../../data/expose";
export interface ExposeEntityDialogParams {
filterAssistants: string[];
- extendedEntities: Record;
+ exposedEntities: Record;
exposeEntities: (entities: string[]) => void;
}
diff --git a/src/panels/config/voice-assistants/show-dialog-voice-settings.ts b/src/panels/config/voice-assistants/show-dialog-voice-settings.ts
index 1acc94bb10..7a03951272 100644
--- a/src/panels/config/voice-assistants/show-dialog-voice-settings.ts
+++ b/src/panels/config/voice-assistants/show-dialog-voice-settings.ts
@@ -1,7 +1,12 @@
import { fireEvent } from "../../../common/dom/fire_event";
+import { ExtEntityRegistryEntry } from "../../../data/entity_registry";
+import { ExposeEntitySettings } from "../../../data/expose";
export interface VoiceSettingsDialogParams {
entityId: string;
+ exposed: ExposeEntitySettings;
+ extEntityReg?: ExtEntityRegistryEntry;
+ exposedEntitiesChanged?: () => void;
}
export const loadVoiceSettingsDialog = () => import("./dialog-voice-settings");
diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts
index 02b18b6122..288646a280 100644
--- a/src/panels/lovelace/hui-root.ts
+++ b/src/panels/lovelace/hui-root.ts
@@ -1052,10 +1052,6 @@ class HUIRoot extends LitElement {
#view {
position: relative;
display: flex;
- background: var(
- --lovelace-background,
- var(--primary-background-color)
- );
padding-top: calc(var(--header-height) + env(safe-area-inset-top));
min-height: 100vh;
box-sizing: border-box;
@@ -1063,8 +1059,13 @@ class HUIRoot extends LitElement {
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
}
- hui-view,
- hui-unused-entities {
+ hui-view {
+ background: var(
+ --lovelace-background,
+ var(--primary-background-color)
+ );
+ }
+ #view > * {
flex: 1 1 100%;
max-width: 100%;
}
diff --git a/src/translations/en.json b/src/translations/en.json
index fa194a5049..6bdbb08f21 100755
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -838,7 +838,10 @@
"input_label": "Enter a request",
"send_text": "Send text",
"start_listening": "Start listening",
- "manage_assistants": "Manage assistants"
+ "manage_assistants": "Manage assistants",
+ "not_supported_microphone": "Microphone is not supported. You need to access Home Assistant from a secure URL (HTTPS) to use it.",
+ "not_supported_microphone_documentation": "Visit {documentation_link} to learn how to use a secure URL",
+ "not_supported_microphone_documentation_link": "the documentation"
},
"generic": {
"cancel": "Cancel",
@@ -1082,6 +1085,7 @@
"expose_header": "Expose",
"aliases_header": "Aliases",
"aliases_description": "Aliases are supported by Assist and Google Assistant.",
+ "aliases_no_unique_id": "Aliases are not supported for entities without an unique id. See the {faq_link} for more detail.",
"ask_pin": "Ask for PIN",
"manual_config": "Managed in configuration.yaml",
"unsupported": "Unsupported"