Compare commits

..

33 Commits

Author SHA1 Message Date
Bram Kragten
3ba6bf272e Bumped version to 20251104.0 2025-11-04 18:21:11 +01:00
Paul Bottein
eec99b2fa3 Rename safety panel to security panel (#27796) 2025-11-04 18:20:10 +01:00
Bram Kragten
d23e45e410 Handle unknown items in target picker (#27795)
* Handle unknown items in target picker

* Update ha-target-picker-item-row.ts

* update colors

* fallback to domain icons
2025-11-04 18:19:02 +01:00
Paul Bottein
3c82d12609 Auto refresh summary dashboard when registries changed (#27794) 2025-11-04 18:17:46 +01:00
Paul Bottein
15d67997e7 Don't show summary card if summary dashboards are empty (#27788)
Don't show summary card if summary dashboard are empty
2025-11-04 18:16:37 +01:00
Paul Bottein
a6dfcb3100 Fix tooltip hide delay (#27786) 2025-11-04 18:16:37 +01:00
karwosts
26c2369228 Fix sankey with external statistics devices (#27784) 2025-11-04 18:16:35 +01:00
Paul Bottein
2eed446492 Display entities without area in summary dashboard (#27777)
* Add support for no area, no floor and no device in entity filter

* Display entities without area in summary dashboard
2025-11-04 18:16:34 +01:00
Wendelin
7ebdeab6b2 Fix-labels-yaml-helper (#27776)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2025-11-04 18:16:33 +01:00
Tobias Bieniek
0c35278f51 Hide media players summary when no entities exist (#27642) 2025-11-04 18:16:32 +01:00
Bram Kragten
561122f03d Bumped version to 20251103.0 2025-11-03 16:34:11 +01:00
Petar Petrov
95311be034 Apply theme variables to pi charts (#27773) 2025-11-03 16:34:07 +01:00
Wendelin
1eda44a102 Fix selected element text color (#27771) 2025-11-03 16:32:51 +01:00
Petar Petrov
d76781eb91 Fix for Y axis label formatting in history graph (#27770) 2025-11-03 16:32:50 +01:00
Petar Petrov
82d44e051f Fix sensor card graph in Safari (#27768) 2025-11-03 16:32:49 +01:00
Aidan Timson
fdc9f5a3b7 Use supervisor endpoint for downloading logs (when avaliable) (#27765) 2025-11-03 16:32:47 +01:00
Paul Bottein
ee6c82aba9 Don't show tooltip on overflow menu in dashboard toolbar (#27763) 2025-11-03 16:32:46 +01:00
Paul Bottein
11d3f5c2ba Fix suggest cards dialog for sections view (#27762) 2025-11-03 16:32:45 +01:00
Aarni Koskela
feb68ce373 Add support for PM4 sensor state (#27754) 2025-11-03 16:32:44 +01:00
Simon Lamon
7f9a9de157 Move label translations to ui.dialog (#27752) 2025-11-03 16:32:43 +01:00
Simon Lamon
8e1b6a3d3b Fixes in backup overflow (#27745)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2025-11-03 16:32:41 +01:00
Jan-Philipp Benecke
6e6e5a53e2 Fix button text overflow (#27744) 2025-11-03 16:32:41 +01:00
Bram Kragten
0408734ec5 Bumped version to 20251029.1 2025-10-30 18:19:31 +01:00
Paul Bottein
317519fc08 Revert "Fix entities card size and add grid contstraints" (#27725) 2025-10-30 18:18:27 +01:00
Paul Bottein
843d79eab4 Don't show tooltip for ha button menu in top bar (#27723) 2025-10-30 18:18:26 +01:00
Paul Bottein
165a757f06 Revert entity naming in target picker chips (#27722) 2025-10-30 18:18:25 +01:00
Aidan Timson
ea8b730142 Revert "Migrate dialog-device-registry-detail to ha-wa-dialog (#27668)" (#27716)
This reverts commit 2a8d935601.
2025-10-30 18:18:24 +01:00
Paul Bottein
e88c97d625 Use entity naming in more cards (#27714)
* Use entity naming in more cards

* Migrate statistic card

* Fix localize
2025-10-30 18:18:23 +01:00
Aidan Timson
7560988b76 Trend feature: make sure content is centered when loading (#27708)
* Make sure content is centered when loading

* Restore from test
2025-10-30 18:18:22 +01:00
Aidan Timson
eecd8077b6 Calendar card height: account for title and stop overflow (#27707) 2025-10-30 18:18:21 +01:00
Paul Bottein
cbab5c3f7b Restore trigger id in overflow menu for trigger (#27702) 2025-10-30 18:18:20 +01:00
Paul Bottein
a5d27c8bb8 Only clear from and to trigger in state trigger (#27700) 2025-10-30 18:18:19 +01:00
Paul Bottein
a6a340b5db Only display add button if at least one entity is selected in entities picker (#27699) 2025-10-30 18:18:18 +01:00
33 changed files with 879 additions and 1262 deletions

View File

@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/autobuild@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0

View File

@@ -18,16 +18,16 @@ module.exports.sourceMapURL = () => {
module.exports.ignorePackages = () => [];
// Files from NPM packages that we should replace with empty file
module.exports.emptyPackages = ({ isHassioBuild, isLandingPageBuild }) =>
module.exports.emptyPackages = ({ isHassioBuild }) =>
[
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
// Icons in supervisor conflict with icons in HA so we don't load.
(isHassioBuild || isLandingPageBuild) &&
isHassioBuild &&
require.resolve(
path.resolve(paths.root_dir, "src/components/ha-icon.ts")
),
(isHassioBuild || isLandingPageBuild) &&
isHassioBuild &&
require.resolve(
path.resolve(paths.root_dir, "src/components/ha-icon-picker.ts")
),
@@ -337,7 +337,6 @@ module.exports.config = {
publicPath: publicPath(latestBuild),
isProdBuild,
latestBuild,
isLandingPageBuild: true,
};
},
};

View File

@@ -41,7 +41,6 @@ const createRspackConfig = ({
isStatsBuild,
isTestBuild,
isHassioBuild,
isLandingPageBuild,
dontHash,
}) => {
if (!dontHash) {
@@ -169,9 +168,7 @@ const createRspackConfig = ({
},
}),
new rspack.NormalModuleReplacementPlugin(
new RegExp(
bundle.emptyPackages({ isHassioBuild, isLandingPageBuild }).join("|")
),
new RegExp(bundle.emptyPackages({ isHassioBuild }).join("|")),
path.resolve(paths.root_dir, "src/util/empty.js")
),
!isProdBuild && new LogStartCompilePlugin(),

View File

@@ -1,25 +1,22 @@
import "@material/mwc-linear-progress";
import { mdiOpenInNew } from "@mdi/js";
import { css, html, nothing, type PropertyValues } from "lit";
import { type PropertyValues, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { extractSearchParam } from "../../src/common/url/search-params";
import "../../src/components/ha-alert";
import "../../src/components/ha-button";
import "../../src/components/ha-fade-in";
import "../../src/components/ha-spinner";
import "../../src/components/ha-svg-icon";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import "../../src/onboarding/onboarding-welcome-links";
import { onBoardingStyles } from "../../src/onboarding/styles";
import { haStyle } from "../../src/resources/styles";
import "./components/landing-page-logs";
import "../../src/onboarding/onboarding-welcome-links";
import "./components/landing-page-network";
import "./components/landing-page-logs";
import { extractSearchParam } from "../../src/common/url/search-params";
import { onBoardingStyles } from "../../src/onboarding/styles";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import { LandingPageBaseElement } from "./landing-page-base-element";
import {
getSupervisorNetworkInfo,
pingSupervisor,
type NetworkInfo,
} from "./data/supervisor";
import { LandingPageBaseElement } from "./landing-page-base-element";
export const ASSUME_CORE_START_SECONDS = 60;
const SCHEDULE_CORE_CHECK_SECONDS = 1;
@@ -97,21 +94,16 @@ class HaLandingPage extends LandingPageBaseElement {
<ha-language-picker
.value=${this.language}
.label=${""}
button-style
native-name
@value-changed=${this._languageChanged}
inline-arrow
></ha-language-picker>
<ha-button
appearance="plain"
variant="neutral"
<a
href="https://www.home-assistant.io/getting-started/onboarding/"
target="_blank"
rel="noreferrer noopener"
>${this.localize("ui.panel.page-onboarding.help")}</a
>
${this.localize("ui.panel.page-onboarding.help")}
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-button>
</div>
`;
}
@@ -226,8 +218,26 @@ class HaLandingPage extends LandingPageBaseElement {
ha-alert p {
text-align: unset;
}
.footer ha-svg-icon {
--mdc-icon-size: var(--ha-space-5);
ha-language-picker {
display: block;
width: 200px;
border-radius: var(--ha-border-radius-sm);
overflow: hidden;
--ha-select-height: 40px;
--mdc-select-fill-color: none;
--mdc-select-label-ink-color: var(--primary-text-color, #212121);
--mdc-select-ink-color: var(--primary-text-color, #212121);
--mdc-select-idle-line-color: transparent;
--mdc-select-hover-line-color: transparent;
--mdc-select-dropdown-icon-color: var(--primary-text-color, #212121);
--mdc-shape-small: 0;
}
a {
text-decoration: none;
color: var(--primary-text-color);
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
}
ha-fade-in {
min-height: calc(100vh - 64px - 88px);

View File

@@ -81,7 +81,7 @@
"@material/mwc-top-app-bar": "0.27.0",
"@material/mwc-top-app-bar-fixed": "0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
"@material/web": "2.4.1",
"@material/web": "2.4.0",
"@mdi/js": "7.4.47",
"@mdi/svg": "7.4.47",
"@replit/codemirror-indentation-markers": "6.5.3",
@@ -89,8 +89,8 @@
"@thomasloven/round-slider": "0.6.0",
"@tsparticles/engine": "3.9.1",
"@tsparticles/preset-links": "3.2.0",
"@vaadin/combo-box": "24.9.4",
"@vaadin/vaadin-themable-mixin": "24.9.4",
"@vaadin/combo-box": "24.9.2",
"@vaadin/vaadin-themable-mixin": "24.9.2",
"@vibrant/color": "4.0.0",
"@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.10",
@@ -111,7 +111,7 @@
"fuse.js": "7.1.0",
"google-timezones-json": "1.2.0",
"gulp-zopfli-green": "6.0.2",
"hls.js": "1.6.14",
"hls.js": "1.6.13",
"home-assistant-js-websocket": "9.5.0",
"idb-keyval": "6.2.2",
"intl-messageformat": "10.7.18",
@@ -154,11 +154,11 @@
"@babel/preset-env": "7.28.5",
"@bundle-stats/plugin-webpack-filter": "4.21.5",
"@lokalise/node-api": "15.3.1",
"@octokit/auth-oauth-device": "8.0.3",
"@octokit/plugin-retry": "8.0.3",
"@octokit/rest": "22.0.1",
"@rsdoctor/rspack-plugin": "1.3.7",
"@rspack/core": "1.6.0",
"@octokit/auth-oauth-device": "8.0.2",
"@octokit/plugin-retry": "8.0.2",
"@octokit/rest": "22.0.0",
"@rsdoctor/rspack-plugin": "1.3.4",
"@rspack/core": "1.5.8",
"@rspack/dev-server": "1.1.4",
"@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.22",
@@ -178,7 +178,7 @@
"@types/tar": "6.1.13",
"@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29",
"@vitest/coverage-v8": "4.0.6",
"@vitest/coverage-v8": "4.0.3",
"babel-loader": "10.0.0",
"babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.3",
@@ -201,7 +201,7 @@
"gulp-rename": "2.1.0",
"html-minifier-terser": "7.2.0",
"husky": "9.1.7",
"jsdom": "27.1.0",
"jsdom": "27.0.1",
"jszip": "3.10.1",
"lint-staged": "16.2.6",
"lit-analyzer": "2.0.3",
@@ -213,13 +213,13 @@
"rspack-manifest-plugin": "5.1.0",
"serve": "14.2.5",
"sinon": "21.0.0",
"tar": "7.5.2",
"tar": "7.5.1",
"terser-webpack-plugin": "5.3.14",
"ts-lit-plugin": "2.0.2",
"typescript": "5.9.3",
"typescript-eslint": "8.46.2",
"vite-tsconfig-paths": "5.1.4",
"vitest": "4.0.6",
"vitest": "4.0.3",
"webpack-stats-plugin": "1.1.3",
"webpackbar": "7.0.0",
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
@@ -231,7 +231,7 @@
"clean-css": "5.3.3",
"@lit/reactive-element": "2.1.1",
"@fullcalendar/daygrid": "6.1.19",
"globals": "16.5.0",
"globals": "16.4.0",
"tslib": "2.8.1",
"@material/mwc-list@^0.27.0": "patch:@material/mwc-list@npm%3A0.27.0#~/.yarn/patches/@material-mwc-list-npm-0.27.0-5344fc9de4.patch"
},

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20251029.0"
version = "20251104.0"
license = "Apache-2.0"
license-files = ["LICENSE*"]
description = "The Home Assistant frontend"

View File

@@ -1,5 +1,4 @@
/* eslint-disable lit/prefer-static-styles */
import { mdiOpenInNew } from "@mdi/js";
import type { PropertyValues } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -7,8 +6,6 @@ import punycode from "punycode";
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
import { extractSearchParamsObject } from "../common/url/search-params";
import "../components/ha-alert";
import "../components/ha-button";
import "../components/ha-svg-icon";
import type { AuthProvider, AuthUrlSearchParams } from "../data/auth";
import { fetchAuthProviders } from "../data/auth";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
@@ -136,8 +133,25 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
justify-content: space-between;
align-items: center;
}
.footer ha-svg-icon {
--mdc-icon-size: var(--ha-space-5);
ha-language-picker {
width: 200px;
border-radius: var(--ha-border-radius-sm);
overflow: hidden;
--ha-select-height: 40px;
--mdc-select-fill-color: none;
--mdc-select-label-ink-color: var(--primary-text-color, #212121);
--mdc-select-ink-color: var(--primary-text-color, #212121);
--mdc-select-idle-line-color: transparent;
--mdc-select-hover-line-color: transparent;
--mdc-select-dropdown-icon-color: var(--primary-text-color, #212121);
--mdc-shape-small: 0;
}
.footer a {
text-decoration: none;
color: var(--primary-text-color);
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
}
h1 {
font-size: var(--ha-font-size-3xl);
@@ -191,21 +205,16 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
<ha-language-picker
.value=${this.language}
.label=${""}
button-style
native-name
@value-changed=${this._languageChanged}
inline-arrow
></ha-language-picker>
<ha-button
appearance="plain"
variant="neutral"
<a
href="https://www.home-assistant.io/docs/authentication/"
target="_blank"
rel="noreferrer noopener"
>${this.localize("ui.panel.page-authorize.help")}</a
>
${this.localize("ui.panel.page-authorize.help")}
<ha-svg-icon slot="end" .path=${mdiOpenInNew}></ha-svg-icon>
</ha-button>
</div>
`;
}

View File

@@ -10,6 +10,7 @@ import type { HomeAssistant } from "../types";
import "./ha-bottom-sheet";
import "./ha-button";
import "./ha-combo-box-item";
import "./ha-icon-button";
import "./ha-input-helper-text";
import "./ha-picker-combo-box";
import type {

View File

@@ -1,7 +1,6 @@
import { mdiMenuDown } from "@mdi/js";
import type { PropertyValues } from "lit";
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../common/dom/fire_event";
import { formatLanguageCode } from "../common/language/format_language";
@@ -9,10 +8,10 @@ import { caseInsensitiveStringCompare } from "../common/string/compare";
import type { FrontendLocaleData } from "../data/translation";
import { translationMetadata } from "../resources/translations-metadata";
import type { HomeAssistant, ValueChangedEvent } from "../types";
import "./ha-button";
import "./ha-generic-picker";
import type { HaGenericPicker } from "./ha-generic-picker";
import "./ha-list-item";
import type { PickerComboBoxItem } from "./ha-picker-combo-box";
import "./ha-select";
export const getLanguageOptions = (
languages: string[],
@@ -22,7 +21,6 @@ export const getLanguageOptions = (
): PickerComboBoxItem[] => {
let options: PickerComboBoxItem[] = [];
const enLocale = { language: "en" } as FrontendLocaleData;
if (nativeName) {
const translations = translationMetadata.translations;
options = languages.map((lang) => {
@@ -38,38 +36,18 @@ export const getLanguageOptions = (
primary = lang;
}
}
let searchLabels = primary;
const browserLangName = formatLanguageCode(
lang,
locale || ({ language: navigator.language } as FrontendLocaleData)
);
if (browserLangName !== primary) {
searchLabels += `;${browserLangName}`;
}
const englishName = formatLanguageCode(lang, enLocale);
if (englishName !== primary && englishName !== browserLangName) {
searchLabels += `;${englishName}`;
}
return {
id: lang,
primary,
search_labels: searchLabels.split(";"),
search_labels: [primary],
};
});
} else if (locale) {
options = languages.map((lang) => {
const primary = formatLanguageCode(lang, locale);
let searchLabels = primary;
const englishName = formatLanguageCode(lang, enLocale);
if (englishName !== primary) {
searchLabels += `;${englishName}`;
}
return {
id: lang,
primary,
search_labels: searchLabels.split(";"),
};
});
options = languages.map((lang) => ({
id: lang,
primary: formatLanguageCode(lang, locale),
search_labels: [formatLanguageCode(lang, locale)],
}));
}
if (!noSort && locale) {
@@ -97,9 +75,6 @@ export class HaLanguagePicker extends LitElement {
@property({ attribute: "native-name", type: Boolean })
public nativeName = false;
@property({ type: Boolean, attribute: "button-style" })
public buttonStyle = false;
@property({ attribute: "no-sort", type: Boolean }) public noSort = false;
@property({ attribute: "inline-arrow", type: Boolean })
@@ -107,8 +82,6 @@ export class HaLanguagePicker extends LitElement {
@state() _defaultLanguages: string[] = [];
@query("ha-generic-picker", true) public genericPicker!: HaGenericPicker;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._computeDefaultLanguageOptions();
@@ -128,13 +101,12 @@ export class HaLanguagePicker extends LitElement {
this.hass?.locale
);
private _getLanguageName = (lang?: string) =>
this._getItems().find((language) => language.id === lang)?.primary;
private _valueRenderer = (value) =>
html`<span slot="headline"
>${this._getLanguageName(value) ?? value}</span
> `;
private _valueRenderer = (value) => {
const language = this._getItems().find(
(lang) => lang.id === value
)?.primary;
return html`<span slot="headline">${language ?? value}</span> `;
};
protected render() {
const value =
@@ -158,28 +130,10 @@ export class HaLanguagePicker extends LitElement {
.getItems=${this._getItems}
@value-changed=${this._changed}
hide-clear-icon
>
${this.buttonStyle
? html`<ha-button
slot="field"
.disabled=${this.disabled}
@click=${this._openPicker}
appearance="plain"
variant="neutral"
>
${this._getLanguageName(value)}
<ha-svg-icon slot="end" .path=${mdiMenuDown}></ha-svg-icon>
</ha-button>`
: nothing}
</ha-generic-picker>
></ha-generic-picker>
`;
}
private _openPicker(ev: Event) {
ev.stopPropagation();
this.genericPicker.open();
}
static styles = css`
ha-generic-picker {
width: 100%;

View File

@@ -87,208 +87,166 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
protected render() {
if (this.addOnTop) {
return html` ${this._renderPicker()} ${this._renderItems()} `;
return html` ${this._renderChips()} ${this._renderItems()} `;
}
return html` ${this._renderItems()} ${this._renderPicker()} `;
return html` ${this._renderItems()} ${this._renderChips()} `;
}
private _renderValueChips() {
const entityIds = this.value?.entity_id
? ensureArray(this.value.entity_id)
: [];
const deviceIds = this.value?.device_id
? ensureArray(this.value.device_id)
: [];
const areaIds = this.value?.area_id ? ensureArray(this.value.area_id) : [];
const floorIds = this.value?.floor_id
? ensureArray(this.value.floor_id)
: [];
const labelIds = this.value?.label_id
? ensureArray(this.value.label_id)
: [];
if (
!entityIds.length &&
!deviceIds.length &&
!areaIds.length &&
!floorIds.length &&
!labelIds.length
) {
return nothing;
}
return html`
<div class="mdc-chip-set items">
${floorIds.length
? floorIds.map(
(floor_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="floor"
.itemId=${floor_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
${areaIds.length
? areaIds.map(
(area_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="area"
.itemId=${area_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
${deviceIds.length
? deviceIds.map(
(device_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="device"
.itemId=${device_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
${entityIds.length
? entityIds.map(
(entity_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="entity"
.itemId=${entity_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
${labelIds.length
? labelIds.map(
(label_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="label"
.itemId=${label_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
</div>
`;
return html`<div class="mdc-chip-set items">
${this.value?.floor_id
? ensureArray(this.value.floor_id).map(
(floor_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="floor"
.itemId=${floor_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
${this.value?.area_id
? ensureArray(this.value.area_id).map(
(area_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="area"
.itemId=${area_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
${this.value?.device_id
? ensureArray(this.value.device_id).map(
(device_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="device"
.itemId=${device_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
${this.value?.entity_id
? ensureArray(this.value.entity_id).map(
(entity_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="entity"
.itemId=${entity_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
${this.value?.label_id
? ensureArray(this.value.label_id).map(
(label_id) => html`
<ha-target-picker-value-chip
.hass=${this.hass}
type="label"
.itemId=${label_id}
@remove-target-item=${this._handleRemove}
@expand-target-item=${this._handleExpand}
></ha-target-picker-value-chip>
`
)
: nothing}
</div>`;
}
private _renderValueGroups() {
const entityIds = this.value?.entity_id
? ensureArray(this.value.entity_id)
: [];
const deviceIds = this.value?.device_id
? ensureArray(this.value.device_id)
: [];
const areaIds = this.value?.area_id ? ensureArray(this.value.area_id) : [];
const floorIds = this.value?.floor_id
? ensureArray(this.value.floor_id)
: [];
const labelIds = this.value?.label_id
? ensureArray(this.value?.label_id)
: [];
return html`<div class="item-groups">
${this.value?.entity_id
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="entity"
.hass=${this.hass}
.items=${{ entity: ensureArray(this.value?.entity_id) }}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.device_id
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="device"
.hass=${this.hass}
.items=${{ device: ensureArray(this.value?.device_id) }}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.floor_id || this.value?.area_id
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="area"
.hass=${this.hass}
.items=${{
floor: ensureArray(this.value?.floor_id),
area: ensureArray(this.value?.area_id),
}}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
>
</ha-target-picker-item-group>
`
: nothing}
${this.value?.label_id
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="label"
.hass=${this.hass}
.items=${{ label: ensureArray(this.value?.label_id) }}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
>
</ha-target-picker-item-group>
`
: nothing}
</div>`;
}
private _renderItems() {
if (
!entityIds.length &&
!deviceIds.length &&
!areaIds.length &&
!floorIds.length &&
!labelIds.length
!this.value?.floor_id &&
!this.value?.area_id &&
!this.value?.device_id &&
!this.value?.entity_id &&
!this.value?.label_id
) {
return nothing;
}
return html`
<div class="item-groups">
${entityIds.length
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="entity"
.hass=${this.hass}
.items=${{ entity: entityIds }}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
>
</ha-target-picker-item-group>
`
: nothing}
${deviceIds.length
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="device"
.hass=${this.hass}
.items=${{ device: deviceIds }}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
>
</ha-target-picker-item-group>
`
: nothing}
${floorIds.length || areaIds.length
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="area"
.hass=${this.hass}
.items=${{
floor: floorIds,
area: areaIds,
}}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
>
</ha-target-picker-item-group>
`
: nothing}
${labelIds.length
? html`
<ha-target-picker-item-group
@remove-target-item=${this._handleRemove}
type="label"
.hass=${this.hass}
.items=${{ label: labelIds }}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityFilter}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
>
</ha-target-picker-item-group>
`
: nothing}
</div>
`;
}
private _renderItems() {
return html`
${this.compact ? this._renderValueChips() : this._renderValueGroups()}
`;
}
private _renderPicker() {
private _renderChips() {
return html`
<div class="add-target-wrapper">
<ha-button
@@ -389,8 +347,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
this._pickerFilter = filter;
};
private _hidePicker(ev) {
ev.stopPropagation();
private _hidePicker() {
this._open = false;
this._pickerWrapperOpen = false;

View File

@@ -638,7 +638,6 @@ export class HaTargetPickerItemRow extends LitElement {
img {
width: 24px;
height: 24px;
z-index: 1;
}
ha-icon-button {
--mdc-icon-button-size: 32px;

View File

@@ -705,7 +705,7 @@ export class HaTargetPickerSelector extends LitElement {
) as EntityComboBoxItem[];
}
if (!filterType && entities.length) {
if (!filterType) {
// show group title
items.push(
this.hass.localize("ui.components.target-picker.type.entities")
@@ -733,7 +733,7 @@ export class HaTargetPickerSelector extends LitElement {
devices = this._filterGroup("device", devices);
}
if (!filterType && devices.length) {
if (!filterType) {
// show group title
items.push(
this.hass.localize("ui.components.target-picker.type.devices")
@@ -769,7 +769,7 @@ export class HaTargetPickerSelector extends LitElement {
) as FloorComboBoxItem[];
}
if (!filterType && areasAndFloors.length) {
if (!filterType) {
// show group title
items.push(
this.hass.localize("ui.components.target-picker.type.areas")
@@ -811,7 +811,7 @@ export class HaTargetPickerSelector extends LitElement {
labels = this._filterGroup("label", labels);
}
if (!filterType && labels.length) {
if (!filterType) {
// show group title
items.push(
this.hass.localize("ui.components.target-picker.type.labels")

View File

@@ -12,7 +12,6 @@ import {
mdiChatSleep,
mdiClipboardList,
mdiClock,
mdiCodeBraces,
mdiCog,
mdiCommentAlert,
mdiCounter,
@@ -114,7 +113,6 @@ export const FALLBACK_DOMAIN_ICONS = {
text: mdiFormTextbox,
time: mdiClock,
timer: mdiTimerOutline,
template: mdiCodeBraces,
todo: mdiClipboardList,
tts: mdiSpeakerMessage,
vacuum: mdiRobotVacuum,

View File

@@ -152,18 +152,10 @@ export class MoreInfoHistory extends LitElement {
}
}
private _setUpdateTimer() {
private _setRedrawTimer() {
// redraw the graph every minute to update the time axis
clearInterval(this._interval);
this._interval = window.setInterval(() => {
// If using statistics, refresh the data
if (this._statistics) {
this._fetchStatistics();
}
// If using history, redraw the graph to update the time axis
if (this._stateHistory) {
this._redrawGraph();
}
}, 1000 * 60);
this._interval = window.setInterval(() => this._redrawGraph(), 1000 * 60);
}
private async _getStatisticsMetaData(statisticIds: string[] | undefined) {
@@ -178,30 +170,6 @@ export class MoreInfoHistory extends LitElement {
return statisticsMetaData;
}
private async _fetchStatistics(): Promise<boolean> {
// Fire off the metadata and fetch at the same time
// to avoid waiting in sequence so the UI responds
// faster.
const _metadata = this._getStatisticsMetaData([this.entityId]);
const _statistics = fetchStatistics(
this.hass!,
subHours(new Date(), 24),
undefined,
[this.entityId],
"5minute",
undefined,
statTypes
);
const [metadata, statistics] = await Promise.all([_metadata, _statistics]);
if (metadata && Object.keys(metadata).length) {
this._metadata = metadata;
this._statistics = statistics;
this._statNames = { [this.entityId]: "" };
return true;
}
return false;
}
private async _getStateHistory(): Promise<void> {
if (
isComponentLoaded(this.hass, "recorder") &&
@@ -212,10 +180,27 @@ export class MoreInfoHistory extends LitElement {
// has not opted into statistics so there is no need to check as it
// requires another round-trip to the server.
if (stateObj && stateObj.attributes.state_class) {
const hasStatistics = await this._fetchStatistics();
if (hasStatistics) {
// Using statistics, set up refresh timer
this._setUpdateTimer();
// Fire off the metadata and fetch at the same time
// to avoid waiting in sequence so the UI responds
// faster.
const _metadata = this._getStatisticsMetaData([this.entityId]);
const _statistics = fetchStatistics(
this.hass!,
subHours(new Date(), 24),
undefined,
[this.entityId],
"5minute",
undefined,
statTypes
);
const [metadata, statistics] = await Promise.all([
_metadata,
_statistics,
]);
if (metadata && Object.keys(metadata).length) {
this._metadata = metadata;
this._statistics = statistics;
this._statNames = { [this.entityId]: "" };
return;
}
}
@@ -253,7 +238,7 @@ export class MoreInfoHistory extends LitElement {
this._error = err;
return undefined;
});
this._setUpdateTimer();
this._setRedrawTimer();
}
static styles = [

View File

@@ -293,18 +293,18 @@ type EMIncomingMessage =
type EMIncomingMessageHandler = (msg: EMIncomingMessageCommands) => boolean;
export interface ExternalConfig {
hasSettingsScreen?: boolean;
hasSidebar?: boolean;
canWriteTag?: boolean;
hasExoPlayer?: boolean;
canCommissionMatter?: boolean;
canImportThreadCredentials?: boolean;
canTransferThreadCredentialsToKeychain?: boolean;
hasAssist?: boolean;
hasBarCodeScanner?: number;
canSetupImprov?: boolean;
downloadFileSupported?: boolean;
appVersion?: string;
hasSettingsScreen: boolean;
hasSidebar: boolean;
canWriteTag: boolean;
hasExoPlayer: boolean;
canCommissionMatter: boolean;
canImportThreadCredentials: boolean;
canTransferThreadCredentialsToKeychain: boolean;
hasAssist: boolean;
hasBarCodeScanner: number;
canSetupImprov: boolean;
downloadFileSupported: boolean;
appVersion: string;
}
export class ExternalMessaging {

View File

@@ -332,15 +332,6 @@ class DialogCalendarEventEditor extends LitElement {
private _allDayToggleChanged(ev) {
this._allDay = ev.target.checked;
// When switching to all-day mode, normalize dates to midnight so time portions don't interfere with date comparisons
if (this._allDay && this._dtstart && this._dtend) {
this._dtstart = new Date(
formatDate(this._dtstart, this._timeZone!) + "T00:00:00"
);
this._dtend = new Date(
formatDate(this._dtend, this._timeZone!) + "T00:00:00"
);
}
}
private _startDateChanged(ev: CustomEvent) {

View File

@@ -257,7 +257,7 @@ class DialogAddAutomationElement
const results = fuse.multiTermsSearch(filter);
if (results) {
return results.map((result) => result.item).filter((item) => item.name);
return results.map((result) => result.item);
}
return items;
}
@@ -294,7 +294,7 @@ class DialogAddAutomationElement
const results = fuse.multiTermsSearch(filter);
if (results) {
return results.map((result) => result.item).filter((item) => item.name);
return results.map((result) => result.item);
}
return items;
}
@@ -383,16 +383,9 @@ class DialogAddAutomationElement
generatedCollections.push({
titleKey: collection.titleKey,
groups: groups.sort((a, b) => {
// make sure device is always on top
if (a.key === "device" || a.key === "device_id") {
return -1;
}
if (b.key === "device" || b.key === "device_id") {
return 1;
}
return stringCompare(a.name, b.name, this.hass.locale.language);
}),
groups: groups.sort((a, b) =>
stringCompare(a.name, b.name, this.hass.locale.language)
),
});
});
return generatedCollections;
@@ -685,7 +678,7 @@ class DialogAddAutomationElement
);
const typeTitle = this.hass.localize(
`ui.panel.config.automation.editor.${automationElementType}s.add`
`ui.panel.config.automation.editor.${automationElementType}s.header`
);
const tabButtons = [
@@ -902,9 +895,7 @@ class DialogAddAutomationElement
return html`
<div class="items-title ${this._itemsScrolled ? "scrolled" : ""}">
${this._tab === "blocks" && !this._filter
? this.hass.localize("ui.panel.config.automation.editor.blocks")
: title}
${title}
</div>
<ha-md-list
dialogInitialFocus=${ifDefined(this._fullScreen ? "" : undefined)}

View File

@@ -20,7 +20,6 @@ import { subscribeEntityRegistry } from "../../../data/entity_registry";
import type { UpdateEntity } from "../../../data/update";
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../types";
import "../../../components/ha-progress-ring";
@customElement("ha-config-updates")
class HaConfigUpdates extends SubscribeMixin(LitElement) {
@@ -57,29 +56,6 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
this._entities?.find((entity) => entity.entity_id === entityId)
);
private _renderUpdateProgress(entity: UpdateEntity) {
if (entity.attributes.update_percentage != null) {
return html`<ha-progress-ring
size="small"
.value=${entity.attributes.update_percentage}
.label=${this.hass.localize(
"ui.panel.config.updates.update_in_progress"
)}
></ha-progress-ring>`;
}
if (entity.attributes.in_progress) {
return html`<ha-spinner
size="small"
.ariaLabel=${this.hass.localize(
"ui.panel.config.updates.update_in_progress"
)}
></ha-spinner>`;
}
return html`<ha-icon-next></ha-icon-next>`;
}
protected render() {
if (!this.updateEntities?.length) {
return nothing;
@@ -130,9 +106,13 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
)}
></state-badge>
${this.narrow && entity.attributes.in_progress
? html`<div class="absolute">
${this._renderUpdateProgress(entity)}
</div>`
? html`<ha-spinner
class="absolute"
size="small"
.ariaLabel=${this.hass.localize(
"ui.panel.config.updates.update_in_progress"
)}
></ha-spinner>`
: nothing}
</div>
<span slot="headline"
@@ -148,9 +128,16 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
: nothing}
</span>
${!this.narrow
? html`<div slot="end">
${this._renderUpdateProgress(entity)}
</div>`
? entity.attributes.in_progress
? html`<div slot="end">
<ha-spinner
size="small"
.ariaLabel=${this.hass.localize(
"ui.panel.config.updates.update_in_progress"
)}
></ha-spinner>
</div>`
: html`<ha-icon-next slot="end"></ha-icon-next>`
: nothing}
</ha-md-list-item>
`;
@@ -206,13 +193,13 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
div[slot="start"] {
position: relative;
}
div.absolute {
ha-spinner.absolute {
position: absolute;
left: 6px;
top: 6px;
}
state-badge.updating {
opacity: 0.2;
opacity: 0.5;
}
`,
];

View File

@@ -109,26 +109,15 @@ export class AssistPipelineDetailConversation extends LitElement {
}
private _supportedLanguagesChanged(ev) {
this._supportedLanguages = ev.detail.value;
if (
this._supportedLanguages === "*" ||
!this._supportedLanguages?.includes(
this.data?.conversation_language || ""
) ||
!this.data?.conversation_language
) {
if (ev.detail.value === "*") {
// wait for update of conversation_engine
setTimeout(() => {
const value = { ...this.data };
if (this._supportedLanguages === "*") {
value.conversation_language = "*";
} else {
value.conversation_language = this._supportedLanguages?.[0] ?? null;
}
value.conversation_language = "*";
fireEvent(this, "value-changed", { value });
}, 0);
}
this._supportedLanguages = ev.detail.value;
}
static styles = css`

View File

@@ -148,9 +148,7 @@ export class AssistPipelineDebug extends LitElement {
).pipeline_runs.reverse();
} catch (e: any) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.voice_assistants.debug.error.fetch_runs"
),
title: "Failed to fetch pipeline runs",
text: e.message,
});
return;
@@ -178,9 +176,7 @@ export class AssistPipelineDebug extends LitElement {
).events;
} catch (e: any) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.voice_assistants.debug.error.fetch_events"
),
title: "Failed to fetch events",
text: e.message,
});
return;

View File

@@ -30,26 +30,16 @@ export class AssistPipelineEvents extends LitElement {
const run = this._processEvents(this.events);
if (!run) {
if (this.events.length) {
return html`<ha-alert alert-type="error"
>${this.hass.localize(
"ui.panel.config.voice_assistants.debug.error.showing_run"
)}</ha-alert
>
return html`<ha-alert alert-type="error">Error showing run</ha-alert>
<ha-card>
<ha-expansion-panel>
<span slot="header"
>${this.hass.localize(
"ui.panel.config.voice_assistants.debug.raw"
)}</span
>
<span slot="header">Raw</span>
<pre>${JSON.stringify(this.events, null, 2)}</pre>
</ha-expansion-panel>
</ha-card>`;
}
return html`<ha-alert alert-type="warning"
>${this.hass.localize(
"ui.panel.config.voice_assistants.debug.no_events"
)}</ha-alert
>There were no events in this run.</ha-alert
>`;
}
return html`

View File

@@ -11,16 +11,31 @@ import type { HomeAssistant } from "../../../../types";
import { formatNumber } from "../../../../common/number/format_number";
import "../../../../components/ha-yaml-editor";
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
import type { LocalizeKeys } from "../../../../common/translations/localize";
const RUN_DATA = ["pipeline", "language"];
const WAKE_WORD_DATA = ["engine"];
const RUN_DATA = {
pipeline: "Pipeline",
language: "Language",
};
const WAKE_WORD_DATA = {
engine: "Engine",
};
const STT_DATA = ["engine"];
const STT_DATA = {
engine: "Engine",
};
const INTENT_DATA = ["engine", "language", "intent_input"];
const INTENT_DATA = {
engine: "Engine",
language: "Language",
intent_input: "Input",
};
const TTS_DATA = ["engine", "language", "voice", "tts_input"];
const TTS_DATA = {
engine: "Engine",
language: "Language",
voice: "Voice",
tts_input: "Input",
};
const STAGES: Record<PipelineRun["stage"], number> = {
ready: 0,
@@ -87,32 +102,24 @@ const renderProgress = (
return html`${durationString}s ✅`;
};
const renderData = (
hass: HomeAssistant,
data: Record<string, any>,
keys: string[]
) =>
keys.map((key) => {
const label = hass.localize(
`ui.panel.config.voice_assistants.debug.stages.${key}` as LocalizeKeys
);
return html`
const renderData = (data: Record<string, any>, keys: Record<string, string>) =>
Object.entries(keys).map(
([key, label]) => html`
<div class="row">
<div>${label}</div>
<div>${data[key]}</div>
</div>
`;
});
`
);
const dataMinusKeysRender = (
hass: HomeAssistant,
data: Record<string, any>,
keys: string[]
keys: Record<string, string>
) => {
const result = {};
let render = false;
for (const key in data) {
if (keys.includes(key) || key === "done") {
if (key in keys || key === "done") {
continue;
}
render = true;
@@ -120,9 +127,7 @@ const dataMinusKeysRender = (
}
return render
? html`<ha-expansion-panel>
<span slot="header"
>${hass.localize("ui.panel.config.voice_assistants.debug.raw")}</span
>
<span slot="header">Raw</span>
<ha-yaml-editor readOnly autoUpdate .value=${result}></ha-yaml-editor>
</ha-expansion-panel>`
: "";
@@ -134,12 +139,6 @@ export class AssistPipelineDebug extends LitElement {
@property({ attribute: false }) public pipelineRun!: PipelineRun;
private _audioElement?: HTMLAudioElement;
private get _isPlaying(): boolean {
return this._audioElement != null && !this._audioElement.paused;
}
protected render(): TemplateResult {
const lastRunStage: string = this.pipelineRun
? ["tts", "intent", "stt", "wake_word"].find(
@@ -178,15 +177,11 @@ export class AssistPipelineDebug extends LitElement {
<ha-card>
<div class="card-content">
<div class="row heading">
<div>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.run"
)}
</div>
<div>Run</div>
<div>${this.pipelineRun.stage}</div>
</div>
${renderData(this.hass, this.pipelineRun.run, RUN_DATA)}
${renderData(this.pipelineRun.run, RUN_DATA)}
${messages.length > 0
? html`
<div class="messages">
@@ -208,39 +203,23 @@ export class AssistPipelineDebug extends LitElement {
<ha-card>
<div class="card-content">
<div class="row heading">
<span
>${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.wake_word"
)}</span
>
<span>Wake word</span>
${renderProgress(this.hass, this.pipelineRun, "wake_word")}
</div>
${this.pipelineRun.wake_word
? html`
<div class="card-content">
${renderData(
this.hass,
this.pipelineRun.wake_word,
WAKE_WORD_DATA
)}
${renderData(this.pipelineRun.wake_word, STT_DATA)}
${this.pipelineRun.wake_word.wake_word_output
? html`<div class="row">
<div>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.model"
)}
</div>
<div>Model</div>
<div>
${this.pipelineRun.wake_word.wake_word_output
.ww_id}
</div>
</div>
<div class="row">
<div>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.timestamp"
)}
</div>
<div>Timestamp</div>
<div>
${this.pipelineRun.wake_word.wake_word_output
.timestamp}
@@ -248,7 +227,6 @@ export class AssistPipelineDebug extends LitElement {
</div>`
: ""}
${dataMinusKeysRender(
this.hass,
this.pipelineRun.wake_word,
WAKE_WORD_DATA
)}
@@ -265,11 +243,7 @@ export class AssistPipelineDebug extends LitElement {
<ha-card>
<div class="card-content">
<div class="row heading">
<span
>${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.speech_to_text"
)}</span
>
<span>Speech-to-text</span>
${renderProgress(
this.hass,
this.pipelineRun,
@@ -280,30 +254,18 @@ export class AssistPipelineDebug extends LitElement {
${this.pipelineRun.stt
? html`
<div class="card-content">
${renderData(this.hass, this.pipelineRun.stt, STT_DATA)}
${renderData(this.pipelineRun.stt, STT_DATA)}
<div class="row">
<div>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.language"
)}
</div>
<div>Language</div>
<div>${this.pipelineRun.stt.metadata.language}</div>
</div>
${this.pipelineRun.stt.stt_output
? html`<div class="row">
<div>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.output"
)}
</div>
<div>Output</div>
<div>${this.pipelineRun.stt.stt_output.text}</div>
</div>`
: ""}
${dataMinusKeysRender(
this.hass,
this.pipelineRun.stt,
STT_DATA
)}
${dataMinusKeysRender(this.pipelineRun.stt, STT_DATA)}
</div>
`
: ""}
@@ -317,28 +279,16 @@ export class AssistPipelineDebug extends LitElement {
<ha-card>
<div class="card-content">
<div class="row heading">
<span
>${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.natural_language_processing"
)}</span
>
<span>Natural Language Processing</span>
${renderProgress(this.hass, this.pipelineRun, "intent")}
</div>
${this.pipelineRun.intent
? html`
<div class="card-content">
${renderData(
this.hass,
this.pipelineRun.intent,
INTENT_DATA
)}
${renderData(this.pipelineRun.intent, INTENT_DATA)}
${this.pipelineRun.intent.intent_output
? html`<div class="row">
<div>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.response_type"
)}
</div>
<div>Response type</div>
<div>
${this.pipelineRun.intent.intent_output
.response.response_type}
@@ -347,11 +297,7 @@ export class AssistPipelineDebug extends LitElement {
${this.pipelineRun.intent.intent_output.response
.response_type === "error"
? html`<div class="row">
<div>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.error.code"
)}
</div>
<div>Error code</div>
<div>
${this.pipelineRun.intent.intent_output
.response.data.code}
@@ -360,27 +306,18 @@ export class AssistPipelineDebug extends LitElement {
: ""}`
: ""}
<div class="row">
<div>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.prefer_local"
)}
</div>
<div>Prefer handling locally</div>
<div>
${this.pipelineRun.intent.prefer_local_intents}
</div>
</div>
<div class="row">
<div>
${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.processed_locally"
)}
</div>
<div>Processed locally</div>
<div>
${this.pipelineRun.intent.processed_locally}
</div>
</div>
${dataMinusKeysRender(
this.hass,
this.pipelineRun.intent,
INTENT_DATA
)}
@@ -397,22 +334,14 @@ export class AssistPipelineDebug extends LitElement {
<ha-card>
<div class="card-content">
<div class="row heading">
<span
>${this.hass.localize(
"ui.panel.config.voice_assistants.debug.stages.text_to_speech"
)}</span
>
<span>Text-to-speech</span>
${renderProgress(this.hass, this.pipelineRun, "tts")}
</div>
${this.pipelineRun.tts
? html`
<div class="card-content">
${renderData(this.hass, this.pipelineRun.tts, TTS_DATA)}
${dataMinusKeysRender(
this.hass,
this.pipelineRun.tts,
TTS_DATA
)}
${renderData(this.pipelineRun.tts, TTS_DATA)}
${dataMinusKeysRender(this.pipelineRun.tts, TTS_DATA)}
</div>
`
: ""}
@@ -420,19 +349,8 @@ export class AssistPipelineDebug extends LitElement {
${this.pipelineRun?.tts?.tts_output
? html`
<div class="card-actions">
<ha-button
.variant=${this._isPlaying ? "danger" : "brand"}
@click=${this._isPlaying
? this._stopTTS
: this._playTTS}
>
${this._isPlaying
? this.hass.localize(
"ui.panel.config.voice_assistants.debug.stop_audio"
)
: this.hass.localize(
"ui.panel.config.voice_assistants.debug.play_audio"
)}
<ha-button @click=${this._playTTS}>
Play Audio
</ha-button>
</div>
`
@@ -443,11 +361,7 @@ export class AssistPipelineDebug extends LitElement {
${maybeRenderError(this.pipelineRun, "tts", lastRunStage)}
<ha-card>
<ha-expansion-panel>
<span slot="header"
>${this.hass.localize(
"ui.panel.config.voice_assistants.debug.raw"
)}</span
>
<span slot="header">Raw</span>
<ha-yaml-editor
read-only
auto-update
@@ -459,48 +373,14 @@ export class AssistPipelineDebug extends LitElement {
}
private _playTTS(): void {
// Stop any existing audio first
this._stopTTS();
const url = this.pipelineRun!.tts!.tts_output!.url;
this._audioElement = new Audio(url);
this._audioElement.addEventListener("error", () => {
showAlertDialog(this, {
title: this.hass.localize(
"ui.panel.config.voice_assistants.debug.error.title"
),
text: this.hass.localize(
"ui.panel.config.voice_assistants.debug.error.playing_audio"
),
});
const audio = new Audio(url);
audio.addEventListener("error", () => {
showAlertDialog(this, { title: "Error", text: "Error playing audio" });
});
this._audioElement.addEventListener("play", () => {
this.requestUpdate();
audio.addEventListener("canplaythrough", () => {
audio.play();
});
this._audioElement.addEventListener("ended", () => {
this.requestUpdate();
});
this._audioElement.addEventListener("canplaythrough", () => {
this._audioElement!.play();
});
}
private _stopTTS(): void {
if (this._audioElement) {
this._audioElement.pause();
this._audioElement.currentTime = 0;
this._audioElement = undefined;
this.requestUpdate();
}
}
public disconnectedCallback(): void {
super.disconnectedCallback();
this._stopTTS();
}
static styles = css`

View File

@@ -214,7 +214,7 @@ export class DialogVoiceAssistantPipelineDetail extends LitElement {
<ha-button
slot="primaryAction"
@click=${this._updatePipeline}
.loading=${this._submitting}
.disabled=${this._submitting}
dialogInitialFocus
>
${this._params.pipeline?.id

View File

@@ -52,8 +52,6 @@ export class HaLogbook extends LitElement {
@property({ attribute: false }) public deviceIds?: string[];
@property({ attribute: false }) public stateFilter?: string[];
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean, reflect: true }) public virtualize = false;
@@ -167,7 +165,7 @@ export class HaLogbook extends LitElement {
protected willUpdate(changedProps: PropertyValues): void {
let changed = changedProps.has("time");
for (const key of ["entityIds", "deviceIds", "stateFilter"]) {
for (const key of ["entityIds", "deviceIds"]) {
if (!changedProps.has(key)) {
continue;
}
@@ -354,19 +352,9 @@ export class HaLogbook extends LitElement {
"recent" in this.time
? findStartOfRecentTime(new Date(), this.time.recent)
: undefined;
let eventsFiltered: LogbookEntry[] | undefined;
if (this.stateFilter && this.stateFilter.length > 0) {
eventsFiltered = streamMessage.events.filter(
(e) => e.state && this.stateFilter?.includes(e.state)
);
} else {
eventsFiltered = [...streamMessage.events];
}
// Put newest ones on top. Reverse works in-place so
// make a copy first.
const newEntries = eventsFiltered.reverse();
const newEntries = [...streamMessage.events].reverse();
if (!this._logbookEntries || !this._logbookEntries.length) {
this._logbookEntries = newEntries;
return;

View File

@@ -80,44 +80,41 @@ export class HuiEnergyCompareCard
return html`
<ha-alert dismissable @alert-dismissed-clicked=${this._stopCompare}>
${this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_compare.info",
{
start: html`<b
>${formatDate(
this._start!,
this.hass.locale,
this.hass.config
)}${dayDifference > 0
? ` -
${this.hass.localize("ui.panel.energy.compare.info", {
start: html`<b
>${formatDate(
this._start!,
this.hass.locale,
this.hass.config
)}${dayDifference > 0
? ` -
${formatDate(
this._end || endOfDay(new Date()),
this.hass.locale,
this.hass.config
)}`
: ""}</b
>`,
end: html`<b
>${formatDate(
this._startCompare,
this.hass.locale,
this.hass.config
)}${dayDifference > 0
? ` -
: ""}</b
>`,
end: html`<b
>${formatDate(
this._startCompare,
this.hass.locale,
this.hass.config
)}${dayDifference > 0
? ` -
${formatDate(this._endCompare, this.hass.locale, this.hass.config)}`
: ""}</b
>
<button class="link" @click=${this._changeCompareMode}>
(${this._compareMode === CompareMode.PREVIOUS
? this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_compare.compare_previous_year"
)
: this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_compare.compare_previous_period"
)})
</button>`,
}
)}
: ""}</b
>
<button class="link" @click=${this._changeCompareMode}>
(${this._compareMode === CompareMode.PREVIOUS
? this.hass.localize(
"ui.panel.energy.compare.compare_previous_year"
)
: this.hass.localize(
"ui.panel.energy.compare.compare_previous_period"
)})
</button>`,
})}
</ha-alert>
`;
}

View File

@@ -61,6 +61,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
@state() private _calendars: Calendar[] = [];
@state() private _eventDisplay = "list-item";
@state() private _narrow = false;
@state() private _error?: string = undefined;
@@ -142,6 +144,7 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
.hass=${this.hass}
.views=${views}
.initialView=${this._config.initial_view!}
.eventDisplay=${this._eventDisplay}
.error=${this._error}
@view-changed=${this._handleViewChanged}
></ha-full-calendar>
@@ -171,6 +174,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
}
private _handleViewChanged(ev: HASSDomEvent<CalendarViewChanged>): void {
this._eventDisplay =
ev.detail.view === "dayGridMonth" ? "list-item" : "auto";
this._startDate = ev.detail.start;
this._endDate = ev.detail.end;
this._fetchCalendarEvents();

View File

@@ -1,6 +1,5 @@
import { css, html, LitElement, nothing } from "lit";
import { customElement, property, 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 parseAspectRatio from "../../../common/util/parse-aspect-ratio";
@@ -98,10 +97,7 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
: `${sandbox_user_params} ${IFRAME_SANDBOX}`;
return html`
<ha-card
class=${classMap({ "hide-background": this._config.hide_background })}
.header=${this._config.title}
>
<ha-card .header=${this._config.title}>
<div
id="root"
style=${styleMap({
@@ -137,12 +133,6 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
flex-direction: column;
}
ha-card.hide-background {
background: transparent;
box-shadow: none;
border: none;
}
#root {
width: 100%;
height: 100%;

View File

@@ -64,8 +64,6 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
@state() private _targetPickerValue: HassServiceTarget = {};
@state() private _stateFilter?: string[];
public getCardSize(): number {
return 9 + (this._config?.title ? 1 : 0);
}
@@ -131,8 +129,6 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
};
this._targetPickerValue = target;
this._stateFilter = ensureArray(config.state_filter);
}
private _getEntityIds(): string[] | undefined {
@@ -213,7 +209,6 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
.hass=${this.hass}
.time=${this._time}
.entityIds=${this._getEntityIds()}
.stateFilter=${this._stateFilter}
narrow
relative-time
virtualize

View File

@@ -323,7 +323,6 @@ export interface IframeCardConfig extends LovelaceCardConfig {
title?: string;
allow?: string;
url: string;
hide_background?: boolean;
}
export interface LightCardConfig extends LovelaceCardConfig {
@@ -346,7 +345,6 @@ export interface LogbookCardConfig extends LovelaceCardConfig {
title?: string;
hours_to_show?: number;
theme?: string;
state_filter?: string[];
}
export interface MapEntityConfig extends EntityConfig {

View File

@@ -16,7 +16,6 @@ const cardConfigStruct = assign(
url: optional(string()),
aspect_ratio: optional(string()),
allow_open_top_navigation: optional(boolean()),
hide_background: optional(boolean()),
})
);
@@ -30,7 +29,6 @@ const SCHEMA = [
{ name: "aspect_ratio", selector: { text: {} } },
],
},
{ name: "hide_background", selector: { boolean: {} } },
] as const;
@customElement("hui-iframe-card-editor")
@@ -58,7 +56,6 @@ export class HuiIframeCardEditor
.data=${this._config}
.schema=${SCHEMA}
.computeLabel=${this._computeLabelCallback}
.computeHelper=${this._computeHelperCallback}
@value-changed=${this._valueChanged}
></ha-form>
`;
@@ -68,29 +65,8 @@ export class HuiIframeCardEditor
fireEvent(this, "config-changed", { config: ev.detail.value });
}
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
switch (schema.name) {
case "hide_background":
return this.hass!.localize(
"ui.panel.lovelace.editor.card.iframe.hide_background"
);
default:
return this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}`
);
}
};
private _computeHelperCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
switch (schema.name) {
case "hide_background":
return this.hass!.localize(
"ui.panel.lovelace.editor.card.iframe.hide_background_helper"
);
default:
return "";
}
};
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) =>
this.hass!.localize(`ui.panel.lovelace.editor.card.generic.${schema.name}`);
}
declare global {

View File

@@ -1,7 +1,5 @@
import type { HassServiceTarget } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing } from "lit";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import {
array,
assert,
@@ -11,21 +9,21 @@ import {
optional,
string,
} from "superstruct";
import type { HassServiceTarget } from "home-assistant-js-websocket";
import { fireEvent } from "../../../../common/dom/fire_event";
import "../../../../components/entity/ha-entities-picker";
import "../../../../components/ha-target-picker";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import "../../../../components/ha-target-picker";
import type { HaEntityPickerEntityFilterFunc } from "../../../../data/entity";
import { filterLogbookCompatibleEntities } from "../../../../data/logbook";
import { targetStruct } from "../../../../data/script";
import { resolveEntityIDs } from "../../../../data/selector";
import { getSensorNumericDeviceClasses } from "../../../../data/sensor";
import type { HomeAssistant } from "../../../../types";
import { DEFAULT_HOURS_TO_SHOW } from "../../cards/hui-logbook-card";
import type { LogbookCardConfig } from "../../cards/types";
import type { LovelaceCardEditor } from "../../types";
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
import { DEFAULT_HOURS_TO_SHOW } from "../../cards/hui-logbook-card";
import { targetStruct } from "../../../../data/script";
import { getSensorNumericDeviceClasses } from "../../../../data/sensor";
import type { HaEntityPickerEntityFilterFunc } from "../../../../data/entity";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
@@ -35,7 +33,6 @@ const cardConfigStruct = assign(
hours_to_show: optional(number()),
theme: optional(string()),
target: optional(targetStruct),
state_filter: optional(array(string())),
})
);
@@ -53,13 +50,6 @@ const SCHEMA = [
},
],
},
{
name: "state_filter",
context: {
filter_entity: "context_entities",
},
selector: { state: { multiple: true } },
},
] as const;
@customElement("hui-logbook-card-editor")
@@ -116,13 +106,7 @@ export class HuiLogbookCardEditor
return html`
<ha-form
.hass=${this.hass}
.data=${this._data(
this._config,
this._targetPicker,
this.hass.entities,
this.hass.devices,
this.hass.areas
)}
.data=${this._config}
.schema=${SCHEMA}
.computeLabel=${this._computeLabelCallback}
@value-changed=${this._valueChanged}
@@ -132,30 +116,12 @@ export class HuiLogbookCardEditor
.hass=${this.hass}
.entityFilter=${this._filterFunc}
.value=${this._targetPicker}
add-on-top
@value-changed=${this._entitiesChanged}
></ha-target-picker>
`;
}
private _data = memoizeOne(
(
config: LogbookCardConfig,
target: HassServiceTarget,
entities: HomeAssistant["entities"],
devices: HomeAssistant["devices"],
areas: HomeAssistant["areas"]
) => ({
...config,
context_entities: resolveEntityIDs(
this.hass!,
target,
entities,
devices,
areas
),
})
);
private _filterFunc: HaEntityPickerEntityFilterFunc = (entity) =>
filterLogbookCompatibleEntities(entity, this._sensorNumericDeviceClasses);
@@ -165,9 +131,7 @@ export class HuiLogbookCardEditor
}
private _valueChanged(ev: CustomEvent): void {
const newConfig = { ...ev.detail.value };
delete newConfig.context_entities;
fireEvent(this, "config-changed", { config: newConfig });
fireEvent(this, "config-changed", { config: ev.detail.value });
}
private _computeLabelCallback = (schema: SchemaUnion<typeof SCHEMA>) => {
@@ -178,23 +142,12 @@ export class HuiLogbookCardEditor
)} (${this.hass!.localize(
"ui.panel.lovelace.editor.card.config.optional"
)})`;
case "state_filter":
return this.hass!.localize(
"ui.panel.lovelace.editor.card.logbook.state_filter"
);
default:
return this.hass!.localize(
`ui.panel.lovelace.editor.card.generic.${schema.name}`
);
}
};
static styles = css`
ha-target-picker {
display: block;
margin-top: var(--ha-space-4);
}
`;
}
declare global {

View File

@@ -3631,36 +3631,6 @@
"older_run": "Older run",
"newer_run": "Newer run",
"start_debug_run": "Start debug run",
"error": {
"code": "Error code",
"fetch_events": "Failed to fetch events",
"fetch_runs": "Failed to fetch pipeline runs",
"playing_audio": "Error playing audio",
"showing_run": "Error showing run",
"title": "Error"
},
"no_events": "There were no events in this run.",
"play_audio": "Play audio",
"raw": "Raw",
"run": "Run",
"stages": {
"engine": "Engine",
"input": "Input",
"language": "Language",
"model": "Model",
"natural_language_processing": "Natural language processing",
"output": "Output",
"pipeline": "Pipeline",
"prefer_local": "Prefer handling locally",
"processed_locally": "Processed locally",
"response_type": "Response type",
"speech_to_text": "Speech-to-text",
"text_to_speech": "Text-to-speech",
"timestamp": "Timestamp",
"voice": "Voice",
"wake_word": "Wake word"
},
"stop_audio": "Stop audio",
"pipeline": {
"header": "Assist pipeline",
"run_text_pipeline": "Run text pipeline",
@@ -7136,11 +7106,6 @@
"card_indicates_energy_used": "This card indicates how much of the electricity consumed by your home was generated using non-fossil fuels like solar, wind, and nuclear. The higher, the better!",
"low_carbon_energy_consumed": "Low-carbon electricity consumed",
"low_carbon_energy_not_calculated": "Consumed low-carbon electricity couldn't be calculated"
},
"energy_compare": {
"info": "You are comparing the period {start} with the period {end}",
"compare_previous_year": "Compare previous year",
"compare_previous_period": "Compare previous period"
}
},
"heading": {
@@ -7704,8 +7669,7 @@
},
"logbook": {
"name": "Activity",
"description": "The Activity card shows a list of events for entities.",
"state_filter": "State filter"
"description": "The Activity card shows a list of events for entities."
},
"history-graph": {
"name": "History graph",
@@ -7783,9 +7747,7 @@
},
"iframe": {
"name": "Webpage",
"description": "The Webpage card allows you to embed your favorite webpage right into Home Assistant.",
"hide_background": "Hide background",
"hide_background_helper": "Useful for pages which allow a transparent background."
"description": "The Webpage card allows you to embed your favorite webpage right into Home Assistant."
},
"light": {
"name": "Light",
@@ -9391,6 +9353,11 @@
"energy": {
"download_data": "[%key:ui::panel::history::download_data%]",
"configure": "[%key:ui::dialogs::quick-bar::commands::navigation::energy%]",
"compare": {
"info": "You are comparing the period {start} with the period {end}",
"compare_previous_year": "Compare previous year",
"compare_previous_period": "Compare previous period"
},
"setup": {
"next": "Next",
"back": "Back",

899
yarn.lock

File diff suppressed because it is too large Load Diff