mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-27 04:52:56 +00:00
Compare commits
26 Commits
20230426.0
...
disabled-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
728b16918b | ||
|
|
0e071a8b7e | ||
|
|
15eab18e07 | ||
|
|
c8e0227a5c | ||
|
|
f2a8528429 | ||
|
|
3ed3dab0a1 | ||
|
|
f99f554f19 | ||
|
|
e069b5eed1 | ||
|
|
3a481ebb1a | ||
|
|
a209fadf18 | ||
|
|
9f1bd1e085 | ||
|
|
edc6da04f7 | ||
|
|
2fb1dd0ec1 | ||
|
|
3f2aac0842 | ||
|
|
d1877595a5 | ||
|
|
6379713f57 | ||
|
|
3b33195ff6 | ||
|
|
c7f1f1bcd1 | ||
|
|
fac4795f14 | ||
|
|
062e402ef1 | ||
|
|
29be64a858 | ||
|
|
b3b74b8328 | ||
|
|
37ba34cb0d | ||
|
|
8ecdde3507 | ||
|
|
04d34aa80c | ||
|
|
26bb1ba146 |
59
build-scripts/list-plugins-and-polyfills.js
Executable file
59
build-scripts/list-plugins-and-polyfills.js
Executable 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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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 = "20230426.0"
|
||||
version = "20230501.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"))) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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!
|
||||
);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user