Compare commits

...

26 Commits

Author SHA1 Message Date
Bram Kragten
728b16918b rtl 2023-05-02 14:44:22 +02:00
Bram Kragten
0e071a8b7e disabled microphone button when not supported 2023-05-02 14:34:47 +02:00
Paul Bottein
15eab18e07 Reduce height for config entry list in integration page (#16370) 2023-05-02 11:12:13 +02:00
Paul Bottein
c8e0227a5c Fix dashboard theme background (#16368) 2023-05-02 11:11:44 +02:00
Bram Kragten
f2a8528429 Use language picker for cloud tts language (#16363)
* Use language picker for cloud tts language

* Update ha-browse-media-tts.ts
2023-05-02 10:49:01 +02:00
Steve Repsher
3ed3dab0a1 Clean up imports for some layouts (#16365) 2023-05-01 20:34:21 +02:00
Bram Kragten
f99f554f19 Bumped version to 20230501.0 2023-05-01 19:54:40 +02:00
Bram Kragten
e069b5eed1 Update en.json 2023-05-01 17:15:26 +02:00
Allen Porter
3a481ebb1a Fix edits for single instance of all day recurring event (#16354) 2023-05-01 14:48:57 +02:00
Steve Repsher
a209fadf18 List Core JS polyfills for browserslist environments (#16356) 2023-05-01 14:47:57 +02:00
Bram Kragten
9f1bd1e085 Fix unused entities (#16359) 2023-05-01 14:46:54 +02:00
Bram Kragten
edc6da04f7 Make sure stt_binary_handler_id is reset (#16360) 2023-05-01 14:46:44 +02:00
Bram Kragten
2fb1dd0ec1 Add icon at unsupported message in voice settings (#16358) 2023-05-01 14:28:26 +02:00
Paul Bottein
3f2aac0842 Fix cloud subscription message in pipeline creation (#16348) 2023-04-28 17:03:34 -04:00
Paul Bottein
d1877595a5 Bumped version to 20230428.0 2023-04-28 17:19:43 +02:00
Paul Bottein
6379713f57 Fix drag and drop with sortablejs (#16343) 2023-04-28 14:40:01 +02:00
Paul Bottein
3b33195ff6 Add camera view support to image element (#16346) 2023-04-28 14:17:33 +02:00
c0ffeeca7
c7f1f1bcd1 Fix typo (#16341)
* Fix typo

* Fix typo
2023-04-28 08:01:24 +00:00
Paul Bottein
fac4795f14 Bumped version to 20230427.0 2023-04-27 17:16:04 +02:00
Bram Kragten
062e402ef1 Show if entity is supported in expose list (#16335)
* Show if entity is supported in expose list

* Add translations and refactor code

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2023-04-27 17:12:12 +02:00
Bram Kragten
29be64a858 Add not supported warning in entity voice settings (#16336)
* Add not supported warning in entity voice settings

* Add alexa support and improve style

* Only toggle supported

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2023-04-27 17:11:49 +02:00
Paul Bottein
b3b74b8328 Move debug and preferred button to top (#16337) 2023-04-27 15:02:57 +02:00
Paul Bottein
37ba34cb0d Virtualize the add exposed entity list (#16333) 2023-04-27 13:22:37 +02:00
dependabot[bot]
8ecdde3507 Bump yaml from 2.2.1 to 2.2.2 (#16310) 2023-04-27 10:12:03 +02:00
Paul Bottein
04d34aa80c Improve search and filtering in expose entity page (#16330) 2023-04-27 09:50:19 +02:00
J. Nick Koston
26bb1ba146 Revert "Avoid fetching unused stats state column for more info" (#16328)
Revert "Avoid fetching unused stats state column for more info (#16141)"

This reverts commit 49a14a7265.
2023-04-27 09:31:06 +02:00
37 changed files with 632 additions and 266 deletions

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env node
// Script to print Babel plugins and Core JS polyfills that will be used by browserslist environments
import { version as babelVersion } from "@babel/core";
import presetEnv from "@babel/preset-env";
import compilationTargets from "@babel/helper-compilation-targets";
import coreJSCompat from "core-js-compat";
import { logPlugin } from "@babel/preset-env/lib/debug.js";
import { babelOptions } from "./bundle.cjs";
const detailsOpen = (heading) =>
`<details>\n<summary><h4>${heading}</h4></summary>\n`;
const detailsClose = "</details>\n";
const dummyAPI = {
version: babelVersion,
assertVersion: () => {},
caller: (callback) =>
callback({
name: "Dummy Bundler",
supportsStaticESM: true,
supportsDynamicImport: true,
supportsTopLevelAwait: true,
supportsExportNamespaceFrom: true,
}),
targets: () => ({}),
};
for (const buildType of ["Modern", "Legacy"]) {
const browserslistEnv = buildType.toLowerCase();
const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" });
const presetEnvOpts = babelOpts.presets[0][1];
// Invoking preset-env in debug mode will log the included plugins
console.log(detailsOpen(`${buildType} Build Babel Plugins`));
presetEnv.default(dummyAPI, {
...presetEnvOpts,
browserslistEnv,
debug: true,
});
console.log(detailsClose);
// Manually log the Core-JS polyfills using the same technique
if (presetEnvOpts.useBuiltIns) {
console.log(detailsOpen(`${buildType} Build Core-JS Polyfills`));
const targets = compilationTargets.default(babelOpts?.targets, {
browserslistEnv,
});
const polyfillList = coreJSCompat({ targets }).list;
console.log(
"The following %i polyfills may be injected by Babel:\n",
polyfillList.length
);
for (const polyfill of polyfillList) {
logPlugin(polyfill, targets, coreJSCompat.data);
}
console.log(detailsClose);
}
}

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env node
// Script to print Babel plugins that will be used by browserslist environments
import { version as babelVersion } from "@babel/core";
import presetEnv from "@babel/preset-env";
import { babelOptions } from "./bundle.cjs";
const dummyAPI = {
version: babelVersion,
assertVersion: () => {},
caller: (callback) =>
callback({
name: "Dummy Bundler",
supportsStaticESM: true,
supportsDynamicImport: true,
supportsTopLevelAwait: true,
supportsExportNamespaceFrom: true,
}),
targets: () => ({}),
};
for (const browserslistEnv of ["modern", "legacy"]) {
console.log("\nBrowsersList Environment = %s\n", browserslistEnv);
presetEnv.default(dummyAPI, {
...babelOptions({ latestBuild: browserslistEnv === "modern" })
.presets[0][1],
browserslistEnv,
debug: true,
});
}

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20230426.0"
version = "20230501.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -9,5 +9,11 @@ export interface AlexaEntity {
export const fetchCloudAlexaEntities = (hass: HomeAssistant) =>
hass.callWS<AlexaEntity[]>({ type: "cloud/alexa/entities" });
export const fetchCloudAlexaEntity = (hass: HomeAssistant, entity_id: string) =>
hass.callWS<AlexaEntity>({
type: "cloud/alexa/entities/get",
entity_id,
});
export const syncCloudAlexaEntities = (hass: HomeAssistant) =>
hass.callWS({ type: "cloud/alexa/sync" });

View File

@@ -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 = (

View File

@@ -27,7 +27,7 @@ declare global {
}
}
const statTypes: StatisticsTypes = ["min", "mean", "max"];
const statTypes: StatisticsTypes = ["state", "min", "mean", "max"];
@customElement("ha-more-info-history")
export class MoreInfoHistory extends LitElement {

View File

@@ -1,3 +1,4 @@
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import "@material/mwc-button/mwc-button";
import {
mdiChevronDown,
@@ -109,7 +110,6 @@ export class HaVoiceCommandDialog extends LitElement {
if (!this._opened) {
return nothing;
}
const supportsSTT = this._pipeline?.stt_engine && AudioRecorder.isSupported;
return html`
<ha-dialog
open
@@ -202,11 +202,12 @@ export class HaVoiceCommandDialog extends LitElement {
dialogInitialFocus
iconTrailing
>
<span slot="trailingIcon">
<div slot="trailingIcon">
${this._showSendButton
? html`
<ha-icon-button
class="listening-icon"
id="microphone-button"
class="move-end"
.path=${mdiSend}
@click=${this._handleSendMessage}
.label=${this.hass.localize(
@@ -215,8 +216,8 @@ export class HaVoiceCommandDialog extends LitElement {
>
</ha-icon-button>
`
: supportsSTT
? html`
: this._pipeline?.stt_engine
? html` <div class="move-end">
${this._audioRecorder?.active
? html`
<div class="bouncer">
@@ -226,17 +227,28 @@ export class HaVoiceCommandDialog extends LitElement {
`
: ""}
<ha-icon-button
class="listening-icon"
.path=${mdiMicrophone}
@click=${this._toggleListening}
id="microphone-button"
.label=${this.hass.localize(
"ui.dialogs.voice_command.start_listening"
)}
.path=${mdiMicrophone}
.disabled=${!AudioRecorder.isSupported}
@click=${this._toggleListening}
>
</ha-icon-button>
`
${!AudioRecorder.isSupported
? html`<simple-tooltip
animation-delay="0"
position="top"
offset="1"
>${this.hass.localize(
"ui.dialogs.voice_command.stt_not_supported"
)}</simple-tooltip
>`
: ""}
</div>`
: ""}
</span>
</div>
</ha-textfield>
${this._agentInfo && this._agentInfo.attribution
? html`
@@ -401,6 +413,7 @@ export class HaVoiceCommandDialog extends LitElement {
}
});
}
this._stt_binary_handler_id = undefined;
this._audioBuffer = [];
const userMessage: Message = {
who: "user",
@@ -463,6 +476,7 @@ export class HaVoiceCommandDialog extends LitElement {
}
if (event.type === "run-end") {
this._stt_binary_handler_id = undefined;
unsub();
}
@@ -509,6 +523,7 @@ export class HaVoiceCommandDialog extends LitElement {
}
// Send empty message to indicate we're done streaming.
this._sendAudioChunk(new Int16Array());
this._stt_binary_handler_id = undefined;
}
this._audioBuffer = undefined;
}
@@ -558,18 +573,22 @@ export class HaVoiceCommandDialog extends LitElement {
return [
haStyleDialog,
css`
ha-icon-button.listening-icon {
#microphone-button {
color: var(--secondary-text-color);
margin-right: -24px;
margin-inline-end: -24px;
margin-inline-start: initial;
direction: var(--direction);
}
ha-icon-button.listening-icon[active] {
#microphone-button[active] {
color: var(--primary-color);
}
.move-end {
position: relative;
right: -24px;
inset-inline-end: -24px;
inset-inline-start: initial;
direction: var(--direction);
}
simple-tooltip {
top: 0;
}
ha-dialog {
--primary-action-button-flex: 1;
--secondary-action-button-flex: 0;
@@ -619,7 +638,6 @@ export class HaVoiceCommandDialog extends LitElement {
}
ha-textfield {
display: block;
overflow: hidden;
}
a.button {
text-decoration: none;

View File

@@ -9,13 +9,6 @@
script.src = src;
return script;
}
window.Polymer = {
lazyRegister: true,
useNativeCSSProperties: true,
dom: "shadow",
suppressTemplateNotifications: true,
suppressBindingNotifications: true,
};
window.polymerSkipLoadingFontRoboto = true;
if (!("customElements" in window &&
"content" in document.createElement("template"))) {

View File

@@ -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";

View File

@@ -498,12 +498,22 @@ class DialogCalendarEventEditor extends LitElement {
this._submitting = false;
return;
}
const eventData = this._calculateData();
if (eventData.rrule && range === RecurrenceRange.THISEVENT) {
// Updates to a single instance of a recurring event by definition
// cannot change the recurrence rule and doing so would be invalid.
// It is difficult to detect if the user changed the recurrence rule
// since updating the date may change it implicitly (e.g. day of week
// of the event changes) so we just assume the users intent based on
// recurrence range and drop any other rrule changes.
eventData.rrule = undefined;
}
try {
await updateCalendarEvent(
this.hass!,
this._calendarId!,
entry.uid!,
this._calculateData(),
eventData,
entry.recurrence_id || "",
range!
);

View File

@@ -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));

View File

@@ -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;

View File

@@ -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";

View File

@@ -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";

View File

@@ -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;
}

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -13,7 +13,7 @@ import {
ExtEntityRegistryEntry,
} from "../../../data/entity_registry";
import { voiceAssistants } from "../../../data/voice";
import { haStyle, haStyleDialog } from "../../../resources/styles";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "./entity-voice-settings";
import { ExposeEntityDialogParams } from "./show-dialog-expose-entity";
@@ -48,6 +48,11 @@ class DialogExposeEntity extends LitElement {
"ui.panel.config.voice_assistants.expose.expose_dialog.header"
);
const entities = this._filterEntities(
this._params.extendedEntities,
this._filter
);
return html`
<ha-dialog open @closed=${this.closeDialog} .heading=${header}>
<div slot="heading">
@@ -76,10 +81,14 @@ class DialogExposeEntity extends LitElement {
></search-input>
</div>
<mwc-list multi>
${this._filterEntities(
this._params.extendedEntities,
this._filter
).map((entity) => this._renderItem(entity))}
<lit-virtualizer
scroller
class="ha-scrollbar"
@click=${this._itemClicked}
.items=${entities}
.renderItem=${this._renderItem}
>
</lit-virtualizer>
</mwc-list>
<mwc-button
slot="primaryAction"
@@ -95,10 +104,7 @@ class DialogExposeEntity extends LitElement {
`;
}
private _handleSelected(ev) {
if (ev.detail.source !== "property") {
return;
}
private _handleSelected = (ev) => {
const entityId = ev.target.value;
if (ev.detail.selected) {
if (this._selected.includes(entityId)) {
@@ -108,6 +114,11 @@ class DialogExposeEntity extends LitElement {
} else {
this._selected = this._selected.filter((item) => item !== entityId);
}
};
private _itemClicked(ev) {
const listItem = ev.target.closest("ha-check-list-item");
listItem.selected = !listItem.selected;
}
private _filterChanged(e) {
@@ -133,21 +144,23 @@ class DialogExposeEntity extends LitElement {
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>`;
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 _expose() {
@@ -158,21 +171,36 @@ class DialogExposeEntity extends LitElement {
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-dialog {
--dialog-content-padding: 0;
--mdc-dialog-min-width: 500px;
--mdc-dialog-max-width: 600px;
}
@media all and (min-width: 600px) {
lit-virtualizer {
height: 500px;
}
@media all and (max-width: 500px), all and (max-height: 800px) {
ha-dialog {
--mdc-dialog-min-width: 600px;
--mdc-dialog-max-height: 80%;
--mdc-dialog-min-width: calc(
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
);
--mdc-dialog-max-width: calc(
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
);
--mdc-dialog-min-height: 100%;
--mdc-dialog-max-height: 100%;
--vertical-align-dialog: flex-end;
--ha-dialog-border-radius: 0px;
}
lit-virtualizer {
height: calc(100vh - 234px);
}
}
search-input {
width: 100%;
display: block;
padding: 24px 16px 0;
padding: 16px 16px 0;
box-sizing: border-box;
}
.header {
@@ -231,6 +259,20 @@ class DialogExposeEntity extends LitElement {
inset-inline-end: 16px;
direction: var(--direction);
}
lit-virtualizer {
width: 100%;
contain: size layout !important;
}
ha-check-list-item {
width: 100%;
height: 72px;
}
ha-check-list-item ha-state-icon {
margin-left: 24px;
margin-inline-start: 24;
margin-inline-end: initial;
direction: var(--direction);
}
`,
];
}

View File

@@ -1,9 +1,19 @@
import {
mdiBug,
mdiClose,
mdiDotsVertical,
mdiStar,
mdiStarOutline,
} from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../common/dom/fire_event";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
import { navigate } from "../../../common/navigate";
import "../../../components/ha-button";
import { createCloseHeading } from "../../../components/ha-dialog";
import "../../../components/ha-form/ha-form";
import "../../../components/ha-header-bar";
import {
AssistPipeline,
AssistPipelineMutableParams,
@@ -11,8 +21,8 @@ import {
} from "../../../data/assist_pipeline";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "./assist-pipeline-detail/assist-pipeline-detail-conversation";
import "./assist-pipeline-detail/assist-pipeline-detail-config";
import "./assist-pipeline-detail/assist-pipeline-detail-conversation";
import "./assist-pipeline-detail/assist-pipeline-detail-stt";
import "./assist-pipeline-detail/assist-pipeline-detail-tts";
import "./debug/assist-render-pipeline-events";
@@ -39,10 +49,10 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
public showDialog(params: VoiceAssistantPipelineDetailsDialogParams): void {
this._params = params;
this._error = undefined;
this._cloudActive = this._params.cloudActiveSubscription;
if (this._params.pipeline) {
this._data = this._params.pipeline;
this._preferred = this._params.preferred;
this._cloudActive = this._params.cloudActiveSubscription;
} else {
this._data = {
language: (
@@ -74,21 +84,62 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
return nothing;
}
const title = this._params.pipeline?.id
? this._params.pipeline.name
: this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.add_assistant_title"
);
return html`
<ha-dialog
open
@closed=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this._params.pipeline?.id
? this._params.pipeline.name
: this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.add_assistant_title"
)
)}
.heading=${title}
>
<ha-header-bar slot="heading">
<ha-icon-button
slot="navigationIcon"
dialogAction="cancel"
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
></ha-icon-button>
<div slot="title" class="main-title" .title=${title}>${title}</div>
${this._params.pipeline?.id
? html`
<ha-icon-button
slot="actionItems"
.label=${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.set_as_preferred"
)}
.path=${this._preferred ? mdiStar : mdiStarOutline}
@click=${this._setPreferred}
.disabled=${Boolean(this._preferred)}
></ha-icon-button>
<ha-button-menu
corner="BOTTOM_END"
menuCorner="END"
slot="actionItems"
@closed=${stopPropagation}
fixed
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-list-item graphic="icon" @request-selected=${this._debug}>
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.debug"
)}
<ha-svg-icon slot="graphic" .path=${mdiBug}></ha-svg-icon>
</ha-list-item>
</ha-button-menu>
`
: nothing}
</ha-header-bar>
<div class="content">
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
@@ -111,8 +162,8 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
(this._data.tts_engine === "cloud" ||
this._data.stt_engine === "cloud")
? html`
<ha-alert alert-type="warning"
>${this.hass.localize(
<ha-alert alert-type="warning">
${this.hass.localize(
"ui.panel.config.voice_assistants.assistants.pipeline.detail.no_cloud_message"
)}
<a
@@ -152,19 +203,6 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
>
${this.hass.localize("ui.common.delete")}
</ha-button>
<ha-button
.disabled=${this._preferred}
slot="secondaryAction"
@click=${this._setPreferred}
>Set as preferred</ha-button
>
<a
href="/config/voice-assistants/debug/${this._params.pipeline
.id}"
slot="secondaryAction"
@click=${this.closeDialog}
><ha-button>Debug</ha-button>
</a>
`
: nothing}
<ha-button
@@ -237,6 +275,12 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
}
}
private _debug(ev) {
if (!shouldHandleRequestSelectedEvent(ev)) return;
navigate(`/config/voice-assistants/debug/${this._params!.pipeline!.id}`);
this.closeDialog();
}
private async _deletePipeline() {
this._submitting = true;
try {
@@ -254,6 +298,15 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
return [
haStyleDialog,
css`
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
display: block;
}
.main-title {
overflow: hidden;
text-overflow: ellipsis;
}
assist-pipeline-detail-config,
assist-pipeline-detail-conversation,
assist-pipeline-detail-stt {

View File

@@ -1,3 +1,4 @@
import { mdiAlertCircle } from "@mdi/js";
import {
css,
CSSResultGroup,
@@ -19,6 +20,7 @@ import {
import "../../../components/ha-aliases-editor";
import "../../../components/ha-settings-row";
import "../../../components/ha-switch";
import { fetchCloudAlexaEntity } from "../../../data/alexa";
import {
CloudStatus,
CloudStatusLoggedIn,
@@ -31,8 +33,8 @@ import {
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import {
GoogleEntity,
fetchCloudGoogleEntity,
GoogleEntity,
} from "../../../data/google_assistant";
import { exposeEntities, voiceAssistants } from "../../../data/voice";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
@@ -53,16 +55,16 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
@state() private _googleEntity?: GoogleEntity;
@state() private _unsupported: Partial<
Record<"cloud.google_assistant" | "cloud.alexa" | "conversation", boolean>
> = {};
protected willUpdate(changedProps: PropertyValues<this>) {
if (!isComponentLoaded(this.hass, "cloud")) {
return;
}
if (changedProps.has("entry") && this.entry) {
fetchCloudGoogleEntity(this.hass, this.entry.entity_id).then(
(googleEntity) => {
this._googleEntity = googleEntity;
}
);
this._fetchEntities();
}
if (!this.hasUpdated) {
fetchCloudStatus(this.hass).then((status) => {
@@ -71,6 +73,31 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
}
}
private async _fetchEntities() {
try {
const googleEntity = await fetchCloudGoogleEntity(
this.hass,
this.entry.entity_id
);
this._googleEntity = googleEntity;
this.requestUpdate("_googleEntity");
} catch (err: any) {
if (err.code === "not_supported") {
this._unsupported["cloud.google_assistant"] = true;
this.requestUpdate("_unsupported");
}
}
try {
await fetchCloudAlexaEntity(this.hass, this.entry.entity_id);
} catch (err: any) {
if (err.code === "not_supported") {
this._unsupported["cloud.alexa"] = true;
this.requestUpdate("_unsupported");
}
}
}
private _getEntityFilterFuncs = memoizeOne(
(googleFilter: EntityFilter, alexaFilter: EntityFilter) => ({
google: generateFilter(
@@ -163,9 +190,28 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
></ha-switch>
</ha-settings-row>
${anyExposed
? showAssistants.map(
(key) => html`
<ha-settings-row>
? showAssistants.map((key) => {
const supported = !this._unsupported[key];
const exposed =
alexaManual && key === "cloud.alexa"
? manExposedAlexa
: googleManual && key === "cloud.google_assistant"
? manExposedGoogle
: this.entry.options?.[key]?.should_expose;
const manualConfig =
(alexaManual && key === "cloud.alexa") ||
(googleManual && key === "cloud.google_assistant");
const support2fa =
key === "cloud.google_assistant" &&
!googleManual &&
supported &&
this._googleEntity?.might_2fa;
return html`
<ha-settings-row .threeLine=${!supported && manualConfig}>
<img
alt=""
src=${brandsUrl({
@@ -177,9 +223,24 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
slot="prefix"
/>
<span slot="heading">${voiceAssistants[key].name}</span>
${key === "cloud.google_assistant" &&
!googleManual &&
this._googleEntity?.might_2fa
${!supported
? html`<div slot="description" class="unsupported">
<ha-svg-icon .path=${mdiAlertCircle}></ha-svg-icon>
${this.hass.localize(
"ui.dialogs.voice-settings.unsupported"
)}
</div>`
: nothing}
${manualConfig
? html`
<div slot="description">
${this.hass.localize(
"ui.dialogs.voice-settings.manual_config"
)}
</div>
`
: nothing}
${support2fa
? html`
<ha-formfield
slot="description"
@@ -193,30 +254,16 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
></ha-checkbox>
</ha-formfield>
`
: (alexaManual && key === "cloud.alexa") ||
(googleManual && key === "cloud.google_assistant")
? html`
<span slot="description">
${this.hass.localize(
"ui.dialogs.voice-settings.manual_config"
)}
</span>
`
: nothing}
<ha-switch
.assistant=${key}
@change=${this._toggleAssistant}
.disabled=${(alexaManual && key === "cloud.alexa") ||
(googleManual && key === "cloud.google_assistant")}
.checked=${alexaManual && key === "cloud.alexa"
? manExposedAlexa
: googleManual && key === "cloud.google_assistant"
? manExposedGoogle
: this.entry.options?.[key]?.should_expose}
.disabled=${manualConfig || (!exposed && !supported)}
.checked=${exposed}
></ha-switch>
</ha-settings-row>
`
)
`;
})
: nothing}
<h3 class="header">
@@ -283,9 +330,15 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
}
private async _toggleAll(ev) {
const expose = ev.target.checked;
const assistants = expose
? ev.target.assistants.filter((key) => !this._unsupported[key])
: ev.target.assistants;
exposeEntities(
this.hass,
ev.target.assistants,
assistants,
[this.entry.entity_id],
ev.target.checked
);
@@ -305,6 +358,7 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
margin: 32px;
margin-top: 0;
--settings-row-prefix-display: contents;
--settings-row-content-display: contents;
}
ha-settings-row {
padding: 0;
@@ -327,6 +381,15 @@ export class EntityVoiceSettings extends SubscribeMixin(LitElement) {
ha-checkbox {
--mdc-checkbox-state-layer-size: 40px;
}
.unsupported {
display: flex;
align-items: center;
}
.unsupported ha-svg-icon {
color: var(--error-color);
--mdc-icon-size: 16px;
margin-right: 4px;
}
.header {
margin-top: 8px;
margin-bottom: 4px;

View File

@@ -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`
<div class="container">
<img
class="logo"
style=${styleMap({
filter: this.manual ? "grayscale(100%)" : undefined,
})}
alt=""
src=${brandsUrl({
domain: voiceAssistants[this.assistant].domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
referrerpolicy="no-referrer"
slot="prefix"
/>
${this.unsupported
? html`
<ha-svg-icon
.path=${mdiAlertCircle}
class="unsupported"
></ha-svg-icon>
`
: nothing}
${this.manual || this.unsupported
? html`
<simple-tooltip
animation-delay="0"
position="top"
offset="4"
fitToVisibleBounds
>
${this.unsupported
? this.hass.localize(
"ui.panel.config.voice_assistants.expose.not_supported"
)
: ""}
${this.unsupported && this.manual ? html`<br />` : nothing}
${this.manual
? this.hass.localize(
"ui.panel.config.voice_assistants.expose.manually_configured"
)
: nothing}
</simple-tooltip>
`
: ""}
</div>
`;
}
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;
}
}

View File

@@ -11,6 +11,7 @@ import {
ExtEntityRegistryEntry,
getExtendedEntityRegistryEntries,
} from "../../../data/entity_registry";
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../../types";
import "./assist-pref";

View File

@@ -3,14 +3,21 @@ import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import {
mdiCloseBoxMultiple,
mdiCloseCircleOutline,
mdiFilterVariant,
mdiPlus,
mdiPlusBoxMultiple,
} from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import {
css,
CSSResultGroup,
html,
LitElement,
nothing,
PropertyValues,
} from "lit";
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 {
@@ -27,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 {
@@ -35,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";
@@ -42,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 {
@@ -73,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;
@@ -139,35 +156,23 @@ export class VoiceAssistantsExpose extends LitElement {
width: "160px",
type: "flex",
template: (assistants, entry) =>
html`${availableAssistants.map((key) =>
assistants.includes(key)
? html`<div>
<img
style="height: 24px; margin-right: 16px;${styleMap({
filter: entry.manAssistants?.includes(key)
? "grayscale(100%)"
: "",
})}"
alt=""
src=${brandsUrl({
domain: voiceAssistants[key].domain,
type: "icon",
darkOptimized: this.hass.themes?.darkMode,
})}
referrerpolicy="no-referrer"
slot="prefix"
/>${entry.manAssistants?.includes(key)
? html`<simple-tooltip
animation-delay="0"
position="bottom"
offset="1"
>
Configured in YAML, not editable in UI
</simple-tooltip>`
: ""}
</div>`
: html`<div style="width: 40px;"></div>`
)}`,
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`
<voice-assistants-expose-assistant-icon
.assistant=${key}
.hass=${this.hass}
.manual=${manual}
.unsupported=${!supported}
>
</voice-assistants-expose-assistant-icon>
`
: html`<div style="width: 40px;"></div>`;
})}`,
},
aliases: {
title: this.hass.localize(
@@ -197,6 +202,12 @@ export class VoiceAssistantsExpose extends LitElement {
.path=${mdiCloseCircleOutline}
></ha-icon-button>`,
},
// For search
entity_id: {
title: "",
hidden: true,
filterable: true,
},
})
);
@@ -423,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<AlexaEntity[]> | undefined;
let googleEntitiesProm: Promise<GoogleEntity[]> | 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();
}
}
@@ -560,6 +591,26 @@ export class VoiceAssistantsExpose extends LitElement {
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>
${this.narrow && activeFilters?.length
? html`
<ha-button-menu slot="filter-menu" multi>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(
"ui.panel.config.devices.picker.filter.filter"
)}
.path=${mdiFilterVariant}
></ha-icon-button>
<mwc-list-item @click=${this._clearFilter}>
${this.hass.localize("ui.components.data-table.filtering_by")}
${activeFilters.join(", ")}
<span class="clear">
${this.hass.localize("ui.common.clear")}
</span>
</mwc-list-item>
</ha-button-menu>
`
: nothing}
</hass-tabs-subpage-data-table>
`;
}

View File

@@ -42,6 +42,7 @@ export class HuiImageElement extends LitElement implements LovelaceElement {
.image=${this._config.image}
.stateImage=${this._config.state_image}
.cameraImage=${this._config.camera_image}
.cameraView=${this._config.camera_view}
.filter=${this._config.filter}
.stateFilter=${this._config.state_filter}
.title=${computeTooltip(this.hass, this._config)}

View File

@@ -1,6 +1,7 @@
import { ActionConfig } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { Condition } from "../common/validate-condition";
import { HuiImage } from "../components/hui-image";
interface LovelaceElementConfigBase {
type: string;
@@ -43,6 +44,7 @@ export interface ImageElementConfig extends LovelaceElementConfigBase {
image?: string;
state_image?: string;
camera_image?: string;
camera_view?: HuiImage["cameraView"];
dark_mode_image?: string;
dark_mode_filter?: string;
filter?: string;

View File

@@ -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;
@@ -1064,6 +1060,12 @@ class HUIRoot extends LitElement {
padding-bottom: env(safe-area-inset-bottom);
}
hui-view {
background: var(
--lovelace-background,
var(--primary-background-color)
);
}
#view > * {
flex: 1 1 100%;
max-width: 100%;
}

View File

@@ -838,6 +838,7 @@
"input_label": "Enter a request",
"send_text": "Send text",
"start_listening": "Start listening",
"stt_not_supported": "You can only use speech-to-text when using HTTPS.",
"manage_assistants": "Manage assistants"
},
"generic": {
@@ -1083,7 +1084,8 @@
"aliases_header": "Aliases",
"aliases_description": "Aliases are supported by Assist and Google Assistant.",
"ask_pin": "Ask for PIN",
"manual_config": "Managed with filters in configuration.yaml"
"manual_config": "Managed in configuration.yaml",
"unsupported": "Unsupported"
},
"restart": {
"heading": "Restart Home Assistant",
@@ -2039,6 +2041,8 @@
"add_assistant_title": "Add assistant",
"add_assistant_action": "Create",
"try_tts": "Try voice",
"debug": "Debug",
"set_as_preferred": "Set as preferred",
"form": {
"name": "Name",
"conversation_engine": "Conversation agent",
@@ -2105,6 +2109,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}",
@@ -2756,7 +2762,7 @@
"alert_password_change_required": "You need to change your password before logging in.",
"alert_email_confirm_necessary": "You need to confirm your email before logging in.",
"cloud_pipeline_title": "Want to use Home Assistant Cloud for your voice assistant?",
"cloud_pipeline_text": "We created a new assistant for you, using the great text-to-speech and speech-to-text engines from Home Assistant Cloud. Would you like to set this assistant as the preferred assistant?"
"cloud_pipeline_text": "We created a new assistant for you, using the superior text-to-speech and speech-to-text engines from Home Assistant Cloud. Would you like to set this assistant as the preferred assistant?"
},
"forgot_password": {
"title": "Forgot password",
@@ -2808,7 +2814,7 @@
"fetching_subscription": "Fetching subscription…",
"tts": {
"title": "Text-to-speech",
"info": "Bring personality to your home by having it speak to you by using our Text-to-Speech services. You can use this in automations and scripts by using the {service} service.",
"info": "Bring personality to your home by having it speak to you by using our text-to-speech services. You can use this in automations and scripts by using the {service} service.",
"default_language": "Default language to use",
"default_gender": "Default gender to use",
"try": "Try",

View File

@@ -16585,9 +16585,9 @@ __metadata:
linkType: hard
"yaml@npm:^2.2.1":
version: 2.2.1
resolution: "yaml@npm:2.2.1"
checksum: 84f68cbe462d5da4e7ded4a8bded949ffa912bc264472e5a684c3d45b22d8f73a3019963a32164023bdf3d83cfb6f5b58ff7b2b10ef5b717c630f40bd6369a23
version: 2.2.2
resolution: "yaml@npm:2.2.2"
checksum: d90c235e099e30094dcff61ba3350437aef53325db4a6bcd04ca96e1bfe7e348b191f6a7a52b5211e2dbc4eeedb22a00b291527da030de7c189728ef3f2b4eb3
languageName: node
linkType: hard