mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-04 16:39:44 +00:00
Compare commits
23 Commits
dropdown
...
20251103.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
561122f03d | ||
|
|
95311be034 | ||
|
|
1eda44a102 | ||
|
|
d76781eb91 | ||
|
|
82d44e051f | ||
|
|
fdc9f5a3b7 | ||
|
|
ee6c82aba9 | ||
|
|
11d3f5c2ba | ||
|
|
feb68ce373 | ||
|
|
7f9a9de157 | ||
|
|
8e1b6a3d3b | ||
|
|
6e6e5a53e2 | ||
|
|
0408734ec5 | ||
|
|
317519fc08 | ||
|
|
843d79eab4 | ||
|
|
165a757f06 | ||
|
|
ea8b730142 | ||
|
|
e88c97d625 | ||
|
|
7560988b76 | ||
|
|
eecd8077b6 | ||
|
|
cbab5c3f7b | ||
|
|
a5d27c8bb8 | ||
|
|
a6a340b5db |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -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
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
title: Dropdown
|
||||
---
|
||||
|
||||
# Dropdown `<ha-dropdown>`
|
||||
|
||||
## Implementation
|
||||
|
||||
A compact, accessible dropdown menu for choosing actions or settings. `ha-dropdown` supports composed menu items (`<ha-dropdown-item>`) for icons, submenus, checkboxes, disabled entries, and destructive variants. Use composition with `slot="trigger"` to control the trigger button and use `<ha-dropdown-item>` for rich item content.
|
||||
|
||||
### Example usage (composition)
|
||||
|
||||
```html
|
||||
<ha-dropdown open>
|
||||
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
|
||||
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon .path="${mdiContentCut}" slot="icon"></ha-svg-icon>
|
||||
Cut
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon .path="${mdiContentCopy}" slot="icon"></ha-svg-icon>
|
||||
Copy
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item disabled>
|
||||
<ha-svg-icon .path="${mdiContentPaste}" slot="icon"></ha-svg-icon>
|
||||
Paste
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item>
|
||||
Show images
|
||||
<ha-dropdown-item slot="submenu" value="show-all-images"
|
||||
>Show all images</ha-dropdown-item
|
||||
>
|
||||
<ha-dropdown-item slot="submenu" value="show-thumbnails"
|
||||
>Show thumbnails</ha-dropdown-item
|
||||
>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item type="checkbox" checked>Emoji shortcuts</ha-dropdown-item>
|
||||
<ha-dropdown-item type="checkbox" checked>Word wrap</ha-dropdown-item>
|
||||
|
||||
<ha-dropdown-item variant="danger">
|
||||
<ha-svg-icon .path="${mdiDelete}" slot="icon"></ha-svg-icon>
|
||||
Delete
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
This component is based on the webawesome dropdown component.
|
||||
Check the [webawesome documentation](https://webawesome.com/docs/components/dropdown/) for more details.
|
||||
@@ -1,133 +0,0 @@
|
||||
import {
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiContentPaste,
|
||||
mdiDelete,
|
||||
} from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-dropdown-item";
|
||||
import "@home-assistant/webawesome/dist/components/icon/icon";
|
||||
import "@home-assistant/webawesome/dist/components/button/button";
|
||||
import "@home-assistant/webawesome/dist/components/dropdown/dropdown";
|
||||
import "../../../../src/components/ha-dropdown";
|
||||
import "@home-assistant/webawesome/dist/components/popup/popup";
|
||||
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
|
||||
@customElement("demo-components-ha-dropdown")
|
||||
export class DemoHaDropdown extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${["light", "dark"].map(
|
||||
(mode) => html`
|
||||
<div class=${mode}>
|
||||
<ha-card header="ha-button in ${mode}">
|
||||
<div class="card-content">
|
||||
<ha-dropdown open>
|
||||
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
|
||||
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon
|
||||
.path=${mdiContentCut}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
Cut
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item>
|
||||
<ha-svg-icon
|
||||
.path=${mdiContentCopy}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
Copy
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item disabled>
|
||||
<ha-svg-icon
|
||||
.path=${mdiContentPaste}
|
||||
slot="icon"
|
||||
></ha-svg-icon>
|
||||
Paste
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item>
|
||||
Show images
|
||||
<ha-dropdown-item slot="submenu" value="show-all-images"
|
||||
>Show All Images</ha-dropdown-item
|
||||
>
|
||||
<ha-dropdown-item slot="submenu" value="show-thumbnails"
|
||||
>Show Thumbnails</ha-dropdown-item
|
||||
>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item type="checkbox" checked
|
||||
>Emoji Shortcuts</ha-dropdown-item
|
||||
>
|
||||
<ha-dropdown-item type="checkbox" checked
|
||||
>Word Wrap</ha-dropdown-item
|
||||
>
|
||||
<ha-dropdown-item variant="danger">
|
||||
<ha-svg-icon .path=${mdiDelete} slot="icon"></ha-svg-icon>
|
||||
Delete
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
applyThemesOnElement(
|
||||
this.shadowRoot!.querySelector(".dark"),
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: "default",
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.dark,
|
||||
.light {
|
||||
display: block;
|
||||
background-color: var(--primary-background-color);
|
||||
padding: 0 50px;
|
||||
}
|
||||
.button {
|
||||
padding: unset;
|
||||
}
|
||||
ha-card {
|
||||
margin: 24px auto;
|
||||
}
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.card-content div {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-dropdown": DemoHaDropdown;
|
||||
}
|
||||
}
|
||||
22
package.json
22
package.json
@@ -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",
|
||||
@@ -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/auth-oauth-device": "8.0.2",
|
||||
"@octokit/plugin-retry": "8.0.2",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@rsdoctor/rspack-plugin": "1.3.7",
|
||||
"@rspack/core": "1.6.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"
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20251029.0"
|
||||
version = "20251103.0"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import DropdownItem from "@home-assistant/webawesome/dist/components/dropdown-item/dropdown-item";
|
||||
import { css, type CSSResultGroup } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
/**
|
||||
* Home Assistant dropdown item component
|
||||
*
|
||||
* @element ha-dropdown-item
|
||||
* @extends {DropdownItem}
|
||||
*
|
||||
* @summary
|
||||
* A stylable dropdown item component supporting Home Assistant theming, variants, and appearances based on webawesome dropdown item.
|
||||
*
|
||||
*/
|
||||
@customElement("ha-dropdown-item")
|
||||
export class HaDropdownItem extends DropdownItem {
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
DropdownItem.styles,
|
||||
css`
|
||||
:host {
|
||||
min-height: 40px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-dropdown-item": HaDropdownItem;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import Dropdown from "@home-assistant/webawesome/dist/components/dropdown/dropdown";
|
||||
import { css, type CSSResultGroup } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
/**
|
||||
* Home Assistant dropdown component
|
||||
*
|
||||
* @element ha-dropdown
|
||||
* @extends {Dropdown}
|
||||
*
|
||||
* @summary
|
||||
* A stylable dropdown component supporting Home Assistant theming, variants, and appearances based on webawesome dropdown.
|
||||
*
|
||||
*/
|
||||
@customElement("ha-dropdown")
|
||||
export class HaDropdown extends Dropdown {
|
||||
@property({ attribute: false }) dropdownTag = "ha-dropdown";
|
||||
|
||||
@property({ attribute: false }) dropdownItemTag = "ha-dropdown-item";
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
Dropdown.styles,
|
||||
css`
|
||||
:host {
|
||||
--wa-color-surface-border: var(--ha-color-border-normal);
|
||||
--wa-color-surface-raised: var(
|
||||
--card-background-color,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
}
|
||||
|
||||
#menu {
|
||||
--wa-shadow-m: 0px 4px 8px 0px var(--ha-color-shadow);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-dropdown": HaDropdown;
|
||||
}
|
||||
}
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -345,7 +345,6 @@ export interface LogbookCardConfig extends LovelaceCardConfig {
|
||||
title?: string;
|
||||
hours_to_show?: number;
|
||||
theme?: string;
|
||||
state_filter?: string[];
|
||||
}
|
||||
|
||||
export interface MapEntityConfig extends EntityConfig {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
string,
|
||||
} from "superstruct";
|
||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/entity/ha-entities-picker";
|
||||
import "../../../../components/ha-target-picker";
|
||||
@@ -25,7 +24,6 @@ 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";
|
||||
import { resolveEntityIDs } from "../../../../data/selector";
|
||||
|
||||
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}
|
||||
@@ -138,25 +122,6 @@ export class HuiLogbookCardEditor
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -166,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>) => {
|
||||
@@ -179,10 +142,6 @@ 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}`
|
||||
|
||||
@@ -29,8 +29,6 @@ import {
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../../components/ha-dropdown-item";
|
||||
import "../../components/ha-dropdown";
|
||||
|
||||
// Client ID used by iOS app
|
||||
const iOSclientId = "https://home-assistant.io/iOS";
|
||||
@@ -148,18 +146,19 @@ class HaRefreshTokens extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<ha-dropdown>
|
||||
<ha-md-button-menu positioning="popover">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown-item
|
||||
<ha-md-menu-item
|
||||
graphic="icon"
|
||||
@click=${this._toggleTokenExpiration}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${token.expire_at
|
||||
? mdiClockRemoveOutline
|
||||
: mdiClockCheckOutline}
|
||||
@@ -171,20 +170,24 @@ class HaRefreshTokens extends LitElement {
|
||||
: this.hass.localize(
|
||||
"ui.panel.profile.refresh_tokens.enable_token_expiration"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
variant="danger"
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
graphic="icon"
|
||||
class="warning"
|
||||
.disabled=${token.is_current}
|
||||
@click=${this._deleteToken}
|
||||
.token=${token}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
class="warning"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize("ui.common.delete")}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
<div slot="headline">
|
||||
${this.hass.localize("ui.common.delete")}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</div>
|
||||
</ha-settings-row>
|
||||
`
|
||||
|
||||
@@ -155,10 +155,6 @@ export const semanticColorStyles = css`
|
||||
|
||||
/* Surfaces */
|
||||
--ha-color-surface-default: var(--ha-color-neutral-95);
|
||||
--ha-color-on-surface-default: var(--ha-color-neutral-05);
|
||||
|
||||
/* shadow */
|
||||
--ha-color-shadow: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -290,9 +286,5 @@ export const darkSemanticColorStyles = css`
|
||||
|
||||
/* Surfaces */
|
||||
--ha-color-surface-default: var(--ha-color-neutral-10);
|
||||
--ha-color-on-surface-default: var(--ha-color-neutral-95);
|
||||
|
||||
/* shadow */
|
||||
--ha-color-shadow: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -52,9 +52,7 @@ export const waColorStyles = css`
|
||||
--wa-color-danger-on-normal: var(--ha-color-on-danger-normal);
|
||||
--wa-color-danger-on-quiet: var(--ha-color-on-danger-quiet);
|
||||
|
||||
--wa-color-text-normal: var(--ha-color-text-primary);
|
||||
--wa-color-surface-default: var(--card-background-color);
|
||||
--wa-color-surface-raised: var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff));
|
||||
--wa-color-surface-default: var(--card-background-color);
|
||||
--wa-panel-border-radius: var(--ha-border-radius-3xl);
|
||||
--wa-panel-border-style: solid;
|
||||
--wa-panel-border-width: 1px;
|
||||
|
||||
@@ -16,17 +16,7 @@ export const waMainStyles = css`
|
||||
--wa-font-weight-action: var(--ha-font-weight-medium);
|
||||
--wa-transition-fast: 75ms;
|
||||
--wa-transition-easing: ease;
|
||||
|
||||
--wa-border-style: solid;
|
||||
--wa-border-width-s: var(--ha-border-width-sm);
|
||||
--wa-border-width-m: var(--ha-border-width-md);
|
||||
--wa-border-width-l: var(--ha-border-width-lg);
|
||||
--wa-border-radius-s: var(--ha-border-radius-sm);
|
||||
--wa-border-radius-m: var(--ha-border-radius-md);
|
||||
--wa-border-radius-l: var(--ha-border-radius-lg);
|
||||
|
||||
--wa-line-height-condensed: 1.25;
|
||||
|
||||
--wa-border-width-l: var(--ha-border-radius-lg);
|
||||
--wa-space-xl: 32px;
|
||||
}
|
||||
|
||||
|
||||
@@ -3626,36 +3626,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",
|
||||
@@ -7115,11 +7085,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": {
|
||||
@@ -7683,8 +7648,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",
|
||||
@@ -9368,6 +9332,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",
|
||||
|
||||
Reference in New Issue
Block a user