diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 518e554e4a..b489ba505e 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -666,6 +666,7 @@ export class HaDataTable extends LitElement { .mdc-data-table__cell.mdc-data-table__cell--flex { display: flex; + overflow: initial; } .mdc-data-table__cell.mdc-data-table__cell--icon { diff --git a/src/panels/config/voice-assistants/expose/expose-assistant-icon.ts b/src/panels/config/voice-assistants/expose/expose-assistant-icon.ts new file mode 100644 index 0000000000..ed4daf7086 --- /dev/null +++ b/src/panels/config/voice-assistants/expose/expose-assistant-icon.ts @@ -0,0 +1,102 @@ +import "@lrnwebcomponents/simple-tooltip/simple-tooltip"; +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 { HomeAssistant } from "../../../../types"; +import { brandsUrl } from "../../../../util/brands-url"; +import "../../../../components/ha-svg-icon"; + +@customElement("voice-assistants-expose-assistant-icon") +export class VoiceAssistantExposeAssistantIcon extends LitElement { + @property() public hass!: HomeAssistant; + + @property({ type: Boolean }) public unsupported!: boolean; + + @property({ type: Boolean }) public manual?: boolean; + + @property() public assistant?: + | "conversation" + | "cloud.alexa" + | "cloud.google_assistant"; + + render() { + if (!this.assistant || !voiceAssistants[this.assistant]) return nothing; + + return html` +
+ + ${this.unsupported + ? html` + + ` + : nothing} + ${this.manual || this.unsupported + ? html` + + ${this.unsupported + ? this.hass.localize( + "ui.panel.config.voice_assistants.expose.not_supported" + ) + : ""} + ${this.unsupported && this.manual ? html`
` : nothing} + ${this.manual + ? this.hass.localize( + "ui.panel.config.voice_assistants.expose.manually_configured" + ) + : nothing} +
+ ` + : ""} +
+ `; + } + + static get styles(): CSSResultGroup { + return css` + .container { + position: relative; + } + .logo { + position: relative; + height: 24px; + margin-right: 16px; + } + .unsupported { + color: var(--error-color); + position: absolute; + --mdc-icon-size: 16px; + right: 10px; + top: -7px; + } + `; + } +} + +declare global { + interface HTMLElementTagNameMap { + "voice-assistants-expose-assistant-icon": VoiceAssistantExposeAssistantIcon; + } +} 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 9199e121c5..707e1af95f 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 @@ -18,7 +18,6 @@ import { import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { ifDefined } from "lit/directives/if-defined"; -import { styleMap } from "lit/directives/style-map"; import memoize from "memoize-one"; import { HASSDomEvent } from "../../../common/dom/fire_event"; import { @@ -35,6 +34,7 @@ import { SelectionChangedEvent, } from "../../../components/data-table/ha-data-table"; import "../../../components/ha-fab"; +import { AlexaEntity, fetchCloudAlexaEntities } from "../../../data/alexa"; import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud"; import { entitiesContext } from "../../../data/context"; import { @@ -43,6 +43,10 @@ import { ExtEntityRegistryEntry, getExtendedEntityRegistryEntries, } from "../../../data/entity_registry"; +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"; @@ -50,10 +54,10 @@ 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 { brandsUrl } from "../../../util/brands-url"; 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 { @@ -81,6 +85,11 @@ export class VoiceAssistantsExpose extends LitElement { @state() private _selectedEntities: string[] = []; + @state() private _supportedEntities?: Record< + "cloud.google_assistant" | "cloud.alexa" | "conversation", + string[] | undefined + >; + @query("hass-tabs-subpage-data-table", true) private _dataTable!: HaTabsSubpageDataTable; @@ -147,35 +156,23 @@ export class VoiceAssistantsExpose extends LitElement { width: "160px", type: "flex", template: (assistants, entry) => - html`${availableAssistants.map((key) => - assistants.includes(key) - ? html`
- ${entry.manAssistants?.includes(key) - ? html` - Configured in YAML, not editable in UI - ` - : ""} -
` - : html`
` - )}`, + html`${availableAssistants.map((key) => { + const supported = + !this._supportedEntities?.[key] || + this._supportedEntities[key].includes(entry.entity_id); + const manual = entry.manAssistants?.includes(key); + return assistants.includes(key) + ? html` + + + ` + : html`
`; + })}`, }, aliases: { title: this.hass.localize( @@ -437,16 +434,36 @@ export class VoiceAssistantsExpose extends LitElement { }); } - private async _fetchExtendedEntities() { + private async _fetchEntities() { this._extEntities = await getExtendedEntityRegistryEntries( this.hass, Object.keys(this._entities) ); + let alexaEntitiesProm: Promise | undefined; + let googleEntitiesProm: Promise | undefined; + if (this.cloudStatus?.logged_in && this.cloudStatus.prefs.alexa_enabled) { + alexaEntitiesProm = fetchCloudAlexaEntities(this.hass); + } + if (this.cloudStatus?.logged_in && this.cloudStatus.prefs.google_enabled) { + googleEntitiesProm = fetchCloudGoogleEntities(this.hass); + } + const [alexaEntities, googleEntities] = await Promise.all([ + alexaEntitiesProm, + googleEntitiesProm, + ]); + this._supportedEntities = { + "cloud.alexa": alexaEntities?.map((entity) => entity.entity_id), + "cloud.google_assistant": googleEntities?.map( + (entity) => entity.entity_id + ), + // TODO add supported entity for assit + conversation: undefined, + }; } public willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has("_entities")) { - this._fetchExtendedEntities(); + this._fetchEntities(); } } diff --git a/src/translations/en.json b/src/translations/en.json index 573ed1bcdb..7b359c878c 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2108,6 +2108,8 @@ "expose_confirm_text": "Do you want to expose {entities} entities to {assistants}?", "unexpose_confirm_title": "Stop exposing selected entities?", "unexpose_confirm_text": "Do you want to stop exposing {entities} entities to {assistants}?", + "manually_configured": "Configured in YAML, not editable in UI", + "not_supported": "Not supported by this assistant", "expose_dialog": { "header": "Expose entities", "expose_to": "to {assistants}",