Compare commits

...

56 Commits

Author SHA1 Message Date
Bram Kragten
536fe6a23c Fix safe area 2023-04-06 18:13:18 +02:00
Bram Kragten
d8ddbefcf6 Fix background theme in Lovelace view 2023-04-06 17:31:55 +02:00
Bram Kragten
0364c5e493 Revert "Fix theme background of view" (#16094
Revert "Fix theme background of view (#16083)"

This reverts commit 723b3844ac.
2023-04-06 17:19:12 +02:00
Bram Kragten
d1ad72c6ff Bumped version to 20230406.0 2023-04-06 15:36:29 +02:00
Bram Kragten
723b3844ac Fix theme background of view (#16083) 2023-04-06 15:34:49 +02:00
Bram Kragten
54f8d33b1f Dont show options button for helpers that dont support it (#16089) 2023-04-06 15:32:16 +02:00
Bram Kragten
4c702ac7c2 fix analytics translations in onboarding (#16086) 2023-04-06 12:41:38 +00:00
Bram Kragten
c7b3c4df27 fix integration filter menu disappearing (#16088) 2023-04-06 14:40:02 +02:00
Bram Kragten
56cc4e8d4d Fix tooltip being cutoff in history panel (#16087) 2023-04-06 14:39:53 +02:00
Paul Bottein
0e8c280763 Change graph zone unit (#16082) 2023-04-06 14:14:24 +02:00
Paul Bottein
ff715c6cb7 Don't check for entity id format for card editors (#16081)
Don't check for entity id format for cards
2023-04-06 14:14:13 +02:00
Bram Kragten
81ebdf1448 Bumped version to 20230405.0 2023-04-05 12:29:59 +02:00
Bram Kragten
c640d9edf2 update url of pipline-debug (#16066) 2023-04-05 12:29:13 +02:00
Bram Kragten
6d29b1cfb8 Fix chart mouse over in more info dialog (#16060) 2023-04-05 11:35:14 +02:00
Bram Kragten
e784205e1e fix panel card height (#16059) 2023-04-05 11:35:06 +02:00
Bram Kragten
1596578f96 Fix automation editor (#16057) 2023-04-05 10:20:21 +02:00
Bram Kragten
28304bb1dc Make deprecation warning instead of error (#16055) 2023-04-04 15:01:33 +02:00
Paul Bottein
32bbc9421b Translate theme settings (#16053) 2023-04-04 14:04:05 +02:00
Paul Bottein
6e35f841cc Fix more info vacuum status (#16051) 2023-04-04 13:36:45 +02:00
Paul Bottein
99e6547807 Fix fan speed for new more info and tile feature (#16050) 2023-04-04 13:36:33 +02:00
renovate[bot]
9764a0f23f Update dependency @webcomponents/webcomponentsjs to v2.8.0 (#16026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-04 00:35:27 -04:00
renovate[bot]
12918580ac Update dependency webpack-dev-server to v4.13.2 (#16049)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-04 03:48:54 +00:00
renovate[bot]
61a7652ae1 Update dependency @webcomponents/scoped-custom-element-registry to v0.0.9 (#16025)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 23:29:49 -04:00
Paulus Schoutsen
c885d08a32 Merge runPipeline code and allow picking language (#16018)
* Merge runPipeline code and allow picking language

* Add download button

* Allow to continue audio conversations once stopped
2023-04-03 16:43:59 -04:00
Paul Bottein
6068d5e5cd Use state as mode for alarm mode tile feature (#16045
* Use state as mode for alarm mode tile feature

* reorder modes
2023-04-03 20:56:54 +02:00
Bram Kragten
890be2c177 Fix drawer in RTL (#16039
* Fix drawer in RTL

* format

* make it update when lang changes

* Update ha-drawer.ts

* Update ha-drawer.ts
2023-04-03 20:55:38 +02:00
Bram Kragten
423709dd23 Add deprecation warning Polymer (#16044) 2023-04-03 20:37:49 +02:00
Bram Kragten
4b73baa098 Bumped version to 20230403.0 2023-04-03 20:18:25 +02:00
Bram Kragten
dba16edabc make supported feature selector any instead of all (#16040)
make filterSupportedFeature any instead of all
2023-04-03 13:53:37 -04:00
Bram Kragten
975f371ba8 Add channel to thread network info (#16041) 2023-04-03 13:53:12 -04:00
Bram Kragten
36b2a1bca3 Add swipe to close action to drawer in modal mode (#16042) 2023-04-03 13:52:54 -04:00
Bram Kragten
fdf36adc3c Fix elements below scrollbar in Safari (#16037) 2023-04-03 19:44:40 +02:00
Paul Bottein
b506791535 Fix history scrollbar (#16036) 2023-04-03 18:41:51 +02:00
renovate[bot]
206574eb9f Update babel monorepo to v7.21.4 (#16029)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 12:10:23 -04:00
Bram Kragten
19ab29d130 Handle safe areas (#16032) 2023-04-03 15:52:02 +02:00
Paul Bottein
f61f0e4e52 Fix for long translation in alarm more info in ios (#16034) 2023-04-03 13:11:57 +00:00
Bram Kragten
a32e6a9ac9 Don't open sidebar when a dialog is open (#16030) 2023-04-03 14:59:42 +02:00
Bram Kragten
d7b8823234 Prevent document scroll when modal drawer is open (#16033) 2023-04-03 14:59:31 +02:00
Steve Repsher
d9b0d5765a Add automatic retries to translation fetches (#16020) 2023-04-02 21:03:18 +02:00
Joakim Sørensen
6eb3fb1076 Use the name and size from the new disks property (#15954)
* Use the name and size from the new disks property

* Move SN down to secondary
2023-04-02 08:07:08 -04:00
Bram Kragten
ddfe02eb70 Bumped version to 20230401.0 2023-04-01 18:20:48 +02:00
Bram Kragten
acaaf25500 Use ha-drawer instead of mwc-drawer (#16013) 2023-04-01 16:18:52 +00:00
renovate[bot]
c6c3e63101 Update dependency @web/dev-server to v0.1.37 (#16008) 2023-04-01 10:31:47 -04:00
renovate[bot]
e0fe4631f9 Update dependency eslint to v8.37.0 (#16007)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-01 01:56:46 -04:00
Paulus Schoutsen
232f70d44c Allow having conversations (#15961)
* Allow having conversations

* Add timeout to run-start data

* willUpdate
2023-03-31 15:44:32 -04:00
Bram Kragten
273904a6eb Add support for supported_features for entity selector (#16003) 2023-03-31 15:07:41 -04:00
Steve Repsher
91caffc4e1 Improve bundle chunk hashes and names (#15991) 2023-03-31 12:49:25 -04:00
Bram Kragten
abcb904def Bumped version to 20230331.0 2023-03-31 16:37:33 +02:00
Bram Kragten
36c5d70597 Add sidebar actions to external bus (#15999) 2023-03-31 16:36:11 +02:00
Paul Bottein
b0b7998757 Fix for long translation in alarm more info (#16000) 2023-03-31 14:24:03 +00:00
Paul Bottein
33ec1e15a9 Fix labels on new more info (#15983) 2023-03-31 15:57:35 +02:00
Paul Bottein
d97ddcd31a Catch alarm control panel errors (#15998) 2023-03-31 15:51:44 +02:00
Paul Bottein
73c286a493 Fix ha header bar height (#15996)
* Fix ha header bar height in more info

* Fix ha header bar height in more places
2023-03-31 15:44:22 +02:00
Franck Nijhof
3e954eef02 Extend get_states (#15985) 2023-03-31 15:29:24 +02:00
Paul Bottein
a94b211d3e Fix ha-settings-row overflow (#15993) 2023-03-31 13:02:36 +02:00
renovate[bot]
1293e5f61f Update typescript-eslint monorepo to v5.57.0 (#15986)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-30 15:35:02 -04:00
121 changed files with 1608 additions and 1038 deletions

View File

@@ -8,6 +8,7 @@ const gulp = require("gulp");
const jszip = require("jszip");
const tar = require("tar");
const { Octokit } = require("@octokit/rest");
const { retry } = require("@octokit/plugin-retry");
const { createOAuthDeviceAuth } = require("@octokit/auth-oauth-device");
const MAX_AGE = 24; // hours
@@ -95,7 +96,7 @@ gulp.task("fetch-nightly-translations", async function () {
// Authenticate with token and request workflow runs from GitHub
console.log("Fetching new translations...");
const octokit = new Octokit({
const octokit = new (Octokit.plugin(retry))({
userAgent: "Fetch Nightly Translations",
auth: tokenAuth.token,
});

View File

@@ -152,14 +152,17 @@ const createWebpackConfig = ({
},
},
output: {
filename: ({ chunk }) => {
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`;
}
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
},
filename: ({ chunk }) =>
!isProdBuild || isStatsBuild || dontHash.has(chunk.name)
? "[name].js"
: "[name]-[contenthash].js",
chunkFilename:
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
isProdBuild && !isStatsBuild ? "[id]-[contenthash].js" : "[name].js",
assetModuleFilename:
isProdBuild && !isStatsBuild ? "[id]-[contenthash][ext]" : "[id][ext]",
hashFunction: "xxhash64",
hashDigest: "base64url",
hashDigestLength: 11, // full length of 64 bit base64url
path: outputPath,
publicPath,
// To silence warning in worker plugin

View File

@@ -92,11 +92,7 @@ export class HassioAddonStore extends LitElement {
.route=${this.route}
.header=${this.supervisor.localize("panel.store")}
>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<ha-button-menu slot="toolbar-icon" @action=${this._handleAction}>
<ha-icon-button
.label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical}

View File

@@ -168,7 +168,7 @@ class HassioAddonConfig extends LitElement {
${this.supervisor.localize("addon.configuration.options.header")}
</h2>
<div class="card-menu">
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-button-menu @action=${this._handleAction}>
<ha-icon-button
.label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical}

View File

@@ -195,11 +195,7 @@ export class HassioBackups extends LitElement {
: "/config"}
supervisor
>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<ha-button-menu slot="toolbar-icon" @action=${this._handleAction}>
<ha-icon-button
.label=${this.supervisor?.localize("common.menu")}
.path=${mdiDotsVertical}

View File

@@ -184,7 +184,7 @@ class HassioHostInfo extends LitElement {
`
: ""}
<ha-button-menu corner="BOTTOM_START">
<ha-button-menu>
<ha-icon-button
.label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical}

View File

@@ -96,8 +96,8 @@
"@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
"@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.8",
"@webcomponents/webcomponentsjs": "2.7.0",
"@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1",
"chart.js": "3.3.2",
"comlink": "4.4.1",
@@ -148,7 +148,7 @@
"xss": "1.0.14"
},
"devDependencies": {
"@babel/core": "7.21.3",
"@babel/core": "7.21.4",
"@babel/plugin-external-helpers": "7.18.6",
"@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-decorators": "7.21.0",
@@ -158,10 +158,11 @@
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/plugin-syntax-import-meta": "7.10.4",
"@babel/plugin-syntax-top-level-await": "7.14.5",
"@babel/preset-env": "7.20.2",
"@babel/preset-typescript": "7.21.0",
"@babel/preset-env": "7.21.4",
"@babel/preset-typescript": "7.21.4",
"@koa/cors": "4.0.0",
"@octokit/auth-oauth-device": "4.0.4",
"@octokit/plugin-retry": "4.1.3",
"@octokit/rest": "19.0.7",
"@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.3",
@@ -184,15 +185,15 @@
"@types/sortablejs": "1.15.1",
"@types/tar": "6.1.4",
"@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "5.56.0",
"@typescript-eslint/parser": "5.56.0",
"@web/dev-server": "0.1.36",
"@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.57.0",
"@web/dev-server": "0.1.37",
"@web/dev-server-rollup": "0.4.0",
"babel-loader": "9.1.2",
"babel-plugin-template-html-minifier": "4.1.0",
"chai": "4.3.7",
"del": "7.0.0",
"eslint": "8.36.0",
"eslint": "8.37.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.0.0",
"eslint-config-prettier": "8.8.0",
@@ -244,7 +245,7 @@
"vinyl-source-stream": "2.0.0",
"webpack": "=5.72.1",
"webpack-cli": "5.0.1",
"webpack-dev-server": "4.13.1",
"webpack-dev-server": "4.13.2",
"webpack-manifest-plugin": "5.0.0",
"webpackbar": "5.0.2",
"workbox-build": "6.5.4"

View File

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

View File

@@ -118,24 +118,40 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
"window",
],
},
device_tracker: {
source_type: ["bluetooth", "bluetooth_le", "gps", "router"],
},
fan: {
direction: ["forward", "reverse"],
},
humidifier: {
device_class: ["humidifier", "dehumidifier"],
},
media_player: {
device_class: ["tv", "speaker", "receiver"],
media_content_type: [
"album",
"app",
"artist",
"channel",
"channels",
"composer",
"contibuting_artist",
"episode",
"game",
"genre",
"image",
"movie",
"music",
"playlist",
"podcast",
"season",
"track",
"tvshow",
"url",
"video",
],
repeat: ["off", "one", "all"],
},
number: {
device_class: ["temperature"],

View File

@@ -1,17 +0,0 @@
import { refine, string } from "superstruct";
const isEntityId = (value: string): boolean => value.includes(".");
export const entityId = () =>
refine(string(), "entity ID (domain.entity)", isEntityId);
const isEntityIdOrAll = (value: string): boolean => {
if (value === "all") {
return true;
}
return isEntityId(value);
};
export const entityIdOrAll = () =>
refine(string(), "entity ID (domain.entity or all)", isEntityIdOrAll);

View File

@@ -276,7 +276,11 @@ export default class HaChartBase extends LitElement {
top: this.chart!.canvas.offsetTop + context.tooltip.caretY + 12 + "px",
left:
this.chart!.canvas.offsetLeft +
clamp(context.tooltip.caretX, 100, this.clientWidth - 100) -
clamp(
context.tooltip.caretX,
100,
this.clientWidth - 100 - this.paddingYAxis
) -
100 +
"px",
};
@@ -302,7 +306,7 @@ export default class HaChartBase extends LitElement {
return css`
:host {
display: block;
position: relative;
position: var(--chart-base-position, relative);
}
.chartContainer {
overflow: hidden;

View File

@@ -2,9 +2,9 @@ import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import { LocalizeFunc } from "../common/translations/localize";
import type { Analytics, AnalyticsPreferences } from "../data/analytics";
import { haStyle } from "../resources/styles";
import type { HomeAssistant } from "../types";
import "./ha-settings-row";
import "./ha-switch";
import type { HaSwitch } from "./ha-switch";
@@ -19,7 +19,7 @@ declare global {
@customElement("ha-analytics")
export class HaAnalytics extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public localize!: LocalizeFunc;
@property({ attribute: false }) public analytics?: Analytics;
@@ -34,12 +34,12 @@ export class HaAnalytics extends LitElement {
return html`
<ha-settings-row>
<span slot="heading" data-for="base">
${this.hass.localize(
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.title`
)}
</span>
<span slot="description" data-for="base">
${this.hass.localize(
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.description`
)}
</span>
@@ -57,12 +57,12 @@ export class HaAnalytics extends LitElement {
html`
<ha-settings-row>
<span slot="heading" data-for=${preference}>
${this.hass.localize(
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.title`
)}
</span>
<span slot="description" data-for=${preference}>
${this.hass.localize(
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.description`
)}
</span>
@@ -77,7 +77,7 @@ export class HaAnalytics extends LitElement {
${!baseEnabled
? html`
<simple-tooltip animation-delay="0" position="right">
${this.hass.localize(
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
)}
</simple-tooltip>
@@ -89,12 +89,12 @@ export class HaAnalytics extends LitElement {
)}
<ha-settings-row>
<span slot="heading" data-for="diagnostics">
${this.hass.localize(
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.title`
)}
</span>
<span slot="description" data-for="diagnostics">
${this.hass.localize(
${this.localize(
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.description`
)}
</span>

View File

@@ -10,7 +10,7 @@ import type { HaIconButton } from "./ha-icon-button";
export class HaButtonMenu extends LitElement {
protected readonly [FOCUS_TARGET];
@property() public corner: Corner = "TOP_START";
@property() public corner: Corner = "BOTTOM_START";
@property() public menuCorner: MenuCorner = "START";

View File

@@ -35,7 +35,7 @@ interface FilterValue {
export class HaRelatedFilterButtonMenu extends LitElement {
@property() public hass!: HomeAssistant;
@property() public corner: Corner = "TOP_START";
@property() public corner: Corner = "BOTTOM_START";
@property({ type: Boolean, reflect: true }) public narrow = false;

View File

@@ -25,8 +25,6 @@ export type ControlSelectOption = {
export class HaControlSelect extends LitElement {
@property({ type: Boolean, reflect: true }) disabled = false;
@property() public label?: string;
@property() public options?: ControlSelectOption[];
@property() public value?: string;
@@ -305,6 +303,16 @@ export class HaControlSelect extends LitElement {
justify-content: center;
flex-direction: column;
text-align: center;
padding: 2px;
width: 100%;
box-sizing: border-box;
}
.option .content span {
display: block;
width: 100%;
-webkit-hyphens: auto;
-moz-hyphens: auto;
hyphens: auto;
}
:host([vertical]) {
width: var(--control-select-thickness);

View File

@@ -1,16 +1,75 @@
import {
DIRECTION_LEFT,
DIRECTION_RIGHT,
Manager,
Swipe,
} from "@egjs/hammerjs";
import { DrawerBase } from "@material/mwc-drawer/mwc-drawer-base";
import { styles } from "@material/mwc-drawer/mwc-drawer.css";
import { css } from "lit";
import { customElement } from "lit/decorators";
import { css, PropertyValues } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
const blockingElements = (document as any).$blockingElements;
@customElement("ha-drawer")
export class HaDrawer extends DrawerBase {
@property() public direction: "ltr" | "rtl" = "ltr";
private _mc?: HammerManager;
protected createAdapter() {
return {
...super.createAdapter(),
trapFocus: () => {
blockingElements.push(this);
this.appContent.inert = true;
document.body.style.overflow = "hidden";
},
releaseFocus: () => {
blockingElements.remove(this);
this.appContent.inert = false;
document.body.style.overflow = "";
},
};
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("direction")) {
this.mdcRoot.dir = this.direction;
}
if (changedProps.has("open") && this.open && this.type === "modal") {
this._mc = new Manager(document, {
touchAction: "pan-y",
});
this._mc.add(
new Swipe({
direction:
this.direction === "rtl" ? DIRECTION_RIGHT : DIRECTION_LEFT,
})
);
this._mc.on("swipeleft swiperight", () => {
fireEvent(this, "hass-toggle-menu", { open: false });
});
} else if (this._mc) {
this._mc.destroy();
this._mc = undefined;
}
}
static override styles = [
styles,
css`
.mdc-drawer {
top: 0;
}
.mdc-drawer.mdc-drawer--modal.mdc-drawer--open {
z-index: 200;
}
.mdc-drawer-app-content {
transform: translateZ(0);
}
`,
];
}

View File

@@ -82,7 +82,6 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
<ha-button-menu
.disabled=${this.disabled}
fixed
corner="BOTTOM_START"
@opened=${this._handleOpen}
@closed=${this._handleClose}
multi

View File

@@ -33,7 +33,7 @@ export class HaHeaderBar extends LitElement {
unsafeCSS(topAppBarStyles),
css`
.mdc-top-app-bar__row {
height: var(--header-bar-height, 64px);
height: var(--header-height);
}
.mdc-top-app-bar {
position: static;

View File

@@ -38,7 +38,6 @@ export class HaIconOverflowMenu extends LitElement {
@click=${this._handleIconOverflowMenuOpened}
@closed=${this._handleIconOverflowMenuClosed}
class="ha-icon-overflow-menu-overflow"
corner="BOTTOM_START"
absolute
>
<ha-icon-button

View File

@@ -70,11 +70,7 @@ class HaQrScanner extends LitElement {
? html`<video></video>
<div id="canvas-container">
${this._cameras && this._cameras.length > 1
? html`<ha-button-menu
corner="BOTTOM_START"
fixed
@closed=${stopPropagation}
>
? html`<ha-button-menu fixed @closed=${stopPropagation}>
<ha-icon-button
slot="trigger"
.label=${this.localize(

View File

@@ -92,7 +92,7 @@ export class HaSettingsRow extends LitElement {
::slotted(ha-switch) {
padding: 16px 0;
}
div[secondary] {
.secondary {
white-space: normal;
}
.prefix-wrap {

View File

@@ -28,8 +28,8 @@ import {
CSSResultGroup,
html,
LitElement,
PropertyValues,
nothing,
PropertyValues,
} from "lit";
import { customElement, eventOptions, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";

View File

@@ -283,7 +283,6 @@ export class HaTargetPicker extends LitElement {
return html`<mwc-menu-surface
open
.anchor=${this._addContainer}
.corner=${"BOTTOM_START"}
@closed=${this._onClosed}
@opened=${this._onOpened}
@opened-changed=${this._openedChanged}

View File

@@ -1,19 +1,43 @@
import { TopAppBarFixedBase } from "@material/mwc-top-app-bar-fixed/mwc-top-app-bar-fixed-base";
import { styles } from "@material/mwc-top-app-bar/mwc-top-app-bar.css";
import { css } from "lit";
import { customElement } from "lit/decorators";
import { customElement, property } from "lit/decorators";
let drawerContent: HTMLElement | undefined;
@customElement("ha-top-app-bar-fixed")
export class HaTopAppBarFixed extends TopAppBarFixedBase {
private get _drawerContent() {
if (!drawerContent) {
drawerContent = document
.querySelector("home-assistant")!
.renderRoot.querySelector("home-assistant-main")!
.renderRoot.querySelector("ha-drawer")!
.renderRoot.querySelector(".mdc-drawer-app-content") as HTMLElement;
}
return drawerContent;
}
@property({ type: Object })
get scrollTarget() {
return this._scrollTarget || this._drawerContent || window;
}
protected updateRootPosition() {}
static override styles = [
styles,
css`
.mdc-top-app-bar {
position: sticky;
top: 0;
}
.mdc-top-app-bar__row {
height: var(--header-height);
border-bottom: var(--app-header-border-bottom);
}
.mdc-top-app-bar--fixed-adjust {
padding-top: var(--header-height);
padding-top: 0;
}
.mdc-top-app-bar {
--mdc-typography-headline6-font-weight: 400;

View File

@@ -53,53 +53,46 @@ export const callAlarmAction = (
};
export type AlarmMode =
| "away"
| "home"
| "night"
| "vacation"
| "custom_bypass"
| "armed_home"
| "armed_away"
| "armed_night"
| "armed_vacation"
| "armed_custom_bypass"
| "disarmed";
type AlarmConfig = {
service: string;
feature?: AlarmControlPanelEntityFeature;
state: string;
path: string;
};
export const ALARM_MODES: Record<AlarmMode, AlarmConfig> = {
away: {
feature: AlarmControlPanelEntityFeature.ARM_AWAY,
service: "alarm_arm_away",
state: "armed_away",
path: mdiLock,
},
home: {
armed_home: {
feature: AlarmControlPanelEntityFeature.ARM_HOME,
service: "alarm_arm_home",
state: "armed_home",
path: mdiHome,
},
custom_bypass: {
feature: AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS,
service: "alarm_arm_custom_bypass",
state: "armed_custom_bypass",
path: mdiShield,
armed_away: {
feature: AlarmControlPanelEntityFeature.ARM_AWAY,
service: "alarm_arm_away",
path: mdiLock,
},
night: {
armed_night: {
feature: AlarmControlPanelEntityFeature.ARM_NIGHT,
service: "alarm_arm_night",
state: "armed_night",
path: mdiMoonWaningCrescent,
},
vacation: {
armed_vacation: {
feature: AlarmControlPanelEntityFeature.ARM_VACATION,
service: "alarm_arm_vacation",
state: "armed_vacation",
path: mdiAirplane,
},
armed_custom_bypass: {
feature: AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS,
service: "alarm_arm_custom_bypass",
path: mdiShield,
},
disarmed: {
service: "alarm_disarm",
state: "disarmed",
path: mdiShieldOff,
},
};

View File

@@ -2,6 +2,7 @@ import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { stateActive } from "../common/entity/state_active";
import { supportsFeature } from "../common/entity/supports-feature";
import { blankBeforePercent } from "../common/translations/blank_before_percent";
import { UNAVAILABLE } from "./entity";
@@ -114,10 +115,12 @@ export function computeCoverPositionStateDisplay(
locale: FrontendLocaleData,
position?: number
) {
const currentPosition =
position ??
stateObj.attributes.current_position ??
stateObj.attributes.current_tilt_position;
const statePosition = stateActive(stateObj)
? stateObj.attributes.current_position ??
stateObj.attributes.current_tilt_position
: undefined;
const currentPosition = position ?? statePosition;
return currentPosition && currentPosition !== 100
? `${Math.round(currentPosition)}${blankBeforePercent(locale)}%`

View File

@@ -9,6 +9,7 @@ import {
HassEntityAttributeBase,
HassEntityBase,
} from "home-assistant-js-websocket";
import { stateActive } from "../common/entity/state_active";
import { blankBeforePercent } from "../common/translations/blank_before_percent";
import { FrontendLocaleData } from "./translation";
@@ -69,7 +70,7 @@ export function fanSpeedToPercentage(
if (speedValue === -1) {
return 0;
}
return Math.round(speedValue * step);
return Math.floor(speedValue * step);
}
export function computeFanSpeedCount(stateObj: FanEntity): number {
@@ -99,9 +100,12 @@ export function computeFanSpeedStateDisplay(
locale: FrontendLocaleData,
speed?: number
) {
const currentSpeed = speed ?? stateObj.attributes.percentage;
const percentage = stateActive(stateObj)
? stateObj.attributes.percentage
: undefined;
const currentSpeed = speed ?? percentage;
return currentSpeed
? `${Math.round(currentSpeed)}${blankBeforePercent(locale)}%`
? `${Math.floor(currentSpeed)}${blankBeforePercent(locale)}%`
: "";
}

View File

@@ -28,8 +28,19 @@ export interface HassioHassOSInfo {
data_disk: string;
}
export interface Datadisk {
name: string;
vendor: string;
model: string;
serial: string;
size: number;
id: string;
dev_path: string;
}
export interface DatadiskList {
devices: string[];
disks: Datadisk[];
}
export const fetchHassioHostInfo = async (

View File

@@ -1,5 +1,7 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { ensureArray } from "../common/array/ensure-array";
import { computeStateDomain } from "../common/entity/compute_state_domain";
import { supportsFeature } from "../common/entity/supports-feature";
import { UiAction } from "../panels/lovelace/components/hui-action-editor";
import type { DeviceRegistryEntry } from "./device_registry";
import type { EntitySources } from "./entity_sources";
@@ -149,6 +151,7 @@ interface EntitySelectorFilter {
integration?: string;
domain?: string | readonly string[];
device_class?: string | readonly string[];
supported_features?: number | [number];
}
export interface EntitySelector {
@@ -358,6 +361,7 @@ export const filterSelectorEntities = (
const {
domain: filterDomain,
device_class: filterDeviceClass,
supported_features: filterSupportedFeature,
integration: filterIntegration,
} = filterEntity;
@@ -383,6 +387,16 @@ export const filterSelectorEntities = (
}
}
if (filterSupportedFeature) {
if (
!ensureArray(filterSupportedFeature).some((feature) =>
supportsFeature(entity, feature)
)
) {
return false;
}
}
if (
filterIntegration &&
entitySources?.[entity.entity_id]?.domain !== filterIntegration

View File

@@ -18,6 +18,7 @@ export interface ThreadDataSet {
network_name: string;
extended_pan_id?: string;
pan_id?: string;
channel?: number;
}
export interface ThreadRouterDiscoveryEvent {

View File

@@ -14,6 +14,7 @@ interface PipelineRunStartEvent extends PipelineEventBase {
language: string;
runner_data: {
stt_binary_handler_id: number | null;
timeout: number;
};
};
}
@@ -40,7 +41,7 @@ interface PipelineSTTStartEvent extends PipelineEventBase {
interface PipelineSTTEndEvent extends PipelineEventBase {
type: "stt-end";
data: {
text: string;
stt_output: { text: string };
};
}
@@ -83,7 +84,7 @@ type PipelineRunEvent =
| PipelineTTSStartEvent
| PipelineTTSEndEvent;
interface PipelineRunOptions {
export interface PipelineRunOptions {
start_stage: "stt" | "intent" | "tts";
end_stage: "stt" | "intent" | "tts";
language?: string;
@@ -98,13 +99,15 @@ export interface PipelineRun {
stage: "ready" | "stt" | "intent" | "tts" | "done" | "error";
run: PipelineRunStartEvent["data"];
error?: PipelineErrorEvent["data"];
stt?: PipelineSTTStartEvent["data"] & Partial<PipelineSTTEndEvent["data"]>;
stt?: PipelineSTTStartEvent["data"] &
Partial<PipelineSTTEndEvent["data"]> & { done: boolean };
intent?: PipelineIntentStartEvent["data"] &
Partial<PipelineIntentEndEvent["data"]>;
tts?: PipelineTTSStartEvent["data"] & Partial<PipelineTTSEndEvent["data"]>;
Partial<PipelineIntentEndEvent["data"]> & { done: boolean };
tts?: PipelineTTSStartEvent["data"] &
Partial<PipelineTTSEndEvent["data"]> & { done: boolean };
}
export const runPipelineFromText = (
export const runVoiceAssistantPipeline = (
hass: HomeAssistant,
callback: (event: PipelineRun) => void,
options: PipelineRunOptions
@@ -138,17 +141,38 @@ export const runPipelineFromText = (
}
if (updateEvent.type === "stt-start") {
run = { ...run, stage: "stt", stt: updateEvent.data };
run = {
...run,
stage: "stt",
stt: { ...updateEvent.data, done: false },
};
} else if (updateEvent.type === "stt-end") {
run = { ...run, stt: { ...run.stt!, ...updateEvent.data } };
run = {
...run,
stt: { ...run.stt!, ...updateEvent.data, done: true },
};
} else if (updateEvent.type === "intent-start") {
run = { ...run, stage: "intent", intent: updateEvent.data };
run = {
...run,
stage: "intent",
intent: { ...updateEvent.data, done: false },
};
} else if (updateEvent.type === "intent-end") {
run = { ...run, intent: { ...run.intent!, ...updateEvent.data } };
run = {
...run,
intent: { ...run.intent!, ...updateEvent.data, done: true },
};
} else if (updateEvent.type === "tts-start") {
run = { ...run, stage: "tts", tts: updateEvent.data };
run = {
...run,
stage: "tts",
tts: { ...updateEvent.data, done: false },
};
} else if (updateEvent.type === "tts-end") {
run = { ...run, tts: { ...run.tts!, ...updateEvent.data } };
run = {
...run,
tts: { ...run.tts!, ...updateEvent.data, done: true },
};
} else if (updateEvent.type === "run-end") {
run = { ...run, stage: "done" };
unsubProm.then((unsub) => unsub());

View File

@@ -62,7 +62,7 @@ export const showDialog = async (
dialogParams: unknown,
dialogImport?: () => Promise<unknown>,
addHistory = true
) => {
): Promise<boolean> => {
if (!(dialogTag in LOADED)) {
if (!dialogImport) {
if (__DEV__) {
@@ -71,7 +71,7 @@ export const showDialog = async (
"Asked to show dialog that's not loaded and can't be imported"
);
}
return;
return false;
}
LOADED[dialogTag] = {
element: dialogImport().then(() => {
@@ -128,6 +128,8 @@ export const showDialog = async (
// so it's guaranteed to be on top of the other elements
root.appendChild(dialogElement);
dialogElement.showDialog(dialogParams);
return true;
};
export const replaceDialog = (dialogElement: HassDialog) => {

View File

@@ -1,9 +1,7 @@
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { computeAttributeNameDisplay } from "../../../../common/entity/compute_attribute_display";
import { stateColorCss } from "../../../../common/entity/state_color";
import { supportsFeature } from "../../../../common/entity/supports-feature";
import "../../../../components/ha-control-select";
@@ -14,6 +12,7 @@ import {
AlarmMode,
ALARM_MODES,
} from "../../../../data/alarm_control_panel";
import { UNAVAILABLE } from "../../../../data/entity";
import { HomeAssistant } from "../../../../types";
import { showEnterCodeDialogDialog } from "./show-enter-code-dialog";
@@ -33,70 +32,70 @@ export class HaMoreInfoAlarmControlPanelModes extends LitElement {
});
});
protected updated(changedProp: Map<string | number | symbol, unknown>): void {
super.updated(changedProp);
if (changedProp.has("stateObj") && this.stateObj) {
const oldStateObj = changedProp.get("stateObj") as HassEntity | undefined;
if (!oldStateObj || this.stateObj.state !== oldStateObj.state) {
this._currentMode = this._getCurrentMode(this.stateObj);
}
protected willUpdate(changedProp: PropertyValues): void {
super.willUpdate(changedProp);
if (changedProp.has("stateObj")) {
this._currentMode = this._getCurrentMode(this.stateObj);
}
}
private _getCurrentMode(stateObj: AlarmControlPanelEntity) {
return this._modes(stateObj).find(
(mode) => ALARM_MODES[mode].state === stateObj.state
);
return this._modes(stateObj).find((mode) => mode === stateObj.state);
}
private async _valueChanged(ev: CustomEvent) {
const mode = (ev.detail as any).value as AlarmMode;
const { state: modeState, service } = ALARM_MODES[mode];
if (modeState === this.stateObj.state) return;
// Force ha-control-select to previous mode because we don't known if the service call will succeed due to code check
this._currentMode = mode;
await this.requestUpdate("_currentMode");
this._currentMode = this._getCurrentMode(this.stateObj!);
private async _setMode(mode: AlarmMode) {
const { service } = ALARM_MODES[mode];
let code: string | undefined;
if (
(mode !== "disarmed" &&
this.stateObj.attributes.code_arm_required &&
this.stateObj.attributes.code_format) ||
(mode === "disarmed" && this.stateObj.attributes.code_format)
this.stateObj!.attributes.code_arm_required &&
this.stateObj!.attributes.code_format) ||
(mode === "disarmed" && this.stateObj!.attributes.code_format)
) {
const disarm = mode === "disarmed";
const response = await showEnterCodeDialogDialog(this, {
codeFormat: this.stateObj.attributes.code_format,
title: this.hass.localize(
codeFormat: this.stateObj!.attributes.code_format,
title: this.hass!.localize(
`ui.dialogs.more_info_control.alarm_control_panel.${
disarm ? "disarm_title" : "arm_title"
}`
),
submitText: this.hass.localize(
submitText: this.hass!.localize(
`ui.dialogs.more_info_control.alarm_control_panel.${
disarm ? "disarm_action" : "arm_action"
}`
),
});
if (!response) {
return;
if (response == null) {
throw new Error("cancel");
}
code = response;
}
await this.hass.callService("alarm_control_panel", service, {
await this.hass!.callService("alarm_control_panel", service, {
entity_id: this.stateObj!.entity_id,
code,
});
}
private async _valueChanged(ev: CustomEvent) {
const mode = (ev.detail as any).value as AlarmMode;
if (mode === this.stateObj!.state) return;
const oldMode = this._getCurrentMode(this.stateObj!);
this._currentMode = mode;
try {
await this._setMode(mode);
} catch (err) {
this._currentMode = oldMode;
}
}
protected render() {
const color = stateColorCss(this.stateObj);
@@ -116,16 +115,14 @@ export class HaMoreInfoAlarmControlPanelModes extends LitElement {
.options=${options}
.value=${this._currentMode}
@value-changed=${this._valueChanged}
.label=${computeAttributeNameDisplay(
this.hass.localize,
this.stateObj,
this.hass.entities,
"percentage"
.ariaLabel=${this.hass.localize(
"ui.dialogs.more_info_control.alarm_control_panel.modes_label"
)}
style=${styleMap({
"--control-select-color": color,
"--modes-count": modes.length.toString(),
})}
.disabled=${this.stateObj!.state === UNAVAILABLE}
>
</ha-control-select>
`;

View File

@@ -3,6 +3,7 @@ import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import { computeAttributeNameDisplay } from "../../../../common/entity/compute_attribute_display";
import { computeStateDisplay } from "../../../../common/entity/compute_state_display";
import { stateActive } from "../../../../common/entity/state_active";
import { stateColorCss } from "../../../../common/entity/state_color";
import "../../../../components/ha-control-select";
import type { ControlSelectOption } from "../../../../components/ha-control-select";
@@ -26,20 +27,25 @@ export class HaMoreInfoFanSpeed extends LitElement {
@property({ attribute: false }) public stateObj!: FanEntity;
@state() value?: number;
@state() sliderValue?: number;
@state() speedValue?: FanSpeed;
protected updated(changedProp: Map<string | number | symbol, unknown>): void {
if (changedProp.has("stateObj")) {
this.value =
this.stateObj.attributes.percentage != null
? Math.max(Math.round(this.stateObj.attributes.percentage), 1)
: undefined;
const percentage = stateActive(this.stateObj)
? this.stateObj.attributes.percentage ?? 0
: 0;
this.sliderValue = Math.max(Math.round(percentage), 0);
this.speedValue = fanPercentageToSpeed(this.stateObj, percentage);
}
}
private _speedValueChanged(ev: CustomEvent) {
const speed = (ev.detail as any).value as FanSpeed;
this.speedValue = speed;
const percentage = fanSpeedToPercentage(this.stateObj, speed);
this.hass.callService("fan", "set_percentage", {
@@ -52,6 +58,8 @@ export class HaMoreInfoFanSpeed extends LitElement {
const value = (ev.detail as any).value;
if (isNaN(value)) return;
this.sliderValue = value;
this.hass.callService("fan", "set_percentage", {
entity_id: this.stateObj!.entity_id,
percentage: value,
@@ -88,16 +96,11 @@ export class HaMoreInfoFanSpeed extends LitElement {
})
).reverse();
const speed = fanPercentageToSpeed(
this.stateObj,
this.stateObj.attributes.percentage ?? 0
);
return html`
<ha-control-select
vertical
.options=${options}
.value=${speed}
.value=${this.speedValue}
@value-changed=${this._speedValueChanged}
.ariaLabel=${computeAttributeNameDisplay(
this.hass.localize,
@@ -108,6 +111,7 @@ export class HaMoreInfoFanSpeed extends LitElement {
style=${styleMap({
"--control-select-color": color,
})}
.disabled=${this.stateObj.state === UNAVAILABLE}
>
</ha-control-select>
`;
@@ -116,9 +120,9 @@ export class HaMoreInfoFanSpeed extends LitElement {
return html`
<ha-control-slider
vertical
.value=${this.value}
min="0"
max="100"
.value=${this.sliderValue}
.step=${this.stateObj.attributes.percentage_step ?? 1}
@value-changed=${this._valueChanged}
.ariaLabel=${computeAttributeNameDisplay(

View File

@@ -31,7 +31,7 @@ class MoreInfoAlarmControlPanel extends LitElement {
"ui.dialogs.more_info_control.alarm_control_panel.disarm_action"
),
});
if (!response) {
if (response == null) {
return;
}
code = response;

View File

@@ -23,6 +23,7 @@ import {
computeAttributeValueDisplay,
} from "../../../common/entity/compute_attribute_display";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { stateActive } from "../../../common/entity/state_active";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import { UNAVAILABLE } from "../../../data/entity";
@@ -119,7 +120,7 @@ class MoreInfoFan extends LitElement {
const liveValue = this._liveSpeed;
const forcedState =
this._liveSpeed != null ? (this._liveSpeed ? "on" : "off") : undefined;
liveValue != null ? (liveValue ? "on" : "off") : undefined;
const stateDisplay = computeStateDisplay(
this.hass.localize,
@@ -135,7 +136,7 @@ class MoreInfoFan extends LitElement {
liveValue
);
if (positionStateDisplay) {
if (positionStateDisplay && (stateActive(this.stateObj!) || liveValue)) {
return positionStateDisplay;
}
return stateDisplay;
@@ -273,7 +274,6 @@ class MoreInfoFan extends LitElement {
supportsPresetMode && this.stateObj.attributes.preset_modes
? html`
<ha-button-menu
corner="BOTTOM_START"
@action=${this._handlePresetMode}
@closed=${stopPropagation}
fixed

View File

@@ -173,7 +173,6 @@ class MoreInfoLight extends LitElement {
${supportsEffects && this.stateObj.attributes.effect_list
? html`
<ha-button-menu
corner="BOTTOM_START"
@action=${this._handleEffectButton}
@closed=${stopPropagation}
fixed

View File

@@ -12,6 +12,8 @@ import {
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { stopPropagation } from "../../../common/dom/stop_propagation";
import { computeAttributeValueDisplay } from "../../../common/entity/compute_attribute_display";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import "../../../components/ha-icon";
@@ -113,11 +115,19 @@ class MoreInfoVacuum extends LitElement {
</span>
<span>
<strong>
${stateObj.attributes.status ||
this.hass.localize(
`component.vacuum.entity_component._.state.${stateObj.state}`
${computeAttributeValueDisplay(
this.hass.localize,
stateObj,
this.hass.locale,
this.hass.entities,
"status"
) ||
stateObj.state}
computeStateDisplay(
this.hass.localize,
stateObj,
this.hass.locale,
this.hass.entities
)}
</strong>
</span>
</div>

View File

@@ -451,6 +451,7 @@ export class MoreInfoDialog extends LitElement {
--dialog-surface-position: static;
--dialog-content-position: static;
--dialog-content-padding: 0;
--chart-base-position: static;
}
ha-header-bar {
@@ -478,7 +479,7 @@ export class MoreInfoDialog extends LitElement {
@media all and (max-width: 450px) {
.child-view > * {
min-height: calc(100vh - 56px);
min-height: calc(100vh - var(--header-height));
}
}

View File

@@ -90,7 +90,7 @@ export class MoreInfoInfo extends LitElement {
@media all and (max-width: 450px) {
.container {
min-height: calc(100vh - 56px);
min-height: calc(100vh - var(--header-height));
}
}

View File

@@ -152,7 +152,6 @@ export class HuiNotificationDrawer extends LitElement {
--mdc-theme-primary: var(--primary-background-color);
border-bottom: 1px solid var(--divider-color);
display: block;
--header-bar-height: var(--header-height);
}
.notifications {

View File

@@ -755,6 +755,9 @@ export class QuickBar extends LitElement {
haStyleScrollbar,
haStyleDialog,
css`
mwc-list {
--mdc-list-vertical-padding: 0;
}
.heading {
display: flex;
align-items: center;

View File

@@ -6,6 +6,7 @@ This is the entry point for providing external app stuff from app entrypoint.
*/
import { fireEvent } from "../common/dom/fire_event";
import { mainWindow } from "../common/dom/get_main_window";
import { HomeAssistantMain } from "../layouts/home-assistant-main";
import type { EMIncomingMessageCommands } from "./external_messaging";
@@ -44,6 +45,40 @@ const handleExternalMessage = (
success: true,
result: null,
});
} else if (msg.command === "sidebar/toggle") {
if (mainWindow.history.state?.open) {
bus.fireMessage({
id: msg.id,
type: "result",
success: false,
error: { code: "not_allowed", message: "dialog open" },
});
return true;
}
fireEvent(hassMainEl, "hass-toggle-menu");
bus.fireMessage({
id: msg.id,
type: "result",
success: true,
result: null,
});
} else if (msg.command === "sidebar/show") {
if (mainWindow.history.state?.open) {
bus.fireMessage({
id: msg.id,
type: "result",
success: false,
error: { code: "not_allowed", message: "dialog open" },
});
return true;
}
fireEvent(hassMainEl, "hass-toggle-menu", { open: true });
bus.fireMessage({
id: msg.id,
type: "result",
success: true,
result: null,
});
} else {
return false;
}

View File

@@ -121,9 +121,23 @@ interface EMIncomingMessageShowNotifications {
command: "notifications/show";
}
interface EMIncomingMessageToggleSidebar {
id: number;
type: "command";
command: "sidebar/toggle";
}
interface EMIncomingMessageShowSidebar {
id: number;
type: "command";
command: "sidebar/show";
}
export type EMIncomingMessageCommands =
| EMIncomingMessageRestart
| EMIncomingMessageShowNotifications;
| EMIncomingMessageShowNotifications
| EMIncomingMessageToggleSidebar
| EMIncomingMessageShowSidebar;
type EMIncomingMessage =
| EMMessageResultSuccess

View File

@@ -99,6 +99,8 @@ class HassSubpage extends LitElement {
display: block;
height: 100%;
background-color: var(--primary-background-color);
overflow: hidden;
position: relative;
}
:host([narrow]) {
@@ -152,7 +154,7 @@ class HassSubpage extends LitElement {
}
#fab {
position: fixed;
position: absolute;
right: calc(16px + env(safe-area-inset-right));
bottom: calc(16px + env(safe-area-inset-bottom));
z-index: 1;

View File

@@ -7,11 +7,12 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import "@material/mwc-drawer/mwc-drawer";
import { customElement, property, state } from "lit/decorators";
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
import { listenMediaQuery } from "../common/dom/media_query";
import { toggleAttribute } from "../common/dom/toggle_attribute";
import { computeRTLDirection } from "../common/util/compute_rtl";
import "../components/ha-drawer";
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
import type { HomeAssistant, Route } from "../types";
import "./partial-panel-resolver";
@@ -19,12 +20,13 @@ import "./partial-panel-resolver";
declare global {
// for fire event
interface HASSDomEvents {
"hass-toggle-menu": undefined;
"hass-toggle-menu": undefined | { open?: boolean };
"hass-edit-sidebar": EditSideBarEvent;
"hass-show-notifications": undefined;
}
interface HTMLElementEventMap {
"hass-edit-sidebar": HASSDomEvent<EditSideBarEvent>;
"hass-toggle-menu": HASSDomEvent<HASSDomEvents["hass-toggle-menu"]>;
}
}
@@ -57,9 +59,10 @@ export class HomeAssistantMain extends LitElement {
const sidebarNarrow = this._sidebarNarrow || this._externalSidebar;
return html`
<mwc-drawer
<ha-drawer
.type=${sidebarNarrow ? "modal" : ""}
.open=${sidebarNarrow ? this._drawerOpen : undefined}
.direction=${computeRTLDirection(this.hass)}
@MDCDrawer:closed=${this._drawerClosed}
>
<ha-sidebar
@@ -75,7 +78,7 @@ export class HomeAssistantMain extends LitElement {
.route=${this.route}
slot="appContent"
></partial-panel-resolver>
</mwc-drawer>
</ha-drawer>
`;
}
@@ -107,7 +110,7 @@ export class HomeAssistantMain extends LitElement {
}
);
this.addEventListener("hass-toggle-menu", () => {
this.addEventListener("hass-toggle-menu", (ev) => {
if (this._sidebarEditMode) {
return;
}
@@ -118,10 +121,16 @@ export class HomeAssistantMain extends LitElement {
return;
}
if (this._sidebarNarrow) {
this._drawerOpen = !this._drawerOpen;
this._drawerOpen = ev.detail?.open ?? !this._drawerOpen;
} else {
fireEvent(this, "hass-dock-sidebar", {
dock: this.hass.dockedSidebar === "auto" ? "docked" : "auto",
dock: ev.detail?.open
? "docked"
: ev.detail?.open === false
? "auto"
: this.hass.dockedSidebar === "auto"
? "docked"
: "auto",
});
}
});
@@ -167,7 +176,6 @@ export class HomeAssistantMain extends LitElement {
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
--mdc-drawer-width: 56px;
--mdc-top-app-bar-width: calc(100% - var(--mdc-drawer-width));
}
:host([expanded]) {
--mdc-drawer-width: calc(256px + env(safe-area-inset-left));

View File

@@ -23,11 +23,11 @@ class OnboardingAnalytics extends LitElement {
protected render(): TemplateResult {
return html`
<p>${this.hass.localize("ui.panel.page-onboarding.analytics.intro")}</p>
<p>${this.localize("ui.panel.page-onboarding.analytics.intro")}</p>
<ha-analytics
translation_key_panel="page-onboarding"
@analytics-preferences-changed=${this._preferencesChanged}
.hass=${this.hass}
.localize=${this.localize}
.analytics=${this._analyticsDetails}
>
</ha-analytics>
@@ -41,7 +41,7 @@ class OnboardingAnalytics extends LitElement {
target="_blank"
rel="noreferrer"
>
${this.hass.localize("ui.panel.page-onboarding.analytics.learn_more")}
${this.localize("ui.panel.page-onboarding.analytics.learn_more")}
</a>
</div>
`;

View File

@@ -182,8 +182,6 @@ export default class HaAutomationActionRow extends LitElement {
: html`
<ha-button-menu
slot="icons"
fixed
corner="BOTTOM_START"
@action=${this._handleAction}
@click=${preventDefault}
>

View File

@@ -128,11 +128,7 @@ export default class HaAutomationAction extends LitElement {
`
)}
</div>
<ha-button-menu
fixed
@action=${this._addAction}
.disabled=${this.disabled}
>
<ha-button-menu @action=${this._addAction} .disabled=${this.disabled}>
<ha-button
slot="trigger"
outlined

View File

@@ -116,8 +116,6 @@ export default class HaAutomationConditionRow extends LitElement {
: html`
<ha-button-menu
slot="icons"
fixed
corner="BOTTOM_START"
@action=${this._handleAction}
@click=${preventDefault}
>

View File

@@ -180,11 +180,7 @@ export default class HaAutomationCondition extends LitElement {
`
)}
</div>
<ha-button-menu
fixed
@action=${this._addCondition}
.disabled=${this.disabled}
>
<ha-button-menu @action=${this._addCondition} .disabled=${this.disabled}>
<ha-button
slot="trigger"
outlined

View File

@@ -141,7 +141,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
</mwc-button>
`
: ""}
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -287,7 +287,6 @@ class HaAutomationPicker extends LitElement {
></ha-icon-button>
<ha-button-related-filter-menu
slot="filter-menu"
corner="BOTTOM_START"
.narrow=${this.narrow}
.hass=${this.hass}
.value=${this._filterValue}

View File

@@ -113,7 +113,7 @@ export class HaAutomationTrace extends LitElement {
</a>
`
: ""}
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
@@ -520,7 +520,7 @@ export class HaAutomationTrace extends LitElement {
}
.main {
height: calc(100% - 56px);
height: calc(100% - var(--header-height));
display: flex;
background-color: var(--card-background-color);
direction: ltr;

View File

@@ -140,8 +140,6 @@ export default class HaAutomationTriggerRow extends LitElement {
: html`
<ha-button-menu
slot="icons"
fixed
corner="BOTTOM_START"
@action=${this._handleAction}
@click=${preventDefault}
>

View File

@@ -168,7 +168,6 @@ class CloudAlexa extends LitElement {
${!emptyFilter
? html`${iconButton}`
: html`<ha-button-menu
corner="BOTTOM_START"
.entityId=${stateObj.entity_id}
@action=${this._exposeChanged}
>
@@ -225,7 +224,7 @@ class CloudAlexa extends LitElement {
.narrow=${this.narrow}
.header=${this.hass!.localize("ui.panel.config.cloud.alexa.title")}
>
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -228,7 +228,6 @@ class CloudGoogleAssistant extends LitElement {
${!emptyFilter
? html`${iconButton}`
: html`<ha-button-menu
corner="BOTTOM_START"
.entityId=${entity.entity_id}
@action=${this._exposeChanged}
>
@@ -302,7 +301,7 @@ class CloudGoogleAssistant extends LitElement {
.hass=${this.hass}
.header=${this.hass!.localize("ui.panel.config.cloud.google.title")}
.narrow=${this.narrow}>
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -45,7 +45,7 @@ class ConfigAnalytics extends LitElement {
<ha-analytics
translation_key_panel="config"
@analytics-preferences-changed=${this._preferencesChanged}
.hass=${this.hass}
.localize=${this.hass.localize}
.analytics=${this._analyticsDetails}
></ha-analytics>
</div>

View File

@@ -69,7 +69,7 @@ class HaConfigSectionUpdates extends LitElement {
.path=${mdiUpdate}
@click=${this._checkUpdates}
></ha-icon-button>
<ha-button-menu corner="BOTTOM_START" multi>
<ha-button-menu multi>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -194,11 +194,7 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) {
.path=${mdiMagnify}
@click=${this._showQuickBar}
></ha-icon-button>
<ha-button-menu
slot="actionItems"
corner="BOTTOM_START"
@action=${this._handleMenuAction}
>
<ha-button-menu slot="actionItems" @action=${this._handleMenuAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -748,7 +748,7 @@ export class HaConfigDevicePage extends LitElement {
${actions.length
? html`
<ha-button-menu corner="BOTTOM_START">
<ha-button-menu>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize(

View File

@@ -448,7 +448,7 @@ export class HaConfigDeviceDashboard extends LitElement {
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
</ha-fab>
<ha-button-menu slot="filter-menu" corner="BOTTOM_START" multi>
<ha-button-menu slot="filter-menu" multi>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(

View File

@@ -663,7 +663,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
)}
</ha-select>`
: ""}
${this._helperConfigEntry
${this._helperConfigEntry && this._helperConfigEntry.supports_options
? html`
<div class="row">
<mwc-button

View File

@@ -620,7 +620,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
</div>
`
: html`
<ha-button-menu slot="filter-menu" corner="BOTTOM_START" multi>
<ha-button-menu slot="filter-menu" multi>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(

View File

@@ -353,12 +353,9 @@ class HaPanelConfig extends HassRouterPage {
tag: "ha-config-areas",
load: () => import("./areas/ha-config-areas"),
},
voice_assistant: {
tag: "assist-pipeline-debug",
load: () =>
import(
"./integrations/integration-panels/voice_assistant/assist/assist-pipeline-debug"
),
"voice-assistants": {
tag: "ha-config-voice-assistants",
load: () => import("./voice-assistants/ha-config-voice-assistants"),
},
automation: {
tag: "ha-config-automation",

View File

@@ -60,7 +60,7 @@ export class HaConfigFlowCard extends LitElement {
}`
)}
></mwc-button>
<ha-button-menu corner="BOTTOM_START">
<ha-button-menu>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -382,7 +382,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
? html`<span class="badge">${disabledCount}</span>`
: ""}
<ha-button-menu
corner="BOTTOM_START"
multi
@action=${this._handleMenuAction}
@click=${this._preventDefault}
@@ -454,9 +453,9 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
"ui.panel.config.integrations.search"
)}
>
${!this._showDisabled && disabledCount
? html`<div class="filters" slot="suffix">
<div
<div class="filters" slot="suffix">
${!this._showDisabled && disabledCount
? html`<div
class="active-filters"
@click=${this._preventDefault}
>
@@ -470,10 +469,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
"ui.panel.config.integrations.disable.show"
)}
></mwc-button>
</div>
${filterMenu}
</div>`
: ""}
</div>`
: ""}
${filterMenu}
</div>
</search-input>
</div>
`}

View File

@@ -370,7 +370,7 @@ export class HaIntegrationCard extends LitElement {
`
: ""}
</div>
<ha-button-menu corner="BOTTOM_START">
<ha-button-menu>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -12,7 +12,7 @@ export class HaIntegrationOverflowMenu extends LitElement {
protected render() {
return html`
<ha-button-menu activatable corner="BOTTOM_START">
<ha-button-menu activatable>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -69,7 +69,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
return html`
<hass-subpage .narrow=${this.narrow} .hass=${this.hass} header="Thread">
<ha-button-menu slot="toolbar-icon" corner="BOTTOM_START">
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
.path=${mdiDotsVertical}
slot="trigger"
@@ -186,7 +186,6 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
<span slot="secondary">${router.server}</span>
${router.extended_address === this._otbrInfo?.extended_address
? html`<ha-button-menu
corner="BOTTOM_START"
slot="meta"
@action=${this._handleRouterAction}
>
@@ -251,6 +250,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
showAlertDialog(this, {
title: dataset.network_name,
text: html`Network name: ${dataset.network_name}<br />
Channel: ${dataset.channel}<br />
Dataset id: ${dataset.dataset_id}<br />
Pan id: ${dataset.pan_id}<br />
Extended Pan id: ${dataset.extended_pan_id}<br />
@@ -263,6 +263,7 @@ export class ThreadConfigPanel extends SubscribeMixin(LitElement) {
showAlertDialog(this, {
title: dataset.network_name,
text: html`Network name: ${dataset.network_name}<br />
Channel: ${dataset.channel}<br />
Dataset id: ${dataset.dataset_id}<br />
Pan id: ${dataset.pan_id}<br />
Extended Pan id: ${dataset.extended_pan_id}`,

View File

@@ -1,344 +1,164 @@
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../../../components/ha-card";
import "../../../../../../components/ha-alert";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import "../../../../../../components/ha-button";
import "../../../../../../components/ha-circular-progress";
import "../../../../../../components/ha-expansion-panel";
import "../../../../../../components/ha-textfield";
import {
PipelineRun,
runPipelineFromText,
PipelineRunOptions,
runVoiceAssistantPipeline,
} from "../../../../../../data/voice_assistant";
import "../../../../../../layouts/hass-subpage";
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
import "../../../../../../components/ha-formfield";
import "../../../../../../components/ha-checkbox";
import { haStyle } from "../../../../../../resources/styles";
import type { HomeAssistant } from "../../../../../../types";
import { formatNumber } from "../../../../../../common/number/format_number";
import { showPromptDialog } from "../../../../../../dialogs/generic/show-dialog-box";
const RUN_DATA = {
pipeline: "Pipeline",
language: "Language",
};
const STT_DATA = {
engine: "Engine",
};
const INTENT_DATA = {
engine: "Engine",
intent_input: "Input",
};
const TTS_DATA = {
engine: "Engine",
tts_input: "Input",
};
const STAGES: Record<PipelineRun["stage"], number> = {
ready: 0,
stt: 1,
intent: 2,
tts: 3,
done: 4,
error: 5,
};
const hasStage = (run: PipelineRun, stage: PipelineRun["stage"]) =>
STAGES[run.init_options.start_stage] <= STAGES[stage] &&
STAGES[stage] <= STAGES[run.init_options.end_stage];
const maybeRenderError = (
run: PipelineRun,
stage: string,
lastRunStage: string
) => {
if (run.stage !== "error" || lastRunStage !== stage) {
return "";
}
return html`<ha-alert alert-type="error">
${run.error!.message} (${run.error!.code})
</ha-alert>`;
};
const renderProgress = (
hass: HomeAssistant,
pipelineRun: PipelineRun,
stage: PipelineRun["stage"]
) => {
const startEvent = pipelineRun.events.find(
(ev) => ev.type === `${stage}-start`
);
const finishEvent = pipelineRun.events.find(
(ev) => ev.type === `${stage}-end`
);
if (!startEvent) {
return "";
}
if (pipelineRun.stage === "error") {
return html``;
}
if (!finishEvent) {
return html`<ha-circular-progress
size="tiny"
active
></ha-circular-progress>`;
}
const duration =
new Date(finishEvent.timestamp).getTime() -
new Date(startEvent.timestamp).getTime();
const durationString = formatNumber(duration / 1000, hass.locale, {
maximumFractionDigits: 2,
});
return html`${durationString}s ✅`;
};
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 = (
data: Record<string, any>,
keys: Record<string, string>
) => {
const result = {};
let render = false;
for (const key in data) {
if (key in keys) {
continue;
}
render = true;
result[key] = data[key];
}
return render ? html`<pre>${JSON.stringify(result, null, 2)}</pre>` : "";
};
import {
showAlertDialog,
showPromptDialog,
} from "../../../../../../dialogs/generic/show-dialog-box";
import "./assist-render-pipeline-run";
import type { HaCheckbox } from "../../../../../../components/ha-checkbox";
import type { HaTextField } from "../../../../../../components/ha-textfield";
import "../../../../../../components/ha-textfield";
import { fileDownload } from "../../../../../../util/file_download";
@customElement("assist-pipeline-debug")
export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
export class AssistPipelineDebug extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow!: boolean;
@state() private _pipelineRun?: PipelineRun;
@state() private _pipelineRuns: PipelineRun[] = [];
@state() private _stopRecording?: () => void;
@query("#continue-conversation")
private _continueConversationCheckbox!: HaCheckbox;
@query("#continue-conversation-text")
private _continueConversationTextField?: HaTextField;
private _audioBuffer?: Int16Array[];
protected render(): TemplateResult {
const lastRunStage: string = this._pipelineRun
? ["tts", "intent", "stt"].find(
(stage) => this._pipelineRun![stage] !== undefined
) || "ready"
: "ready";
@state() private _finished = false;
@state() private _languageOverride?: string;
protected render(): TemplateResult {
return html`
<hass-subpage
.narrow=${this.narrow}
.hass=${this.hass}
header="Assist Pipeline"
>
${this._pipelineRuns.length > 0
? html`
<ha-button
slot="toolbar-icon"
@click=${this._clearConversation}
.disabled=${!this._finished}
>
Clear
</ha-button>
<ha-button
slot="toolbar-icon"
@click=${this._downloadConversation}
>
Download
</ha-button>
`
: html`
<ha-button slot="toolbar-icon" @click=${this._setLanguage}>
Set Language
</ha-button>
`}
<div class="content">
<div class="start-row">
<ha-button
raised
@click=${this._runTextPipeline}
.disabled=${this._pipelineRun &&
!["error", "done"].includes(this._pipelineRun.stage)}
>
Run Text Pipeline
</ha-button>
<ha-button
raised
@click=${this._runAudioPipeline}
.disabled=${this._pipelineRun &&
!["error", "done"].includes(this._pipelineRun.stage)}
>
Run Audio Pipeline
</ha-button>
${this._pipelineRuns.length === 0
? html`
<ha-button raised @click=${this._runTextPipeline}>
Run Text Pipeline
</ha-button>
<ha-button raised @click=${this._runAudioPipeline}>
Run Audio Pipeline
</ha-button>
`
: this._pipelineRuns[0].init_options.start_stage === "intent"
? html`
<ha-textfield
id="continue-conversation-text"
label="Response"
.disabled=${!this._finished}
@keydown=${this._handleContinueKeyDown}
></ha-textfield>
<ha-button
@click=${this._runTextPipeline}
.disabled=${!this._finished}
>
Send
</ha-button>
`
: this._finished
? html`
<ha-button @click=${this._runAudioPipeline}>
Continue talking
</ha-button>
`
: html`
<ha-formfield label="Continue conversation">
<ha-checkbox
id="continue-conversation"
checked
></ha-checkbox>
</ha-formfield>
`}
</div>
${this._pipelineRun
? html`
<ha-card>
<div class="card-content">
<div class="row heading">
<div>Run</div>
<div>${this._pipelineRun.stage}</div>
</div>
${renderData(this._pipelineRun.run, RUN_DATA)}
</div>
</ha-card>
${maybeRenderError(this._pipelineRun, "ready", lastRunStage)}
${hasStage(this._pipelineRun, "stt")
? html`
<ha-card>
<div class="card-content">
<div class="row heading">
<span>Speech-to-Text</span>
${renderProgress(
this.hass,
this._pipelineRun,
"stt"
)}
</div>
${this._pipelineRun.stt
? html`
<div class="card-content">
${renderData(this._pipelineRun.stt, STT_DATA)}
${dataMinusKeysRender(
this._pipelineRun.stt,
STT_DATA
)}
</div>
`
: ""}
</div>
${this._pipelineRun.stage === "stt" &&
this._stopRecording
? html`
<div class="card-actions">
<ha-button @click=${this._stopRecording}>
Stop Recording
</ha-button>
</div>
`
: ""}
</ha-card>
`
: ""}
${maybeRenderError(this._pipelineRun, "stt", lastRunStage)}
${hasStage(this._pipelineRun, "intent")
? html`
<ha-card>
<div class="card-content">
<div class="row heading">
<span>Natural Language Processing</span>
${renderProgress(
this.hass,
this._pipelineRun,
"intent"
)}
</div>
${this._pipelineRun.intent
? html`
<div class="card-content">
${renderData(
this._pipelineRun.intent,
INTENT_DATA
)}
${dataMinusKeysRender(
this._pipelineRun.intent,
INTENT_DATA
)}
</div>
`
: ""}
</div>
</ha-card>
`
: ""}
${maybeRenderError(this._pipelineRun, "intent", lastRunStage)}
${hasStage(this._pipelineRun, "tts")
? html`
<ha-card>
<div class="card-content">
<div class="row heading">
<span>Text-to-Speech</span>
${renderProgress(
this.hass,
this._pipelineRun,
"tts"
)}
</div>
${this._pipelineRun.tts
? html`
<div class="card-content">
${renderData(this._pipelineRun.tts, TTS_DATA)}
</div>
`
: ""}
</div>
${this._pipelineRun?.tts?.tts_output
? html`
<div class="card-actions">
<ha-button @click=${this._playTTS}>
Play Audio
</ha-button>
</div>
`
: ""}
</ha-card>
`
: ""}
${maybeRenderError(this._pipelineRun, "tts", lastRunStage)}
<ha-card>
<ha-expansion-panel>
<span slot="header">Raw</span>
<pre>${JSON.stringify(this._pipelineRun, null, 2)}</pre>
</ha-expansion-panel>
</ha-card>
`
: ""}
${this._pipelineRuns.map((run) =>
run === null
? ""
: html`
<assist-render-pipeline-run
.hass=${this.hass}
.pipelineRun=${run}
></assist-render-pipeline-run>
`
)}
</div>
</hass-subpage>
`;
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (
!changedProperties.has("_pipelineRun") ||
!this._pipelineRun ||
this._pipelineRun.init_options.start_stage !== "stt"
) {
return;
}
if (this._pipelineRun.stage === "stt" && this._audioBuffer) {
// Send the buffer over the WS to the STT engine.
for (const buffer of this._audioBuffer) {
this._sendAudioChunk(buffer);
}
this._audioBuffer = undefined;
}
if (this._pipelineRun.stage !== "stt" && this._stopRecording) {
this._stopRecording();
}
private get conversationId(): string | null {
return this._pipelineRuns.length === 0
? null
: this._pipelineRuns[0].intent?.intent_output?.conversation_id || null;
}
private async _runTextPipeline() {
const text = await showPromptDialog(this, {
title: "Input text",
confirmText: "Run",
});
const textfield = this._continueConversationTextField;
let text: string | null;
if (textfield) {
text = textfield.value;
} else {
text = await showPromptDialog(this, {
title: "Input text",
confirmText: "Run",
});
}
if (!text) {
return;
}
this._pipelineRun = undefined;
runPipelineFromText(
this.hass,
await this._doRunPipeline(
(run) => {
this._pipelineRun = run;
if (["done", "error"].includes(run.stage)) {
this._finished = true;
if (textfield) {
textfield.value = "";
}
}
},
{
start_stage: "intent",
@@ -351,7 +171,13 @@ export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
private async _runAudioPipeline() {
// @ts-ignore-next-line
const context = new (window.AudioContext || window.webkitAudioContext)();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
let stream: MediaStream;
try {
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
} catch (err) {
return;
}
await context.audioWorklet.addModule(
new URL("./recorder.worklet.js", import.meta.url)
);
@@ -360,32 +186,71 @@ export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
const recorder = new AudioWorkletNode(context, "recorder.worklet");
this.hass.connection.socket!.binaryType = "arraybuffer";
this._stopRecording = () => {
let run: PipelineRun | undefined;
let stopRecording: (() => void) | undefined = () => {
stopRecording = undefined;
// We're currently STTing, so finish audio
if (run?.stage === "stt" && run.stt!.done === false) {
if (this._audioBuffer) {
for (const chunk of this._audioBuffer) {
this._sendAudioChunk(chunk);
}
}
// Send empty message to indicate we're done streaming.
this._sendAudioChunk(new Int16Array());
}
this._audioBuffer = undefined;
stream.getTracks()[0].stop();
context.close();
this._stopRecording = undefined;
this._audioBuffer = undefined;
// Send empty message to indicate we're done streaming.
this._sendAudioChunk(new Int16Array());
};
this._audioBuffer = [];
source.connect(recorder).connect(context.destination);
recorder.port.onmessage = (e) => {
if (!stopRecording) {
return;
}
if (this._audioBuffer) {
this._audioBuffer.push(e.data);
return;
} else {
this._sendAudioChunk(e.data);
}
if (this._pipelineRun?.stage !== "stt") {
return;
}
this._sendAudioChunk(e.data);
};
this._pipelineRun = undefined;
runPipelineFromText(
this.hass,
(run) => {
this._pipelineRun = run;
await this._doRunPipeline(
(updatedRun) => {
run = updatedRun;
// When we start STT stage, the WS has a binary handler
if (updatedRun.stage === "stt" && this._audioBuffer) {
// Send the buffer over the WS to the STT engine.
for (const buffer of this._audioBuffer) {
this._sendAudioChunk(buffer);
}
this._audioBuffer = undefined;
}
// Stop recording if the server is done with STT stage
if (!["ready", "stt"].includes(updatedRun.stage) && stopRecording) {
stopRecording();
}
// Play audio when we're done.
if (updatedRun.stage === "done") {
const url = updatedRun.tts!.tts_output!.url;
const audio = new Audio(url);
audio.addEventListener("ended", () => {
if (this._continueConversationCheckbox.checked) {
this._runAudioPipeline();
} else {
this._finished = true;
}
});
audio.play();
} else if (updatedRun.stage === "error") {
this._finished = true;
}
},
{
start_stage: "stt",
@@ -394,19 +259,76 @@ export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
);
}
private async _doRunPipeline(
callback: (event: PipelineRun) => void,
options: PipelineRunOptions
) {
this._finished = false;
let added = false;
try {
await runVoiceAssistantPipeline(
this.hass,
(updatedRun) => {
if (added) {
this._pipelineRuns = [updatedRun, ...this._pipelineRuns.slice(1)];
} else {
this._pipelineRuns = [updatedRun, ...this._pipelineRuns];
added = true;
}
callback(updatedRun);
},
{
...options,
language: this._languageOverride,
conversation_id: this.conversationId,
}
);
} catch (err: any) {
await showAlertDialog(this, {
title: "Error starting pipeline",
text: err.message || err,
});
}
}
private _sendAudioChunk(chunk: Int16Array) {
// Turn into 8 bit so we can prefix our handler ID.
const data = new Uint8Array(1 + chunk.length * 2);
data[0] = this._pipelineRun!.run.runner_data.stt_binary_handler_id!;
data[0] = this._pipelineRuns[0].run.runner_data.stt_binary_handler_id!;
data.set(new Uint8Array(chunk.buffer), 1);
this.hass.connection.socket!.send(data);
}
private _playTTS(): void {
const url = this._pipelineRun!.tts!.tts_output!.url;
const audio = new Audio(url);
audio.play();
private _handleContinueKeyDown(ev) {
if (ev.keyCode === 13) {
this._runTextPipeline();
}
}
private _clearConversation() {
this._pipelineRuns = [];
}
private _downloadConversation() {
fileDownload(
`data:text/plain;charset=utf-8,${encodeURIComponent(
JSON.stringify(this._pipelineRuns, null, 2)
)}`,
`conversation.json`
);
}
private async _setLanguage() {
const language = await showPromptDialog(this, {
title: "Language override",
inputLabel: "Language",
inputType: "text",
confirmText: "Set",
});
if (language) {
this._languageOverride = language;
}
}
static styles = [
@@ -419,32 +341,19 @@ export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
direction: ltr;
}
.start-row {
text-align: center;
}
.start-row ha-button {
margin: 16px;
}
ha-card,
ha-alert {
display: block;
margin-bottom: 16px;
}
.run-pipeline-card ha-textfield {
display: block;
}
.row {
display: flex;
justify-content: space-between;
justify-content: space-around;
align-items: center;
margin: 0 16px 16px;
}
pre {
margin: 0;
.start-row ha-textfield {
flex: 1;
}
ha-expansion-panel {
padding-left: 8px;
assist-render-pipeline-run {
padding-top: 16px;
}
.heading {
font-weight: 500;
margin-bottom: 16px;
assist-render-pipeline-run + assist-render-pipeline-run {
border-top: 3px solid black;
}
`,
];

View File

@@ -0,0 +1,337 @@
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../../../../components/ha-card";
import "../../../../../../components/ha-alert";
import "../../../../../../components/ha-button";
import "../../../../../../components/ha-circular-progress";
import "../../../../../../components/ha-expansion-panel";
import type { PipelineRun } from "../../../../../../data/voice_assistant";
import type { HomeAssistant } from "../../../../../../types";
import { formatNumber } from "../../../../../../common/number/format_number";
const RUN_DATA = {
pipeline: "Pipeline",
language: "Language",
};
const STT_DATA = {
engine: "Engine",
};
const INTENT_DATA = {
engine: "Engine",
intent_input: "Input",
};
const TTS_DATA = {
engine: "Engine",
tts_input: "Input",
};
const STAGES: Record<PipelineRun["stage"], number> = {
ready: 0,
stt: 1,
intent: 2,
tts: 3,
done: 4,
error: 5,
};
const hasStage = (run: PipelineRun, stage: PipelineRun["stage"]) =>
STAGES[run.init_options.start_stage] <= STAGES[stage] &&
STAGES[stage] <= STAGES[run.init_options.end_stage];
const maybeRenderError = (
run: PipelineRun,
stage: string,
lastRunStage: string
) => {
if (run.stage !== "error" || lastRunStage !== stage) {
return "";
}
return html`
<ha-alert alert-type="error">
${run.error!.message} (${run.error!.code})
</ha-alert>
`;
};
const renderProgress = (
hass: HomeAssistant,
pipelineRun: PipelineRun,
stage: PipelineRun["stage"]
) => {
const startEvent = pipelineRun.events.find(
(ev) => ev.type === `${stage}-start`
);
const finishEvent = pipelineRun.events.find(
(ev) => ev.type === `${stage}-end`
);
if (!startEvent) {
return "";
}
if (pipelineRun.stage === "error") {
return html``;
}
if (!finishEvent) {
return html`
<ha-circular-progress size="tiny" active></ha-circular-progress>
`;
}
const duration =
new Date(finishEvent.timestamp).getTime() -
new Date(startEvent.timestamp).getTime();
const durationString = formatNumber(duration / 1000, hass.locale, {
maximumFractionDigits: 2,
});
return html`${durationString}s ✅`;
};
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 = (
data: Record<string, any>,
keys: Record<string, string>
) => {
const result = {};
let render = false;
for (const key in data) {
if (key in keys || key === "done") {
continue;
}
render = true;
result[key] = data[key];
}
return render ? html`<pre>${JSON.stringify(result, null, 2)}</pre>` : "";
};
@customElement("assist-render-pipeline-run")
export class AssistPipelineDebug extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() private pipelineRun!: PipelineRun;
protected render(): TemplateResult {
const lastRunStage: string = this.pipelineRun
? ["tts", "intent", "stt"].find(
(stage) => this.pipelineRun![stage] !== undefined
) || "ready"
: "ready";
const messages: Array<{ from: string; text: string }> = [];
const userMessage =
this.pipelineRun.init_options.input?.text ||
this.pipelineRun?.stt?.stt_output?.text;
if (userMessage) {
messages.push({
from: "user",
text: userMessage,
});
}
if (
this.pipelineRun?.intent?.intent_output?.response?.speech?.plain?.speech
) {
messages.push({
from: "hass",
text: this.pipelineRun.intent.intent_output.response.speech.plain
.speech,
});
}
return html`
<ha-card>
<div class="card-content">
<div class="row heading">
<div>Run</div>
<div>${this.pipelineRun.stage}</div>
</div>
${renderData(this.pipelineRun.run, RUN_DATA)}
${messages.length > 0
? html`
<div class="messages">
${messages.map(
({ from, text }) => html`
<div class=${`message ${from}`}>${text}</div>
`
)}
</div>
<div style="clear:both"></div>
`
: ""}
</div>
</ha-card>
${maybeRenderError(this.pipelineRun, "ready", lastRunStage)}
${hasStage(this.pipelineRun, "stt")
? html`
<ha-card>
<div class="card-content">
<div class="row heading">
<span>Speech-to-Text</span>
${renderProgress(this.hass, this.pipelineRun, "stt")}
</div>
${this.pipelineRun.stt
? html`
<div class="card-content">
${renderData(this.pipelineRun.stt, STT_DATA)}
${dataMinusKeysRender(this.pipelineRun.stt, STT_DATA)}
</div>
`
: ""}
</div>
</ha-card>
`
: ""}
${maybeRenderError(this.pipelineRun, "stt", lastRunStage)}
${hasStage(this.pipelineRun, "intent")
? html`
<ha-card>
<div class="card-content">
<div class="row heading">
<span>Natural Language Processing</span>
${renderProgress(this.hass, this.pipelineRun, "intent")}
</div>
${this.pipelineRun.intent
? html`
<div class="card-content">
${renderData(this.pipelineRun.intent, INTENT_DATA)}
${dataMinusKeysRender(
this.pipelineRun.intent,
INTENT_DATA
)}
</div>
`
: ""}
</div>
</ha-card>
`
: ""}
${maybeRenderError(this.pipelineRun, "intent", lastRunStage)}
${hasStage(this.pipelineRun, "tts")
? html`
<ha-card>
<div class="card-content">
<div class="row heading">
<span>Text-to-Speech</span>
${renderProgress(this.hass, this.pipelineRun, "tts")}
</div>
${this.pipelineRun.tts
? html`
<div class="card-content">
${renderData(this.pipelineRun.tts, TTS_DATA)}
</div>
`
: ""}
</div>
${this.pipelineRun?.tts?.tts_output
? html`
<div class="card-actions">
<ha-button @click=${this._playTTS}>
Play Audio
</ha-button>
</div>
`
: ""}
</ha-card>
`
: ""}
${maybeRenderError(this.pipelineRun, "tts", lastRunStage)}
<ha-card>
<ha-expansion-panel>
<span slot="header">Raw</span>
<pre>${JSON.stringify(this.pipelineRun, null, 2)}</pre>
</ha-expansion-panel>
</ha-card>
`;
}
private _playTTS(): void {
const url = this.pipelineRun!.tts!.tts_output!.url;
const audio = new Audio(url);
audio.play();
}
static styles = css`
:host {
display: block;
}
ha-card,
ha-alert {
display: block;
margin-bottom: 16px;
}
.row {
display: flex;
justify-content: space-between;
}
pre {
margin: 0;
}
ha-expansion-panel {
padding-left: 8px;
}
.heading {
font-weight: 500;
margin-bottom: 16px;
}
.messages {
margin-top: 8px;
}
.message {
font-size: 18px;
margin: 8px 0;
padding: 8px;
border-radius: 15px;
clear: both;
}
.message.user {
margin-left: 24px;
margin-inline-start: 24px;
margin-inline-end: initial;
float: var(--float-end);
text-align: right;
border-bottom-right-radius: 0px;
background-color: var(--light-primary-color);
color: var(--text-light-primary-color, var(--primary-text-color));
direction: var(--direction);
}
.message.hass {
margin-right: 24px;
margin-inline-end: 24px;
margin-inline-start: initial;
float: var(--float-start);
border-bottom-left-radius: 0px;
background-color: var(--primary-color);
color: var(--text-primary-color);
direction: var(--direction);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"assist-render-pipeline-run": AssistPipelineDebug;
}
}

View File

@@ -115,7 +115,7 @@ export class HaConfigLogs extends LitElement {
${isComponentLoaded(this.hass, "hassio") &&
this.hass.userData?.showAdvanced
? html`
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-button-menu slot="toolbar-icon">
<ha-button
slot="trigger"
.label=${this._logProviders.find(

View File

@@ -267,11 +267,7 @@ export class HaConfigLovelaceDashboards extends LitElement {
>
${this.hass.userData?.showAdvanced
? html`
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
activatable
>
<ha-button-menu slot="toolbar-icon" activatable>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -248,7 +248,7 @@ export class HassioNetwork extends LitElement {
</ha-circular-progress>`
: this.hass.localize("ui.common.save")}
</mwc-button>
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-button-menu @action=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${"ui.common.menu"}

View File

@@ -79,7 +79,7 @@ class HaConfigRepairsDashboard extends SubscribeMixin(LitElement) {
.header=${this.hass.localize("ui.panel.config.repairs.caption")}
>
<div slot="toolbar-icon">
<ha-button-menu corner="BOTTOM_START" multi>
<ha-button-menu multi>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -225,7 +225,6 @@ class HaSceneDashboard extends LitElement {
></ha-icon-button>
<ha-button-related-filter-menu
slot="filter-menu"
corner="BOTTOM_START"
.narrow=${this.narrow}
.hass=${this.hass}
.value=${this._filterValue}

View File

@@ -228,7 +228,6 @@ export class HaSceneEditor extends SubscribeMixin(
: this.hass.localize("ui.panel.config.scene.editor.default_name")}
>
<ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleMenuAction}
activatable

View File

@@ -191,7 +191,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
</mwc-button>
`
: ""}
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -227,7 +227,6 @@ class HaScriptPicker extends LitElement {
></ha-icon-button>
<ha-button-related-filter-menu
slot="filter-menu"
corner="BOTTOM_START"
.narrow=${this.narrow}
.hass=${this.hass}
.value=${this._filterValue}

View File

@@ -116,7 +116,7 @@ export class HaScriptTrace extends LitElement {
`
: ""}
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
@@ -508,7 +508,7 @@ export class HaScriptTrace extends LitElement {
}
.main {
height: calc(100% - 56px);
height: calc(100% - var(--header-height));
display: flex;
background-color: var(--card-background-color);
}

View File

@@ -22,6 +22,7 @@ import {
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { bytesToString } from "../../../util/bytes-to-string";
import { MoveDatadiskDialogParams } from "./show-dialog-move-datadisk";
const calculateMoveTime = memoizeOne((hostInfo: HassioHostInfo): number => {
@@ -39,7 +40,7 @@ class MoveDatadiskDialog extends LitElement {
@state() private _selectedDevice?: string;
@state() private _devices?: DatadiskList["devices"];
@state() private _disks?: DatadiskList["disks"];
@state() private _osInfo?: HassioHassOSInfo;
@@ -55,7 +56,7 @@ class MoveDatadiskDialog extends LitElement {
const data = await listDatadisks(this.hass);
if (data.devices.length > 0) {
this._devices = data.devices;
this._disks = data.disks;
} else {
this.closeDialog();
await showAlertDialog(this, {
@@ -80,7 +81,7 @@ class MoveDatadiskDialog extends LitElement {
public closeDialog(): void {
this._selectedDevice = undefined;
this._devices = undefined;
this._disks = undefined;
this._moving = false;
this._hostInfo = undefined;
this._osInfo = undefined;
@@ -88,7 +89,7 @@ class MoveDatadiskDialog extends LitElement {
}
protected render() {
if (!this._hostInfo || !this._osInfo || !this._devices) {
if (!this._hostInfo || !this._osInfo || !this._disks) {
return nothing;
}
@@ -132,10 +133,19 @@ class MoveDatadiskDialog extends LitElement {
dialogInitialFocus
fixedMenuPosition
>
${this._devices.map(
(device) =>
html`<mwc-list-item .value=${device}>
${device}
${this._disks.map(
(disk) =>
html`<mwc-list-item twoline .value=${disk.id}>
<span>${disk.vendor} ${disk.model}</span>
<span slot="secondary">
${this.hass.localize(
"ui.panel.config.storage.datadisk.extra_information",
{
size: bytesToString(disk.size),
serial: disk.serial,
}
)}
</span>
</mwc-list-item>`
)}
</ha-select>

View File

@@ -44,7 +44,7 @@ class HaConfigSectionStorage extends LitElement {
>
${this._hostInfo
? html`
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<ha-button-menu slot="toolbar-icon">
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}

View File

@@ -0,0 +1,41 @@
import { customElement, property } from "lit/decorators";
import {
HassRouterPage,
RouterOptions,
} from "../../../layouts/hass-router-page";
import { HomeAssistant } from "../../../types";
@customElement("ha-config-voice-assistants")
class HaConfigVoiceAssistants extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public narrow!: boolean;
@property() public isWide!: boolean;
protected routerOptions: RouterOptions = {
defaultPage: "debug",
routes: {
debug: {
tag: "assist-pipeline-debug",
load: () =>
import(
"../integrations/integration-panels/voice_assistant/assist/assist-pipeline-debug"
),
},
},
};
protected updatePageEl(pageEl) {
pageEl.hass = this.hass;
pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.route = this.routeTail;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-voice-assistants": HaConfigVoiceAssistants;
}
}

View File

@@ -515,22 +515,7 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
css`
.content {
padding: 0 16px 16px;
}
state-history-charts {
height: calc(100vh - 136px);
}
:host([narrow]) state-history-charts {
height: calc(100vh - 198px);
}
.progress-wrapper {
height: calc(100vh - 136px);
}
:host([narrow]) .progress-wrapper {
height: calc(100vh - 198px);
padding-bottom: max(env(safe-area-inset-bottom), 16px);
}
:host([virtualize]) {
@@ -539,6 +524,10 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
.progress-wrapper {
position: relative;
display: flex;
align-items: center;
flex-direction: column;
padding: 16px;
}
.filters {
@@ -566,13 +555,6 @@ class HaPanelHistory extends SubscribeMixin(LitElement) {
}
}
ha-circular-progress {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.start-search {
padding-top: 16px;
text-align: center;

View File

@@ -214,7 +214,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
}
}
if (domain === "fan" && stateActive(stateObj)) {
if (domain === "fan") {
const speedStateDisplay = computeFanSpeedStateDisplay(
stateObj as FanEntity,
this.hass!.locale
@@ -231,12 +231,11 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
this.hass!.entities
);
if (domain === "cover" && stateActive(stateObj)) {
if (domain === "cover") {
const positionStateDisplay = computeCoverPositionStateDisplay(
stateObj as CoverEntity,
this.hass!.locale
);
if (positionStateDisplay) {
return `${stateDisplay}${positionStateDisplay}`;
}

View File

@@ -80,7 +80,7 @@ export class HuiCardOptions extends LitElement {
@click=${this._cardUp}
?disabled=${this.path![1] === 0}
></ha-icon-button>
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-button-menu @action=${this._handleAction}>
<ha-icon-button
slot="trigger"
.label=${this.hass!.localize(

View File

@@ -3,7 +3,6 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { array, assert, assign, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import type { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
@@ -15,7 +14,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
name: optional(string()),
states: optional(array()),
theme: optional(string()),

View File

@@ -2,7 +2,6 @@ import { CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
@@ -15,7 +14,7 @@ import { configElementStyle } from "./config-elements-style";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
name: optional(string()),
show_name: optional(boolean()),
icon: optional(string()),

View File

@@ -18,7 +18,6 @@ import {
} from "superstruct";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { customType } from "../../../../common/structs/is-custom-type";
import { entityId } from "../../../../common/structs/is-entity-id";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-card";
@@ -184,7 +183,7 @@ const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
title: optional(union([string(), boolean()])),
entity: optional(entityId()),
entity: optional(string()),
theme: optional(string()),
icon: optional(string()),
show_header_toggle: optional(boolean()),

View File

@@ -2,7 +2,6 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
@@ -14,7 +13,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
name: optional(string()),
icon: optional(string()),
attribute: optional(string()),

View File

@@ -12,7 +12,6 @@ import {
string,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
@@ -31,7 +30,7 @@ const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
name: optional(string()),
entity: optional(entityId()),
entity: optional(string()),
unit: optional(string()),
min: optional(number()),
max: optional(number()),

View File

@@ -2,7 +2,6 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
@@ -13,7 +12,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
name: optional(string()),
theme: optional(string()),
})

View File

@@ -2,7 +2,6 @@ import { CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
@@ -16,7 +15,7 @@ const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
name: optional(string()),
entity: optional(entityId()),
entity: optional(string()),
theme: optional(string()),
icon: optional(string()),
hold_action: optional(actionConfigStruct),

View File

@@ -2,7 +2,6 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/entity/ha-entity-picker";
import "../../../../components/ha-theme-picker";
import { HomeAssistant } from "../../../../types";
@@ -14,7 +13,7 @@ import { EditorTarget, EntitiesEditorEvent } from "../types";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
theme: optional(string()),
})
);

View File

@@ -2,7 +2,6 @@ import { CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, boolean, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
@@ -15,7 +14,7 @@ import { configElementStyle } from "./config-elements-style";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
image: optional(string()),
name: optional(string()),
camera_image: optional(string()),

View File

@@ -2,7 +2,6 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
@@ -13,7 +12,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
name: optional(string()),
theme: optional(string()),
})

View File

@@ -11,7 +11,6 @@ import {
union,
} from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
@@ -24,7 +23,7 @@ import { DEFAULT_HOURS_TO_SHOW } from "../../cards/hui-sensor-card";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
name: optional(string()),
icon: optional(string()),
graph: optional(union([literal("line"), literal("none")])),

View File

@@ -2,7 +2,6 @@ import { html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { assert, assign, object, optional, string } from "superstruct";
import { fireEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
import type { HomeAssistant } from "../../../../types";
@@ -13,7 +12,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
name: optional(string()),
theme: optional(string()),
})

View File

@@ -14,7 +14,6 @@ import {
string,
} from "superstruct";
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
import { entityId } from "../../../../common/structs/is-entity-id";
import { LocalizeFunc } from "../../../../common/translations/localize";
import "../../../../components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../components/ha-form/types";
@@ -35,7 +34,7 @@ import "./hui-tile-card-features-editor";
const cardConfigStruct = assign(
baseLovelaceCardConfig,
object({
entity: optional(entityId()),
entity: optional(string()),
name: optional(string()),
icon: optional(string()),
color: optional(string()),

Some files were not shown because too many files have changed in this diff Show More