mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
20230502.0 (#16382)
This commit is contained in:
commit
29aa762f7c
@ -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`<hass-subpage
|
||||
@ -44,7 +49,7 @@ class HassioDashboard extends LitElement {
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiStorePlus}
|
||||
></ha-svg-icon> </ha-fab
|
||||
></ha-svg-icon></ha-fab
|
||||
></a>
|
||||
</hass-subpage>`;
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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`
|
||||
<div class="layout horizontal center-center row">
|
||||
<ha-textfield
|
||||
.disabled=${this.disabled}
|
||||
dialogInitialFocus=${index}
|
||||
.index=${index}
|
||||
class="flex-auto"
|
||||
@ -37,6 +40,7 @@ class AliasesEditor extends LitElement {
|
||||
@keydown=${this._keyDownAlias}
|
||||
></ha-textfield>
|
||||
<ha-icon-button
|
||||
.disabled=${this.disabled}
|
||||
.index=${index}
|
||||
slot="navigationIcon"
|
||||
label=${this.hass!.localize("ui.dialogs.aliases.remove_alias", {
|
||||
@ -49,7 +53,7 @@ class AliasesEditor extends LitElement {
|
||||
`
|
||||
)}
|
||||
<div class="layout horizontal center-center">
|
||||
<mwc-button @click=${this._addAlias}>
|
||||
<mwc-button @click=${this._addAlias} .disabled=${this.disabled}>
|
||||
${this.hass!.localize("ui.dialogs.aliases.add_alias")}
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-button>
|
||||
|
@ -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 {
|
||||
|
@ -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`
|
||||
<div class="cloud-options">
|
||||
<ha-select
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
<ha-language-picker
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.media-browser.tts.language"
|
||||
)}
|
||||
.value=${selectedVoice[0]}
|
||||
@selected=${this._handleLanguageChange}
|
||||
.languages=${languages}
|
||||
@closed=${stopPropagation}
|
||||
@value-changed=${this._handleLanguageChange}
|
||||
>
|
||||
${languages.map(
|
||||
([key, label]) =>
|
||||
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
||||
)}
|
||||
</ha-select>
|
||||
</ha-language-picker>
|
||||
|
||||
<ha-select
|
||||
fixedMenuPosition
|
||||
@ -184,10 +181,10 @@ class BrowseMediaTTS extends LitElement {
|
||||
}
|
||||
|
||||
async _handleLanguageChange(ev) {
|
||||
if (ev.target.value === this._cloudOptions![0]) {
|
||||
if (ev.detail.value === this._cloudOptions![0]) {
|
||||
return;
|
||||
}
|
||||
this._cloudOptions = [ev.target.value, this._cloudOptions![1]];
|
||||
this._cloudOptions = [ev.detail.value, this._cloudOptions![1]];
|
||||
}
|
||||
|
||||
async _handleGenderChange(ev) {
|
||||
@ -256,7 +253,8 @@ class BrowseMediaTTS extends LitElement {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.cloud-options ha-select {
|
||||
.cloud-options ha-select,
|
||||
ha-language-picker {
|
||||
width: 48%;
|
||||
}
|
||||
ha-textarea {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { translationMetadata } from "../../resources/translations-metadata";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
export interface CloudTTSInfo {
|
||||
@ -11,7 +10,7 @@ export const getCloudTTSInfo = (hass: HomeAssistant) =>
|
||||
hass.callWS<CloudTTSInfo>({ 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 = (
|
||||
|
@ -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<string, ExposeEntitySettings> }>({
|
||||
type: "homeassistant/expose_entity/list",
|
||||
});
|
@ -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`<entity-voice-settings
|
||||
.hass=${this.hass}
|
||||
.entityId=${this.entry.entity_id}
|
||||
.entry=${this.entry}
|
||||
.exposed=${this._calculateExposed(this.entry)}
|
||||
></entity-voice-settings>`;
|
||||
}
|
||||
|
||||
|
@ -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`
|
||||
<div class="content">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.no_unique_id",
|
||||
"entity_id",
|
||||
this.entityId,
|
||||
"faq_link",
|
||||
html`<a
|
||||
href=${documentationUrl(this.hass, "/faq/unique_id")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize("ui.dialogs.entity_registry.faq")}</a
|
||||
>`
|
||||
)}
|
||||
<ha-alert alert-type="warning">
|
||||
${this.hass.localize("ui.dialogs.entity_registry.no_unique_id", {
|
||||
entity_id: this.entityId,
|
||||
faq_link: html`<a
|
||||
href=${documentationUrl(this.hass, "/faq/unique_id")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize("ui.dialogs.entity_registry.faq")}</a
|
||||
>`,
|
||||
})}
|
||||
</ha-alert>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -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`
|
||||
<ha-dialog
|
||||
open
|
||||
@ -150,10 +155,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
.hasMeta=${pipeline.id === this._preferredPipeline}
|
||||
>
|
||||
${pipeline.name}${pipeline.id === this._preferredPipeline
|
||||
? html`<ha-svg-icon
|
||||
slot="meta"
|
||||
.path=${mdiStar}
|
||||
></ha-svg-icon>`
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
slot="meta"
|
||||
.path=${mdiStar}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: nothing}
|
||||
</ha-list-item>`
|
||||
)}
|
||||
@ -203,7 +210,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
iconTrailing
|
||||
>
|
||||
<span slot="trailingIcon">
|
||||
${this._showSendButton
|
||||
${this._showSendButton || !supportsSTT
|
||||
? html`
|
||||
<ha-icon-button
|
||||
class="listening-icon"
|
||||
@ -215,8 +222,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: supportsSTT
|
||||
? html`
|
||||
: html`
|
||||
${this._audioRecorder?.active
|
||||
? html`
|
||||
<div class="bouncer">
|
||||
@ -224,18 +230,27 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
<div class="double-bounce2"></div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<ha-icon-button
|
||||
class="listening-icon"
|
||||
.path=${mdiMicrophone}
|
||||
@click=${this._toggleListening}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.voice_command.start_listening"
|
||||
)}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
|
||||
<div class="listening-icon">
|
||||
<ha-icon-button
|
||||
.path=${mdiMicrophone}
|
||||
@click=${this._toggleListening}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.voice_command.start_listening"
|
||||
)}
|
||||
>
|
||||
</ha-icon-button>
|
||||
${!supportsMicrophone
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${mdiAlertCircle}
|
||||
class="unsupported"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: null}
|
||||
</div>
|
||||
`}
|
||||
</span>
|
||||
</ha-textfield>
|
||||
${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`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.voice_command.not_supported_microphone"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.voice_command.not_supported_microphone_documentation",
|
||||
{
|
||||
documentation_link: html`
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/configuration/securing/#remote-access"
|
||||
)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.voice_command.not_supported_microphone_documentation_link"
|
||||
)}
|
||||
</a>
|
||||
`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
`,
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -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";
|
||||
|
@ -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));
|
||||
|
@ -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"'
|
||||
)}
|
||||
<br /><br />
|
||||
<div class="row">
|
||||
<ha-language-picker
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.tts.default_language"
|
||||
)}
|
||||
.disabled=${this.savingPreferences}
|
||||
.value=${defaultVoice[0]}
|
||||
.languages=${languages}
|
||||
@value-changed=${this._handleLanguageChange}
|
||||
>
|
||||
</ha-language-picker>
|
||||
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.tts.default_language"
|
||||
)}
|
||||
.disabled=${this.savingPreferences}
|
||||
.value=${defaultVoice[0]}
|
||||
@selected=${this._handleLanguageChange}
|
||||
>
|
||||
${languages.map(
|
||||
([key, label]) =>
|
||||
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
||||
)}
|
||||
</ha-select>
|
||||
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.tts.default_gender"
|
||||
)}
|
||||
.disabled=${this.savingPreferences}
|
||||
.value=${defaultVoice[1]}
|
||||
@selected=${this._handleGenderChange}
|
||||
>
|
||||
${genders.map(
|
||||
([key, label]) =>
|
||||
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
||||
)}
|
||||
</ha-select>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.cloud.account.tts.default_gender"
|
||||
)}
|
||||
.disabled=${this.savingPreferences}
|
||||
.value=${defaultVoice[1]}
|
||||
@selected=${this._handleGenderChange}
|
||||
>
|
||||
${genders.map(
|
||||
([key, label]) =>
|
||||
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
||||
)}
|
||||
</ha-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._openTryDialog}>
|
||||
@ -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;
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -174,9 +174,11 @@ export class HaIntegrationCard extends LitElement {
|
||||
${this.items.map(
|
||||
(item) =>
|
||||
html`<ha-list-item
|
||||
dense
|
||||
hasMeta
|
||||
.entryId=${item.entry_id}
|
||||
@click=${this._selectConfigEntry}
|
||||
class="config-entry"
|
||||
>${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;
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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<ReturnType<typeof this._schema>>
|
||||
): 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`
|
||||
|
@ -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<string, ExtEntityRegistryEntry>;
|
||||
@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<string, ExtEntityRegistryEntry>) =>
|
||||
Object.values(extEntities).filter(
|
||||
(entity) => entity.options?.conversation?.should_expose
|
||||
).length
|
||||
private _exposedEntitiesCount = memoizeOne(
|
||||
(exposedEntities: Record<string, ExposeEntitySettings>) =>
|
||||
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,
|
||||
}
|
||||
)}
|
||||
|
@ -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<string, ExtEntityRegistryEntry>;
|
||||
@property({ attribute: false }) public exposedEntities?: Record<
|
||||
string,
|
||||
ExposeEntitySettings
|
||||
>;
|
||||
|
||||
@property() public cloudStatus?: CloudStatusLoggedIn;
|
||||
|
||||
@state() private _exposeNew?: boolean;
|
||||
|
||||
private _exposedEntities = memoizeOne(
|
||||
(extEntities: Record<string, ExtEntityRegistryEntry>) =>
|
||||
Object.values(extEntities).filter(
|
||||
(entity) => entity.options?.["cloud.alexa"]?.should_expose
|
||||
).length
|
||||
private _exposedEntitiesCount = memoizeOne(
|
||||
(exposedEntities: Record<string, ExposeEntitySettings>) =>
|
||||
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,
|
||||
}
|
||||
)}
|
||||
|
@ -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<string, ExtEntityRegistryEntry>;
|
||||
@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<string, ExtEntityRegistryEntry>) =>
|
||||
Object.values(extEntities).filter(
|
||||
(entity) => entity.options?.["cloud.google_assistant"]?.should_expose
|
||||
private _exposedEntitiesCount = memoizeOne(
|
||||
(exposedEntities: Record<string, ExposeEntitySettings>) =>
|
||||
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,
|
||||
}
|
||||
)}
|
||||
|
@ -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<string, ExtEntityRegistryEntry>, filter?: string) => {
|
||||
(
|
||||
exposedEntities: Record<string, ExposeEntitySettings>,
|
||||
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`
|
||||
<ha-check-list-item
|
||||
graphic="icon"
|
||||
twoLine
|
||||
.value=${entity.entity_id}
|
||||
.selected=${this._selected.includes(entity.entity_id)}
|
||||
@request-selected=${this._handleSelected}
|
||||
>
|
||||
<ha-state-icon
|
||||
title=${ifDefined(entityState?.state)}
|
||||
slot="graphic"
|
||||
.state=${entityState}
|
||||
></ha-state-icon>
|
||||
${computeEntityRegistryName(this.hass!, entity)}
|
||||
<span slot="secondary">${entity.entity_id}</span>
|
||||
</ha-check-list-item>
|
||||
`;
|
||||
};
|
||||
private _renderItem = (entityState: HassEntity) => html`
|
||||
<ha-check-list-item
|
||||
graphic="icon"
|
||||
twoLine
|
||||
.value=${entityState.entity_id}
|
||||
.selected=${this._selected.includes(entityState.entity_id)}
|
||||
@request-selected=${this._handleSelected}
|
||||
>
|
||||
<ha-state-icon
|
||||
title=${ifDefined(entityState?.state)}
|
||||
slot="graphic"
|
||||
.state=${entityState}
|
||||
></ha-state-icon>
|
||||
${computeStateName(entityState)}
|
||||
<span slot="secondary">${entityState.entity_id}</span>
|
||||
</ha-check-list-item>
|
||||
`;
|
||||
|
||||
private _expose() {
|
||||
this._params!.exposeEntities(this._selected);
|
||||
|
@ -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;
|
||||
|
@ -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<void> {
|
||||
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")
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<entity-voice-settings
|
||||
.hass=${this.hass}
|
||||
.entry=${this._extEntityReg}
|
||||
.entityId=${this._params.entityId}
|
||||
.entry=${this._params.extEntityReg}
|
||||
.exposed=${this._params.exposed}
|
||||
@entity-entry-updated=${this._entityEntryUpdated}
|
||||
@exposed-entities-changed=${this._exposedEntitiesChanged}
|
||||
></entity-voice-settings>
|
||||
</div>
|
||||
</ha-dialog>
|
||||
@ -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 {
|
||||
|
@ -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) {
|
||||
)}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${!this.entry.options?.[key]?.disable_2fa}
|
||||
.checked=${!this.entry!.options?.[key]?.disable_2fa}
|
||||
@change=${this._2faChanged}
|
||||
></ha-checkbox>
|
||||
</ha-formfield>
|
||||
@ -274,12 +281,26 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
|
||||
${this.hass.localize("ui.dialogs.voice-settings.aliases_description")}
|
||||
</p>
|
||||
|
||||
<ha-aliases-editor
|
||||
.hass=${this.hass}
|
||||
.aliases=${this._aliases ?? this.entry.aliases}
|
||||
@value-changed=${this._aliasesChanged}
|
||||
@blur=${this._saveAliases}
|
||||
></ha-aliases-editor>
|
||||
${!this.entry
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.voice-settings.aliases_no_unique_id",
|
||||
{
|
||||
faq_link: html`<a
|
||||
href=${documentationUrl(this.hass, "/faq/unique_id")}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize("ui.dialogs.entity_registry.faq")}</a
|
||||
>`,
|
||||
}
|
||||
)}
|
||||
</ha-alert>`
|
||||
: html`<ha-aliases-editor
|
||||
.hass=${this.hass}
|
||||
.aliases=${this._aliases ?? this.entry.aliases}
|
||||
@value-changed=${this._aliasesChanged}
|
||||
@blur=${this._saveAliases}
|
||||
></ha-aliases-editor>`}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
@ -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";
|
||||
|
@ -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<string, ExtEntityRegistryEntry>;
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||
@ -56,7 +52,7 @@ export class HaConfigVoiceAssistantsAssistants extends LitElement {
|
||||
<assist-pref
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
.extEntities=${this._extEntities}
|
||||
.exposedEntities=${this.exposedEntities}
|
||||
></assist-pref>
|
||||
`
|
||||
: nothing}
|
||||
@ -64,13 +60,13 @@ export class HaConfigVoiceAssistantsAssistants extends LitElement {
|
||||
? html`
|
||||
<cloud-alexa-pref
|
||||
.hass=${this.hass}
|
||||
.extEntities=${this._extEntities}
|
||||
.exposedEntities=${this.exposedEntities}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${computeRTLDirection(this.hass)}
|
||||
></cloud-alexa-pref>
|
||||
<cloud-google-pref
|
||||
.hass=${this.hass}
|
||||
.extEntities=${this._extEntities}
|
||||
.exposedEntities=${this.exposedEntities}
|
||||
.cloudStatus=${this.cloudStatus}
|
||||
dir=${computeRTLDirection(this.hass)}
|
||||
></cloud-google-pref>
|
||||
@ -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;
|
||||
|
@ -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<string, ExtEntityRegistryEntry> | undefined,
|
||||
entities: Record<string, ExtEntityRegistryEntry>,
|
||||
exposedEntities: Record<string, ExposeEntitySettings>,
|
||||
devices: HomeAssistant["devices"],
|
||||
areas: HomeAssistant["areas"],
|
||||
cloudStatus: CloudStatus | undefined,
|
||||
@ -296,12 +304,11 @@ export class VoiceAssistantsExpose extends LitElement {
|
||||
|
||||
const result: Record<string, DataTableRowData> = {};
|
||||
|
||||
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`<hass-loading-screen></hass-loading-screen>`;
|
||||
}
|
||||
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() {
|
||||
|
@ -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<string, ExposeEntitySettings>;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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<string, ExtEntityRegistryEntry>;
|
||||
exposedEntities: Record<string, ExposeEntitySettings>;
|
||||
exposeEntities: (entities: string[]) => void;
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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%;
|
||||
}
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user