mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-29 19:19:27 +00:00
Compare commits
1 Commits
add-suppor
...
allow-part
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a548d13931 |
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3.6.0
|
||||
with:
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3.6.0
|
||||
with:
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3.6.0
|
||||
with:
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3.6.0
|
||||
with:
|
||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
8
.github/workflows/demo_deployment.yaml
vendored
8
.github/workflows/demo_deployment.yaml
vendored
@@ -17,13 +17,13 @@ jobs:
|
||||
deploy_dev:
|
||||
runs-on: ubuntu-latest
|
||||
name: Demo Development
|
||||
if: github.event_name != 'push' || github.ref_name != 'master'
|
||||
if: github.event_name != 'push' || github.ref != 'master'
|
||||
environment:
|
||||
name: Demo Development
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
@@ -53,13 +53,13 @@ jobs:
|
||||
deploy_master:
|
||||
runs-on: ubuntu-latest
|
||||
name: Demo Production
|
||||
if: github.event_name == 'push' && github.ref_name == 'master'
|
||||
if: github.event_name == 'push' && github.ref == 'master'
|
||||
environment:
|
||||
name: Demo Production
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3.6.0
|
||||
|
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3.6.0
|
||||
|
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
|
||||
- name: Verify version
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
|
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
|
@@ -89,7 +89,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||
"@babel/preset-env",
|
||||
{
|
||||
useBuiltIns: "entry",
|
||||
corejs: { version: "3.30", proposals: true },
|
||||
corejs: { version: "3.29", proposals: true },
|
||||
bugfixes: true,
|
||||
},
|
||||
],
|
||||
|
@@ -8,7 +8,6 @@ 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
|
||||
@@ -96,7 +95,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.plugin(retry))({
|
||||
const octokit = new Octokit({
|
||||
userAgent: "Fetch Nightly Translations",
|
||||
auth: tokenAuth.token,
|
||||
});
|
||||
|
@@ -3,7 +3,7 @@ const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { marked } = require("marked");
|
||||
const { glob } = require("glob");
|
||||
const glob = require("glob");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
const env = require("../env.cjs");
|
||||
|
@@ -92,7 +92,11 @@ export class HassioAddonStore extends LitElement {
|
||||
.route=${this.route}
|
||||
.header=${this.supervisor.localize("panel.store")}
|
||||
>
|
||||
<ha-button-menu slot="toolbar-icon" @action=${this._handleAction}>
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.supervisor.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
@@ -216,7 +220,7 @@ export class HassioAddonStore extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _filterChanged(e) {
|
||||
private async _filterChanged(e) {
|
||||
this._filter = e.detail.value;
|
||||
}
|
||||
|
||||
|
@@ -168,7 +168,7 @@ class HassioAddonConfig extends LitElement {
|
||||
${this.supervisor.localize("addon.configuration.options.header")}
|
||||
</h2>
|
||||
<div class="card-menu">
|
||||
<ha-button-menu @action=${this._handleAction}>
|
||||
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
|
||||
<ha-icon-button
|
||||
.label=${this.supervisor.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
|
@@ -29,6 +29,7 @@ import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../../src/common/navigate";
|
||||
import "../../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
@@ -46,7 +47,6 @@ import {
|
||||
HassioAddonSetOptionParams,
|
||||
HassioAddonSetSecurityParams,
|
||||
installHassioAddon,
|
||||
rebuildLocalAddon,
|
||||
restartHassioAddon,
|
||||
setHassioAddonOption,
|
||||
setHassioAddonSecurity,
|
||||
@@ -640,12 +640,13 @@ class HassioAddonInfo extends LitElement {
|
||||
</ha-progress-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
<ha-progress-button
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
@click=${this._rebuildClicked}
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/rebuild"
|
||||
>
|
||||
${this.supervisor.localize("addon.dashboard.rebuild")}
|
||||
</ha-progress-button>
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: ""}`
|
||||
: ""}
|
||||
@@ -965,21 +966,6 @@ class HassioAddonInfo extends LitElement {
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _rebuildClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
try {
|
||||
await rebuildLocalAddon(this.hass, this.addon.slug);
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor.localize("addon.dashboard.action_error.rebuild"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _startClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
@@ -1138,6 +1124,10 @@ class HassioAddonInfo extends LitElement {
|
||||
ha-svg-icon.stopped {
|
||||
color: var(--error-color);
|
||||
}
|
||||
ha-call-api-button {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
protection-enable mwc-button {
|
||||
--mdc-theme-primary: white;
|
||||
}
|
||||
|
@@ -195,7 +195,11 @@ export class HassioBackups extends LitElement {
|
||||
: "/config"}
|
||||
supervisor
|
||||
>
|
||||
<ha-button-menu slot="toolbar-icon" @action=${this._handleAction}>
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
slot="toolbar-icon"
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.supervisor?.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
|
@@ -316,7 +316,7 @@ export class DialogHassioNetwork
|
||||
>
|
||||
<div class="radio-row">
|
||||
<ha-formfield
|
||||
.label=${this.supervisor.localize("dialog.network.auto")}
|
||||
.label=${this.supervisor.localize("dialog.network.dhcp")}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
|
@@ -44,6 +44,10 @@ export const hassioStyle = css`
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 0.25fr));
|
||||
}
|
||||
}
|
||||
ha-call-api-button {
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
margin-top: 16px;
|
||||
|
@@ -184,7 +184,7 @@ class HassioHostInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-button-menu>
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<ha-icon-button
|
||||
.label=${this.supervisor.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
|
49
package.json
49
package.json
@@ -26,13 +26,13 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.2",
|
||||
"@codemirror/autocomplete": "6.5.1",
|
||||
"@codemirror/autocomplete": "6.4.2",
|
||||
"@codemirror/commands": "6.2.2",
|
||||
"@codemirror/language": "6.6.0",
|
||||
"@codemirror/legacy-modes": "6.3.2",
|
||||
"@codemirror/search": "6.3.0",
|
||||
"@codemirror/state": "6.2.0",
|
||||
"@codemirror/view": "6.9.4",
|
||||
"@codemirror/view": "6.9.3",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.5.1",
|
||||
"@formatjs/intl-getcanonicallocales": "2.1.0",
|
||||
@@ -90,18 +90,18 @@
|
||||
"@polymer/paper-toast": "3.0.1",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "23.3.10",
|
||||
"@vaadin/vaadin-themable-mixin": "23.3.10",
|
||||
"@vaadin/combo-box": "23.3.9",
|
||||
"@vaadin/vaadin-themable-mixin": "23.3.9",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@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.9",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.8",
|
||||
"@webcomponents/webcomponentsjs": "2.7.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"chart.js": "3.3.2",
|
||||
"comlink": "4.4.1",
|
||||
"core-js": "3.30.1",
|
||||
"core-js": "3.29.1",
|
||||
"cropperjs": "1.5.13",
|
||||
"date-fns": "2.29.3",
|
||||
"date-fns-tz": "2.0.0",
|
||||
@@ -116,7 +116,7 @@
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.3",
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.7.2",
|
||||
"lit": "2.7.0",
|
||||
"marked": "4.3.0",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
@@ -148,7 +148,7 @@
|
||||
"xss": "1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.21.4",
|
||||
"@babel/core": "7.21.3",
|
||||
"@babel/plugin-external-helpers": "7.18.6",
|
||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
||||
"@babel/plugin-proposal-decorators": "7.21.0",
|
||||
@@ -158,17 +158,16 @@
|
||||
"@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.21.4",
|
||||
"@babel/preset-typescript": "7.21.4",
|
||||
"@babel/preset-env": "7.20.2",
|
||||
"@babel/preset-typescript": "7.21.0",
|
||||
"@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",
|
||||
"@rollup/plugin-commonjs": "24.1.0",
|
||||
"@rollup/plugin-commonjs": "24.0.1",
|
||||
"@rollup/plugin-json": "6.0.0",
|
||||
"@rollup/plugin-node-resolve": "15.0.2",
|
||||
"@rollup/plugin-node-resolve": "15.0.1",
|
||||
"@rollup/plugin-replace": "5.0.2",
|
||||
"@types/chromecast-caf-receiver": "5.0.12",
|
||||
"@types/chromecast-caf-sender": "1.0.5",
|
||||
@@ -185,15 +184,15 @@
|
||||
"@types/sortablejs": "1.15.1",
|
||||
"@types/tar": "6.1.4",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "5.58.0",
|
||||
"@typescript-eslint/parser": "5.58.0",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"@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.38.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",
|
||||
@@ -201,24 +200,24 @@
|
||||
"eslint-plugin-disable": "2.0.3",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"eslint-plugin-lit": "1.8.2",
|
||||
"eslint-plugin-lit-a11y": "2.4.1",
|
||||
"eslint-plugin-lit-a11y": "2.4.0",
|
||||
"eslint-plugin-unused-imports": "2.0.0",
|
||||
"eslint-plugin-wc": "1.4.0",
|
||||
"esprima": "4.0.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.1.1",
|
||||
"glob": "10.1.0",
|
||||
"glob": "9.3.2",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-flatmap": "1.0.2",
|
||||
"gulp-json-transform": "0.4.8",
|
||||
"gulp-merge-json": "2.1.2",
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-zopfli-green": "6.0.1",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"html-minifier-terser": "7.1.0",
|
||||
"husky": "8.0.3",
|
||||
"instant-mocha": "1.5.1",
|
||||
"instant-mocha": "1.5.0",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "13.2.1",
|
||||
"lint-staged": "13.2.0",
|
||||
"lit-analyzer": "1.2.1",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.0",
|
||||
@@ -245,7 +244,7 @@
|
||||
"vinyl-source-stream": "2.0.0",
|
||||
"webpack": "=5.72.1",
|
||||
"webpack-cli": "5.0.1",
|
||||
"webpack-dev-server": "4.13.3",
|
||||
"webpack-dev-server": "4.13.1",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
"webpackbar": "5.0.2",
|
||||
"workbox-build": "6.5.4"
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20230411.0"
|
||||
version = "20230401.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -9,7 +9,6 @@ import {
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-alert";
|
||||
import "../components/ha-checkbox";
|
||||
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
|
||||
@@ -21,12 +20,13 @@ import {
|
||||
DataEntryFlowStep,
|
||||
DataEntryFlowStepForm,
|
||||
} from "../data/data_entry_flow";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import "./ha-password-manager-polyfill";
|
||||
|
||||
type State = "loading" | "error" | "step";
|
||||
|
||||
@customElement("ha-auth-flow")
|
||||
export class HaAuthFlow extends LitElement {
|
||||
export class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
@property({ attribute: false }) public authProvider?: AuthProvider;
|
||||
|
||||
@property() public clientId?: string;
|
||||
@@ -35,8 +35,6 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
@property() public oauth2State?: string;
|
||||
|
||||
@property() public localize!: LocalizeFunc;
|
||||
|
||||
@state() private _state: State = "loading";
|
||||
|
||||
@state() private _stepData?: Record<string, any>;
|
||||
|
@@ -82,13 +82,12 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
.redirectUri=${this.redirectUri}
|
||||
.oauth2State=${this.oauth2State}
|
||||
.authProvider=${this._authProvider}
|
||||
.localize=${this.localize}
|
||||
></ha-auth-flow>
|
||||
|
||||
${inactiveProviders.length > 0
|
||||
? html`
|
||||
<ha-pick-auth-provider
|
||||
.localize=${this.localize}
|
||||
.resources=${this.resources}
|
||||
.clientId=${this.clientId}
|
||||
.authProviders=${inactiveProviders}
|
||||
@pick-auth-provider=${this._handleAuthProviderPick}
|
||||
|
@@ -2,10 +2,10 @@ import "@material/mwc-list";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-icon-next";
|
||||
import "../components/ha-list-item";
|
||||
import { AuthProvider } from "../data/auth";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
@@ -14,11 +14,9 @@ declare global {
|
||||
}
|
||||
|
||||
@customElement("ha-pick-auth-provider")
|
||||
export class HaPickAuthProvider extends LitElement {
|
||||
export class HaPickAuthProvider extends litLocalizeLiteMixin(LitElement) {
|
||||
@property() public authProviders: AuthProvider[] = [];
|
||||
|
||||
@property() public localize!: LocalizeFunc;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p>
|
||||
|
@@ -24,47 +24,26 @@ import { LocalizeFunc } from "../translations/localize";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { supportsFeatureFromAttributes } from "./supports-feature";
|
||||
|
||||
export const computeStateDisplaySingleEntity = (
|
||||
localize: LocalizeFunc,
|
||||
stateObj: HassEntity,
|
||||
locale: FrontendLocaleData,
|
||||
entity: EntityRegistryDisplayEntry | undefined,
|
||||
state?: string
|
||||
): string =>
|
||||
computeStateDisplayFromEntityAttributes(
|
||||
localize,
|
||||
locale,
|
||||
entity,
|
||||
stateObj.entity_id,
|
||||
stateObj.attributes,
|
||||
state !== undefined ? state : stateObj.state
|
||||
);
|
||||
|
||||
export const computeStateDisplay = (
|
||||
localize: LocalizeFunc,
|
||||
stateObj: HassEntity,
|
||||
locale: FrontendLocaleData,
|
||||
entities: HomeAssistant["entities"],
|
||||
state?: string
|
||||
): string => {
|
||||
const entity = entities[stateObj.entity_id] as
|
||||
| EntityRegistryDisplayEntry
|
||||
| undefined;
|
||||
|
||||
return computeStateDisplayFromEntityAttributes(
|
||||
): string =>
|
||||
computeStateDisplayFromEntityAttributes(
|
||||
localize,
|
||||
locale,
|
||||
entity,
|
||||
entities,
|
||||
stateObj.entity_id,
|
||||
stateObj.attributes,
|
||||
state !== undefined ? state : stateObj.state
|
||||
);
|
||||
};
|
||||
|
||||
export const computeStateDisplayFromEntityAttributes = (
|
||||
localize: LocalizeFunc,
|
||||
locale: FrontendLocaleData,
|
||||
entity: EntityRegistryDisplayEntry | undefined,
|
||||
entities: HomeAssistant["entities"],
|
||||
entityId: string,
|
||||
attributes: any,
|
||||
state: string
|
||||
@@ -73,6 +52,8 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
return localize(`state.default.${state}`);
|
||||
}
|
||||
|
||||
const entity = entities[entityId] as EntityRegistryDisplayEntry | undefined;
|
||||
|
||||
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
||||
if (isNumericFromAttributes(attributes)) {
|
||||
// state is duration
|
||||
|
17
src/common/structs/is-entity-id.ts
Normal file
17
src/common/structs/is-entity-id.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
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);
|
@@ -4,7 +4,7 @@ import { FrontendLocaleData } from "../../data/translation";
|
||||
export const blankBeforePercent = (
|
||||
localeOptions: FrontendLocaleData
|
||||
): string => {
|
||||
switch (localeOptions?.language) {
|
||||
switch (localeOptions.language) {
|
||||
case "cz":
|
||||
case "de":
|
||||
case "fi":
|
||||
|
77
src/components/buttons/ha-call-api-button.ts
Normal file
77
src/components/buttons/ha-call-api-button.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./ha-progress-button";
|
||||
|
||||
@customElement("ha-call-api-button")
|
||||
class HaCallApiButton extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public method: "POST" | "GET" | "PUT" | "DELETE" = "POST";
|
||||
|
||||
@property() public data = {};
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public progress = false;
|
||||
|
||||
@property() public path?: string;
|
||||
|
||||
@query("ha-progress-button", true) private _progressButton;
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<ha-progress-button
|
||||
.progress=${this.progress}
|
||||
@click=${this._buttonTapped}
|
||||
?disabled=${this.disabled}
|
||||
><slot></slot
|
||||
></ha-progress-button>
|
||||
`;
|
||||
}
|
||||
|
||||
async _buttonTapped() {
|
||||
this.progress = true;
|
||||
const eventData: {
|
||||
method: string;
|
||||
path: string;
|
||||
data: any;
|
||||
success?: boolean;
|
||||
response?: any;
|
||||
} = {
|
||||
method: this.method,
|
||||
path: this.path!,
|
||||
data: this.data,
|
||||
};
|
||||
|
||||
try {
|
||||
const resp = await this.hass.callApi(this.method, this.path!, this.data);
|
||||
this.progress = false;
|
||||
this._progressButton.actionSuccess();
|
||||
eventData.success = true;
|
||||
eventData.response = resp;
|
||||
} catch (err: any) {
|
||||
this.progress = false;
|
||||
this._progressButton.actionError();
|
||||
eventData.success = false;
|
||||
eventData.response = err;
|
||||
}
|
||||
|
||||
fireEvent(this, "hass-api-called", eventData as any);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host([disabled]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-call-api-button": HaCallApiButton;
|
||||
}
|
||||
}
|
@@ -276,11 +276,7 @@ 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 - this.paddingYAxis
|
||||
) -
|
||||
clamp(context.tooltip.caretX, 100, this.clientWidth - 100) -
|
||||
100 +
|
||||
"px",
|
||||
};
|
||||
@@ -306,7 +302,7 @@ export default class HaChartBase extends LitElement {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
position: var(--chart-base-position, relative);
|
||||
position: relative;
|
||||
}
|
||||
.chartContainer {
|
||||
overflow: hidden;
|
||||
|
@@ -73,7 +73,7 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||
main?: boolean;
|
||||
title: TemplateResult | string;
|
||||
label?: TemplateResult | string;
|
||||
type?: "numeric" | "icon" | "icon-button" | "overflow-menu" | "flex";
|
||||
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
|
||||
template?: (data: any, row: T) => TemplateResult | string | typeof nothing;
|
||||
width?: string;
|
||||
maxWidth?: string;
|
||||
@@ -359,10 +359,10 @@ export class HaDataTable extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
if (row.append) {
|
||||
return html`<div class="mdc-data-table__row">${row.content}</div>`;
|
||||
return html` <div class="mdc-data-table__row">${row.content}</div> `;
|
||||
}
|
||||
if (row.empty) {
|
||||
return html`<div class="mdc-data-table__row"></div>`;
|
||||
return html` <div class="mdc-data-table__row"></div> `;
|
||||
}
|
||||
return html`
|
||||
<div
|
||||
@@ -406,7 +406,6 @@ export class HaDataTable extends LitElement {
|
||||
<div
|
||||
role=${column.main ? "rowheader" : "cell"}
|
||||
class="mdc-data-table__cell ${classMap({
|
||||
"mdc-data-table__cell--flex": column.type === "flex",
|
||||
"mdc-data-table__cell--numeric": column.type === "numeric",
|
||||
"mdc-data-table__cell--icon": column.type === "icon",
|
||||
"mdc-data-table__cell--icon-button":
|
||||
@@ -664,10 +663,6 @@ export class HaDataTable extends LitElement {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell.mdc-data-table__cell--flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell.mdc-data-table__cell--icon {
|
||||
overflow: initial;
|
||||
}
|
||||
@@ -984,7 +979,6 @@ export class HaDataTable extends LitElement {
|
||||
}
|
||||
lit-virtualizer {
|
||||
contain: size layout !important;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,129 +0,0 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiDeleteOutline, mdiPlus } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-area-picker";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
@customElement("ha-aliases-editor")
|
||||
class AliasesEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public aliases!: string[];
|
||||
|
||||
protected render() {
|
||||
if (!this.aliases) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.aliases.map(
|
||||
(alias, index) => html`
|
||||
<div class="layout horizontal center-center row">
|
||||
<ha-textfield
|
||||
dialogInitialFocus=${index}
|
||||
.index=${index}
|
||||
class="flex-auto"
|
||||
.label=${this.hass!.localize("ui.dialogs.aliases.input_label", {
|
||||
number: index + 1,
|
||||
})}
|
||||
.value=${alias}
|
||||
?data-last=${index === this.aliases.length - 1}
|
||||
@input=${this._editAlias}
|
||||
@keydown=${this._keyDownAlias}
|
||||
></ha-textfield>
|
||||
<ha-icon-button
|
||||
.index=${index}
|
||||
slot="navigationIcon"
|
||||
label=${this.hass!.localize("ui.dialogs.aliases.remove_alias", {
|
||||
number: index + 1,
|
||||
})}
|
||||
@click=${this._removeAlias}
|
||||
.path=${mdiDeleteOutline}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<div class="layout horizontal center-center">
|
||||
<mwc-button @click=${this._addAlias}>
|
||||
${this.hass!.localize("ui.dialogs.aliases.add_alias")}
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _addAlias() {
|
||||
this.aliases = [...this.aliases, ""];
|
||||
this._fireChanged(this.aliases);
|
||||
await this.updateComplete;
|
||||
const field = this.shadowRoot?.querySelector(`ha-textfield[data-last]`) as
|
||||
| HaTextField
|
||||
| undefined;
|
||||
field?.focus();
|
||||
}
|
||||
|
||||
private async _editAlias(ev: Event) {
|
||||
const index = (ev.target as any).index;
|
||||
const aliases = [...this.aliases];
|
||||
aliases[index] = (ev.target as any).value;
|
||||
this._fireChanged(aliases);
|
||||
}
|
||||
|
||||
private async _keyDownAlias(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter") {
|
||||
ev.stopPropagation();
|
||||
this._addAlias();
|
||||
}
|
||||
}
|
||||
|
||||
private async _removeAlias(ev: Event) {
|
||||
const index = (ev.target as any).index;
|
||||
const aliases = [...this.aliases];
|
||||
aliases.splice(index, 1);
|
||||
this._fireChanged(aliases);
|
||||
}
|
||||
|
||||
private _fireChanged(value) {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
ha-icon-button {
|
||||
display: block;
|
||||
}
|
||||
mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
#alias_input {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.alias {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-aliases-editor": AliasesEditor;
|
||||
}
|
||||
}
|
@@ -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 localize!: LocalizeFunc;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@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.localize(
|
||||
${this.hass.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.title`
|
||||
)}
|
||||
</span>
|
||||
<span slot="description" data-for="base">
|
||||
${this.localize(
|
||||
${this.hass.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.localize(
|
||||
${this.hass.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.title`
|
||||
)}
|
||||
</span>
|
||||
<span slot="description" data-for=${preference}>
|
||||
${this.localize(
|
||||
${this.hass.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.localize(
|
||||
${this.hass.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.localize(
|
||||
${this.hass.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.title`
|
||||
)}
|
||||
</span>
|
||||
<span slot="description" data-for="diagnostics">
|
||||
${this.localize(
|
||||
${this.hass.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.description`
|
||||
)}
|
||||
</span>
|
||||
|
@@ -10,7 +10,7 @@ import type { HaIconButton } from "./ha-icon-button";
|
||||
export class HaButtonMenu extends LitElement {
|
||||
protected readonly [FOCUS_TARGET];
|
||||
|
||||
@property() public corner: Corner = "BOTTOM_START";
|
||||
@property() public corner: Corner = "TOP_START";
|
||||
|
||||
@property() public menuCorner: MenuCorner = "START";
|
||||
|
||||
|
@@ -35,7 +35,7 @@ interface FilterValue {
|
||||
export class HaRelatedFilterButtonMenu extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public corner: Corner = "BOTTOM_START";
|
||||
@property() public corner: Corner = "TOP_START";
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
|
@@ -310,8 +310,6 @@ export class HaControlSelect extends LitElement {
|
||||
.option .content span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
:host([vertical]) {
|
||||
|
@@ -1,106 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValueMap,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { Agent, listAgents } from "../data/conversation";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-list-item";
|
||||
import "./ha-select";
|
||||
import type { HaSelect } from "./ha-select";
|
||||
|
||||
const NONE = "__NONE_OPTION__";
|
||||
@customElement("ha-conversation-agent-picker")
|
||||
export class HaConversationAgentPicker extends LitElement {
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@state() _agents?: Agent[];
|
||||
|
||||
@state() _defaultAgent: string | null = null;
|
||||
|
||||
protected render() {
|
||||
if (!this._agents) {
|
||||
return nothing;
|
||||
}
|
||||
const value = this.value ?? (this.required ? this._defaultAgent : NONE);
|
||||
return html`
|
||||
<ha-select
|
||||
.label=${this.label ||
|
||||
this.hass!.localize(
|
||||
"ui.components.coversation-agent-picker.conversation_agent"
|
||||
)}
|
||||
.value=${value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
@selected=${this._changed}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
${!this.required
|
||||
? html`<ha-list-item .value=${NONE}>
|
||||
${this.hass!.localize(
|
||||
"ui.components.coversation-agent-picker.none"
|
||||
)}
|
||||
</ha-list-item>`
|
||||
: nothing}
|
||||
${this._agents.map(
|
||||
(agent) =>
|
||||
html`<ha-list-item .value=${agent.id}>${agent.name}</ha-list-item>`
|
||||
)}
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(
|
||||
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
||||
): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
listAgents(this.hass).then((agents) => {
|
||||
this._agents = agents.agents;
|
||||
this._defaultAgent = agents.default_agent;
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _changed(ev): void {
|
||||
const target = ev.target as HaSelect;
|
||||
if (
|
||||
!this.hass ||
|
||||
target.value === "" ||
|
||||
target.value === this.value ||
|
||||
(this.value === undefined && target.value === NONE)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.value = target.value === NONE ? undefined : target.value;
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-conversation-agent-picker": HaConversationAgentPicker;
|
||||
}
|
||||
}
|
@@ -1,83 +1,18 @@
|
||||
import { DrawerBase } from "@material/mwc-drawer/mwc-drawer-base";
|
||||
import { styles } from "@material/mwc-drawer/mwc-drawer.css";
|
||||
import { css, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
const blockingElements = (document as any).$blockingElements;
|
||||
import { css } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@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._setupSwipe();
|
||||
} else if (this._mc) {
|
||||
this._mc.destroy();
|
||||
this._mc = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private async _setupSwipe() {
|
||||
const hammer = await import("../resources/hammer");
|
||||
this._mc = new hammer.Manager(document, {
|
||||
touchAction: "pan-y",
|
||||
});
|
||||
this._mc.add(
|
||||
new hammer.Swipe({
|
||||
direction:
|
||||
this.direction === "rtl"
|
||||
? hammer.DIRECTION_RIGHT
|
||||
: hammer.DIRECTION_LEFT,
|
||||
})
|
||||
);
|
||||
this._mc.on("swipeleft swiperight", () => {
|
||||
fireEvent(this, "hass-toggle-menu", { open: false });
|
||||
});
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
styles,
|
||||
css`
|
||||
.mdc-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
}
|
||||
.mdc-drawer.mdc-drawer--modal.mdc-drawer--open {
|
||||
z-index: 200;
|
||||
}
|
||||
.mdc-drawer-app-content {
|
||||
overflow: unset;
|
||||
flex: none;
|
||||
padding-left: var(--mdc-drawer-width);
|
||||
padding-inline-start: var(--mdc-drawer-width);
|
||||
padding-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
.mdc-drawer--modal.mdc-drawer--open {
|
||||
left: min(0px, var(--drawer-modal-left-offset));
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -71,7 +71,6 @@ export class HaFormExpendable extends LitElement implements HaFormElement {
|
||||
display: block;
|
||||
--expansion-panel-content-padding: 0;
|
||||
border-radius: 6px;
|
||||
--ha-card-border-radius: 6px;
|
||||
}
|
||||
ha-svg-icon,
|
||||
ha-icon {
|
||||
|
@@ -82,6 +82,7 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
||||
<ha-button-menu
|
||||
.disabled=${this.disabled}
|
||||
fixed
|
||||
corner="BOTTOM_START"
|
||||
@opened=${this._handleOpen}
|
||||
@closed=${this._handleClose}
|
||||
multi
|
||||
|
@@ -38,6 +38,7 @@ export class HaIconOverflowMenu extends LitElement {
|
||||
@click=${this._handleIconOverflowMenuOpened}
|
||||
@closed=${this._handleIconOverflowMenuClosed}
|
||||
class="ha-icon-overflow-menu-overflow"
|
||||
corner="BOTTOM_START"
|
||||
absolute
|
||||
>
|
||||
<ha-icon-button
|
||||
|
@@ -30,9 +30,6 @@ export class HaListItem extends ListItemBase {
|
||||
margin-inline-end: 0px !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.mdc-deprecated-list-item__meta {
|
||||
display: var(--mdc-list-item-meta-display);
|
||||
}
|
||||
:host([multiline-secondary]) {
|
||||
height: auto;
|
||||
}
|
||||
@@ -57,9 +54,6 @@ export class HaListItem extends ListItemBase {
|
||||
.mdc-deprecated-list-item__primary-text::before {
|
||||
display: none;
|
||||
}
|
||||
:host([disabled]) {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -70,7 +70,11 @@ class HaQrScanner extends LitElement {
|
||||
? html`<video></video>
|
||||
<div id="canvas-container">
|
||||
${this._cameras && this._cameras.length > 1
|
||||
? html`<ha-button-menu fixed @closed=${stopPropagation}>
|
||||
? html`<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
fixed
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.localize(
|
||||
|
@@ -1,45 +0,0 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { ConversationAgentSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-conversation-agent-picker";
|
||||
|
||||
@customElement("ha-selector-conversation_agent")
|
||||
export class HaConversationAgentSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: ConversationAgentSelector;
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
protected render() {
|
||||
return html`<ha-conversation-agent-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-conversation-agent-picker>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-conversation-agent-picker {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-conversation_agent": HaConversationAgentSelector;
|
||||
}
|
||||
}
|
@@ -135,7 +135,7 @@ export class HaSelectSelector extends LitElement {
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !value.length}
|
||||
.value=${""}
|
||||
.value=${this._filter}
|
||||
.items=${optionItems}
|
||||
.allowCustomValue=${this.selector.select.custom_value ?? false}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@@ -213,7 +213,7 @@ export class HaSelectSelector extends LitElement {
|
||||
private _valueChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail?.value || ev.target.value;
|
||||
if (this.disabled || value === undefined || value === this.value) {
|
||||
if (this.disabled || value === undefined) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
|
@@ -1,50 +0,0 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { STTSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-stt-picker";
|
||||
|
||||
@customElement("ha-selector-stt")
|
||||
export class HaSTTSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: STTSelector;
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@property({ attribute: false }) public context?: {
|
||||
language?: string;
|
||||
};
|
||||
|
||||
protected render() {
|
||||
return html`<ha-stt-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.language=${this.selector.stt?.language || this.context?.language}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-stt-picker>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-stt-picker {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-stt": HaSTTSelector;
|
||||
}
|
||||
}
|
@@ -1,50 +0,0 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { TTSSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-tts-picker";
|
||||
|
||||
@customElement("ha-selector-tts")
|
||||
export class HaTTSSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public selector!: TTSSelector;
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@property({ attribute: false }) public context?: {
|
||||
language?: string;
|
||||
};
|
||||
|
||||
protected render() {
|
||||
return html`<ha-tts-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.language=${this.selector.tts?.language || this.context?.language}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-tts-picker>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-tts-picker {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-tts": HaTTSSelector;
|
||||
}
|
||||
}
|
@@ -6,7 +6,7 @@ import { HomeAssistant } from "../../types";
|
||||
import "../../panels/lovelace/components/hui-action-editor";
|
||||
import { ActionConfig } from "../../data/lovelace";
|
||||
|
||||
@customElement("ha-selector-ui_action")
|
||||
@customElement("ha-selector-ui-action")
|
||||
export class HaSelectorUiAction extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@@ -24,7 +24,7 @@ export class HaSelectorUiAction extends LitElement {
|
||||
.label=${this.label}
|
||||
.hass=${this.hass}
|
||||
.config=${this.value}
|
||||
.actions=${this.selector.ui_action?.actions}
|
||||
.actions=${this.selector["ui-action"]?.actions}
|
||||
.tooltipText=${this.helper}
|
||||
@value-changed=${this._valueChanged}
|
||||
></hui-action-editor>
|
||||
|
@@ -6,7 +6,7 @@ import { UiColorSelector } from "../../data/selector";
|
||||
import "../../panels/lovelace/components/hui-color-picker";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-selector-ui_color")
|
||||
@customElement("ha-selector-ui-color")
|
||||
export class HaSelectorUiColor extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
|
@@ -17,7 +17,6 @@ const LOAD_ELEMENTS = {
|
||||
boolean: () => import("./ha-selector-boolean"),
|
||||
color_rgb: () => import("./ha-selector-color-rgb"),
|
||||
config_entry: () => import("./ha-selector-config-entry"),
|
||||
conversation_agent: () => import("./ha-selector-conversation-agent"),
|
||||
constant: () => import("./ha-selector-constant"),
|
||||
date: () => import("./ha-selector-date"),
|
||||
datetime: () => import("./ha-selector-datetime"),
|
||||
@@ -31,7 +30,6 @@ const LOAD_ELEMENTS = {
|
||||
object: () => import("./ha-selector-object"),
|
||||
select: () => import("./ha-selector-select"),
|
||||
state: () => import("./ha-selector-state"),
|
||||
stt: () => import("./ha-selector-stt"),
|
||||
target: () => import("./ha-selector-target"),
|
||||
template: () => import("./ha-selector-template"),
|
||||
text: () => import("./ha-selector-text"),
|
||||
@@ -39,15 +37,12 @@ const LOAD_ELEMENTS = {
|
||||
icon: () => import("./ha-selector-icon"),
|
||||
media: () => import("./ha-selector-media"),
|
||||
theme: () => import("./ha-selector-theme"),
|
||||
tts: () => import("./ha-selector-tts"),
|
||||
location: () => import("./ha-selector-location"),
|
||||
color_temp: () => import("./ha-selector-color-temp"),
|
||||
ui_action: () => import("./ha-selector-ui-action"),
|
||||
ui_color: () => import("./ha-selector-ui-color"),
|
||||
"ui-action": () => import("./ha-selector-ui-action"),
|
||||
"ui-color": () => import("./ha-selector-ui-color"),
|
||||
};
|
||||
|
||||
const LEGACY_UI_SELECTORS = new Set(["ui-action", "ui-color"]);
|
||||
|
||||
@customElement("ha-selector")
|
||||
export class HaSelector extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@@ -77,11 +72,7 @@ export class HaSelector extends LitElement {
|
||||
}
|
||||
|
||||
private get _type() {
|
||||
const type = Object.keys(this.selector)[0];
|
||||
if (LEGACY_UI_SELECTORS.has(type)) {
|
||||
return type.replace("-", "_");
|
||||
}
|
||||
return type;
|
||||
return Object.keys(this.selector)[0];
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
@@ -97,10 +88,6 @@ export class HaSelector extends LitElement {
|
||||
if ("device" in selector) {
|
||||
return handleLegacyDeviceSelector(selector);
|
||||
}
|
||||
const type = Object.keys(this.selector)[0];
|
||||
if (LEGACY_UI_SELECTORS.has(type)) {
|
||||
return { [type.replace("-", "_")]: selector[type] };
|
||||
}
|
||||
return selector;
|
||||
});
|
||||
|
||||
|
@@ -28,8 +28,8 @@ import {
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, eventOptions, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
|
@@ -1,126 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { listSTTEngines, STTEngine } from "../data/stt";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-list-item";
|
||||
import "./ha-select";
|
||||
import type { HaSelect } from "./ha-select";
|
||||
|
||||
const NONE = "__NONE_OPTION__";
|
||||
|
||||
@customElement("ha-stt-picker")
|
||||
export class HaSTTPicker extends LitElement {
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public language?: string;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@state() _engines: STTEngine[] = [];
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const value =
|
||||
this.value ??
|
||||
(this.required
|
||||
? this._engines.find((engine) => engine.language_supported)
|
||||
: NONE);
|
||||
return html`
|
||||
<ha-select
|
||||
.label=${this.label ||
|
||||
this.hass!.localize("ui.components.stt-picker.stt")}
|
||||
.value=${value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
@selected=${this._changed}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
${!this.required
|
||||
? html`<ha-list-item .value=${NONE}>
|
||||
${this.hass!.localize("ui.components.stt-picker.none")}
|
||||
</ha-list-item>`
|
||||
: nothing}
|
||||
${this._engines.map((engine) => {
|
||||
const stateObj = this.hass!.states[engine.engine_id];
|
||||
return html`<ha-list-item
|
||||
.value=${engine.engine_id}
|
||||
.disabled=${engine.language_supported === false}
|
||||
>
|
||||
${stateObj ? computeStateName(stateObj) : engine.engine_id}
|
||||
</ha-list-item>`;
|
||||
})}
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (!this.hasUpdated) {
|
||||
this._updateEngines();
|
||||
} else if (changedProperties.has("language")) {
|
||||
this._debouncedUpdateEngines();
|
||||
}
|
||||
}
|
||||
|
||||
private _debouncedUpdateEngines = debounce(() => this._updateEngines(), 500);
|
||||
|
||||
private async _updateEngines() {
|
||||
this._engines = (await listSTTEngines(this.hass, this.language)).providers;
|
||||
|
||||
if (
|
||||
this.value &&
|
||||
!this._engines.find((engine) => engine.engine_id === this.value)
|
||||
?.language_supported
|
||||
) {
|
||||
this.value = undefined;
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _changed(ev): void {
|
||||
const target = ev.target as HaSelect;
|
||||
if (
|
||||
!this.hass ||
|
||||
target.value === "" ||
|
||||
target.value === this.value ||
|
||||
(this.value === undefined && target.value === NONE)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.value = target.value === NONE ? undefined : target.value;
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-stt-picker": HaSTTPicker;
|
||||
}
|
||||
}
|
@@ -283,6 +283,7 @@ 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}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
@@ -1,126 +0,0 @@
|
||||
import { debounce } from "chart.js/helpers";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { listTTSEngines, TTSEngine } from "../data/tts";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-list-item";
|
||||
import "./ha-select";
|
||||
import type { HaSelect } from "./ha-select";
|
||||
|
||||
const NONE = "__NONE_OPTION__";
|
||||
|
||||
@customElement("ha-tts-picker")
|
||||
export class HaTTSPicker extends LitElement {
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public language?: string;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@state() _engines: TTSEngine[] = [];
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const value =
|
||||
this.value ??
|
||||
(this.required
|
||||
? this._engines.find((engine) => engine.language_supported)
|
||||
: NONE);
|
||||
return html`
|
||||
<ha-select
|
||||
.label=${this.label ||
|
||||
this.hass!.localize("ui.components.tts-picker.tts")}
|
||||
.value=${value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
@selected=${this._changed}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
${!this.required
|
||||
? html`<ha-list-item .value=${NONE}>
|
||||
${this.hass!.localize("ui.components.tts-picker.none")}
|
||||
</ha-list-item>`
|
||||
: nothing}
|
||||
${this._engines.map((engine) => {
|
||||
const stateObj = this.hass!.states[engine.engine_id];
|
||||
return html`<ha-list-item
|
||||
.value=${engine.engine_id}
|
||||
.disabled=${engine.language_supported === false}
|
||||
>
|
||||
${stateObj ? computeStateName(stateObj) : engine.engine_id}
|
||||
</ha-list-item>`;
|
||||
})}
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (!this.hasUpdated) {
|
||||
this._updateEngines();
|
||||
} else if (changedProperties.has("language")) {
|
||||
this._debouncedUpdateEngines();
|
||||
}
|
||||
}
|
||||
|
||||
private _debouncedUpdateEngines = debounce(() => this._updateEngines(), 500);
|
||||
|
||||
private async _updateEngines() {
|
||||
this._engines = (await listTTSEngines(this.hass, this.language)).providers;
|
||||
|
||||
if (
|
||||
this.value &&
|
||||
!this._engines.find((engine) => engine.engine_id === this.value)
|
||||
?.language_supported
|
||||
) {
|
||||
this.value = undefined;
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _changed(ev): void {
|
||||
const target = ev.target as HaSelect;
|
||||
if (
|
||||
!this.hass ||
|
||||
target.value === "" ||
|
||||
target.value === this.value ||
|
||||
(this.value === undefined && target.value === NONE)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.value = target.value === NONE ? undefined : target.value;
|
||||
fireEvent(this, "value-changed", { value: this.value });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-tts-picker": HaTTSPicker;
|
||||
}
|
||||
}
|
@@ -53,46 +53,53 @@ export const callAlarmAction = (
|
||||
};
|
||||
|
||||
export type AlarmMode =
|
||||
| "armed_home"
|
||||
| "armed_away"
|
||||
| "armed_night"
|
||||
| "armed_vacation"
|
||||
| "armed_custom_bypass"
|
||||
| "away"
|
||||
| "home"
|
||||
| "night"
|
||||
| "vacation"
|
||||
| "custom_bypass"
|
||||
| "disarmed";
|
||||
|
||||
type AlarmConfig = {
|
||||
service: string;
|
||||
feature?: AlarmControlPanelEntityFeature;
|
||||
state: string;
|
||||
path: string;
|
||||
};
|
||||
export const ALARM_MODES: Record<AlarmMode, AlarmConfig> = {
|
||||
armed_home: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_HOME,
|
||||
service: "alarm_arm_home",
|
||||
path: mdiHome,
|
||||
},
|
||||
armed_away: {
|
||||
away: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_AWAY,
|
||||
service: "alarm_arm_away",
|
||||
state: "armed_away",
|
||||
path: mdiLock,
|
||||
},
|
||||
armed_night: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_NIGHT,
|
||||
service: "alarm_arm_night",
|
||||
path: mdiMoonWaningCrescent,
|
||||
home: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_HOME,
|
||||
service: "alarm_arm_home",
|
||||
state: "armed_home",
|
||||
path: mdiHome,
|
||||
},
|
||||
armed_vacation: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_VACATION,
|
||||
service: "alarm_arm_vacation",
|
||||
path: mdiAirplane,
|
||||
},
|
||||
armed_custom_bypass: {
|
||||
custom_bypass: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS,
|
||||
service: "alarm_arm_custom_bypass",
|
||||
state: "armed_custom_bypass",
|
||||
path: mdiShield,
|
||||
},
|
||||
night: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_NIGHT,
|
||||
service: "alarm_arm_night",
|
||||
state: "armed_night",
|
||||
path: mdiMoonWaningCrescent,
|
||||
},
|
||||
vacation: {
|
||||
feature: AlarmControlPanelEntityFeature.ARM_VACATION,
|
||||
service: "alarm_arm_vacation",
|
||||
state: "armed_vacation",
|
||||
path: mdiAirplane,
|
||||
},
|
||||
disarmed: {
|
||||
service: "alarm_disarm",
|
||||
state: "disarmed",
|
||||
path: mdiShieldOff,
|
||||
},
|
||||
};
|
||||
|
@@ -1,300 +0,0 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { ConversationResult } from "./conversation";
|
||||
import type { ResolvedMediaSource } from "./media_source";
|
||||
import type { SpeechMetadata } from "./stt";
|
||||
|
||||
export interface AssistPipeline {
|
||||
id: string;
|
||||
conversation_engine: string;
|
||||
language: string;
|
||||
name: string;
|
||||
stt_engine: string;
|
||||
tts_engine: string;
|
||||
}
|
||||
|
||||
export interface AssistPipelineMutableParams {
|
||||
conversation_engine: string;
|
||||
language: string;
|
||||
name: string;
|
||||
stt_engine: string;
|
||||
tts_engine: string;
|
||||
}
|
||||
|
||||
export interface assistSessionListing {
|
||||
pipeline_session_id: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface PipelineEventBase {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface PipelineRunStartEvent extends PipelineEventBase {
|
||||
type: "run-start";
|
||||
data: {
|
||||
pipeline: string;
|
||||
language: string;
|
||||
runner_data: {
|
||||
stt_binary_handler_id: number | null;
|
||||
timeout: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
interface PipelineRunEndEvent extends PipelineEventBase {
|
||||
type: "run-end";
|
||||
data: Record<string, never>;
|
||||
}
|
||||
|
||||
interface PipelineErrorEvent extends PipelineEventBase {
|
||||
type: "error";
|
||||
data: {
|
||||
code: string;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineSTTStartEvent extends PipelineEventBase {
|
||||
type: "stt-start";
|
||||
data: {
|
||||
engine: string;
|
||||
metadata: SpeechMetadata;
|
||||
};
|
||||
}
|
||||
interface PipelineSTTEndEvent extends PipelineEventBase {
|
||||
type: "stt-end";
|
||||
data: {
|
||||
stt_output: { text: string };
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineIntentStartEvent extends PipelineEventBase {
|
||||
type: "intent-start";
|
||||
data: {
|
||||
engine: string;
|
||||
intent_input: string;
|
||||
};
|
||||
}
|
||||
interface PipelineIntentEndEvent extends PipelineEventBase {
|
||||
type: "intent-end";
|
||||
data: {
|
||||
intent_output: ConversationResult;
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineTTSStartEvent extends PipelineEventBase {
|
||||
type: "tts-start";
|
||||
data: {
|
||||
engine: string;
|
||||
tts_input: string;
|
||||
};
|
||||
}
|
||||
interface PipelineTTSEndEvent extends PipelineEventBase {
|
||||
type: "tts-end";
|
||||
data: {
|
||||
tts_output: ResolvedMediaSource;
|
||||
};
|
||||
}
|
||||
|
||||
export type PipelineRunEvent =
|
||||
| PipelineRunStartEvent
|
||||
| PipelineRunEndEvent
|
||||
| PipelineErrorEvent
|
||||
| PipelineSTTStartEvent
|
||||
| PipelineSTTEndEvent
|
||||
| PipelineIntentStartEvent
|
||||
| PipelineIntentEndEvent
|
||||
| PipelineTTSStartEvent
|
||||
| PipelineTTSEndEvent;
|
||||
|
||||
export type PipelineRunOptions = (
|
||||
| {
|
||||
start_stage: "intent" | "tts";
|
||||
input: { text: string };
|
||||
}
|
||||
| {
|
||||
start_stage: "stt";
|
||||
input: { sample_rate: number };
|
||||
}
|
||||
) & {
|
||||
end_stage: "stt" | "intent" | "tts";
|
||||
pipeline?: string;
|
||||
conversation_id?: string | null;
|
||||
};
|
||||
|
||||
export interface PipelineRun {
|
||||
init_options?: PipelineRunOptions;
|
||||
events: PipelineRunEvent[];
|
||||
stage: "ready" | "stt" | "intent" | "tts" | "done" | "error";
|
||||
run: PipelineRunStartEvent["data"];
|
||||
error?: PipelineErrorEvent["data"];
|
||||
stt?: PipelineSTTStartEvent["data"] &
|
||||
Partial<PipelineSTTEndEvent["data"]> & { done: boolean };
|
||||
intent?: PipelineIntentStartEvent["data"] &
|
||||
Partial<PipelineIntentEndEvent["data"]> & { done: boolean };
|
||||
tts?: PipelineTTSStartEvent["data"] &
|
||||
Partial<PipelineTTSEndEvent["data"]> & { done: boolean };
|
||||
}
|
||||
|
||||
export const processEvent = (
|
||||
run: PipelineRun | undefined,
|
||||
event: PipelineRunEvent,
|
||||
options?: PipelineRunOptions
|
||||
): PipelineRun | undefined => {
|
||||
if (event.type === "run-start") {
|
||||
run = {
|
||||
init_options: options,
|
||||
stage: "ready",
|
||||
run: event.data,
|
||||
events: [event],
|
||||
};
|
||||
return run;
|
||||
}
|
||||
|
||||
if (!run) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Received unexpected event before receiving session", event);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (event.type === "stt-start") {
|
||||
run = {
|
||||
...run,
|
||||
stage: "stt",
|
||||
stt: { ...event.data, done: false },
|
||||
};
|
||||
} else if (event.type === "stt-end") {
|
||||
run = {
|
||||
...run,
|
||||
stt: { ...run.stt!, ...event.data, done: true },
|
||||
};
|
||||
} else if (event.type === "intent-start") {
|
||||
run = {
|
||||
...run,
|
||||
stage: "intent",
|
||||
intent: { ...event.data, done: false },
|
||||
};
|
||||
} else if (event.type === "intent-end") {
|
||||
run = {
|
||||
...run,
|
||||
intent: { ...run.intent!, ...event.data, done: true },
|
||||
};
|
||||
} else if (event.type === "tts-start") {
|
||||
run = {
|
||||
...run,
|
||||
stage: "tts",
|
||||
tts: { ...event.data, done: false },
|
||||
};
|
||||
} else if (event.type === "tts-end") {
|
||||
run = {
|
||||
...run,
|
||||
tts: { ...run.tts!, ...event.data, done: true },
|
||||
};
|
||||
} else if (event.type === "run-end") {
|
||||
run = { ...run, stage: "done" };
|
||||
} else if (event.type === "error") {
|
||||
run = { ...run, stage: "error", error: event.data };
|
||||
} else {
|
||||
run = { ...run };
|
||||
}
|
||||
|
||||
run.events = [...run.events, event];
|
||||
|
||||
return run;
|
||||
};
|
||||
|
||||
export const runAssistPipeline = (
|
||||
hass: HomeAssistant,
|
||||
callback: (event: PipelineRun) => void,
|
||||
options: PipelineRunOptions
|
||||
) => {
|
||||
let run: PipelineRun | undefined;
|
||||
|
||||
const unsubProm = hass.connection.subscribeMessage<PipelineRunEvent>(
|
||||
(updateEvent) => {
|
||||
run = processEvent(run, updateEvent, options);
|
||||
|
||||
if (updateEvent.type === "run-end" || updateEvent.type === "error") {
|
||||
unsubProm.then((unsub) => unsub());
|
||||
}
|
||||
|
||||
if (run) {
|
||||
callback(run);
|
||||
}
|
||||
},
|
||||
{
|
||||
...options,
|
||||
type: "assist_pipeline/run",
|
||||
}
|
||||
);
|
||||
|
||||
return unsubProm;
|
||||
};
|
||||
|
||||
export const listAssistPipelineRuns = (
|
||||
hass: HomeAssistant,
|
||||
pipeline_id: string
|
||||
) =>
|
||||
hass.callWS<{
|
||||
pipeline_sessions: assistSessionListing[];
|
||||
}>({
|
||||
type: "assist_pipeline/pipeline_debug/list",
|
||||
pipeline_id,
|
||||
});
|
||||
|
||||
export const getAssistPipelineRun = (
|
||||
hass: HomeAssistant,
|
||||
pipeline_id: string,
|
||||
pipeline_session_id: string
|
||||
) =>
|
||||
hass.callWS<{
|
||||
runs: {
|
||||
events: PipelineRunEvent[];
|
||||
}[];
|
||||
}>({
|
||||
type: "assist_pipeline/pipeline_debug/get",
|
||||
pipeline_id,
|
||||
pipeline_session_id,
|
||||
});
|
||||
|
||||
export const fetchAssistPipelines = (hass: HomeAssistant) =>
|
||||
hass.callWS<{
|
||||
pipelines: AssistPipeline[];
|
||||
preferred_pipeline: string | null;
|
||||
}>({
|
||||
type: "assist_pipeline/pipeline/list",
|
||||
});
|
||||
|
||||
export const createAssistPipeline = (
|
||||
hass: HomeAssistant,
|
||||
pipeline: AssistPipelineMutableParams
|
||||
) =>
|
||||
hass.callWS<AssistPipeline>({
|
||||
type: "assist_pipeline/pipeline/create",
|
||||
...pipeline,
|
||||
});
|
||||
|
||||
export const updateAssistPipeline = (
|
||||
hass: HomeAssistant,
|
||||
pipeline_id: string,
|
||||
pipeline: Partial<AssistPipelineMutableParams>
|
||||
) =>
|
||||
hass.callWS<AssistPipeline>({
|
||||
type: "assist_pipeline/pipeline/update",
|
||||
pipeline_id,
|
||||
...pipeline,
|
||||
});
|
||||
|
||||
export const setAssistPipelinePreferred = (
|
||||
hass: HomeAssistant,
|
||||
pipeline_id: string
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "assist_pipeline/pipeline/set_preferred",
|
||||
pipeline_id,
|
||||
});
|
||||
|
||||
export const deleteAssistPipeline = (hass: HomeAssistant, pipelineId: string) =>
|
||||
hass.callWS<void>({
|
||||
type: "assist_pipeline/pipeline/delete",
|
||||
pipeline_id: pipelineId,
|
||||
});
|
@@ -123,8 +123,6 @@ export interface TimePatternTrigger extends BaseTrigger {
|
||||
export interface WebhookTrigger extends BaseTrigger {
|
||||
platform: "webhook";
|
||||
webhook_id: string;
|
||||
allowed_methods?: string[];
|
||||
local_only?: boolean;
|
||||
}
|
||||
|
||||
export interface ZoneTrigger extends BaseTrigger {
|
||||
|
@@ -9,6 +9,15 @@ interface CloudStatusNotLoggedIn {
|
||||
http_use_ssl: boolean;
|
||||
}
|
||||
|
||||
export interface GoogleEntityConfig {
|
||||
should_expose?: boolean | null;
|
||||
disable_2fa?: boolean;
|
||||
}
|
||||
|
||||
export interface AlexaEntityConfig {
|
||||
should_expose?: boolean | null;
|
||||
}
|
||||
|
||||
export interface CertificateInformation {
|
||||
common_name: string;
|
||||
expire_date: string;
|
||||
@@ -21,6 +30,14 @@ export interface CloudPreferences {
|
||||
remote_enabled: boolean;
|
||||
google_secure_devices_pin: string | undefined;
|
||||
cloudhooks: { [webhookId: string]: CloudWebhook };
|
||||
google_default_expose: string[] | null;
|
||||
google_entity_configs: {
|
||||
[entityId: string]: GoogleEntityConfig;
|
||||
};
|
||||
alexa_default_expose: string[] | null;
|
||||
alexa_entity_configs: {
|
||||
[entityId: string]: AlexaEntityConfig;
|
||||
};
|
||||
alexa_report_state: boolean;
|
||||
google_report_state: boolean;
|
||||
tts_default_voice: [string, string];
|
||||
@@ -40,13 +57,6 @@ export interface CloudStatusLoggedIn {
|
||||
remote_domain: string | undefined;
|
||||
remote_connected: boolean;
|
||||
remote_certificate: undefined | CertificateInformation;
|
||||
remote_certificate_status:
|
||||
| null
|
||||
| "error"
|
||||
| "generating"
|
||||
| "loaded"
|
||||
| "loading"
|
||||
| "ready";
|
||||
http_use_ssl: boolean;
|
||||
active_subscription: boolean;
|
||||
}
|
||||
@@ -140,8 +150,10 @@ export const updateCloudPref = (
|
||||
prefs: {
|
||||
google_enabled?: CloudPreferences["google_enabled"];
|
||||
alexa_enabled?: CloudPreferences["alexa_enabled"];
|
||||
alexa_default_expose?: CloudPreferences["alexa_default_expose"];
|
||||
alexa_report_state?: CloudPreferences["alexa_report_state"];
|
||||
google_report_state?: CloudPreferences["google_report_state"];
|
||||
google_default_expose?: CloudPreferences["google_default_expose"];
|
||||
google_secure_devices_pin?: CloudPreferences["google_secure_devices_pin"];
|
||||
tts_default_voice?: CloudPreferences["tts_default_voice"];
|
||||
}
|
||||
@@ -153,14 +165,25 @@ export const updateCloudPref = (
|
||||
|
||||
export const updateCloudGoogleEntityConfig = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string,
|
||||
disable_2fa: boolean
|
||||
entityId: string,
|
||||
values: GoogleEntityConfig
|
||||
) =>
|
||||
hass.callWS({
|
||||
hass.callWS<GoogleEntityConfig>({
|
||||
type: "cloud/google_assistant/entities/update",
|
||||
entity_id,
|
||||
disable_2fa,
|
||||
entity_id: entityId,
|
||||
...values,
|
||||
});
|
||||
|
||||
export const cloudSyncGoogleAssistant = (hass: HomeAssistant) =>
|
||||
hass.callApi("POST", "cloud/google_actions/sync");
|
||||
|
||||
export const updateCloudAlexaEntityConfig = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
values: AlexaEntityConfig
|
||||
) =>
|
||||
hass.callWS<AlexaEntityConfig>({
|
||||
type: "cloud/alexa/entities/update",
|
||||
entity_id: entityId,
|
||||
...values,
|
||||
});
|
||||
|
@@ -56,11 +56,6 @@ export interface AgentInfo {
|
||||
attribution?: { name: string; url: string };
|
||||
}
|
||||
|
||||
export interface Agent {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const processConversationInput = (
|
||||
hass: HomeAssistant,
|
||||
text: string,
|
||||
@@ -75,20 +70,9 @@ export const processConversationInput = (
|
||||
language,
|
||||
});
|
||||
|
||||
export const listAgents = (
|
||||
hass: HomeAssistant
|
||||
): Promise<{ agents: Agent[]; default_agent: string | null }> =>
|
||||
hass.callWS({
|
||||
type: "conversation/agent/list",
|
||||
});
|
||||
|
||||
export const getAgentInfo = (
|
||||
hass: HomeAssistant,
|
||||
agent_id?: string
|
||||
): Promise<AgentInfo> =>
|
||||
export const getAgentInfo = (hass: HomeAssistant): Promise<AgentInfo> =>
|
||||
hass.callWS({
|
||||
type: "conversation/agent/info",
|
||||
agent_id,
|
||||
});
|
||||
|
||||
export const prepareConversation = (
|
||||
|
@@ -2,7 +2,6 @@ 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";
|
||||
@@ -115,12 +114,10 @@ export function computeCoverPositionStateDisplay(
|
||||
locale: FrontendLocaleData,
|
||||
position?: number
|
||||
) {
|
||||
const statePosition = stateActive(stateObj)
|
||||
? stateObj.attributes.current_position ??
|
||||
stateObj.attributes.current_tilt_position
|
||||
: undefined;
|
||||
|
||||
const currentPosition = position ?? statePosition;
|
||||
const currentPosition =
|
||||
position ??
|
||||
stateObj.attributes.current_position ??
|
||||
stateObj.attributes.current_tilt_position;
|
||||
|
||||
return currentPosition && currentPosition !== 100
|
||||
? `${Math.round(currentPosition)}${blankBeforePercent(locale)}%`
|
||||
|
@@ -9,7 +9,6 @@ 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";
|
||||
|
||||
@@ -70,7 +69,7 @@ export function fanSpeedToPercentage(
|
||||
if (speedValue === -1) {
|
||||
return 0;
|
||||
}
|
||||
return Math.floor(speedValue * step);
|
||||
return Math.round(speedValue * step);
|
||||
}
|
||||
|
||||
export function computeFanSpeedCount(stateObj: FanEntity): number {
|
||||
@@ -100,12 +99,9 @@ export function computeFanSpeedStateDisplay(
|
||||
locale: FrontendLocaleData,
|
||||
speed?: number
|
||||
) {
|
||||
const percentage = stateActive(stateObj)
|
||||
? stateObj.attributes.percentage
|
||||
: undefined;
|
||||
const currentSpeed = speed ?? percentage;
|
||||
const currentSpeed = speed ?? stateObj.attributes.percentage;
|
||||
|
||||
return currentSpeed
|
||||
? `${Math.floor(currentSpeed)}${blankBeforePercent(locale)}%`
|
||||
? `${Math.round(currentSpeed)}${blankBeforePercent(locale)}%`
|
||||
: "";
|
||||
}
|
||||
|
@@ -9,14 +9,5 @@ export interface GoogleEntity {
|
||||
export const fetchCloudGoogleEntities = (hass: HomeAssistant) =>
|
||||
hass.callWS<GoogleEntity[]>({ type: "cloud/google_assistant/entities" });
|
||||
|
||||
export const fetchCloudGoogleEntity = (
|
||||
hass: HomeAssistant,
|
||||
entity_id: string
|
||||
) =>
|
||||
hass.callWS<GoogleEntity>({
|
||||
type: "cloud/google_assistant/entities/get",
|
||||
entity_id,
|
||||
});
|
||||
|
||||
export const syncCloudGoogleEntities = (hass: HomeAssistant) =>
|
||||
hass.callApi("POST", "cloud/google_actions/sync");
|
||||
|
@@ -381,23 +381,3 @@ export const fetchAddonInfo = (
|
||||
? `/store/addons/${addonSlug}` // Use /store/addons when add-on is not installed
|
||||
: `/addons/${addonSlug}/info` // Use /addons when add-on is installed
|
||||
);
|
||||
|
||||
export const rebuildLocalAddon = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
): Promise<void> => {
|
||||
if (atLeastVersion(hass.config.version, 2021, 2, 4)) {
|
||||
return hass.callWS<void>({
|
||||
type: "supervisor/api",
|
||||
endpoint: `/addons/${slug}/rebuild`,
|
||||
method: "post",
|
||||
timeout: null,
|
||||
});
|
||||
}
|
||||
return (
|
||||
await hass.callApi<HassioResponse<void>>(
|
||||
"POST",
|
||||
`hassio/addons/${slug}rebuild`
|
||||
)
|
||||
).data;
|
||||
};
|
||||
|
@@ -28,19 +28,8 @@ 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 (
|
||||
|
@@ -291,7 +291,7 @@ const processTimelineEntity = (
|
||||
state_localize: computeStateDisplayFromEntityAttributes(
|
||||
localize,
|
||||
language,
|
||||
entities[entityId],
|
||||
entities,
|
||||
entityId,
|
||||
{
|
||||
...(state.a || first.a),
|
||||
|
@@ -14,7 +14,6 @@ export type Selector =
|
||||
| BooleanSelector
|
||||
| ColorRGBSelector
|
||||
| ColorTempSelector
|
||||
| ConversationAgentSelector
|
||||
| ConfigEntrySelector
|
||||
| ConstantSelector
|
||||
| DateSelector
|
||||
@@ -35,12 +34,10 @@ export type Selector =
|
||||
| StateSelector
|
||||
| StatisticSelector
|
||||
| StringSelector
|
||||
| STTSelector
|
||||
| TargetSelector
|
||||
| TemplateSelector
|
||||
| ThemeSelector
|
||||
| TimeSelector
|
||||
| TTSSelector
|
||||
| UiActionSelector
|
||||
| UiColorSelector;
|
||||
|
||||
@@ -88,11 +85,6 @@ export interface ColorTempSelector {
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface ConversationAgentSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
conversation_agent: {} | null;
|
||||
}
|
||||
|
||||
export interface ConfigEntrySelector {
|
||||
config_entry: {
|
||||
integration?: string;
|
||||
@@ -302,10 +294,6 @@ export interface StringSelector {
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface STTSelector {
|
||||
stt: { language?: string } | null;
|
||||
}
|
||||
|
||||
export interface TargetSelector {
|
||||
target: {
|
||||
entity?: EntitySelectorFilter | readonly EntitySelectorFilter[];
|
||||
@@ -327,19 +315,15 @@ export interface TimeSelector {
|
||||
time: {} | null;
|
||||
}
|
||||
|
||||
export interface TTSSelector {
|
||||
tts: { language?: string } | null;
|
||||
}
|
||||
|
||||
export interface UiActionSelector {
|
||||
ui_action: {
|
||||
"ui-action": {
|
||||
actions?: UiAction[];
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface UiColorSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
ui_color: {} | null;
|
||||
"ui-color": {} | null;
|
||||
}
|
||||
|
||||
export const filterSelectorDevices = (
|
||||
@@ -405,8 +389,8 @@ export const filterSelectorEntities = (
|
||||
|
||||
if (filterSupportedFeature) {
|
||||
if (
|
||||
!ensureArray(filterSupportedFeature).some((feature) =>
|
||||
supportsFeature(entity, feature)
|
||||
ensureArray(filterSupportedFeature).some(
|
||||
(feature) => !supportsFeature(entity, feature)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
|
@@ -1,5 +1,3 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface SpeechMetadata {
|
||||
language: string;
|
||||
format: "wav" | "ogg";
|
||||
@@ -17,17 +15,3 @@ export interface SpeechMetadata {
|
||||
| 48000;
|
||||
channel: 1 | 2;
|
||||
}
|
||||
|
||||
export interface STTEngine {
|
||||
engine_id: string;
|
||||
language_supported?: boolean;
|
||||
}
|
||||
|
||||
export const listSTTEngines = (
|
||||
hass: HomeAssistant,
|
||||
language?: string
|
||||
): Promise<{ providers: STTEngine[] }> =>
|
||||
hass.callWS({
|
||||
type: "stt/engine/list",
|
||||
language,
|
||||
});
|
||||
|
@@ -18,7 +18,6 @@ export interface ThreadDataSet {
|
||||
network_name: string;
|
||||
extended_pan_id?: string;
|
||||
pan_id?: string;
|
||||
channel?: number;
|
||||
}
|
||||
|
||||
export interface ThreadRouterDiscoveryEvent {
|
||||
|
@@ -1,15 +1,5 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface TTSEngine {
|
||||
engine_id: string;
|
||||
language_supported?: boolean;
|
||||
}
|
||||
|
||||
export interface TTSVoice {
|
||||
voice_id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const convertTextToSpeech = (
|
||||
hass: HomeAssistant,
|
||||
data: {
|
||||
@@ -28,23 +18,3 @@ export const isTTSMediaSource = (mediaContentId: string) =>
|
||||
|
||||
export const getProviderFromTTSMediaSource = (mediaContentId: string) =>
|
||||
mediaContentId.substring(TTS_MEDIA_SOURCE_PREFIX.length);
|
||||
|
||||
export const listTTSEngines = (
|
||||
hass: HomeAssistant,
|
||||
language?: string
|
||||
): Promise<{ providers: TTSEngine[] }> =>
|
||||
hass.callWS({
|
||||
type: "tts/engine/list",
|
||||
language,
|
||||
});
|
||||
|
||||
export const listTTSVoices = (
|
||||
hass: HomeAssistant,
|
||||
engine_id: string,
|
||||
language: string
|
||||
): Promise<{ voices: TTSVoice[] }> =>
|
||||
hass.callWS({
|
||||
type: "tts/engine/voices",
|
||||
engine_id,
|
||||
language,
|
||||
});
|
||||
|
@@ -1,45 +0,0 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const voiceAssistants = {
|
||||
conversation: { domain: "assist_pipeline", name: "Assist" },
|
||||
"cloud.alexa": {
|
||||
domain: "alexa",
|
||||
name: "Amazon Alexa",
|
||||
},
|
||||
"cloud.google_assistant": {
|
||||
domain: "google_assistant",
|
||||
name: "Google Assistant",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const voiceAssistantKeys = Object.keys(voiceAssistants);
|
||||
|
||||
export const setExposeNewEntities = (
|
||||
hass: HomeAssistant,
|
||||
assistant: string,
|
||||
expose_new: boolean
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "homeassistant/expose_new_entities/set",
|
||||
assistant,
|
||||
expose_new,
|
||||
});
|
||||
|
||||
export const getExposeNewEntities = (hass: HomeAssistant, assistant: string) =>
|
||||
hass.callWS<{ expose_new: boolean }>({
|
||||
type: "homeassistant/expose_new_entities/get",
|
||||
assistant,
|
||||
});
|
||||
|
||||
export const exposeEntities = (
|
||||
hass: HomeAssistant,
|
||||
assistants: string[],
|
||||
entity_ids: string[],
|
||||
should_expose: boolean
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "homeassistant/expose_entity",
|
||||
assistants,
|
||||
entity_ids,
|
||||
should_expose,
|
||||
});
|
174
src/data/voice_assistant.ts
Normal file
174
src/data/voice_assistant.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { ConversationResult } from "./conversation";
|
||||
import type { ResolvedMediaSource } from "./media_source";
|
||||
import type { SpeechMetadata } from "./stt";
|
||||
|
||||
interface PipelineEventBase {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface PipelineRunStartEvent extends PipelineEventBase {
|
||||
type: "run-start";
|
||||
data: {
|
||||
pipeline: string;
|
||||
language: string;
|
||||
runner_data: {
|
||||
stt_binary_handler_id: number | null;
|
||||
timeout: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
interface PipelineRunEndEvent extends PipelineEventBase {
|
||||
type: "run-end";
|
||||
data: Record<string, never>;
|
||||
}
|
||||
|
||||
interface PipelineErrorEvent extends PipelineEventBase {
|
||||
type: "error";
|
||||
data: {
|
||||
code: string;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineSTTStartEvent extends PipelineEventBase {
|
||||
type: "stt-start";
|
||||
data: {
|
||||
engine: string;
|
||||
metadata: SpeechMetadata;
|
||||
};
|
||||
}
|
||||
interface PipelineSTTEndEvent extends PipelineEventBase {
|
||||
type: "stt-end";
|
||||
data: {
|
||||
stt_output: { text: string };
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineIntentStartEvent extends PipelineEventBase {
|
||||
type: "intent-start";
|
||||
data: {
|
||||
engine: string;
|
||||
intent_input: string;
|
||||
};
|
||||
}
|
||||
interface PipelineIntentEndEvent extends PipelineEventBase {
|
||||
type: "intent-end";
|
||||
data: {
|
||||
intent_output: ConversationResult;
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineTTSStartEvent extends PipelineEventBase {
|
||||
type: "tts-start";
|
||||
data: {
|
||||
engine: string;
|
||||
tts_input: string;
|
||||
};
|
||||
}
|
||||
interface PipelineTTSEndEvent extends PipelineEventBase {
|
||||
type: "tts-end";
|
||||
data: {
|
||||
tts_output: ResolvedMediaSource;
|
||||
};
|
||||
}
|
||||
|
||||
type PipelineRunEvent =
|
||||
| PipelineRunStartEvent
|
||||
| PipelineRunEndEvent
|
||||
| PipelineErrorEvent
|
||||
| PipelineSTTStartEvent
|
||||
| PipelineSTTEndEvent
|
||||
| PipelineIntentStartEvent
|
||||
| PipelineIntentEndEvent
|
||||
| PipelineTTSStartEvent
|
||||
| PipelineTTSEndEvent;
|
||||
|
||||
interface PipelineRunOptions {
|
||||
start_stage: "stt" | "intent" | "tts";
|
||||
end_stage: "stt" | "intent" | "tts";
|
||||
language?: string;
|
||||
pipeline?: string;
|
||||
input?: { text: string };
|
||||
conversation_id?: string | null;
|
||||
}
|
||||
|
||||
export interface PipelineRun {
|
||||
init_options: PipelineRunOptions;
|
||||
events: PipelineRunEvent[];
|
||||
stage: "ready" | "stt" | "intent" | "tts" | "done" | "error";
|
||||
run: PipelineRunStartEvent["data"];
|
||||
error?: PipelineErrorEvent["data"];
|
||||
stt?: PipelineSTTStartEvent["data"] & Partial<PipelineSTTEndEvent["data"]>;
|
||||
intent?: PipelineIntentStartEvent["data"] &
|
||||
Partial<PipelineIntentEndEvent["data"]>;
|
||||
tts?: PipelineTTSStartEvent["data"] & Partial<PipelineTTSEndEvent["data"]>;
|
||||
}
|
||||
|
||||
export const runPipelineFromText = (
|
||||
hass: HomeAssistant,
|
||||
callback: (event: PipelineRun) => void,
|
||||
options: PipelineRunOptions
|
||||
) => {
|
||||
let run: PipelineRun | undefined;
|
||||
|
||||
const unsubProm = hass.connection.subscribeMessage<PipelineRunEvent>(
|
||||
(updateEvent) => {
|
||||
if (updateEvent.type === "run-start") {
|
||||
run = {
|
||||
init_options: options,
|
||||
stage: "ready",
|
||||
run: updateEvent.data,
|
||||
error: undefined,
|
||||
stt: undefined,
|
||||
intent: undefined,
|
||||
tts: undefined,
|
||||
events: [updateEvent],
|
||||
};
|
||||
callback(run);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!run) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
"Received unexpected event before receiving session",
|
||||
updateEvent
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (updateEvent.type === "stt-start") {
|
||||
run = { ...run, stage: "stt", stt: updateEvent.data };
|
||||
} else if (updateEvent.type === "stt-end") {
|
||||
run = { ...run, stt: { ...run.stt!, ...updateEvent.data } };
|
||||
} else if (updateEvent.type === "intent-start") {
|
||||
run = { ...run, stage: "intent", intent: updateEvent.data };
|
||||
} else if (updateEvent.type === "intent-end") {
|
||||
run = { ...run, intent: { ...run.intent!, ...updateEvent.data } };
|
||||
} else if (updateEvent.type === "tts-start") {
|
||||
run = { ...run, stage: "tts", tts: updateEvent.data };
|
||||
} else if (updateEvent.type === "tts-end") {
|
||||
run = { ...run, tts: { ...run.tts!, ...updateEvent.data } };
|
||||
} else if (updateEvent.type === "run-end") {
|
||||
run = { ...run, stage: "done" };
|
||||
unsubProm.then((unsub) => unsub());
|
||||
} else if (updateEvent.type === "error") {
|
||||
run = { ...run, stage: "error", error: updateEvent.data };
|
||||
unsubProm.then((unsub) => unsub());
|
||||
} else {
|
||||
run = { ...run };
|
||||
}
|
||||
|
||||
run.events = [...run.events, updateEvent];
|
||||
|
||||
callback(run);
|
||||
},
|
||||
{
|
||||
...options,
|
||||
type: "voice_assistant/run",
|
||||
}
|
||||
);
|
||||
|
||||
return unsubProm;
|
||||
};
|
@@ -1,13 +1,16 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { mdiDeleteOutline, mdiPlus } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-area-picker";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../components/ha-textfield";
|
||||
import { haStyle, haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { AliasesDialogParams } from "./show-dialog-aliases";
|
||||
import "../../components/ha-aliases-editor";
|
||||
|
||||
@customElement("dialog-aliases")
|
||||
class DialogAliases extends LitElement {
|
||||
@@ -54,11 +57,43 @@ class DialogAliases extends LitElement {
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<ha-aliases-editor
|
||||
.hass=${this.hass}
|
||||
.aliases=${this._aliases}
|
||||
@value-changed=${this._aliasesChanged}
|
||||
></ha-aliases-editor>
|
||||
<div class="form">
|
||||
${this._aliases.map(
|
||||
(alias, index) => html`
|
||||
<div class="layout horizontal center-center row">
|
||||
<ha-textfield
|
||||
dialogInitialFocus=${index}
|
||||
.index=${index}
|
||||
class="flex-auto"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.aliases.input_label",
|
||||
{ number: index + 1 }
|
||||
)}
|
||||
.value=${alias}
|
||||
?data-last=${index === this._aliases.length - 1}
|
||||
@input=${this._editAlias}
|
||||
@keydown=${this._keyDownAlias}
|
||||
></ha-textfield>
|
||||
<ha-icon-button
|
||||
.index=${index}
|
||||
slot="navigationIcon"
|
||||
label=${this.hass!.localize(
|
||||
"ui.dialogs.aliases.remove_alias",
|
||||
{ number: index + 1 }
|
||||
)}
|
||||
@click=${this._removeAlias}
|
||||
.path=${mdiDeleteOutline}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<div class="layout horizontal center-center">
|
||||
<mwc-button @click=${this._addAlias}>
|
||||
${this.hass!.localize("ui.dialogs.aliases.add_alias")}
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@@ -78,8 +113,32 @@ class DialogAliases extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _aliasesChanged(ev: CustomEvent): void {
|
||||
this._aliases = ev.detail.value;
|
||||
private async _addAlias() {
|
||||
this._aliases = [...this._aliases, ""];
|
||||
await this.updateComplete;
|
||||
const field = this.shadowRoot?.querySelector(`ha-textfield[data-last]`) as
|
||||
| HaTextField
|
||||
| undefined;
|
||||
field?.focus();
|
||||
}
|
||||
|
||||
private async _editAlias(ev: Event) {
|
||||
const index = (ev.target as any).index;
|
||||
this._aliases[index] = (ev.target as any).value;
|
||||
}
|
||||
|
||||
private async _keyDownAlias(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter") {
|
||||
ev.stopPropagation();
|
||||
this._addAlias();
|
||||
}
|
||||
}
|
||||
|
||||
private async _removeAlias(ev: Event) {
|
||||
const index = (ev.target as any).index;
|
||||
const aliases = [...this._aliases];
|
||||
aliases.splice(index, 1);
|
||||
this._aliases = aliases;
|
||||
}
|
||||
|
||||
private async _updateAliases(): Promise<void> {
|
||||
|
117
src/dialogs/domain-toggler/dialog-domain-toggler.ts
Normal file
117
src/dialogs/domain-toggler/dialog-domain-toggler.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import "../../components/ha-formfield";
|
||||
import "../../components/ha-switch";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { HassDialog } from "../make-dialog-manager";
|
||||
import { HaDomainTogglerDialogParams } from "./show-dialog-domain-toggler";
|
||||
|
||||
@customElement("dialog-domain-toggler")
|
||||
class DomainTogglerDialog
|
||||
extends LitElement
|
||||
implements HassDialog<HaDomainTogglerDialogParams>
|
||||
{
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: HaDomainTogglerDialogParams;
|
||||
|
||||
public showDialog(params: HaDomainTogglerDialogParams): void {
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const domains = this._params.domains
|
||||
.map((domain) => [domainToName(this.hass.localize, domain), domain])
|
||||
.sort();
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._params.title ||
|
||||
this.hass.localize("ui.dialogs.domain_toggler.title")
|
||||
)}
|
||||
>
|
||||
${this._params.description
|
||||
? html`<div class="description">${this._params.description}</div>`
|
||||
: ""}
|
||||
<div class="domains">
|
||||
${domains.map(
|
||||
(domain) =>
|
||||
html`
|
||||
<ha-formfield .label=${domain[0]}>
|
||||
<ha-switch
|
||||
.domain=${domain[1]}
|
||||
.checked=${!this._params!.exposedDomains ||
|
||||
this._params!.exposedDomains.includes(domain[1])}
|
||||
@change=${this._handleSwitch}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<mwc-button .domain=${domain[1]} @click=${this._handleReset}>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.domain_toggler.reset_entities"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSwitch(ev) {
|
||||
this._params!.toggleDomain(ev.currentTarget.domain, ev.target.checked);
|
||||
ev.currentTarget.blur();
|
||||
}
|
||||
|
||||
private _handleReset(ev) {
|
||||
this._params!.resetDomain(ev.currentTarget.domain);
|
||||
ev.currentTarget.blur();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 500px;
|
||||
}
|
||||
.description {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.domains {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-row-gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-domain-toggler": DomainTogglerDialog;
|
||||
}
|
||||
}
|
23
src/dialogs/domain-toggler/show-dialog-domain-toggler.ts
Normal file
23
src/dialogs/domain-toggler/show-dialog-domain-toggler.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export interface HaDomainTogglerDialogParams {
|
||||
title?: string;
|
||||
description?: string;
|
||||
domains: string[];
|
||||
exposedDomains: string[] | null;
|
||||
toggleDomain: (domain: string, turnOn: boolean) => void;
|
||||
resetDomain: (domain: string) => void;
|
||||
}
|
||||
|
||||
export const loadDomainTogglerDialog = () => import("./dialog-domain-toggler");
|
||||
|
||||
export const showDomainTogglerDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: HaDomainTogglerDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-domain-toggler",
|
||||
dialogImport: loadDomainTogglerDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -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 false;
|
||||
return;
|
||||
}
|
||||
LOADED[dialogTag] = {
|
||||
element: dialogImport().then(() => {
|
||||
@@ -128,8 +128,6 @@ 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) => {
|
||||
|
@@ -40,7 +40,9 @@ export class HaMoreInfoAlarmControlPanelModes extends LitElement {
|
||||
}
|
||||
|
||||
private _getCurrentMode(stateObj: AlarmControlPanelEntity) {
|
||||
return this._modes(stateObj).find((mode) => mode === stateObj.state);
|
||||
return this._modes(stateObj).find(
|
||||
(mode) => ALARM_MODES[mode].state === stateObj.state
|
||||
);
|
||||
}
|
||||
|
||||
private async _setMode(mode: AlarmMode) {
|
||||
@@ -84,7 +86,7 @@ export class HaMoreInfoAlarmControlPanelModes extends LitElement {
|
||||
private async _valueChanged(ev: CustomEvent) {
|
||||
const mode = (ev.detail as any).value as AlarmMode;
|
||||
|
||||
if (mode === this.stateObj!.state) return;
|
||||
if (ALARM_MODES[mode].state === this.stateObj!.state) return;
|
||||
|
||||
const oldMode = this._getCurrentMode(this.stateObj!);
|
||||
this._currentMode = mode;
|
||||
|
@@ -3,7 +3,6 @@ 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";
|
||||
@@ -27,25 +26,20 @@ export class HaMoreInfoFanSpeed extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public stateObj!: FanEntity;
|
||||
|
||||
@state() sliderValue?: number;
|
||||
|
||||
@state() speedValue?: FanSpeed;
|
||||
@state() value?: number;
|
||||
|
||||
protected updated(changedProp: Map<string | number | symbol, unknown>): void {
|
||||
if (changedProp.has("stateObj")) {
|
||||
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);
|
||||
this.value =
|
||||
this.stateObj.attributes.percentage != null
|
||||
? Math.max(Math.round(this.stateObj.attributes.percentage), 1)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
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", {
|
||||
@@ -58,8 +52,6 @@ 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,
|
||||
@@ -96,11 +88,16 @@ 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=${this.speedValue}
|
||||
.value=${speed}
|
||||
@value-changed=${this._speedValueChanged}
|
||||
.ariaLabel=${computeAttributeNameDisplay(
|
||||
this.hass.localize,
|
||||
@@ -122,7 +119,7 @@ export class HaMoreInfoFanSpeed extends LitElement {
|
||||
vertical
|
||||
min="0"
|
||||
max="100"
|
||||
.value=${this.sliderValue}
|
||||
.value=${this.value}
|
||||
.step=${this.stateObj.attributes.percentage_step ?? 1}
|
||||
@value-changed=${this._valueChanged}
|
||||
.ariaLabel=${computeAttributeNameDisplay(
|
||||
|
@@ -1,49 +0,0 @@
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { ExtEntityRegistryEntry } from "../../../../data/entity_registry";
|
||||
import "../../../../panels/config/voice-assistants/entity-voice-settings";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
|
||||
@customElement("ha-more-info-view-voice-assistants")
|
||||
class MoreInfoViewVoiceAssistants extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entry!: ExtEntityRegistryEntry;
|
||||
|
||||
@property() public params?;
|
||||
|
||||
protected render() {
|
||||
if (!this.params) {
|
||||
return nothing;
|
||||
}
|
||||
return html`<entity-voice-settings
|
||||
.hass=${this.hass}
|
||||
.entry=${this.entry}
|
||||
></entity-voice-settings>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
flex: 1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-more-info-view-voice-assistants": MoreInfoViewVoiceAssistants;
|
||||
}
|
||||
}
|
@@ -1,16 +0,0 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
|
||||
export const loadVoiceAssistantsView = () =>
|
||||
import("./ha-more-info-view-voice-assistants");
|
||||
|
||||
export const showVoiceAssistantsView = (
|
||||
element: HTMLElement,
|
||||
title: string
|
||||
): void => {
|
||||
fireEvent(element, "show-child-view", {
|
||||
viewTag: "ha-more-info-view-voice-assistants",
|
||||
viewImport: loadVoiceAssistantsView,
|
||||
viewTitle: title,
|
||||
viewParams: {},
|
||||
});
|
||||
};
|
@@ -23,7 +23,6 @@ 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";
|
||||
@@ -120,7 +119,7 @@ class MoreInfoFan extends LitElement {
|
||||
const liveValue = this._liveSpeed;
|
||||
|
||||
const forcedState =
|
||||
liveValue != null ? (liveValue ? "on" : "off") : undefined;
|
||||
this._liveSpeed != null ? (this._liveSpeed ? "on" : "off") : undefined;
|
||||
|
||||
const stateDisplay = computeStateDisplay(
|
||||
this.hass.localize,
|
||||
@@ -136,7 +135,7 @@ class MoreInfoFan extends LitElement {
|
||||
liveValue
|
||||
);
|
||||
|
||||
if (positionStateDisplay && (stateActive(this.stateObj!) || liveValue)) {
|
||||
if (positionStateDisplay) {
|
||||
return positionStateDisplay;
|
||||
}
|
||||
return stateDisplay;
|
||||
@@ -274,6 +273,7 @@ class MoreInfoFan extends LitElement {
|
||||
supportsPresetMode && this.stateObj.attributes.preset_modes
|
||||
? html`
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handlePresetMode}
|
||||
@closed=${stopPropagation}
|
||||
fixed
|
||||
|
@@ -173,6 +173,7 @@ class MoreInfoLight extends LitElement {
|
||||
${supportsEffects && this.stateObj.attributes.effect_list
|
||||
? html`
|
||||
<ha-button-menu
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleEffectButton}
|
||||
@closed=${stopPropagation}
|
||||
fixed
|
||||
|
@@ -12,8 +12,6 @@ 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";
|
||||
@@ -115,19 +113,11 @@ class MoreInfoVacuum extends LitElement {
|
||||
</span>
|
||||
<span>
|
||||
<strong>
|
||||
${computeAttributeValueDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.entities,
|
||||
"status"
|
||||
${stateObj.attributes.status ||
|
||||
this.hass.localize(
|
||||
`component.vacuum.entity_component._.state.${stateObj.state}`
|
||||
) ||
|
||||
computeStateDisplay(
|
||||
this.hass.localize,
|
||||
stateObj,
|
||||
this.hass.locale,
|
||||
this.hass.entities
|
||||
)}
|
||||
stateObj.state}
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
|
@@ -181,10 +181,10 @@ export class MoreInfoDialog extends LitElement {
|
||||
this.setView("settings");
|
||||
}
|
||||
|
||||
private _showChildView(ev: CustomEvent): void {
|
||||
private async _showChildView(ev: CustomEvent): Promise<void> {
|
||||
const view = ev.detail as ChildView;
|
||||
if (view.viewImport) {
|
||||
view.viewImport();
|
||||
await view.viewImport();
|
||||
}
|
||||
this._childView = view;
|
||||
}
|
||||
@@ -369,14 +369,12 @@ export class MoreInfoDialog extends LitElement {
|
||||
tabindex="-1"
|
||||
dialogInitialFocus
|
||||
@show-child-view=${this._showChildView}
|
||||
@entity-entry-updated=${this._entryUpdated}
|
||||
>
|
||||
${this._childView
|
||||
? html`
|
||||
<div class="child-view">
|
||||
${dynamicElement(this._childView.viewTag, {
|
||||
hass: this.hass,
|
||||
entry: this._entry,
|
||||
params: this._childView.viewParams,
|
||||
})}
|
||||
</div>
|
||||
@@ -403,6 +401,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.entry=${this._entry}
|
||||
@entity-entry-updated=${this._entryUpdated}
|
||||
></ha-more-info-settings>
|
||||
`
|
||||
: this._currView === "related"
|
||||
@@ -452,7 +451,6 @@ export class MoreInfoDialog extends LitElement {
|
||||
--dialog-surface-position: static;
|
||||
--dialog-content-position: static;
|
||||
--dialog-content-padding: 0;
|
||||
--chart-base-position: static;
|
||||
}
|
||||
|
||||
ha-header-bar {
|
||||
|
@@ -27,7 +27,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const statTypes: StatisticsTypes = ["min", "mean", "max"];
|
||||
const statTypes: StatisticsTypes = ["state", "min", "mean", "max"];
|
||||
|
||||
@customElement("ha-more-info-history")
|
||||
export class MoreInfoHistory extends LitElement {
|
||||
|
@@ -6,6 +6,7 @@ import "@webcomponents/scoped-custom-element-registry/scoped-custom-element-regi
|
||||
import "../layouts/home-assistant";
|
||||
import "../resources/ha-style";
|
||||
import "../resources/roboto";
|
||||
import "../util/legacy-support";
|
||||
|
||||
setPassiveTouchGestures(true);
|
||||
setCancelSyntheticClickEvents(false);
|
||||
|
@@ -6,7 +6,6 @@ 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";
|
||||
|
||||
@@ -46,15 +45,6 @@ const handleExternalMessage = (
|
||||
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,
|
||||
@@ -63,16 +53,10 @@ const handleExternalMessage = (
|
||||
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 });
|
||||
fireEvent(hassMainEl, "hass-toggle-menu", {
|
||||
open: true,
|
||||
screenPercentage: msg.data?.screenPercentage,
|
||||
});
|
||||
bus.fireMessage({
|
||||
id: msg.id,
|
||||
type: "result",
|
||||
|
@@ -131,6 +131,7 @@ interface EMIncomingMessageShowSidebar {
|
||||
id: number;
|
||||
type: "command";
|
||||
command: "sidebar/show";
|
||||
data?: { screenPercentage: number };
|
||||
}
|
||||
|
||||
export type EMIncomingMessageCommands =
|
||||
|
@@ -1,9 +1,12 @@
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import "@material/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
|
||||
class HaInitPage extends LitElement {
|
||||
@property({ type: Boolean }) public error = false;
|
||||
|
||||
@state() private _showProgressIndicator = false;
|
||||
|
||||
@state() private _retryInSeconds = 60;
|
||||
|
||||
private _showProgressIndicatorTimeout?: NodeJS.Timeout;
|
||||
@@ -33,7 +36,9 @@ class HaInitPage extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<div id="progress-indicator-wrapper">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
${this._showProgressIndicator
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: ""}
|
||||
</div>
|
||||
<div id="loading-text">Loading data</div>
|
||||
`;
|
||||
@@ -49,15 +54,10 @@ class HaInitPage extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues<this>) {
|
||||
if (changedProperties.has("error") && this.error) {
|
||||
import("@material/mwc-button");
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._showProgressIndicatorTimeout = setTimeout(() => {
|
||||
import("../components/ha-circular-progress");
|
||||
this._showProgressIndicatorTimeout = setTimeout(async () => {
|
||||
await import("../components/ha-circular-progress");
|
||||
this._showProgressIndicator = true;
|
||||
}, 5000);
|
||||
|
||||
this._retryInterval = setInterval(() => {
|
||||
|
@@ -3,6 +3,8 @@ import { property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { Route } from "../types";
|
||||
import "./hass-error-screen";
|
||||
import "./hass-loading-screen";
|
||||
|
||||
const extractPage = (path: string, defaultPage: string) => {
|
||||
if (path === "") {
|
||||
@@ -254,12 +256,10 @@ export class HassRouterPage extends ReactiveElement {
|
||||
}
|
||||
|
||||
protected createLoadingScreen() {
|
||||
import("./hass-loading-screen");
|
||||
return document.createElement("hass-loading-screen");
|
||||
}
|
||||
|
||||
protected createErrorScreen(error: string) {
|
||||
import("./hass-error-screen");
|
||||
const errorEl = document.createElement("hass-error-screen");
|
||||
errorEl.error = error;
|
||||
return errorEl;
|
||||
|
@@ -99,8 +99,6 @@ class HassSubpage extends LitElement {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: var(--primary-background-color);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
@@ -154,7 +152,7 @@ class HassSubpage extends LitElement {
|
||||
}
|
||||
|
||||
#fab {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
right: calc(16px + env(safe-area-inset-right));
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
|
@@ -11,7 +11,6 @@ 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";
|
||||
@@ -20,7 +19,9 @@ import "./partial-panel-resolver";
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"hass-toggle-menu": undefined | { open?: boolean };
|
||||
"hass-toggle-menu":
|
||||
| undefined
|
||||
| { open?: boolean; screenPercentage?: number };
|
||||
"hass-edit-sidebar": EditSideBarEvent;
|
||||
"hass-show-notifications": undefined;
|
||||
}
|
||||
@@ -62,7 +63,6 @@ export class HomeAssistantMain extends LitElement {
|
||||
<ha-drawer
|
||||
.type=${sidebarNarrow ? "modal" : ""}
|
||||
.open=${sidebarNarrow ? this._drawerOpen : undefined}
|
||||
.direction=${computeRTLDirection(this.hass)}
|
||||
@MDCDrawer:closed=${this._drawerClosed}
|
||||
>
|
||||
<ha-sidebar
|
||||
@@ -122,6 +122,10 @@ export class HomeAssistantMain extends LitElement {
|
||||
}
|
||||
if (this._sidebarNarrow) {
|
||||
this._drawerOpen = ev.detail?.open ?? !this._drawerOpen;
|
||||
const offset = ev.detail?.screenPercentage
|
||||
? -256 + screen.width * (ev.detail.screenPercentage / 100)
|
||||
: 0;
|
||||
this.style.setProperty("--drawer-modal-left-offset", `${offset}px`);
|
||||
} else {
|
||||
fireEvent(this, "hass-dock-sidebar", {
|
||||
dock: ev.detail?.open
|
||||
|
@@ -65,7 +65,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
update(changedProps: PropertyValues<this>) {
|
||||
update(changedProps) {
|
||||
if (this.hass?.states && this.hass.config && this.hass.services) {
|
||||
this.render = this.renderHass;
|
||||
this.update = super.update;
|
||||
@@ -74,7 +74,7 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
super.update(changedProps);
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._initializeHass();
|
||||
setTimeout(() => registerServiceWorker(this), 1000);
|
||||
|
@@ -23,11 +23,11 @@ class OnboardingAnalytics extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<p>${this.localize("ui.panel.page-onboarding.analytics.intro")}</p>
|
||||
<p>${this.hass.localize("ui.panel.page-onboarding.analytics.intro")}</p>
|
||||
<ha-analytics
|
||||
translation_key_panel="page-onboarding"
|
||||
@analytics-preferences-changed=${this._preferencesChanged}
|
||||
.localize=${this.localize}
|
||||
.hass=${this.hass}
|
||||
.analytics=${this._analyticsDetails}
|
||||
>
|
||||
</ha-analytics>
|
||||
@@ -41,7 +41,7 @@ class OnboardingAnalytics extends LitElement {
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.localize("ui.panel.page-onboarding.analytics.learn_more")}
|
||||
${this.hass.localize("ui.panel.page-onboarding.analytics.learn_more")}
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
@@ -1,21 +1,20 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiCheck, mdiDotsHorizontal } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { ConfigEntry, subscribeConfigEntries } from "../data/config_entries";
|
||||
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
|
||||
import {
|
||||
getConfigFlowInProgressCollection,
|
||||
localizeConfigFlowTitle,
|
||||
@@ -28,8 +27,6 @@ import {
|
||||
loadConfigFlowDialog,
|
||||
showConfigFlowDialog,
|
||||
} from "../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { showAddIntegrationDialog } from "../panels/config/integrations/show-add-integration-dialog";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./action-badge";
|
||||
import "./integration-badge";
|
||||
@@ -43,7 +40,7 @@ const HIDDEN_DOMAINS = new Set([
|
||||
]);
|
||||
|
||||
@customElement("onboarding-integrations")
|
||||
class OnboardingIntegrations extends SubscribeMixin(LitElement) {
|
||||
class OnboardingIntegrations extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public onboardingLocalize!: LocalizeFunc;
|
||||
@@ -52,56 +49,30 @@ class OnboardingIntegrations extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _discovered?: DataEntryFlowProgress[];
|
||||
|
||||
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
||||
return [
|
||||
subscribeConfigFlowInProgress(this.hass, (flows) => {
|
||||
this._discovered = flows;
|
||||
const integrations: Set<string> = new Set();
|
||||
for (const flow of flows) {
|
||||
// To render title placeholders
|
||||
if (flow.context.title_placeholders) {
|
||||
integrations.add(flow.handler);
|
||||
}
|
||||
private _unsubEvents?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.hass.loadBackendTranslation("title", undefined, true);
|
||||
this._unsubEvents = subscribeConfigFlowInProgress(this.hass, (flows) => {
|
||||
this._discovered = flows;
|
||||
const integrations: Set<string> = new Set();
|
||||
for (const flow of flows) {
|
||||
// To render title placeholders
|
||||
if (flow.context.title_placeholders) {
|
||||
integrations.add(flow.handler);
|
||||
}
|
||||
this.hass.loadBackendTranslation("config", Array.from(integrations));
|
||||
}),
|
||||
subscribeConfigEntries(
|
||||
this.hass,
|
||||
(messages) => {
|
||||
let fullUpdate = false;
|
||||
const newEntries: ConfigEntry[] = [];
|
||||
messages.forEach((message) => {
|
||||
if (message.type === null || message.type === "added") {
|
||||
if (HIDDEN_DOMAINS.has(message.entry.domain)) {
|
||||
return;
|
||||
}
|
||||
newEntries.push(message.entry);
|
||||
if (message.type === null) {
|
||||
fullUpdate = true;
|
||||
}
|
||||
} else if (message.type === "removed") {
|
||||
this._entries = this._entries!.filter(
|
||||
(entry) => entry.entry_id !== message.entry.entry_id
|
||||
);
|
||||
} else if (message.type === "updated") {
|
||||
if (HIDDEN_DOMAINS.has(message.entry.domain)) {
|
||||
return;
|
||||
}
|
||||
const newEntry = message.entry;
|
||||
this._entries = this._entries!.map((entry) =>
|
||||
entry.entry_id === newEntry.entry_id ? newEntry : entry
|
||||
);
|
||||
}
|
||||
});
|
||||
if (!newEntries.length && !fullUpdate) {
|
||||
return;
|
||||
}
|
||||
const existingEntries = fullUpdate ? [] : this._entries;
|
||||
this._entries = [...existingEntries!, ...newEntries];
|
||||
},
|
||||
{ type: ["device", "hub", "service"] }
|
||||
),
|
||||
];
|
||||
}
|
||||
this.hass.loadBackendTranslation("config", Array.from(integrations));
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubEvents) {
|
||||
this._unsubEvents();
|
||||
this._unsubEvents = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -178,19 +149,25 @@ class OnboardingIntegrations extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.hass.loadBackendTranslation("title", undefined, true);
|
||||
this._scanUSBDevices();
|
||||
loadConfigFlowDialog();
|
||||
this._loadConfigEntries();
|
||||
}
|
||||
|
||||
private _createFlow() {
|
||||
showAddIntegrationDialog(this);
|
||||
showConfigFlowDialog(this, {
|
||||
dialogClosedCallback: () => {
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass!.connection).refresh();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _continueFlow(ev) {
|
||||
showConfigFlowDialog(this, {
|
||||
continueFlowId: ev.currentTarget.flowId,
|
||||
dialogClosedCallback: () => {
|
||||
this._loadConfigEntries();
|
||||
getConfigFlowInProgressCollection(this.hass!.connection).refresh();
|
||||
},
|
||||
});
|
||||
@@ -203,6 +180,18 @@ class OnboardingIntegrations extends SubscribeMixin(LitElement) {
|
||||
await scanUSBDevices(this.hass);
|
||||
}
|
||||
|
||||
private async _loadConfigEntries() {
|
||||
const entries = await getConfigEntries(this.hass!, {
|
||||
type: ["device", "hub", "service"],
|
||||
});
|
||||
// We filter out the config entries that are automatically created during onboarding.
|
||||
// It is one that we create automatically and it will confuse the user
|
||||
// if it starts showing up during onboarding.
|
||||
this._entries = entries.filter(
|
||||
(entry) => !HIDDEN_DOMAINS.has(entry.domain)
|
||||
);
|
||||
}
|
||||
|
||||
private async _finish() {
|
||||
fireEvent(this, "onboarding-step", {
|
||||
type: "integration",
|
||||
|
@@ -182,6 +182,8 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
: html`
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
fixed
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
>
|
||||
|
@@ -128,7 +128,11 @@ export default class HaAutomationAction extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<ha-button-menu @action=${this._addAction} .disabled=${this.disabled}>
|
||||
<ha-button-menu
|
||||
fixed
|
||||
@action=${this._addAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-button
|
||||
slot="trigger"
|
||||
outlined
|
||||
|
@@ -116,6 +116,8 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
: html`
|
||||
<ha-button-menu
|
||||
slot="icons"
|
||||
fixed
|
||||
corner="BOTTOM_START"
|
||||
@action=${this._handleAction}
|
||||
@click=${preventDefault}
|
||||
>
|
||||
|
@@ -180,7 +180,11 @@ export default class HaAutomationCondition extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<ha-button-menu @action=${this._addCondition} .disabled=${this.disabled}>
|
||||
<ha-button-menu
|
||||
fixed
|
||||
@action=${this._addCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-button
|
||||
slot="trigger"
|
||||
outlined
|
||||
|
@@ -141,7 +141,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
|
@@ -287,6 +287,7 @@ 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}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user