mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-04 16:39:44 +00:00
Compare commits
1 Commits
dropdown
...
toolbar-to
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0188404a7 |
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;
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ const SENSOR_DEVICE_CLASSES = [
|
||||
"pm1",
|
||||
"pm10",
|
||||
"pm25",
|
||||
"pm4",
|
||||
"power_factor",
|
||||
"power",
|
||||
"precipitation",
|
||||
|
||||
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.6",
|
||||
"@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.4",
|
||||
"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.4",
|
||||
"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"
|
||||
|
||||
@@ -214,7 +214,6 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
|
||||
"pm1",
|
||||
"pm10",
|
||||
"pm25",
|
||||
"pm4",
|
||||
"power_factor",
|
||||
"power",
|
||||
"pressure",
|
||||
|
||||
@@ -652,13 +652,6 @@ export class HaChartBase extends LitElement {
|
||||
textBorderWidth: 2,
|
||||
},
|
||||
},
|
||||
pie: {
|
||||
label: {
|
||||
color: style.getPropertyValue("--primary-text-color"),
|
||||
textBorderColor: style.getPropertyValue("--primary-background-color"),
|
||||
textBorderWidth: 2,
|
||||
},
|
||||
},
|
||||
sankey: {
|
||||
label: {
|
||||
color: style.getPropertyValue("--primary-text-color"),
|
||||
|
||||
@@ -87,8 +87,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
private _previousYAxisLabelValue = 0;
|
||||
|
||||
private _yAxisMaximumFractionDigits = 0;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-chart-base
|
||||
@@ -759,12 +757,8 @@ export class StateHistoryChartLine extends LitElement {
|
||||
Math.log10(Math.abs(value - this._previousYAxisLabelValue || 1))
|
||||
)
|
||||
);
|
||||
this._yAxisMaximumFractionDigits = Math.max(
|
||||
this._yAxisMaximumFractionDigits,
|
||||
maximumFractionDigits
|
||||
);
|
||||
const label = formatNumber(value, this.hass.locale, {
|
||||
maximumFractionDigits: this._yAxisMaximumFractionDigits,
|
||||
maximumFractionDigits,
|
||||
});
|
||||
const width = measureTextWidth(label, 12) + 5;
|
||||
if (width > this._yWidth) {
|
||||
|
||||
@@ -59,7 +59,6 @@ export class HaButton extends Button {
|
||||
line-height: 1;
|
||||
|
||||
transition: background-color 0.15s ease-in-out;
|
||||
text-wrap: wrap;
|
||||
}
|
||||
|
||||
:host([size="small"]) .button {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { atLeastVersion } from "../common/config/version";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface LogProvider {
|
||||
@@ -10,8 +8,4 @@ export interface LogProvider {
|
||||
export const fetchErrorLog = (hass: HomeAssistant) =>
|
||||
hass.callApi<string>("GET", "error_log");
|
||||
|
||||
export const getErrorLogDownloadUrl = (hass: HomeAssistant) =>
|
||||
isComponentLoaded(hass, "hassio") &&
|
||||
atLeastVersion(hass.config.version, 2025, 10)
|
||||
? "/api/hassio/core/logs/latest"
|
||||
: "/api/error_log";
|
||||
export const getErrorLogDownloadUrl = "/api/error_log";
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -97,9 +97,6 @@ export const ENTITY_COMPONENT_ICONS: Record<string, ComponentIcons> = {
|
||||
pm25: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
pm4: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
power: {
|
||||
default: "mdi:flash",
|
||||
},
|
||||
@@ -677,9 +674,6 @@ export const ENTITY_COMPONENT_ICONS: Record<string, ComponentIcons> = {
|
||||
pm25: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
pm4: {
|
||||
default: "mdi:molecule",
|
||||
},
|
||||
power: {
|
||||
default: "mdi:flash",
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1137,11 +1137,11 @@ class DialogAddAutomationElement
|
||||
}
|
||||
.groups .selected {
|
||||
background-color: var(--ha-color-fill-primary-normal-active);
|
||||
--md-list-item-label-text-color: var(--ha-color-on-primary-normal);
|
||||
--icon-primary-color: var(--ha-color-on-primary-normal);
|
||||
--md-list-item-label-text-color: var(--primary-color);
|
||||
--icon-primary-color: var(--primary-color);
|
||||
}
|
||||
.groups .selected ha-svg-icon {
|
||||
color: var(--ha-color-on-primary-normal);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.collection-title {
|
||||
|
||||
@@ -125,6 +125,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
|
||||
@query("#overflow-menu") private _overflowMenu?: HaMdMenu;
|
||||
|
||||
private _overflowBackup?: BackupContent;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("location-changed", this._locationChanged);
|
||||
@@ -260,7 +262,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
type: "overflow-menu",
|
||||
template: (backup) => html`
|
||||
<ha-icon-button
|
||||
.backup=${backup}
|
||||
.selected=${backup}
|
||||
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
@click=${this._toggleOverflowMenu}
|
||||
@@ -292,6 +294,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
this._overflowMenu.close();
|
||||
return;
|
||||
}
|
||||
this._overflowBackup = ev.target.selected;
|
||||
this._overflowMenu.anchorElement = ev.target;
|
||||
this._overflowMenu.show();
|
||||
};
|
||||
@@ -569,17 +572,15 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
navigate(`/config/backup/details/${id}`);
|
||||
}
|
||||
|
||||
private async _downloadBackup(ev): Promise<void> {
|
||||
const backup = ev.parentElement.anchorElement.backup;
|
||||
if (!backup) {
|
||||
private async _downloadBackup(): Promise<void> {
|
||||
if (!this._overflowBackup) {
|
||||
return;
|
||||
}
|
||||
downloadBackup(this.hass, this, backup, this.config);
|
||||
downloadBackup(this.hass, this, this._overflowBackup, this.config);
|
||||
}
|
||||
|
||||
private async _deleteBackup(ev): Promise<void> {
|
||||
const backup = ev.parentElement.anchorElement.backup;
|
||||
if (!backup) {
|
||||
private async _deleteBackup(): Promise<void> {
|
||||
if (!this._overflowBackup) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -595,9 +596,11 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteBackup(this.hass, backup.backup_id);
|
||||
if (this._selected.includes(backup.backup_id)) {
|
||||
this._selected = this._selected.filter((id) => id !== backup.backup_id);
|
||||
await deleteBackup(this.hass, this._overflowBackup.backup_id);
|
||||
if (this._selected.includes(this._overflowBackup.backup_id)) {
|
||||
this._selected = this._selected.filter(
|
||||
(id) => id !== this._overflowBackup!.backup_id
|
||||
);
|
||||
}
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -790,10 +790,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
const timeString = new Date().toISOString().replace(/:/g, "-");
|
||||
const logFileName = `home-assistant_${integration}_${timeString}.log`;
|
||||
const signedUrl = await getSignedPath(
|
||||
this.hass,
|
||||
getErrorLogDownloadUrl(this.hass)
|
||||
);
|
||||
const signedUrl = await getSignedPath(this.hass, getErrorLogDownloadUrl);
|
||||
fileDownload(signedUrl.path, logFileName);
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class DialogLabelDetail
|
||||
this.hass,
|
||||
this._params.entry
|
||||
? this._params.entry.name || this._params.entry.label_id
|
||||
: this.hass!.localize("ui.dialogs.label-detail.new_label")
|
||||
: this.hass!.localize("ui.panel.config.labels.detail.new_label")
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
@@ -95,9 +95,11 @@ class DialogLabelDetail
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@input=${this._input}
|
||||
.label=${this.hass!.localize("ui.dialogs.label-detail.name")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.labels.detail.name"
|
||||
)}
|
||||
.validationMessage=${this.hass!.localize(
|
||||
"ui.dialogs.label-detail.required_error_msg"
|
||||
"ui.panel.config.labels.detail.required_error_msg"
|
||||
)}
|
||||
required
|
||||
></ha-textfield>
|
||||
@@ -106,21 +108,25 @@ class DialogLabelDetail
|
||||
.hass=${this.hass}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize("ui.dialogs.label-detail.icon")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.labels.detail.icon"
|
||||
)}
|
||||
></ha-icon-picker>
|
||||
<ha-color-picker
|
||||
.value=${this._color}
|
||||
.configValue=${"color"}
|
||||
.hass=${this.hass}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize("ui.dialogs.label-detail.color")}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.labels.detail.color"
|
||||
)}
|
||||
></ha-color-picker>
|
||||
<ha-textarea
|
||||
.value=${this._description}
|
||||
.configValue=${"description"}
|
||||
@input=${this._input}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.label-detail.description"
|
||||
"ui.panel.config.labels.detail.description"
|
||||
)}
|
||||
></ha-textarea>
|
||||
</div>
|
||||
@@ -134,7 +140,7 @@ class DialogLabelDetail
|
||||
@click=${this._deleteEntry}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize("ui.common.delete")}
|
||||
${this.hass!.localize("ui.panel.config.labels.detail.delete")}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
@@ -144,8 +150,8 @@ class DialogLabelDetail
|
||||
.disabled=${this._submitting || !this._name}
|
||||
>
|
||||
${this._params.entry
|
||||
? this.hass!.localize("ui.common.update")
|
||||
: this.hass!.localize("ui.common.create")}
|
||||
? this.hass!.localize("ui.panel.config.labels.detail.update")
|
||||
: this.hass!.localize("ui.panel.config.labels.detail.create")}
|
||||
</ha-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
|
||||
@@ -415,7 +415,7 @@ class ErrorLogCard extends LitElement {
|
||||
const downloadUrl =
|
||||
this.provider && this.provider !== "core"
|
||||
? getHassioLogDownloadUrl(this.provider)
|
||||
: getErrorLogDownloadUrl(this.hass);
|
||||
: getErrorLogDownloadUrl;
|
||||
const logFileName =
|
||||
this.provider && this.provider !== "core"
|
||||
? `${this.provider}_${timeString}.log`
|
||||
|
||||
@@ -2,6 +2,8 @@ import { mdiDotsVertical, mdiDownload, mdiRefresh, mdiText } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { atLeastVersion } from "../../../common/config/version";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
@@ -13,6 +15,7 @@ import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import { getErrorLogDownloadUrl } from "../../../data/error_log";
|
||||
import { coreLatestLogsUrl } from "../../../data/hassio/supervisor";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import type { LoggedError } from "../../../data/system_log";
|
||||
import {
|
||||
@@ -228,7 +231,11 @@ export class SystemLogCard extends LitElement {
|
||||
|
||||
private async _downloadLogs() {
|
||||
const timeString = new Date().toISOString().replace(/:/g, "-");
|
||||
const downloadUrl = getErrorLogDownloadUrl(this.hass);
|
||||
const downloadUrl =
|
||||
isComponentLoaded(this.hass, "hassio") &&
|
||||
atLeastVersion(this.hass.config.version, 2025, 10)
|
||||
? coreLatestLogsUrl
|
||||
: getErrorLogDownloadUrl;
|
||||
const logFileName = `home-assistant_${timeString}.log`;
|
||||
const signedUrl = await getSignedPath(this.hass, downloadUrl);
|
||||
fileDownload(signedUrl.path, logFileName);
|
||||
|
||||
@@ -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>`
|
||||
: "";
|
||||
@@ -178,15 +183,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 +209,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 +233,6 @@ export class AssistPipelineDebug extends LitElement {
|
||||
</div>`
|
||||
: ""}
|
||||
${dataMinusKeysRender(
|
||||
this.hass,
|
||||
this.pipelineRun.wake_word,
|
||||
WAKE_WORD_DATA
|
||||
)}
|
||||
@@ -265,11 +249,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 +260,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 +285,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 +303,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 +312,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 +340,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>
|
||||
`
|
||||
: ""}
|
||||
@@ -426,13 +361,7 @@ export class AssistPipelineDebug extends LitElement {
|
||||
? 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"
|
||||
)}
|
||||
${this._isPlaying ? "Stop audio" : "Play audio"}
|
||||
</ha-button>
|
||||
</div>
|
||||
`
|
||||
@@ -443,11 +372,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
|
||||
@@ -466,14 +391,7 @@ export class AssistPipelineDebug extends LitElement {
|
||||
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"
|
||||
),
|
||||
});
|
||||
showAlertDialog(this, { title: "Error", text: "Error playing audio" });
|
||||
});
|
||||
|
||||
this._audioElement.addEventListener("play", () => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -17,7 +17,6 @@ import type { HomeAssistant } from "../../../../types";
|
||||
import { showSaveSuccessToast } from "../../../../util/toast-saved-success";
|
||||
import "../../cards/hui-card";
|
||||
import "../../sections/hui-section";
|
||||
import { getViewType } from "../../views/get-view-type";
|
||||
import { addCards, addSection } from "../config-util";
|
||||
import type { LovelaceContainerPath } from "../lovelace-path";
|
||||
import { parseLovelaceContainerPath } from "../lovelace-path";
|
||||
@@ -67,9 +66,7 @@ export class HuiDialogSuggestCard extends LitElement {
|
||||
const { viewIndex } = parseLovelaceContainerPath(this._params.path);
|
||||
const viewConfig = this._params!.lovelaceConfig.views[viewIndex];
|
||||
|
||||
return (
|
||||
!isStrategyView(viewConfig) && getViewType(viewConfig) === "sections"
|
||||
);
|
||||
return !isStrategyView(viewConfig) && viewConfig.type === "sections";
|
||||
}
|
||||
|
||||
private _renderPreview() {
|
||||
|
||||
@@ -218,9 +218,6 @@ export class HuiGraphHeaderFooter
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-spinner {
|
||||
position: absolute;
|
||||
top: calc(50% - 14px);
|
||||
|
||||
@@ -322,7 +322,6 @@ class HUIRoot extends LitElement {
|
||||
.path=${item.icon}
|
||||
slot="trigger"
|
||||
.label=${label}
|
||||
hide-title
|
||||
></ha-icon-button>
|
||||
${item.subItems
|
||||
.filter((subItem) => subItem.visible)
|
||||
@@ -349,10 +348,8 @@ class HUIRoot extends LitElement {
|
||||
.id="button-${index}"
|
||||
.path=${item.icon}
|
||||
@click=${item.buttonAction}
|
||||
.label=${label}
|
||||
></ha-icon-button>
|
||||
<ha-tooltip placement="bottom" .for="button-${index}">
|
||||
${label}
|
||||
</ha-tooltip>
|
||||
`;
|
||||
result.push(button);
|
||||
});
|
||||
@@ -391,11 +388,12 @@ class HUIRoot extends LitElement {
|
||||
slot="trigger"
|
||||
id="dashboardmenu"
|
||||
.path=${mdiDotsVertical}
|
||||
.label=${this.hass!.localize("ui.panel.lovelace.editor.menu.open")}
|
||||
hide-title
|
||||
></ha-icon-button>
|
||||
${listItems}
|
||||
</ha-button-menu>
|
||||
<ha-tooltip placement="bottom" for="dashboardmenu">
|
||||
${this.hass!.localize("ui.panel.lovelace.editor.menu.open")}
|
||||
</ha-tooltip>
|
||||
`);
|
||||
}
|
||||
return html`${result}`;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1706,14 +1706,6 @@
|
||||
"update": "[%key:ui::panel::config::devices::update%]",
|
||||
"unknown_error": "[%key:ui::panel::config::devices::unknown_error%]"
|
||||
},
|
||||
"label-detail": {
|
||||
"new_label": "New label",
|
||||
"name": "Name",
|
||||
"icon": "Icon",
|
||||
"color": "Color",
|
||||
"description": "Description",
|
||||
"required_error_msg": "[%key:ui::panel::config::zone::detail::required_error_msg%]"
|
||||
},
|
||||
"voice-settings": {
|
||||
"expose_header": "Expose",
|
||||
"aliases_header": "Aliases",
|
||||
@@ -2414,7 +2406,18 @@
|
||||
"introduction": "Labels can help you organize your areas, devices, and entities. They can be used to filter in the UI, or use them as a target in automations.",
|
||||
"introduction2": "Go to the area, device, or entity you want to add a label to, and press the edit button to assign labels to them.",
|
||||
"confirm_remove_title": "Remove label?",
|
||||
"confirm_remove": "Are you sure you want to remove label {label}? It will be removed from all areas, devices, and entities."
|
||||
"confirm_remove": "Are you sure you want to remove label {label}? It will be removed from all areas, devices, and entities.",
|
||||
"detail": {
|
||||
"new_label": "New label",
|
||||
"name": "Name",
|
||||
"icon": "Icon",
|
||||
"color": "Color",
|
||||
"description": "Description",
|
||||
"delete": "Delete",
|
||||
"update": "Update",
|
||||
"create": "Create",
|
||||
"required_error_msg": "[%key:ui::panel::config::zone::detail::required_error_msg%]"
|
||||
}
|
||||
},
|
||||
"areas": {
|
||||
"caption": "Areas",
|
||||
@@ -3626,36 +3629,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 +7088,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": {
|
||||
@@ -9368,6 +9336,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",
|
||||
|
||||
@@ -201,7 +201,6 @@ describe("getStates", () => {
|
||||
"pm1",
|
||||
"pm10",
|
||||
"pm25",
|
||||
"pm4",
|
||||
"power_factor",
|
||||
"power",
|
||||
"pressure",
|
||||
@@ -216,7 +215,7 @@ describe("getStates", () => {
|
||||
"volume_flow_rate",
|
||||
])
|
||||
);
|
||||
expect(result.length).toBe(35);
|
||||
expect(result.length).toBe(34);
|
||||
});
|
||||
|
||||
it("should return empty array for unknown attribute", () => {
|
||||
|
||||
Reference in New Issue
Block a user