mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-25 18:26:35 +00:00
20230426.0 (#16327)
This commit is contained in:
commit
b7667d2cbf
21
.browserslistrc
Normal file
21
.browserslistrc
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[modern]
|
||||||
|
# Support for dynamic import is the main litmus test for serving modern builds.
|
||||||
|
# Although officially a ES2020 feature, browsers implemented it early, so this
|
||||||
|
# enables all of ES2017 and some features in ES2018.
|
||||||
|
supports es6-module-dynamic-import
|
||||||
|
|
||||||
|
# Exclude Safari 11-12 because of a bug in tagged template literals
|
||||||
|
# https://bugs.webkit.org/show_bug.cgi?id=190756
|
||||||
|
# Note: Dropping version 11 also enables several more ES2018 features
|
||||||
|
not Safari < 13
|
||||||
|
not iOS < 13
|
||||||
|
|
||||||
|
# Exclude unsupported browsers
|
||||||
|
not dead
|
||||||
|
|
||||||
|
[legacy]
|
||||||
|
# Legacy builds are transpiled to ES5 (strict mode) but also must support some features that cannot be polyfilled:
|
||||||
|
# - web sockets to communicate with backend
|
||||||
|
# - inline SVG used widely in buttons, widgets, etc.
|
||||||
|
# - custom events used for most user interactions
|
||||||
|
supports use-strict and supports websockets and supports svg-html5 and supports customevent
|
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 }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
|
8
.github/workflows/ci.yaml
vendored
8
.github/workflows/ci.yaml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
@ -48,7 +48,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
@ -66,7 +66,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
@ -84,7 +84,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
uses: actions/setup-node@v3.6.0
|
||||||
with:
|
with:
|
||||||
|
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
|
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ jobs:
|
|||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
with:
|
with:
|
||||||
ref: master
|
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 }}
|
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
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')
|
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
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
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
|
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
contents: write # Required to upload release assets
|
contents: write # Required to upload release assets
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
|
|
||||||
- name: Verify version
|
- name: Verify version
|
||||||
uses: home-assistant/actions/helpers/verify-version@master
|
uses: home-assistant/actions/helpers/verify-version@master
|
||||||
@ -75,7 +75,7 @@ jobs:
|
|||||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2022.10.1
|
uses: home-assistant/wheels@2023.04.0
|
||||||
with:
|
with:
|
||||||
abi: cp310
|
abi: cp310
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
|
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.5.0
|
uses: actions/checkout@v3.5.2
|
||||||
|
|
||||||
- name: Upload Translations
|
- name: Upload Translations
|
||||||
run: |
|
run: |
|
||||||
|
@ -84,17 +84,23 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
|
|||||||
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
compact: false,
|
compact: false,
|
||||||
|
assumptions: {
|
||||||
|
privateFieldsAsProperties: true,
|
||||||
|
setPublicClassFields: true,
|
||||||
|
setSpreadProperties: true,
|
||||||
|
},
|
||||||
|
browserslistEnv: latestBuild ? "modern" : "legacy",
|
||||||
presets: [
|
presets: [
|
||||||
!latestBuild && [
|
[
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
{
|
{
|
||||||
useBuiltIns: "entry",
|
useBuiltIns: latestBuild ? false : "entry",
|
||||||
corejs: { version: "3.30", proposals: true },
|
corejs: latestBuild ? false : { version: "3.30", proposals: true },
|
||||||
bugfixes: true,
|
bugfixes: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"@babel/preset-typescript",
|
"@babel/preset-typescript",
|
||||||
].filter(Boolean),
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
[
|
||||||
path.resolve(
|
path.resolve(
|
||||||
@ -106,22 +112,8 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|||||||
ignoreModuleNotFound: true,
|
ignoreModuleNotFound: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
// Support some proposals still in TC39 process
|
||||||
!latestBuild && [
|
|
||||||
"@babel/plugin-proposal-object-rest-spread",
|
|
||||||
{ loose: true, useBuiltIns: true },
|
|
||||||
],
|
|
||||||
// Only support the syntax, Webpack will handle it.
|
|
||||||
"@babel/plugin-syntax-import-meta",
|
|
||||||
"@babel/plugin-syntax-dynamic-import",
|
|
||||||
"@babel/plugin-syntax-top-level-await",
|
|
||||||
// Support various proposals
|
|
||||||
"@babel/plugin-proposal-optional-chaining",
|
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
|
||||||
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
||||||
["@babel/plugin-proposal-private-methods", { loose: true }],
|
|
||||||
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
|
|
||||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
|
||||||
// Minify template literals for production
|
// Minify template literals for production
|
||||||
isProdBuild && [
|
isProdBuild && [
|
||||||
"template-html-minifier",
|
"template-html-minifier",
|
||||||
|
@ -24,8 +24,7 @@ gulp.task(
|
|||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
"gen-service-worker-app-dev",
|
"gen-service-worker-app-dev",
|
||||||
"gen-icons-json",
|
"gen-icons-json",
|
||||||
"gen-pages-dev",
|
"gen-pages-app-dev",
|
||||||
"gen-index-app-dev",
|
|
||||||
"build-translations",
|
"build-translations",
|
||||||
"build-locale-data"
|
"build-locale-data"
|
||||||
),
|
),
|
||||||
@ -50,10 +49,6 @@ gulp.task(
|
|||||||
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
||||||
// Don't compress running tests
|
// Don't compress running tests
|
||||||
...(env.isTestBuild() ? [] : ["compress-app"]),
|
...(env.isTestBuild() ? [] : ["compress-app"]),
|
||||||
gulp.parallel(
|
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod")
|
||||||
"gen-pages-prod",
|
|
||||||
"gen-index-app-prod",
|
|
||||||
"gen-service-worker-app-prod"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -19,7 +19,7 @@ gulp.task(
|
|||||||
"translations-enable-merge-backend",
|
"translations-enable-merge-backend",
|
||||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||||
"copy-static-cast",
|
"copy-static-cast",
|
||||||
"gen-index-cast-dev",
|
"gen-pages-cast-dev",
|
||||||
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
|
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -35,6 +35,6 @@ gulp.task(
|
|||||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||||
"copy-static-cast",
|
"copy-static-cast",
|
||||||
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
|
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
|
||||||
"gen-index-cast-prod"
|
"gen-pages-cast-prod"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -21,7 +21,7 @@ gulp.task(
|
|||||||
"translations-enable-merge-backend",
|
"translations-enable-merge-backend",
|
||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
"gen-icons-json",
|
"gen-icons-json",
|
||||||
"gen-index-demo-dev",
|
"gen-pages-demo-dev",
|
||||||
"build-translations",
|
"build-translations",
|
||||||
"build-locale-data"
|
"build-locale-data"
|
||||||
),
|
),
|
||||||
@ -42,6 +42,6 @@ gulp.task(
|
|||||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
||||||
"copy-static-demo",
|
"copy-static-demo",
|
||||||
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
|
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
|
||||||
"gen-index-demo-prod"
|
"gen-pages-demo-prod"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -8,344 +8,223 @@ const paths = require("../paths.cjs");
|
|||||||
const env = require("../env.cjs");
|
const env = require("../env.cjs");
|
||||||
const { htmlMinifierOptions, terserOptions } = require("../bundle.cjs");
|
const { htmlMinifierOptions, terserOptions } = require("../bundle.cjs");
|
||||||
|
|
||||||
const templatePath = (tpl) =>
|
const renderTemplate = (templateFile, data = {}) => {
|
||||||
path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`);
|
const compiled = template(
|
||||||
|
fs.readFileSync(templateFile, { encoding: "utf-8" })
|
||||||
const readFile = (pth) => fs.readFileSync(pth).toString();
|
);
|
||||||
|
|
||||||
const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
|
|
||||||
const compiled = template(readFile(pathFunc(pth)));
|
|
||||||
return compiled({
|
return compiled({
|
||||||
...data,
|
...data,
|
||||||
useRollup: env.useRollup(),
|
useRollup: env.useRollup(),
|
||||||
useWDS: env.useWDS(),
|
useWDS: env.useWDS(),
|
||||||
renderTemplate,
|
// Resolve any child/nested templates relative to the parent and pass the same data
|
||||||
|
renderTemplate: (childTemplate) =>
|
||||||
|
renderTemplate(
|
||||||
|
path.resolve(path.dirname(templateFile), childTemplate),
|
||||||
|
data
|
||||||
|
),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderDemoTemplate = (pth, data = {}) =>
|
const WRAP_TAGS = { ".js": "script", ".css": "style" };
|
||||||
renderTemplate(pth, data, (tpl) =>
|
|
||||||
path.resolve(paths.demo_dir, "src/html/", `${tpl}.html.template`)
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderCastTemplate = (pth, data = {}) =>
|
const minifyHtml = (content, ext) => {
|
||||||
renderTemplate(pth, data, (tpl) =>
|
const wrapTag = WRAP_TAGS[ext] || "";
|
||||||
path.resolve(paths.cast_dir, "src/html/", `${tpl}.html.template`)
|
const begTag = wrapTag && `<${wrapTag}>`;
|
||||||
);
|
const endTag = wrapTag && `</${wrapTag}>`;
|
||||||
|
return minify(begTag + content + endTag, {
|
||||||
const renderGalleryTemplate = (pth, data = {}) =>
|
|
||||||
renderTemplate(pth, data, (tpl) =>
|
|
||||||
path.resolve(paths.gallery_dir, "src/html/", `${tpl}.html.template`)
|
|
||||||
);
|
|
||||||
|
|
||||||
const minifyHtml = (content) =>
|
|
||||||
minify(content, {
|
|
||||||
...htmlMinifierOptions,
|
...htmlMinifierOptions,
|
||||||
conservativeCollapse: false,
|
conservativeCollapse: false,
|
||||||
minifyJS: terserOptions({
|
minifyJS: terserOptions({
|
||||||
latestBuild: false, // Shared scripts should be ES5
|
latestBuild: false, // Shared scripts should be ES5
|
||||||
isTestBuild: true, // Don't need source maps
|
isTestBuild: true, // Don't need source maps
|
||||||
}),
|
}),
|
||||||
});
|
}).then((wrapped) =>
|
||||||
|
wrapTag ? wrapped.slice(begTag.length, -endTag.length) : wrapped
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const PAGES = ["onboarding", "authorize"];
|
// Function to generate a dev task for each project's configuration
|
||||||
|
// Note Currently WDS paths are hard-coded to only work for app
|
||||||
|
const genPagesDevTask =
|
||||||
|
(
|
||||||
|
pageEntries,
|
||||||
|
inputRoot,
|
||||||
|
outputRoot,
|
||||||
|
useWDS = false,
|
||||||
|
inputSub = "src/html",
|
||||||
|
publicRoot = ""
|
||||||
|
) =>
|
||||||
|
async () => {
|
||||||
|
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||||
|
const content = renderTemplate(
|
||||||
|
path.resolve(inputRoot, inputSub, `${page}.template`),
|
||||||
|
{
|
||||||
|
latestEntryJS: entries.map((entry) =>
|
||||||
|
useWDS
|
||||||
|
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
||||||
|
: `${publicRoot}/frontend_latest/${entry}.js`
|
||||||
|
),
|
||||||
|
es5EntryJS: entries.map(
|
||||||
|
(entry) => `${publicRoot}/frontend_es5/${entry}.js`
|
||||||
|
),
|
||||||
|
latestCustomPanelJS: useWDS
|
||||||
|
? "http://localhost:8000/src/entrypoints/custom-panel.ts"
|
||||||
|
: `${publicRoot}/frontend_latest/custom-panel.js`,
|
||||||
|
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
fs.outputFileSync(path.resolve(outputRoot, page), content);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
gulp.task("gen-pages-dev", (done) => {
|
// Same as previous but for production builds
|
||||||
for (const page of PAGES) {
|
// (includes minification and hashed file names from manifest)
|
||||||
const content = renderTemplate(page, {
|
const genPagesProdTask =
|
||||||
latestPageJS: `/frontend_latest/${page}.js`,
|
(
|
||||||
|
pageEntries,
|
||||||
es5PageJS: `/frontend_es5/${page}.js`,
|
inputRoot,
|
||||||
});
|
outputRoot,
|
||||||
|
outputLatest,
|
||||||
fs.outputFileSync(
|
outputES5,
|
||||||
path.resolve(paths.app_output_root, `${page}.html`),
|
inputSub = "src/html"
|
||||||
content
|
) =>
|
||||||
);
|
async () => {
|
||||||
}
|
const latestManifest = require(path.resolve(outputLatest, "manifest.json"));
|
||||||
done();
|
const es5Manifest = outputES5
|
||||||
});
|
? require(path.resolve(outputES5, "manifest.json"))
|
||||||
|
: {};
|
||||||
gulp.task("gen-pages-prod", async () => {
|
const minifiedHTML = [];
|
||||||
const latestManifest = require(path.resolve(
|
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||||
paths.app_output_latest,
|
const content = renderTemplate(
|
||||||
"manifest.json"
|
path.resolve(inputRoot, inputSub, `${page}.template`),
|
||||||
));
|
{
|
||||||
const es5Manifest = require(path.resolve(
|
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
|
||||||
paths.app_output_es5,
|
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
|
||||||
"manifest.json"
|
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||||
));
|
es5CustomPanelJS: es5Manifest["custom-panel.js"],
|
||||||
|
}
|
||||||
const minifiedHTML = [];
|
);
|
||||||
for (const page of PAGES) {
|
minifiedHTML.push(
|
||||||
const content = renderTemplate(page, {
|
minifyHtml(content, path.extname(page)).then((minified) =>
|
||||||
latestPageJS: latestManifest[`${page}.js`],
|
fs.outputFileSync(path.resolve(outputRoot, page), minified)
|
||||||
es5PageJS: es5Manifest[`${page}.js`],
|
|
||||||
});
|
|
||||||
|
|
||||||
minifiedHTML.push(
|
|
||||||
minifyHtml(content).then((minified) =>
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.app_output_root, `${page}.html`),
|
|
||||||
minified
|
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
await Promise.all(minifiedHTML);
|
||||||
await Promise.all(minifiedHTML);
|
};
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-app-dev", (done) => {
|
// Map HTML pages to their required entrypoints
|
||||||
let latestAppJS;
|
const APP_PAGE_ENTRIES = {
|
||||||
let latestCoreJS;
|
"authorize.html": ["authorize"],
|
||||||
let latestCustomPanelJS;
|
"onboarding.html": ["onboarding"],
|
||||||
|
"index.html": ["core", "app"],
|
||||||
|
};
|
||||||
|
|
||||||
if (env.useWDS()) {
|
gulp.task(
|
||||||
latestAppJS = "http://localhost:8000/src/entrypoints/app.ts";
|
"gen-pages-app-dev",
|
||||||
latestCoreJS = "http://localhost:8000/src/entrypoints/core.ts";
|
genPagesDevTask(
|
||||||
latestCustomPanelJS =
|
APP_PAGE_ENTRIES,
|
||||||
"http://localhost:8000/src/entrypoints/custom-panel.ts";
|
paths.polymer_dir,
|
||||||
} else {
|
paths.app_output_root,
|
||||||
latestAppJS = "/frontend_latest/app.js";
|
env.useWDS()
|
||||||
latestCoreJS = "/frontend_latest/core.js";
|
)
|
||||||
latestCustomPanelJS = "/frontend_latest/custom-panel.js";
|
);
|
||||||
}
|
|
||||||
|
|
||||||
const content = renderTemplate("index", {
|
gulp.task(
|
||||||
latestAppJS,
|
"gen-pages-app-prod",
|
||||||
latestCoreJS,
|
genPagesProdTask(
|
||||||
latestCustomPanelJS,
|
APP_PAGE_ENTRIES,
|
||||||
|
paths.polymer_dir,
|
||||||
es5AppJS: "/frontend_es5/app.js",
|
paths.app_output_root,
|
||||||
es5CoreJS: "/frontend_es5/core.js",
|
|
||||||
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
|
|
||||||
}).replace(/#THEMEC/g, "{{ theme_color }}");
|
|
||||||
|
|
||||||
fs.outputFileSync(path.resolve(paths.app_output_root, "index.html"), content);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-app-prod", async () => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.app_output_latest,
|
paths.app_output_latest,
|
||||||
"manifest.json"
|
paths.app_output_es5
|
||||||
));
|
)
|
||||||
const es5Manifest = require(path.resolve(
|
);
|
||||||
paths.app_output_es5,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const content = renderTemplate("index", {
|
|
||||||
latestAppJS: latestManifest["app.js"],
|
|
||||||
latestCoreJS: latestManifest["core.js"],
|
|
||||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
|
||||||
|
|
||||||
es5AppJS: es5Manifest["app.js"],
|
const CAST_PAGE_ENTRIES = {
|
||||||
es5CoreJS: es5Manifest["core.js"],
|
"faq.html": ["launcher"],
|
||||||
es5CustomPanelJS: es5Manifest["custom-panel.js"],
|
"index.html": ["launcher"],
|
||||||
});
|
"media.html": ["media"],
|
||||||
const minified = (await minifyHtml(content)).replace(
|
"receiver.html": ["receiver"],
|
||||||
/#THEMEC/g,
|
};
|
||||||
"{{ theme_color }}"
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.outputFileSync(
|
gulp.task(
|
||||||
path.resolve(paths.app_output_root, "index.html"),
|
"gen-pages-cast-dev",
|
||||||
minified
|
genPagesDevTask(CAST_PAGE_ENTRIES, paths.cast_dir, paths.cast_output_root)
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-cast-dev", (done) => {
|
gulp.task(
|
||||||
const contentReceiver = renderCastTemplate("receiver", {
|
"gen-pages-cast-prod",
|
||||||
latestReceiverJS: "/frontend_latest/receiver.js",
|
genPagesProdTask(
|
||||||
});
|
CAST_PAGE_ENTRIES,
|
||||||
fs.outputFileSync(
|
paths.cast_dir,
|
||||||
path.resolve(paths.cast_output_root, "receiver.html"),
|
paths.cast_output_root,
|
||||||
contentReceiver
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentMedia = renderCastTemplate("media", {
|
|
||||||
latestMediaJS: "/frontend_latest/media.js",
|
|
||||||
es5MediaJS: "/frontend_es5/media.js",
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "media.html"),
|
|
||||||
contentMedia
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentFAQ = renderCastTemplate("launcher-faq", {
|
|
||||||
latestLauncherJS: "/frontend_latest/launcher.js",
|
|
||||||
es5LauncherJS: "/frontend_es5/launcher.js",
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "faq.html"),
|
|
||||||
contentFAQ
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentLauncher = renderCastTemplate("launcher", {
|
|
||||||
latestLauncherJS: "/frontend_latest/launcher.js",
|
|
||||||
es5LauncherJS: "/frontend_es5/launcher.js",
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "index.html"),
|
|
||||||
contentLauncher
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-cast-prod", (done) => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.cast_output_latest,
|
paths.cast_output_latest,
|
||||||
"manifest.json"
|
paths.cast_output_es5
|
||||||
));
|
)
|
||||||
const es5Manifest = require(path.resolve(
|
);
|
||||||
paths.cast_output_es5,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
|
|
||||||
const contentReceiver = renderCastTemplate("receiver", {
|
const DEMO_PAGE_ENTRIES = { "index.html": ["main"] };
|
||||||
latestReceiverJS: latestManifest["receiver.js"],
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "receiver.html"),
|
|
||||||
contentReceiver
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentMedia = renderCastTemplate("media", {
|
gulp.task(
|
||||||
latestMediaJS: latestManifest["media.js"],
|
"gen-pages-demo-dev",
|
||||||
es5MediaJS: es5Manifest["media.js"],
|
genPagesDevTask(DEMO_PAGE_ENTRIES, paths.demo_dir, paths.demo_output_root)
|
||||||
});
|
);
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "media.html"),
|
|
||||||
contentMedia
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentFAQ = renderCastTemplate("launcher-faq", {
|
gulp.task(
|
||||||
latestLauncherJS: latestManifest["launcher.js"],
|
"gen-pages-demo-prod",
|
||||||
es5LauncherJS: es5Manifest["launcher.js"],
|
genPagesProdTask(
|
||||||
});
|
DEMO_PAGE_ENTRIES,
|
||||||
fs.outputFileSync(
|
paths.demo_dir,
|
||||||
path.resolve(paths.cast_output_root, "faq.html"),
|
paths.demo_output_root,
|
||||||
contentFAQ
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentLauncher = renderCastTemplate("launcher", {
|
|
||||||
latestLauncherJS: latestManifest["launcher.js"],
|
|
||||||
es5LauncherJS: es5Manifest["launcher.js"],
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "index.html"),
|
|
||||||
contentLauncher
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-demo-dev", (done) => {
|
|
||||||
const content = renderDemoTemplate("index", {
|
|
||||||
latestDemoJS: "/frontend_latest/main.js",
|
|
||||||
|
|
||||||
es5DemoJS: "/frontend_es5/main.js",
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.demo_output_root, "index.html"),
|
|
||||||
content
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-demo-prod", async () => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.demo_output_latest,
|
paths.demo_output_latest,
|
||||||
"manifest.json"
|
paths.demo_output_es5
|
||||||
));
|
)
|
||||||
const es5Manifest = require(path.resolve(
|
);
|
||||||
paths.demo_output_es5,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const content = renderDemoTemplate("index", {
|
|
||||||
latestDemoJS: latestManifest["main.js"],
|
|
||||||
|
|
||||||
es5DemoJS: es5Manifest["main.js"],
|
const GALLERY_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
|
||||||
});
|
|
||||||
const minified = await minifyHtml(content);
|
|
||||||
|
|
||||||
fs.outputFileSync(
|
gulp.task(
|
||||||
path.resolve(paths.demo_output_root, "index.html"),
|
"gen-pages-gallery-dev",
|
||||||
minified
|
genPagesDevTask(
|
||||||
);
|
GALLERY_PAGE_ENTRIES,
|
||||||
});
|
paths.gallery_dir,
|
||||||
|
paths.gallery_output_root
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
gulp.task("gen-index-gallery-dev", (done) => {
|
gulp.task(
|
||||||
const content = renderGalleryTemplate("index", {
|
"gen-pages-gallery-prod",
|
||||||
latestGalleryJS: "./frontend_latest/entrypoint.js",
|
genPagesProdTask(
|
||||||
});
|
GALLERY_PAGE_ENTRIES,
|
||||||
|
paths.gallery_dir,
|
||||||
|
paths.gallery_output_root,
|
||||||
|
paths.gallery_output_latest
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
fs.outputFileSync(
|
const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] };
|
||||||
path.resolve(paths.gallery_output_root, "index.html"),
|
|
||||||
content
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-gallery-prod", async () => {
|
gulp.task(
|
||||||
const latestManifest = require(path.resolve(
|
"gen-pages-hassio-dev",
|
||||||
paths.gallery_output_latest,
|
genPagesDevTask(
|
||||||
"manifest.json"
|
HASSIO_PAGE_ENTRIES,
|
||||||
));
|
paths.hassio_dir,
|
||||||
const content = renderGalleryTemplate("index", {
|
paths.hassio_output_root,
|
||||||
latestGalleryJS: latestManifest["entrypoint.js"],
|
undefined,
|
||||||
});
|
"src",
|
||||||
const minified = await minifyHtml(content);
|
paths.hassio_publicPath
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
fs.outputFileSync(
|
gulp.task(
|
||||||
path.resolve(paths.gallery_output_root, "index.html"),
|
"gen-pages-hassio-prod",
|
||||||
minified
|
genPagesProdTask(
|
||||||
);
|
HASSIO_PAGE_ENTRIES,
|
||||||
});
|
paths.hassio_dir,
|
||||||
|
paths.hassio_output_root,
|
||||||
gulp.task("gen-index-hassio-dev", async () => {
|
|
||||||
writeHassioEntrypoint(
|
|
||||||
`${paths.hassio_publicPath}/frontend_latest/entrypoint.js`,
|
|
||||||
`${paths.hassio_publicPath}/frontend_es5/entrypoint.js`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-hassio-prod", async () => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.hassio_output_latest,
|
paths.hassio_output_latest,
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const es5Manifest = require(path.resolve(
|
|
||||||
paths.hassio_output_es5,
|
paths.hassio_output_es5,
|
||||||
"manifest.json"
|
"src"
|
||||||
));
|
)
|
||||||
writeHassioEntrypoint(
|
);
|
||||||
latestManifest["entrypoint.js"],
|
|
||||||
es5Manifest["entrypoint.js"]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
|
|
||||||
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
|
|
||||||
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve(paths.hassio_output_root, "entrypoint.js"),
|
|
||||||
`
|
|
||||||
function loadES5() {
|
|
||||||
var el = document.createElement('script');
|
|
||||||
el.src = '${es5Entrypoint}';
|
|
||||||
document.body.appendChild(el);
|
|
||||||
}
|
|
||||||
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
|
|
||||||
loadES5();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
new Function("import('${latestEntrypoint}')")();
|
|
||||||
} catch (err) {
|
|
||||||
loadES5();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
{ encoding: "utf-8" }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,7 @@ const gulp = require("gulp");
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { marked } = require("marked");
|
const { marked } = require("marked");
|
||||||
const glob = require("glob");
|
const { glob } = require("glob");
|
||||||
const yaml = require("js-yaml");
|
const yaml = require("js-yaml");
|
||||||
|
|
||||||
const env = require("../env.cjs");
|
const env = require("../env.cjs");
|
||||||
@ -159,7 +159,7 @@ gulp.task(
|
|||||||
"gather-gallery-pages"
|
"gather-gallery-pages"
|
||||||
),
|
),
|
||||||
"copy-static-gallery",
|
"copy-static-gallery",
|
||||||
"gen-index-gallery-dev",
|
"gen-pages-gallery-dev",
|
||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
env.useRollup()
|
env.useRollup()
|
||||||
? "rollup-dev-server-gallery"
|
? "rollup-dev-server-gallery"
|
||||||
@ -193,6 +193,6 @@ gulp.task(
|
|||||||
),
|
),
|
||||||
"copy-static-gallery",
|
"copy-static-gallery",
|
||||||
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
|
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
|
||||||
"gen-index-gallery-prod"
|
"gen-pages-gallery-prod"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -17,7 +17,7 @@ gulp.task(
|
|||||||
},
|
},
|
||||||
"clean-hassio",
|
"clean-hassio",
|
||||||
"gen-dummy-icons-json",
|
"gen-dummy-icons-json",
|
||||||
"gen-index-hassio-dev",
|
"gen-pages-hassio-dev",
|
||||||
"build-supervisor-translations",
|
"build-supervisor-translations",
|
||||||
"copy-translations-supervisor",
|
"copy-translations-supervisor",
|
||||||
"build-locale-data",
|
"build-locale-data",
|
||||||
@ -39,7 +39,7 @@ gulp.task(
|
|||||||
"build-locale-data",
|
"build-locale-data",
|
||||||
"copy-locale-data-supervisor",
|
"copy-locale-data-supervisor",
|
||||||
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
|
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
|
||||||
"gen-index-hassio-prod",
|
"gen-pages-hassio-prod",
|
||||||
...// Don't compress running tests
|
...// Don't compress running tests
|
||||||
(env.isTestBuild() ? [] : ["compress-hassio"])
|
(env.isTestBuild() ? [] : ["compress-hassio"])
|
||||||
)
|
)
|
||||||
|
@ -19,6 +19,7 @@ const modules = {
|
|||||||
"intl-relativetimeformat": "RelativeTimeFormat",
|
"intl-relativetimeformat": "RelativeTimeFormat",
|
||||||
"intl-datetimeformat": "DateTimeFormat",
|
"intl-datetimeformat": "DateTimeFormat",
|
||||||
"intl-numberformat": "NumberFormat",
|
"intl-numberformat": "NumberFormat",
|
||||||
|
"intl-displaynames": "DisplayNames",
|
||||||
};
|
};
|
||||||
|
|
||||||
gulp.task("create-locale-data", (done) => {
|
gulp.task("create-locale-data", (done) => {
|
||||||
|
30
build-scripts/list-preset-env-plugins.js
Executable file
30
build-scripts/list-preset-env-plugins.js
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Script to print Babel plugins that will be used by browserslist environments
|
||||||
|
|
||||||
|
import { version as babelVersion } from "@babel/core";
|
||||||
|
import presetEnv from "@babel/preset-env";
|
||||||
|
import { babelOptions } from "./bundle.cjs";
|
||||||
|
|
||||||
|
const dummyAPI = {
|
||||||
|
version: babelVersion,
|
||||||
|
assertVersion: () => {},
|
||||||
|
caller: (callback) =>
|
||||||
|
callback({
|
||||||
|
name: "Dummy Bundler",
|
||||||
|
supportsStaticESM: true,
|
||||||
|
supportsDynamicImport: true,
|
||||||
|
supportsTopLevelAwait: true,
|
||||||
|
supportsExportNamespaceFrom: true,
|
||||||
|
}),
|
||||||
|
targets: () => ({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const browserslistEnv of ["modern", "legacy"]) {
|
||||||
|
console.log("\nBrowsersList Environment = %s\n", browserslistEnv);
|
||||||
|
presetEnv.default(dummyAPI, {
|
||||||
|
...babelOptions({ latestBuild: browserslistEnv === "modern" })
|
||||||
|
.presets[0][1],
|
||||||
|
browserslistEnv,
|
||||||
|
debug: true,
|
||||||
|
});
|
||||||
|
}
|
24
cast/src/html/_social_meta.html.template
Normal file
24
cast/src/html/_social_meta.html.template
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<meta property="fb:app_id" content="338291289691179" />
|
||||||
|
<meta property="og:title" content="Home Assistant Cast" />
|
||||||
|
<meta property="og:site_name" content="Home Assistant Cast" />
|
||||||
|
<meta property="og:url" content="https://cast.home-assistant.io/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://cast.home-assistant.io/images/google-nest-hub.png"
|
||||||
|
/>
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:site" content="@home_assistant" />
|
||||||
|
<meta name="twitter:title" content="Home Assistant Cast" />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="twitter:image"
|
||||||
|
content="https://cast.home-assistant.io/images/google-nest-hub.png"
|
||||||
|
/>
|
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Home Assistant Cast - FAQ</title>
|
<title>Home Assistant Cast - FAQ</title>
|
||||||
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
|
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
|
||||||
<%= renderTemplate('_style_base') %>
|
<%= renderTemplate("../../../src/html/_style_base.html.template") %>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-color: #e5e5e5;
|
background-color: #e5e5e5;
|
||||||
@ -35,25 +35,14 @@
|
|||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= renderTemplate('_js_base') %>
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import("<%= latestLauncherJS %>");
|
<% for (const entry of latestEntryJS) { %>
|
||||||
|
import("<%= entry %>");
|
||||||
|
<% } %>
|
||||||
window.latestJS = true;
|
window.latestJS = true;
|
||||||
</script>
|
</script>
|
||||||
|
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
||||||
<script>
|
|
||||||
if (!window.latestJS) {
|
|
||||||
<% if (useRollup) { %>
|
|
||||||
_ls("/static/js/s.min.js").onload = function() {
|
|
||||||
System.import("<%= es5LauncherJS %>");
|
|
||||||
};
|
|
||||||
<% } else { %>
|
|
||||||
_ls("<%= es5LauncherJS %>");
|
|
||||||
<% } %>
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<hc-layout subtitle="FAQ">
|
<hc-layout subtitle="FAQ">
|
||||||
<style>
|
<style>
|
||||||
a {
|
a {
|
35
cast/src/html/index.html.template
Normal file
35
cast/src/html/index.html.template
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Home Assistant Cast</title>
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
|
||||||
|
<%= renderTemplate("../../../src/html/_style_base.html.template") %>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<%= renderTemplate("_social_meta.html.template") %>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
|
<hc-connect></hc-connect>
|
||||||
|
<script>
|
||||||
|
<% for (const entry of latestEntryJS) { %>
|
||||||
|
import("<%= entry %>");
|
||||||
|
<% } %>
|
||||||
|
window.latestJS = true;
|
||||||
|
</script>
|
||||||
|
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
||||||
|
<script>
|
||||||
|
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
|
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||||
|
|
||||||
|
ga('create', 'UA-57927901-9', 'auto');
|
||||||
|
ga('send', 'pageview', location.pathname.includes("auth_callback") === -1 ? location.pathname : "/");
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,57 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Home Assistant Cast</title>
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
|
|
||||||
<%= renderTemplate('_style_base') %>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #e5e5e5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta property="fb:app_id" content="338291289691179">
|
|
||||||
<meta property="og:title" content="Home Assistant Cast">
|
|
||||||
<meta property="og:site_name" content="Home Assistant Cast">
|
|
||||||
<meta property="og:url" content="https://cast.home-assistant.io/">
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen.">
|
|
||||||
<meta property="og:image" content="https://cast.home-assistant.io/images/google-nest-hub.png">
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
|
||||||
<meta name="twitter:site" content="@home_assistant">
|
|
||||||
<meta name="twitter:title" content="Home Assistant Cast">
|
|
||||||
<meta name="twitter:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen.">
|
|
||||||
<meta name="twitter:image" content="https://cast.home-assistant.io/images/google-nest-hub.png">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<%= renderTemplate('_js_base') %>
|
|
||||||
|
|
||||||
<hc-connect></hc-connect>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import("<%= latestLauncherJS %>");
|
|
||||||
window.latestJS = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
if (!window.latestJS) {
|
|
||||||
<% if (useRollup) { %>
|
|
||||||
_ls("/static/js/s.min.js").onload = function() {
|
|
||||||
System.import("<%= es5LauncherJS %>");
|
|
||||||
};
|
|
||||||
<% } else { %>
|
|
||||||
_ls("<%= es5LauncherJS %>");
|
|
||||||
<% } %>
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', 'UA-57927901-9', 'auto');
|
|
||||||
ga('send', 'pageview', location.pathname.includes("auth_callback") === -1 ? location.pathname : "/");
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -22,25 +22,14 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= renderTemplate('_js_base') %>
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
|
|
||||||
<cast-media-player></cast-media-player>
|
<cast-media-player></cast-media-player>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import("<%= latestMediaJS %>");
|
<% for (const entry of latestEntryJS) { %>
|
||||||
|
import("<%= entry %>");
|
||||||
|
<% } %>
|
||||||
window.latestJS = true;
|
window.latestJS = true;
|
||||||
</script>
|
</script>
|
||||||
|
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
||||||
<script>
|
|
||||||
if (!window.latestJS) {
|
|
||||||
<% if (useRollup) { %>
|
|
||||||
_ls("/static/js/s.min.js").onload = function() {
|
|
||||||
System.import("<%= es5MediaJS %>");
|
|
||||||
};
|
|
||||||
<% } else { %>
|
|
||||||
_ls("<%= es5MediaJS %>");
|
|
||||||
<% } %>
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
|
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
|
||||||
<script type="module" src="<%= latestReceiverJS %>"></script>
|
<% for (const entry of latestEntryJS) { %>
|
||||||
<%= renderTemplate('_style_base') %>
|
<script type="module" src="<%= entry %>"></script>
|
||||||
|
<% } %>
|
||||||
|
<%= renderTemplate("../../../src/html/_style_base.html.template") %>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
26
demo/src/html/_social_meta.html.template
Normal file
26
demo/src/html/_social_meta.html.template
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<meta property="fb:app_id" content="338291289691179" />
|
||||||
|
<meta property="og:title" content="Home Assistant Demo" />
|
||||||
|
<meta property="og:site_name" content="Home Assistant" />
|
||||||
|
<meta property="og:url" content="https://demo.home-assistant.io/" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Open source home automation that puts local control and privacy first."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://www.home-assistant.io/images/default-social.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:site" content="@home_assistant" />
|
||||||
|
|
||||||
|
<meta name="twitter:title" content="Home Assistant" />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="Open source home automation that puts local control and privacy first."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="twitter:image"
|
||||||
|
content="https://www.home-assistant.io/images/default-social.png"
|
||||||
|
/>
|
@ -1,9 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<title>Home Assistant Demo</title>
|
||||||
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
|
<%= renderTemplate("../../../src/html/_header.html.template") %>
|
||||||
<link rel="icon" href="/static/icons/favicon.ico" />
|
|
||||||
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" />
|
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" />
|
||||||
<link
|
<link
|
||||||
rel="apple-touch-icon"
|
rel="apple-touch-icon"
|
||||||
@ -35,33 +34,7 @@
|
|||||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||||
/>
|
/>
|
||||||
<meta name="theme-color" content="#03a9f4" />
|
<meta name="theme-color" content="#03a9f4" />
|
||||||
<meta property="fb:app_id" content="338291289691179" />
|
<%= renderTemplate("_social_meta.html.template") %>
|
||||||
<meta property="og:title" content="Home Assistant Demo" />
|
|
||||||
<meta property="og:site_name" content="Home Assistant" />
|
|
||||||
<meta property="og:url" content="https://demo.home-assistant.io/" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta
|
|
||||||
property="og:description"
|
|
||||||
content="Open source home automation that puts local control and privacy first."
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:image"
|
|
||||||
content="https://www.home-assistant.io/images/default-social.png"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta name="twitter:site" content="@home_assistant" />
|
|
||||||
|
|
||||||
<meta name="twitter:title" content="Home Assistant" />
|
|
||||||
<meta
|
|
||||||
name="twitter:description"
|
|
||||||
content="Open source home automation that puts local control and privacy first."
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="twitter:image"
|
|
||||||
content="https://www.home-assistant.io/images/default-social.png"
|
|
||||||
/>
|
|
||||||
<title>Home Assistant Demo</title>
|
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
background-color: var(--primary-background-color, #fafafa);
|
background-color: var(--primary-background-color, #fafafa);
|
||||||
@ -107,29 +80,22 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
|
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ha-demo></ha-demo>
|
<ha-demo></ha-demo>
|
||||||
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
<%= renderTemplate('_js_base') %>
|
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %>
|
||||||
<%= renderTemplate('_preload_roboto') %>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import("<%= latestDemoJS %>");
|
if (!window.globalThis) {
|
||||||
window.latestJS = true;
|
window.globalThis = window;
|
||||||
</script>
|
}
|
||||||
|
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
|
||||||
<script>
|
if (!isS11_12) {
|
||||||
if (!window.latestJS) {
|
<% for (const entry of latestEntryJS) { %>
|
||||||
<% if (useRollup) { %>
|
import("<%= entry %>");
|
||||||
_ls("/static/js/s.min.js").onload = function() {
|
|
||||||
System.import("<%= es5DemoJS %>");
|
|
||||||
};
|
|
||||||
<% } else { %>
|
|
||||||
_ls("<%= es5DemoJS %>");
|
|
||||||
<% } %>
|
<% } %>
|
||||||
|
window.latestJS = true;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
||||||
<script>
|
<script>
|
||||||
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
|
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
|
||||||
(function (d, t) {
|
(function (d, t) {
|
||||||
|
@ -8,8 +8,9 @@
|
|||||||
/>
|
/>
|
||||||
<meta name="theme-color" content="#2157BC" />
|
<meta name="theme-color" content="#2157BC" />
|
||||||
<title>Home Assistant Design</title>
|
<title>Home Assistant Design</title>
|
||||||
|
<% for (const entry of latestEntryJS) { %>
|
||||||
<script type="module" src="<%= latestGalleryJS %>"></script>
|
<script type="module" src="<%= entry %>"></script>
|
||||||
|
<% } %>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Roboto, Noto, sans-serif;
|
font-family: Roboto, Noto, sans-serif;
|
||||||
|
@ -336,7 +336,7 @@ const SCHEMAS: {
|
|||||||
["and", "another_one"],
|
["and", "another_one"],
|
||||||
["option", "1000"],
|
["option", "1000"],
|
||||||
],
|
],
|
||||||
name: "select many otions",
|
name: "select many options",
|
||||||
default: "default",
|
default: "default",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -364,7 +364,7 @@ const SCHEMAS: {
|
|||||||
and: "another_one",
|
and: "another_one",
|
||||||
option: "1000",
|
option: "1000",
|
||||||
},
|
},
|
||||||
name: "multi many otions",
|
name: "multi many options",
|
||||||
default: ["default"],
|
default: ["default"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -216,7 +216,7 @@ export class HassioAddonStore extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _filterChanged(e) {
|
private _filterChanged(e) {
|
||||||
this._filter = e.detail.value;
|
this._filter = e.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import { navigate } from "../../../../src/common/navigate";
|
import { navigate } from "../../../../src/common/navigate";
|
||||||
import "../../../../src/components/buttons/ha-call-api-button";
|
|
||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
@ -47,6 +46,7 @@ import {
|
|||||||
HassioAddonSetOptionParams,
|
HassioAddonSetOptionParams,
|
||||||
HassioAddonSetSecurityParams,
|
HassioAddonSetSecurityParams,
|
||||||
installHassioAddon,
|
installHassioAddon,
|
||||||
|
rebuildLocalAddon,
|
||||||
restartHassioAddon,
|
restartHassioAddon,
|
||||||
setHassioAddonOption,
|
setHassioAddonOption,
|
||||||
setHassioAddonSecurity,
|
setHassioAddonSecurity,
|
||||||
@ -640,13 +640,12 @@ class HassioAddonInfo extends LitElement {
|
|||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
${this.addon.build
|
${this.addon.build
|
||||||
? html`
|
? html`
|
||||||
<ha-call-api-button
|
<ha-progress-button
|
||||||
class="warning"
|
class="warning"
|
||||||
.hass=${this.hass}
|
@click=${this._rebuildClicked}
|
||||||
.path="hassio/addons/${this.addon.slug}/rebuild"
|
|
||||||
>
|
>
|
||||||
${this.supervisor.localize("addon.dashboard.rebuild")}
|
${this.supervisor.localize("addon.dashboard.rebuild")}
|
||||||
</ha-call-api-button>
|
</ha-progress-button>
|
||||||
`
|
`
|
||||||
: ""}`
|
: ""}`
|
||||||
: ""}
|
: ""}
|
||||||
@ -966,6 +965,21 @@ class HassioAddonInfo extends LitElement {
|
|||||||
button.progress = false;
|
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> {
|
private async _startClicked(ev: CustomEvent): Promise<void> {
|
||||||
const button = ev.currentTarget as any;
|
const button = ev.currentTarget as any;
|
||||||
button.progress = true;
|
button.progress = true;
|
||||||
@ -1124,10 +1138,6 @@ class HassioAddonInfo extends LitElement {
|
|||||||
ha-svg-icon.stopped {
|
ha-svg-icon.stopped {
|
||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
ha-call-api-button {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
protection-enable mwc-button {
|
protection-enable mwc-button {
|
||||||
--mdc-theme-primary: white;
|
--mdc-theme-primary: white;
|
||||||
}
|
}
|
||||||
|
@ -316,7 +316,7 @@ export class DialogHassioNetwork
|
|||||||
>
|
>
|
||||||
<div class="radio-row">
|
<div class="radio-row">
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
.label=${this.supervisor.localize("dialog.network.dhcp")}
|
.label=${this.supervisor.localize("dialog.network.auto")}
|
||||||
>
|
>
|
||||||
<ha-radio
|
<ha-radio
|
||||||
@change=${this._handleRadioValueChanged}
|
@change=${this._handleRadioValueChanged}
|
||||||
|
22
hassio/src/entrypoint.js.template
Normal file
22
hassio/src/entrypoint.js.template
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
(function () {
|
||||||
|
function loadES5(src) {
|
||||||
|
var el = document.createElement("script");
|
||||||
|
el.src = src;
|
||||||
|
document.body.appendChild(el);
|
||||||
|
}
|
||||||
|
if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) {
|
||||||
|
<% for (const entry of es5EntryJS) { %>
|
||||||
|
loadES5("<%= entry %>");
|
||||||
|
<% } %>
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
<% for (const entry of latestEntryJS) { %>
|
||||||
|
new Function("import('<%= entry %>')")();
|
||||||
|
<% } %>
|
||||||
|
} catch (err) {
|
||||||
|
<% for (const entry of es5EntryJS) { %>
|
||||||
|
loadES5("<%= entry %>");
|
||||||
|
<% } %>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
@ -44,10 +44,6 @@ export const hassioStyle = css`
|
|||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 0.25fr));
|
grid-template-columns: repeat(auto-fit, minmax(300px, 0.25fr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ha-call-api-button {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
.error {
|
.error {
|
||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
|
53
package.json
53
package.json
@ -26,20 +26,21 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "6.0.2",
|
"@braintree/sanitize-url": "6.0.2",
|
||||||
"@codemirror/autocomplete": "6.4.2",
|
"@codemirror/autocomplete": "6.5.1",
|
||||||
"@codemirror/commands": "6.2.2",
|
"@codemirror/commands": "6.2.3",
|
||||||
"@codemirror/language": "6.6.0",
|
"@codemirror/language": "6.6.0",
|
||||||
"@codemirror/legacy-modes": "6.3.2",
|
"@codemirror/legacy-modes": "6.3.2",
|
||||||
"@codemirror/search": "6.3.0",
|
"@codemirror/search": "6.3.0",
|
||||||
"@codemirror/state": "6.2.0",
|
"@codemirror/state": "6.2.0",
|
||||||
"@codemirror/view": "6.9.3",
|
"@codemirror/view": "6.9.5",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.5.1",
|
"@formatjs/intl-datetimeformat": "6.7.0",
|
||||||
|
"@formatjs/intl-displaynames": "6.3.1",
|
||||||
"@formatjs/intl-getcanonicallocales": "2.1.0",
|
"@formatjs/intl-getcanonicallocales": "2.1.0",
|
||||||
"@formatjs/intl-locale": "3.1.1",
|
"@formatjs/intl-locale": "3.2.1",
|
||||||
"@formatjs/intl-numberformat": "8.3.5",
|
"@formatjs/intl-numberformat": "8.4.1",
|
||||||
"@formatjs/intl-pluralrules": "5.1.10",
|
"@formatjs/intl-pluralrules": "5.2.1",
|
||||||
"@formatjs/intl-relativetimeformat": "11.1.10",
|
"@formatjs/intl-relativetimeformat": "11.2.1",
|
||||||
"@fullcalendar/core": "6.1.5",
|
"@fullcalendar/core": "6.1.5",
|
||||||
"@fullcalendar/daygrid": "6.1.5",
|
"@fullcalendar/daygrid": "6.1.5",
|
||||||
"@fullcalendar/interaction": "6.1.5",
|
"@fullcalendar/interaction": "6.1.5",
|
||||||
@ -90,8 +91,8 @@
|
|||||||
"@polymer/paper-toast": "3.0.1",
|
"@polymer/paper-toast": "3.0.1",
|
||||||
"@polymer/polymer": "3.5.1",
|
"@polymer/polymer": "3.5.1",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@vaadin/combo-box": "23.3.10",
|
"@vaadin/combo-box": "23.3.11",
|
||||||
"@vaadin/vaadin-themable-mixin": "23.3.10",
|
"@vaadin/vaadin-themable-mixin": "23.3.11",
|
||||||
"@vibrant/color": "3.2.1-alpha.1",
|
"@vibrant/color": "3.2.1-alpha.1",
|
||||||
"@vibrant/core": "3.2.1-alpha.1",
|
"@vibrant/core": "3.2.1-alpha.1",
|
||||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||||
@ -101,18 +102,18 @@
|
|||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"chart.js": "3.3.2",
|
"chart.js": "3.3.2",
|
||||||
"comlink": "4.4.1",
|
"comlink": "4.4.1",
|
||||||
"core-js": "3.30.0",
|
"core-js": "3.30.1",
|
||||||
"cropperjs": "1.5.13",
|
"cropperjs": "1.5.13",
|
||||||
"date-fns": "2.29.3",
|
"date-fns": "2.29.3",
|
||||||
"date-fns-tz": "2.0.0",
|
"date-fns-tz": "2.0.0",
|
||||||
"deep-clone-simple": "1.1.1",
|
"deep-clone-simple": "1.1.1",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"fuse.js": "6.6.2",
|
"fuse.js": "6.6.2",
|
||||||
"google-timezones-json": "1.0.2",
|
"google-timezones-json": "1.1.0",
|
||||||
"hls.js": "1.3.5",
|
"hls.js": "1.3.5",
|
||||||
"home-assistant-js-websocket": "8.0.1",
|
"home-assistant-js-websocket": "8.0.1",
|
||||||
"idb-keyval": "6.2.0",
|
"idb-keyval": "6.2.0",
|
||||||
"intl-messageformat": "10.3.3",
|
"intl-messageformat": "10.3.4",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"leaflet": "1.9.3",
|
"leaflet": "1.9.3",
|
||||||
"leaflet-draw": "1.0.4",
|
"leaflet-draw": "1.0.4",
|
||||||
@ -149,15 +150,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.21.4",
|
"@babel/core": "7.21.4",
|
||||||
"@babel/plugin-external-helpers": "7.18.6",
|
|
||||||
"@babel/plugin-proposal-class-properties": "7.18.6",
|
|
||||||
"@babel/plugin-proposal-decorators": "7.21.0",
|
"@babel/plugin-proposal-decorators": "7.21.0",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
|
|
||||||
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
|
|
||||||
"@babel/plugin-proposal-optional-chaining": "7.21.0",
|
|
||||||
"@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-env": "7.21.4",
|
||||||
"@babel/preset-typescript": "7.21.4",
|
"@babel/preset-typescript": "7.21.4",
|
||||||
"@koa/cors": "4.0.0",
|
"@koa/cors": "4.0.0",
|
||||||
@ -166,7 +159,7 @@
|
|||||||
"@octokit/rest": "19.0.7",
|
"@octokit/rest": "19.0.7",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@open-wc/dev-server-hmr": "0.1.4",
|
||||||
"@rollup/plugin-babel": "6.0.3",
|
"@rollup/plugin-babel": "6.0.3",
|
||||||
"@rollup/plugin-commonjs": "24.0.1",
|
"@rollup/plugin-commonjs": "24.1.0",
|
||||||
"@rollup/plugin-json": "6.0.0",
|
"@rollup/plugin-json": "6.0.0",
|
||||||
"@rollup/plugin-node-resolve": "15.0.2",
|
"@rollup/plugin-node-resolve": "15.0.2",
|
||||||
"@rollup/plugin-replace": "5.0.2",
|
"@rollup/plugin-replace": "5.0.2",
|
||||||
@ -185,8 +178,8 @@
|
|||||||
"@types/sortablejs": "1.15.1",
|
"@types/sortablejs": "1.15.1",
|
||||||
"@types/tar": "6.1.4",
|
"@types/tar": "6.1.4",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "5.57.1",
|
"@typescript-eslint/eslint-plugin": "5.59.0",
|
||||||
"@typescript-eslint/parser": "5.57.1",
|
"@typescript-eslint/parser": "5.59.0",
|
||||||
"@web/dev-server": "0.1.38",
|
"@web/dev-server": "0.1.38",
|
||||||
"@web/dev-server-rollup": "0.4.1",
|
"@web/dev-server-rollup": "0.4.1",
|
||||||
"babel-loader": "9.1.2",
|
"babel-loader": "9.1.2",
|
||||||
@ -200,23 +193,23 @@
|
|||||||
"eslint-import-resolver-webpack": "0.13.2",
|
"eslint-import-resolver-webpack": "0.13.2",
|
||||||
"eslint-plugin-disable": "2.0.3",
|
"eslint-plugin-disable": "2.0.3",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.27.5",
|
||||||
"eslint-plugin-lit": "1.8.2",
|
"eslint-plugin-lit": "1.8.3",
|
||||||
"eslint-plugin-lit-a11y": "2.4.1",
|
"eslint-plugin-lit-a11y": "2.4.1",
|
||||||
"eslint-plugin-unused-imports": "2.0.0",
|
"eslint-plugin-unused-imports": "2.0.0",
|
||||||
"eslint-plugin-wc": "1.4.0",
|
"eslint-plugin-wc": "1.4.0",
|
||||||
"esprima": "4.0.1",
|
"esprima": "4.0.1",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
"fs-extra": "11.1.1",
|
"fs-extra": "11.1.1",
|
||||||
"glob": "9.3.4",
|
"glob": "10.2.1",
|
||||||
"gulp": "4.0.2",
|
"gulp": "4.0.2",
|
||||||
"gulp-flatmap": "1.0.2",
|
"gulp-flatmap": "1.0.2",
|
||||||
"gulp-json-transform": "0.4.8",
|
"gulp-json-transform": "0.4.8",
|
||||||
"gulp-merge-json": "2.1.2",
|
"gulp-merge-json": "2.1.2",
|
||||||
"gulp-rename": "2.0.0",
|
"gulp-rename": "2.0.0",
|
||||||
"gulp-zopfli-green": "6.0.1",
|
"gulp-zopfli-green": "6.0.1",
|
||||||
"html-minifier-terser": "7.1.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "8.0.3",
|
"husky": "8.0.3",
|
||||||
"instant-mocha": "1.5.0",
|
"instant-mocha": "1.5.1",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "13.2.1",
|
"lint-staged": "13.2.1",
|
||||||
"lit-analyzer": "1.2.1",
|
"lit-analyzer": "1.2.1",
|
||||||
@ -234,7 +227,7 @@
|
|||||||
"rollup-plugin-terser": "7.0.2",
|
"rollup-plugin-terser": "7.0.2",
|
||||||
"rollup-plugin-visualizer": "5.9.0",
|
"rollup-plugin-visualizer": "5.9.0",
|
||||||
"serve-handler": "6.1.5",
|
"serve-handler": "6.1.5",
|
||||||
"sinon": "15.0.3",
|
"sinon": "15.0.4",
|
||||||
"source-map-url": "0.4.1",
|
"source-map-url": "0.4.1",
|
||||||
"systemjs": "6.14.1",
|
"systemjs": "6.14.1",
|
||||||
"tar": "6.1.13",
|
"tar": "6.1.13",
|
||||||
@ -245,7 +238,7 @@
|
|||||||
"vinyl-source-stream": "2.0.0",
|
"vinyl-source-stream": "2.0.0",
|
||||||
"webpack": "=5.72.1",
|
"webpack": "=5.72.1",
|
||||||
"webpack-cli": "5.0.1",
|
"webpack-cli": "5.0.1",
|
||||||
"webpack-dev-server": "4.13.2",
|
"webpack-dev-server": "4.13.3",
|
||||||
"webpack-manifest-plugin": "5.0.0",
|
"webpack-manifest-plugin": "5.0.0",
|
||||||
"webpackbar": "5.0.2",
|
"webpackbar": "5.0.2",
|
||||||
"workbox-build": "6.5.4"
|
"workbox-build": "6.5.4"
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20230411.1"
|
version = "20230426.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "../components/ha-alert";
|
import "../components/ha-alert";
|
||||||
import "../components/ha-checkbox";
|
import "../components/ha-checkbox";
|
||||||
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
|
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
|
||||||
@ -20,13 +21,12 @@ import {
|
|||||||
DataEntryFlowStep,
|
DataEntryFlowStep,
|
||||||
DataEntryFlowStepForm,
|
DataEntryFlowStepForm,
|
||||||
} from "../data/data_entry_flow";
|
} from "../data/data_entry_flow";
|
||||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
|
||||||
import "./ha-password-manager-polyfill";
|
import "./ha-password-manager-polyfill";
|
||||||
|
|
||||||
type State = "loading" | "error" | "step";
|
type State = "loading" | "error" | "step";
|
||||||
|
|
||||||
@customElement("ha-auth-flow")
|
@customElement("ha-auth-flow")
|
||||||
export class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
export class HaAuthFlow extends LitElement {
|
||||||
@property({ attribute: false }) public authProvider?: AuthProvider;
|
@property({ attribute: false }) public authProvider?: AuthProvider;
|
||||||
|
|
||||||
@property() public clientId?: string;
|
@property() public clientId?: string;
|
||||||
@ -35,6 +35,8 @@ export class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public oauth2State?: string;
|
@property() public oauth2State?: string;
|
||||||
|
|
||||||
|
@property() public localize!: LocalizeFunc;
|
||||||
|
|
||||||
@state() private _state: State = "loading";
|
@state() private _state: State = "loading";
|
||||||
|
|
||||||
@state() private _stepData?: Record<string, any>;
|
@state() private _stepData?: Record<string, any>;
|
||||||
|
@ -82,12 +82,13 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
.redirectUri=${this.redirectUri}
|
.redirectUri=${this.redirectUri}
|
||||||
.oauth2State=${this.oauth2State}
|
.oauth2State=${this.oauth2State}
|
||||||
.authProvider=${this._authProvider}
|
.authProvider=${this._authProvider}
|
||||||
|
.localize=${this.localize}
|
||||||
></ha-auth-flow>
|
></ha-auth-flow>
|
||||||
|
|
||||||
${inactiveProviders.length > 0
|
${inactiveProviders.length > 0
|
||||||
? html`
|
? html`
|
||||||
<ha-pick-auth-provider
|
<ha-pick-auth-provider
|
||||||
.resources=${this.resources}
|
.localize=${this.localize}
|
||||||
.clientId=${this.clientId}
|
.clientId=${this.clientId}
|
||||||
.authProviders=${inactiveProviders}
|
.authProviders=${inactiveProviders}
|
||||||
@pick-auth-provider=${this._handleAuthProviderPick}
|
@pick-auth-provider=${this._handleAuthProviderPick}
|
||||||
|
@ -2,10 +2,10 @@ import "@material/mwc-list";
|
|||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "../components/ha-icon-next";
|
import "../components/ha-icon-next";
|
||||||
import "../components/ha-list-item";
|
import "../components/ha-list-item";
|
||||||
import { AuthProvider } from "../data/auth";
|
import { AuthProvider } from "../data/auth";
|
||||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -14,9 +14,11 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ha-pick-auth-provider")
|
@customElement("ha-pick-auth-provider")
|
||||||
export class HaPickAuthProvider extends litLocalizeLiteMixin(LitElement) {
|
export class HaPickAuthProvider extends LitElement {
|
||||||
@property() public authProviders: AuthProvider[] = [];
|
@property() public authProviders: AuthProvider[] = [];
|
||||||
|
|
||||||
|
@property() public localize!: LocalizeFunc;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p>
|
<p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p>
|
||||||
|
@ -50,6 +50,7 @@ import {
|
|||||||
mdiRobotVacuum,
|
mdiRobotVacuum,
|
||||||
mdiScriptText,
|
mdiScriptText,
|
||||||
mdiSineWave,
|
mdiSineWave,
|
||||||
|
mdiSpeakerMessage,
|
||||||
mdiSpeedometer,
|
mdiSpeedometer,
|
||||||
mdiSunWireless,
|
mdiSunWireless,
|
||||||
mdiThermometer,
|
mdiThermometer,
|
||||||
@ -75,8 +76,8 @@ export const DEFAULT_DOMAIN_ICON = mdiBookmark;
|
|||||||
|
|
||||||
/** Icons for each domain */
|
/** Icons for each domain */
|
||||||
export const FIXED_DOMAIN_ICONS = {
|
export const FIXED_DOMAIN_ICONS = {
|
||||||
alert: mdiAlert,
|
|
||||||
air_quality: mdiAirFilter,
|
air_quality: mdiAirFilter,
|
||||||
|
alert: mdiAlert,
|
||||||
calendar: mdiCalendar,
|
calendar: mdiCalendar,
|
||||||
climate: mdiThermostat,
|
climate: mdiThermostat,
|
||||||
configurator: mdiCog,
|
configurator: mdiCog,
|
||||||
@ -106,10 +107,12 @@ export const FIXED_DOMAIN_ICONS = {
|
|||||||
script: mdiScriptText,
|
script: mdiScriptText,
|
||||||
select: mdiFormatListBulleted,
|
select: mdiFormatListBulleted,
|
||||||
sensor: mdiEye,
|
sensor: mdiEye,
|
||||||
siren: mdiBullhorn,
|
|
||||||
simple_alarm: mdiBell,
|
simple_alarm: mdiBell,
|
||||||
|
siren: mdiBullhorn,
|
||||||
|
stt: mdiMicrophoneMessage,
|
||||||
text: mdiFormTextbox,
|
text: mdiFormTextbox,
|
||||||
timer: mdiTimerOutline,
|
timer: mdiTimerOutline,
|
||||||
|
tts: mdiSpeakerMessage,
|
||||||
updater: mdiCloudUpload,
|
updater: mdiCloudUpload,
|
||||||
vacuum: mdiRobotVacuum,
|
vacuum: mdiRobotVacuum,
|
||||||
zone: mdiMapMarkerRadius,
|
zone: mdiMapMarkerRadius,
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
export const SpeechRecognition =
|
|
||||||
window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
||||||
export const SpeechGrammarList =
|
|
||||||
window.SpeechGrammarList || window.webkitSpeechGrammarList;
|
|
||||||
export const SpeechRecognitionEvent =
|
|
||||||
// @ts-expect-error
|
|
||||||
window.SpeechRecognitionEvent || window.webkitSpeechRecognitionEvent;
|
|
@ -193,11 +193,9 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// state of button is a timestamp
|
// state is a timestamp
|
||||||
if (
|
if (
|
||||||
domain === "button" ||
|
["button", "input_button", "scene", "stt", "tts"].includes(domain) ||
|
||||||
domain === "input_button" ||
|
|
||||||
domain === "scene" ||
|
|
||||||
(domain === "sensor" && attributes.device_class === "timestamp")
|
(domain === "sensor" && attributes.device_class === "timestamp")
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
22
src/common/language/format_language.ts
Normal file
22
src/common/language/format_language.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
|
|
||||||
|
export const formatLanguageCode = (
|
||||||
|
languageCode: string,
|
||||||
|
locale: FrontendLocaleData
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
return formatLanguageCodeMem(locale)?.of(languageCode) ?? languageCode;
|
||||||
|
} catch {
|
||||||
|
return languageCode;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatLanguageCodeMem = memoizeOne((locale: FrontendLocaleData) =>
|
||||||
|
Intl && "DisplayNames" in Intl
|
||||||
|
? new Intl.DisplayNames(locale.language, {
|
||||||
|
type: "language",
|
||||||
|
fallback: "code",
|
||||||
|
})
|
||||||
|
: undefined
|
||||||
|
);
|
@ -2,6 +2,7 @@ import { shouldPolyfill as shouldPolyfillLocale } from "@formatjs/intl-locale/li
|
|||||||
import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-pluralrules/lib/should-polyfill";
|
import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-pluralrules/lib/should-polyfill";
|
||||||
import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-relativetimeformat/lib/should-polyfill";
|
import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-relativetimeformat/lib/should-polyfill";
|
||||||
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
|
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
|
||||||
|
import { shouldPolyfill as shouldPolyfillDisplayName } from "@formatjs/intl-displaynames/lib/should-polyfill";
|
||||||
import IntlMessageFormat from "intl-messageformat";
|
import IntlMessageFormat from "intl-messageformat";
|
||||||
import { Resources, TranslationDict } from "../../types";
|
import { Resources, TranslationDict } from "../../types";
|
||||||
import { getLocalLanguage } from "../../util/common-translation";
|
import { getLocalLanguage } from "../../util/common-translation";
|
||||||
@ -83,6 +84,10 @@ if (__BUILD__ === "latest") {
|
|||||||
polyfills.push(import("@formatjs/intl-datetimeformat/polyfill"));
|
polyfills.push(import("@formatjs/intl-datetimeformat/polyfill"));
|
||||||
polyfills.push(import("@formatjs/intl-datetimeformat/add-all-tz"));
|
polyfills.push(import("@formatjs/intl-datetimeformat/add-all-tz"));
|
||||||
}
|
}
|
||||||
|
if (shouldPolyfillDisplayName(locale)) {
|
||||||
|
polyfills.push(import("@formatjs/intl-displaynames/polyfill"));
|
||||||
|
polyfills.push(import("@formatjs/intl-displaynames/locale-data/en"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const polyfillsLoaded =
|
export const polyfillsLoaded =
|
||||||
@ -216,6 +221,17 @@ export const loadPolyfillLocales = async (language: string) => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Intl.DateTimeFormat.__addLocaleData(await result.json());
|
Intl.DateTimeFormat.__addLocaleData(await result.json());
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
Intl.DisplayNames &&
|
||||||
|
// @ts-ignore
|
||||||
|
typeof Intl.DisplayNames.__addLocaleData === "function"
|
||||||
|
) {
|
||||||
|
const result = await fetch(
|
||||||
|
`/static/locale-data/intl-displaynames/${language}.json`
|
||||||
|
);
|
||||||
|
// @ts-ignore
|
||||||
|
Intl.DisplayNames.__addLocaleData(await result.json());
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -73,7 +73,7 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
|||||||
main?: boolean;
|
main?: boolean;
|
||||||
title: TemplateResult | string;
|
title: TemplateResult | string;
|
||||||
label?: TemplateResult | string;
|
label?: TemplateResult | string;
|
||||||
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
|
type?: "numeric" | "icon" | "icon-button" | "overflow-menu" | "flex";
|
||||||
template?: (data: any, row: T) => TemplateResult | string | typeof nothing;
|
template?: (data: any, row: T) => TemplateResult | string | typeof nothing;
|
||||||
width?: string;
|
width?: string;
|
||||||
maxWidth?: string;
|
maxWidth?: string;
|
||||||
@ -359,10 +359,10 @@ export class HaDataTable extends LitElement {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
if (row.append) {
|
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) {
|
if (row.empty) {
|
||||||
return html` <div class="mdc-data-table__row"></div> `;
|
return html`<div class="mdc-data-table__row"></div>`;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@ -406,6 +406,7 @@ export class HaDataTable extends LitElement {
|
|||||||
<div
|
<div
|
||||||
role=${column.main ? "rowheader" : "cell"}
|
role=${column.main ? "rowheader" : "cell"}
|
||||||
class="mdc-data-table__cell ${classMap({
|
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--numeric": column.type === "numeric",
|
||||||
"mdc-data-table__cell--icon": column.type === "icon",
|
"mdc-data-table__cell--icon": column.type === "icon",
|
||||||
"mdc-data-table__cell--icon-button":
|
"mdc-data-table__cell--icon-button":
|
||||||
@ -663,6 +664,10 @@ export class HaDataTable extends LitElement {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__cell.mdc-data-table__cell--flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
.mdc-data-table__cell.mdc-data-table__cell--icon {
|
.mdc-data-table__cell.mdc-data-table__cell--icon {
|
||||||
overflow: initial;
|
overflow: initial;
|
||||||
}
|
}
|
||||||
|
129
src/components/ha-aliases-editor.ts
Normal file
129
src/components/ha-aliases-editor.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
109
src/components/ha-assist-pipeline-picker.ts
Normal file
109
src/components/ha-assist-pipeline-picker.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
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 { formatLanguageCode } from "../common/language/format_language";
|
||||||
|
import { AssistPipeline, listAssistPipelines } from "../data/assist_pipeline";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import "./ha-list-item";
|
||||||
|
import "./ha-select";
|
||||||
|
import type { HaSelect } from "./ha-select";
|
||||||
|
|
||||||
|
const PREFERRED = "__PREFERRED_PIPELINE_OPTION__";
|
||||||
|
|
||||||
|
@customElement("ha-assist-pipeline-picker")
|
||||||
|
export class HaAssistPipelinePicker 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() _pipelines?: AssistPipeline[];
|
||||||
|
|
||||||
|
@state() _preferredPipeline: string | null = null;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._pipelines) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const value = this.value ?? PREFERRED;
|
||||||
|
return html`
|
||||||
|
<ha-select
|
||||||
|
.label=${this.label ||
|
||||||
|
this.hass!.localize("ui.components.pipeline-picker.pipeline")}
|
||||||
|
.value=${value}
|
||||||
|
.required=${this.required}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@selected=${this._changed}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
>
|
||||||
|
<ha-list-item .value=${PREFERRED}>
|
||||||
|
${this.hass!.localize("ui.components.pipeline-picker.preferred", {
|
||||||
|
preferred: this._pipelines.find(
|
||||||
|
(pipeline) => pipeline.id === this._preferredPipeline
|
||||||
|
)?.name,
|
||||||
|
})}
|
||||||
|
</ha-list-item>
|
||||||
|
${this._pipelines.map(
|
||||||
|
(pipeline) =>
|
||||||
|
html`<ha-list-item .value=${pipeline.id}>
|
||||||
|
${pipeline.name}
|
||||||
|
(${formatLanguageCode(pipeline.language, this.hass.locale)})
|
||||||
|
</ha-list-item>`
|
||||||
|
)}
|
||||||
|
</ha-select>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(
|
||||||
|
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
||||||
|
): void {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
listAssistPipelines(this.hass).then((pipelines) => {
|
||||||
|
this._pipelines = pipelines.pipelines;
|
||||||
|
this._preferredPipeline = pipelines.preferred_pipeline;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 === PREFERRED)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.value = target.value === PREFERRED ? undefined : target.value;
|
||||||
|
fireEvent(this, "value-changed", { value: this.value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-assist-pipeline-picker": HaAssistPipelinePicker;
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,9 @@ export class HaButton extends Button {
|
|||||||
margin-inline-end: 8px;
|
margin-inline-end: 8px;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
.mdc-button {
|
||||||
|
height: var(--button-height, 36px);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,9 @@ import { customElement, property, query } from "lit/decorators";
|
|||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-list-item";
|
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
|
import "./ha-list-item";
|
||||||
|
import "./ha-textfield";
|
||||||
import type { HaTextField } from "./ha-textfield";
|
import type { HaTextField } from "./ha-textfield";
|
||||||
|
|
||||||
registerStyles(
|
registerStyles(
|
||||||
|
159
src/components/ha-conversation-agent-picker.ts
Normal file
159
src/components/ha-conversation-agent-picker.ts
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
|
import { debounce } from "../common/util/debounce";
|
||||||
|
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 language?: 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[];
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._agents) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const value =
|
||||||
|
this.value ??
|
||||||
|
(this.required &&
|
||||||
|
(!this.language ||
|
||||||
|
this._agents
|
||||||
|
.find((agent) => agent.id === "homeassistant")
|
||||||
|
?.supported_languages.includes(this.language))
|
||||||
|
? "homeassistant"
|
||||||
|
: 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}
|
||||||
|
.disabled=${agent.supported_languages !== "*" &&
|
||||||
|
agent.supported_languages.length === 0}
|
||||||
|
>
|
||||||
|
${agent.name}
|
||||||
|
</ha-list-item>`
|
||||||
|
)}
|
||||||
|
</ha-select>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
this._updateAgents();
|
||||||
|
} else if (changedProperties.has("language")) {
|
||||||
|
this._debouncedUpdateAgents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _debouncedUpdateAgents = debounce(() => this._updateAgents(), 500);
|
||||||
|
|
||||||
|
private async _updateAgents() {
|
||||||
|
const { agents } = await listAgents(
|
||||||
|
this.hass,
|
||||||
|
this.language,
|
||||||
|
this.hass.config.country || undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
this._agents = agents;
|
||||||
|
|
||||||
|
if (!this.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedAgent = agents.find((agent) => agent.id === this.value);
|
||||||
|
|
||||||
|
fireEvent(this, "supported-languages-changed", {
|
||||||
|
value: selectedAgent?.supported_languages,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
!selectedAgent ||
|
||||||
|
(selectedAgent.supported_languages !== "*" &&
|
||||||
|
selectedAgent.supported_languages.length === 0)
|
||||||
|
) {
|
||||||
|
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 });
|
||||||
|
fireEvent(this, "supported-languages-changed", {
|
||||||
|
value: this._agents!.find((agent) => agent.id === this.value)
|
||||||
|
?.supported_languages,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-conversation-agent-picker": HaConversationAgentPicker;
|
||||||
|
}
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"supported-languages-changed": { value: "*" | string[] | undefined };
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import { FOCUS_TARGET } from "../dialogs/make-dialog-manager";
|
|||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
const SUPPRESS_DEFAULT_PRESS_SELECTOR = ["button"];
|
const SUPPRESS_DEFAULT_PRESS_SELECTOR = ["button", "ha-list-item"];
|
||||||
|
|
||||||
export const createCloseHeading = (
|
export const createCloseHeading = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -92,7 +92,7 @@ export class HaDialog extends DialogBase {
|
|||||||
padding: 24px 24px 0 24px;
|
padding: 24px 24px 0 24px;
|
||||||
}
|
}
|
||||||
.mdc-dialog__actions {
|
.mdc-dialog__actions {
|
||||||
padding: 0 24px 24px 24px;
|
padding: 12px 24px 12px 24px;
|
||||||
}
|
}
|
||||||
.mdc-dialog__title::before {
|
.mdc-dialog__title::before {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
import {
|
|
||||||
DIRECTION_LEFT,
|
|
||||||
DIRECTION_RIGHT,
|
|
||||||
Manager,
|
|
||||||
Swipe,
|
|
||||||
} from "@egjs/hammerjs";
|
|
||||||
import { DrawerBase } from "@material/mwc-drawer/mwc-drawer-base";
|
import { DrawerBase } from "@material/mwc-drawer/mwc-drawer-base";
|
||||||
import { styles } from "@material/mwc-drawer/mwc-drawer.css";
|
import { styles } from "@material/mwc-drawer/mwc-drawer.css";
|
||||||
import { css, PropertyValues } from "lit";
|
import { css, PropertyValues } from "lit";
|
||||||
@ -40,35 +34,51 @@ export class HaDrawer extends DrawerBase {
|
|||||||
this.mdcRoot.dir = this.direction;
|
this.mdcRoot.dir = this.direction;
|
||||||
}
|
}
|
||||||
if (changedProps.has("open") && this.open && this.type === "modal") {
|
if (changedProps.has("open") && this.open && this.type === "modal") {
|
||||||
this._mc = new Manager(document, {
|
this._setupSwipe();
|
||||||
touchAction: "pan-y",
|
|
||||||
});
|
|
||||||
this._mc.add(
|
|
||||||
new Swipe({
|
|
||||||
direction:
|
|
||||||
this.direction === "rtl" ? DIRECTION_RIGHT : DIRECTION_LEFT,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
this._mc.on("swipeleft swiperight", () => {
|
|
||||||
fireEvent(this, "hass-toggle-menu", { open: false });
|
|
||||||
});
|
|
||||||
} else if (this._mc) {
|
} else if (this._mc) {
|
||||||
this._mc.destroy();
|
this._mc.destroy();
|
||||||
this._mc = undefined;
|
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 = [
|
static override styles = [
|
||||||
styles,
|
styles,
|
||||||
css`
|
css`
|
||||||
.mdc-drawer {
|
.mdc-drawer {
|
||||||
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
border-color: var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||||
}
|
}
|
||||||
.mdc-drawer.mdc-drawer--modal.mdc-drawer--open {
|
.mdc-drawer.mdc-drawer--modal.mdc-drawer--open {
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
}
|
}
|
||||||
.mdc-drawer-app-content {
|
.mdc-drawer-app-content {
|
||||||
transform: translateZ(0);
|
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;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@ -71,6 +71,7 @@ export class HaFormExpendable extends LitElement implements HaFormElement {
|
|||||||
display: block;
|
display: block;
|
||||||
--expansion-panel-content-padding: 0;
|
--expansion-panel-content-padding: 0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
--ha-card-border-radius: 6px;
|
||||||
}
|
}
|
||||||
ha-svg-icon,
|
ha-svg-icon,
|
||||||
ha-icon {
|
ha-icon {
|
||||||
|
@ -33,6 +33,11 @@ export class HaFormGrid extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public computeHelper?: (schema: HaFormSchema) => string;
|
@property() public computeHelper?: (schema: HaFormSchema) => string;
|
||||||
|
|
||||||
|
public async focus() {
|
||||||
|
await this.updateComplete;
|
||||||
|
this.renderRoot.querySelector("ha-form")?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
if (changedProps.has("schema")) {
|
if (changedProps.has("schema")) {
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
|
ReactiveElement,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
@ -56,13 +57,18 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public localizeValue?: (key: string) => string;
|
@property() public localizeValue?: (key: string) => string;
|
||||||
|
|
||||||
public focus() {
|
public async focus() {
|
||||||
const root = this.shadowRoot?.querySelector(".root");
|
await this.updateComplete;
|
||||||
|
const root = this.renderRoot.querySelector(".root");
|
||||||
if (!root) {
|
if (!root) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const child of root.children) {
|
for (const child of root.children) {
|
||||||
if (child.tagName !== "HA-ALERT") {
|
if (child.tagName !== "HA-ALERT") {
|
||||||
|
if (child instanceof ReactiveElement) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await child.updateComplete;
|
||||||
|
}
|
||||||
(child as HTMLElement).focus();
|
(child as HTMLElement).focus();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -211,6 +211,7 @@ export class Gauge extends LitElement {
|
|||||||
font-size: 50px;
|
font-size: 50px;
|
||||||
fill: var(--primary-text-color);
|
fill: var(--primary-text-color);
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
|
direction: ltr;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
162
src/components/ha-language-picker.ts
Normal file
162
src/components/ha-language-picker.ts
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
|
import { formatLanguageCode } from "../common/language/format_language";
|
||||||
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
|
import { FrontendLocaleData } from "../data/translation";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import "./ha-list-item";
|
||||||
|
import "./ha-select";
|
||||||
|
import type { HaSelect } from "./ha-select";
|
||||||
|
|
||||||
|
@customElement("ha-language-picker")
|
||||||
|
export class HaLanguagePicker extends LitElement {
|
||||||
|
@property() public value?: string;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public languages?: string[];
|
||||||
|
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public nativeName = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public noSort = false;
|
||||||
|
|
||||||
|
@state() _defaultLanguages: string[] = [];
|
||||||
|
|
||||||
|
@query("ha-select") private _select!: HaSelect;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this._computeDefaultLanguageOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
if (changedProperties.has("languages") || changedProperties.has("value")) {
|
||||||
|
this._select.layoutOptions();
|
||||||
|
if (this._select.value !== this.value) {
|
||||||
|
fireEvent(this, "value-changed", { value: this._select.value });
|
||||||
|
}
|
||||||
|
if (!this.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const languageOptions = this._getLanguagesOptions(
|
||||||
|
this.languages ?? this._defaultLanguages,
|
||||||
|
this.hass.locale,
|
||||||
|
this.nativeName
|
||||||
|
);
|
||||||
|
const selectedItem = languageOptions.find(
|
||||||
|
(option) => option.value === this.value
|
||||||
|
);
|
||||||
|
if (!selectedItem) {
|
||||||
|
this.value = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getLanguagesOptions = memoizeOne(
|
||||||
|
(languages: string[], locale: FrontendLocaleData, nativeName: boolean) => {
|
||||||
|
let options: { label: string; value: string }[] = [];
|
||||||
|
|
||||||
|
if (nativeName) {
|
||||||
|
const translations = this.hass.translationMetadata.translations;
|
||||||
|
options = languages.map((lang) => ({
|
||||||
|
value: lang,
|
||||||
|
label: translations[lang]?.nativeName ?? lang,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
options = languages.map((lang) => ({
|
||||||
|
value: lang,
|
||||||
|
label: formatLanguageCode(lang, locale),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.noSort) {
|
||||||
|
options.sort((a, b) =>
|
||||||
|
caseInsensitiveStringCompare(a.label, b.label, locale.language)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _computeDefaultLanguageOptions() {
|
||||||
|
if (!this.hass.translationMetadata?.translations) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._defaultLanguages = Object.keys(
|
||||||
|
this.hass.translationMetadata.translations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
const languageOptions = this._getLanguagesOptions(
|
||||||
|
this.languages ?? this._defaultLanguages,
|
||||||
|
this.hass.locale,
|
||||||
|
this.nativeName
|
||||||
|
);
|
||||||
|
|
||||||
|
const value =
|
||||||
|
this.value ?? (this.required ? languageOptions[0]?.value : this.value);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-select
|
||||||
|
.label=${this.label ||
|
||||||
|
this.hass.localize("ui.components.language-picker.language")}
|
||||||
|
.value=${value}
|
||||||
|
.required=${this.required}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@selected=${this._changed}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
>
|
||||||
|
${languageOptions.length === 0
|
||||||
|
? html`<ha-list-item value=""
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.components.language-picker.no_languages"
|
||||||
|
)}</ha-list-item
|
||||||
|
>`
|
||||||
|
: languageOptions.map(
|
||||||
|
(option) => html`
|
||||||
|
<ha-list-item .value=${option.value}
|
||||||
|
>${option.label}</ha-list-item
|
||||||
|
>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-select>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.value = target.value;
|
||||||
|
fireEvent(this, "value-changed", { value: this.value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-language-picker": HaLanguagePicker;
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,9 @@ export class HaListItem extends ListItemBase {
|
|||||||
margin-inline-end: 0px !important;
|
margin-inline-end: 0px !important;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
.mdc-deprecated-list-item__meta {
|
||||||
|
display: var(--mdc-list-item-meta-display);
|
||||||
|
}
|
||||||
:host([multiline-secondary]) {
|
:host([multiline-secondary]) {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
@ -54,6 +57,9 @@ export class HaListItem extends ListItemBase {
|
|||||||
.mdc-deprecated-list-item__primary-text::before {
|
.mdc-deprecated-list-item__primary-text::before {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
:host([disabled]) {
|
||||||
|
color: var(--disabled-text-color);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
45
src/components/ha-selector/ha-selector-assist-pipeline.ts
Normal file
45
src/components/ha-selector/ha-selector-assist-pipeline.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { AssistPipelineSelector } from "../../data/selector";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-assist-pipeline-picker";
|
||||||
|
|
||||||
|
@customElement("ha-selector-assist_pipeline")
|
||||||
|
export class HaAssistPipelineSelector extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public selector!: AssistPipelineSelector;
|
||||||
|
|
||||||
|
@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-assist-pipeline-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value}
|
||||||
|
.label=${this.label}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-assist-pipeline-picker>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
ha-conversation-agent-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-assist_pipeline": HaAssistPipelineSelector;
|
||||||
|
}
|
||||||
|
}
|
51
src/components/ha-selector/ha-selector-conversation-agent.ts
Normal file
51
src/components/ha-selector/ha-selector-conversation-agent.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public context?: {
|
||||||
|
language?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`<ha-conversation-agent-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value}
|
||||||
|
.language=${this.selector.conversation_agent?.language ||
|
||||||
|
this.context?.language}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
50
src/components/ha-selector/ha-selector-language.ts
Normal file
50
src/components/ha-selector/ha-selector-language.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { LanguageSelector } from "../../data/selector";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-language-picker";
|
||||||
|
|
||||||
|
@customElement("ha-selector-language")
|
||||||
|
export class HaLanguageSelector extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public selector!: LanguageSelector;
|
||||||
|
|
||||||
|
@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-language-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value}
|
||||||
|
.label=${this.label}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.languages=${this.selector.language?.languages}
|
||||||
|
.nativeName=${Boolean(this.selector?.language?.native_name)}
|
||||||
|
.noSort=${Boolean(this.selector?.language?.no_sort)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-language-picker>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
ha-language-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-language": HaLanguageSelector;
|
||||||
|
}
|
||||||
|
}
|
@ -135,7 +135,7 @@ export class HaSelectSelector extends LitElement {
|
|||||||
.helper=${this.helper}
|
.helper=${this.helper}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required && !value.length}
|
.required=${this.required && !value.length}
|
||||||
.value=${this._filter}
|
.value=${""}
|
||||||
.items=${optionItems}
|
.items=${optionItems}
|
||||||
.allowCustomValue=${this.selector.select.custom_value ?? false}
|
.allowCustomValue=${this.selector.select.custom_value ?? false}
|
||||||
@filter-changed=${this._filterChanged}
|
@filter-changed=${this._filterChanged}
|
||||||
@ -213,7 +213,7 @@ export class HaSelectSelector extends LitElement {
|
|||||||
private _valueChanged(ev) {
|
private _valueChanged(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const value = ev.detail?.value || ev.target.value;
|
const value = ev.detail?.value || ev.target.value;
|
||||||
if (this.disabled || value === undefined) {
|
if (this.disabled || value === undefined || value === this.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
|
50
src/components/ha-selector/ha-selector-stt.ts
Normal file
50
src/components/ha-selector/ha-selector-stt.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -30,6 +30,13 @@ export class HaTextSelector extends LitElement {
|
|||||||
|
|
||||||
@state() private _unmaskedPassword = false;
|
@state() private _unmaskedPassword = false;
|
||||||
|
|
||||||
|
public async focus() {
|
||||||
|
await this.updateComplete;
|
||||||
|
(
|
||||||
|
this.renderRoot.querySelector("ha-textarea, ha-textfield") as HTMLElement
|
||||||
|
)?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (this.selector.text?.multiline) {
|
if (this.selector.text?.multiline) {
|
||||||
return html`<ha-textarea
|
return html`<ha-textarea
|
||||||
|
52
src/components/ha-selector/ha-selector-tts-voice.ts
Normal file
52
src/components/ha-selector/ha-selector-tts-voice.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { css, html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { TTSVoiceSelector } from "../../data/selector";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-tts-voice-picker";
|
||||||
|
|
||||||
|
@customElement("ha-selector-tts_voice")
|
||||||
|
export class HaTTSVoiceSelector extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public selector!: TTSVoiceSelector;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
engineId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`<ha-tts-voice-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value}
|
||||||
|
.label=${this.label}
|
||||||
|
.helper=${this.helper}
|
||||||
|
.language=${this.selector.tts_voice?.language || this.context?.language}
|
||||||
|
.engineId=${this.selector.tts_voice?.engineId || this.context?.engineId}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
|
></ha-tts-voice-picker>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
ha-tts-picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-tts-voice": HaTTSVoiceSelector;
|
||||||
|
}
|
||||||
|
}
|
50
src/components/ha-selector/ha-selector-tts.ts
Normal file
50
src/components/ha-selector/ha-selector-tts.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
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 "../../panels/lovelace/components/hui-action-editor";
|
||||||
import { ActionConfig } from "../../data/lovelace";
|
import { ActionConfig } from "../../data/lovelace";
|
||||||
|
|
||||||
@customElement("ha-selector-ui-action")
|
@customElement("ha-selector-ui_action")
|
||||||
export class HaSelectorUiAction extends LitElement {
|
export class HaSelectorUiAction extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ export class HaSelectorUiAction extends LitElement {
|
|||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.config=${this.value}
|
.config=${this.value}
|
||||||
.actions=${this.selector["ui-action"]?.actions}
|
.actions=${this.selector.ui_action?.actions}
|
||||||
.tooltipText=${this.helper}
|
.tooltipText=${this.helper}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></hui-action-editor>
|
></hui-action-editor>
|
||||||
|
@ -6,7 +6,7 @@ import { UiColorSelector } from "../../data/selector";
|
|||||||
import "../../panels/lovelace/components/hui-color-picker";
|
import "../../panels/lovelace/components/hui-color-picker";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
@customElement("ha-selector-ui-color")
|
@customElement("ha-selector-ui_color")
|
||||||
export class HaSelectorUiColor extends LitElement {
|
export class HaSelectorUiColor extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@ -14,9 +14,11 @@ const LOAD_ELEMENTS = {
|
|||||||
addon: () => import("./ha-selector-addon"),
|
addon: () => import("./ha-selector-addon"),
|
||||||
area: () => import("./ha-selector-area"),
|
area: () => import("./ha-selector-area"),
|
||||||
attribute: () => import("./ha-selector-attribute"),
|
attribute: () => import("./ha-selector-attribute"),
|
||||||
|
assist_pipeline: () => import("./ha-selector-assist-pipeline"),
|
||||||
boolean: () => import("./ha-selector-boolean"),
|
boolean: () => import("./ha-selector-boolean"),
|
||||||
color_rgb: () => import("./ha-selector-color-rgb"),
|
color_rgb: () => import("./ha-selector-color-rgb"),
|
||||||
config_entry: () => import("./ha-selector-config-entry"),
|
config_entry: () => import("./ha-selector-config-entry"),
|
||||||
|
conversation_agent: () => import("./ha-selector-conversation-agent"),
|
||||||
constant: () => import("./ha-selector-constant"),
|
constant: () => import("./ha-selector-constant"),
|
||||||
date: () => import("./ha-selector-date"),
|
date: () => import("./ha-selector-date"),
|
||||||
datetime: () => import("./ha-selector-datetime"),
|
datetime: () => import("./ha-selector-datetime"),
|
||||||
@ -25,11 +27,13 @@ const LOAD_ELEMENTS = {
|
|||||||
entity: () => import("./ha-selector-entity"),
|
entity: () => import("./ha-selector-entity"),
|
||||||
statistic: () => import("./ha-selector-statistic"),
|
statistic: () => import("./ha-selector-statistic"),
|
||||||
file: () => import("./ha-selector-file"),
|
file: () => import("./ha-selector-file"),
|
||||||
|
language: () => import("./ha-selector-language"),
|
||||||
navigation: () => import("./ha-selector-navigation"),
|
navigation: () => import("./ha-selector-navigation"),
|
||||||
number: () => import("./ha-selector-number"),
|
number: () => import("./ha-selector-number"),
|
||||||
object: () => import("./ha-selector-object"),
|
object: () => import("./ha-selector-object"),
|
||||||
select: () => import("./ha-selector-select"),
|
select: () => import("./ha-selector-select"),
|
||||||
state: () => import("./ha-selector-state"),
|
state: () => import("./ha-selector-state"),
|
||||||
|
stt: () => import("./ha-selector-stt"),
|
||||||
target: () => import("./ha-selector-target"),
|
target: () => import("./ha-selector-target"),
|
||||||
template: () => import("./ha-selector-template"),
|
template: () => import("./ha-selector-template"),
|
||||||
text: () => import("./ha-selector-text"),
|
text: () => import("./ha-selector-text"),
|
||||||
@ -37,12 +41,16 @@ const LOAD_ELEMENTS = {
|
|||||||
icon: () => import("./ha-selector-icon"),
|
icon: () => import("./ha-selector-icon"),
|
||||||
media: () => import("./ha-selector-media"),
|
media: () => import("./ha-selector-media"),
|
||||||
theme: () => import("./ha-selector-theme"),
|
theme: () => import("./ha-selector-theme"),
|
||||||
|
tts: () => import("./ha-selector-tts"),
|
||||||
|
tts_voice: () => import("./ha-selector-tts-voice"),
|
||||||
location: () => import("./ha-selector-location"),
|
location: () => import("./ha-selector-location"),
|
||||||
color_temp: () => import("./ha-selector-color-temp"),
|
color_temp: () => import("./ha-selector-color-temp"),
|
||||||
"ui-action": () => import("./ha-selector-ui-action"),
|
ui_action: () => import("./ha-selector-ui-action"),
|
||||||
"ui-color": () => import("./ha-selector-ui-color"),
|
ui_color: () => import("./ha-selector-ui-color"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LEGACY_UI_SELECTORS = new Set(["ui-action", "ui-color"]);
|
||||||
|
|
||||||
@customElement("ha-selector")
|
@customElement("ha-selector")
|
||||||
export class HaSelector extends LitElement {
|
export class HaSelector extends LitElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@ -67,12 +75,17 @@ export class HaSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public context?: Record<string, any>;
|
@property() public context?: Record<string, any>;
|
||||||
|
|
||||||
public focus() {
|
public async focus() {
|
||||||
this.shadowRoot?.getElementById("selector")?.focus();
|
await this.updateComplete;
|
||||||
|
(this.renderRoot.querySelector("#selector") as HTMLElement)?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _type() {
|
private get _type() {
|
||||||
return Object.keys(this.selector)[0];
|
const type = Object.keys(this.selector)[0];
|
||||||
|
if (LEGACY_UI_SELECTORS.has(type)) {
|
||||||
|
return type.replace("-", "_");
|
||||||
|
}
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProps: PropertyValues) {
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
@ -88,6 +101,10 @@ export class HaSelector extends LitElement {
|
|||||||
if ("device" in selector) {
|
if ("device" in selector) {
|
||||||
return handleLegacyDeviceSelector(selector);
|
return handleLegacyDeviceSelector(selector);
|
||||||
}
|
}
|
||||||
|
const type = Object.keys(this.selector)[0];
|
||||||
|
if (LEGACY_UI_SELECTORS.has(type)) {
|
||||||
|
return { [type.replace("-", "_")]: selector[type] };
|
||||||
|
}
|
||||||
return selector;
|
return selector;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -52,18 +52,17 @@ export class HaSettingsRow extends LitElement {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.body > .secondary {
|
.body > .secondary {
|
||||||
font-family: var(--paper-font-body1_-_font-family);
|
display: block;
|
||||||
-webkit-font-smoothing: var(
|
padding-top: 4px;
|
||||||
--paper-font-body1_-_-webkit-font-smoothing
|
font-family: var(
|
||||||
);
|
--mdc-typography-body2-font-family,
|
||||||
font-size: var(--paper-font-body1_-_font-size);
|
var(--mdc-typography-font-family, Roboto, sans-serif)
|
||||||
font-weight: var(--paper-font-body1_-_font-weight);
|
|
||||||
line-height: var(--paper-font-body1_-_line-height);
|
|
||||||
|
|
||||||
color: var(
|
|
||||||
--paper-item-body-secondary-color,
|
|
||||||
var(--secondary-text-color)
|
|
||||||
);
|
);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-size: var(--mdc-typography-body2-font-size, 0.875rem);
|
||||||
|
font-weight: var(--mdc-typography-body2-font-weight, 400);
|
||||||
|
line-height: normal;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
.body[two-line] {
|
.body[two-line] {
|
||||||
min-height: calc(
|
min-height: calc(
|
||||||
|
@ -810,6 +810,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
tooltip.innerHTML = item.querySelector(".item-text")!.innerHTML;
|
tooltip.innerHTML = item.querySelector(".item-text")!.innerHTML;
|
||||||
tooltip.style.display = "block";
|
tooltip.style.display = "block";
|
||||||
|
tooltip.style.position = "fixed";
|
||||||
tooltip.style.top = `${top}px`;
|
tooltip.style.top = `${top}px`;
|
||||||
tooltip.style.left = `${item.offsetLeft + item.clientWidth + 4}px`;
|
tooltip.style.left = `${item.offsetLeft + item.clientWidth + 4}px`;
|
||||||
}
|
}
|
||||||
@ -840,6 +841,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
haStyleScrollbar,
|
haStyleScrollbar,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
|
overflow: visible;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
156
src/components/ha-stt-picker.ts
Normal file
156
src/components/ha-stt-picker.ts
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
PropertyValues,
|
||||||
|
} 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__";
|
||||||
|
|
||||||
|
const NAME_MAP = { cloud: "Home Assistant Cloud" };
|
||||||
|
|
||||||
|
@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() {
|
||||||
|
if (!this._engines) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const value =
|
||||||
|
this.value ??
|
||||||
|
(this.required
|
||||||
|
? this._engines.find(
|
||||||
|
(engine) => engine.supported_languages?.length !== 0
|
||||||
|
)
|
||||||
|
: 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) => {
|
||||||
|
let label = engine.engine_id;
|
||||||
|
if (engine.engine_id.includes(".")) {
|
||||||
|
const stateObj = this.hass!.states[engine.engine_id];
|
||||||
|
label = stateObj ? computeStateName(stateObj) : engine.engine_id;
|
||||||
|
} else if (engine.engine_id in NAME_MAP) {
|
||||||
|
label = NAME_MAP[engine.engine_id];
|
||||||
|
}
|
||||||
|
return html`<ha-list-item
|
||||||
|
.value=${engine.engine_id}
|
||||||
|
.disabled=${engine.supported_languages?.length === 0}
|
||||||
|
>
|
||||||
|
${label}
|
||||||
|
</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,
|
||||||
|
this.hass.config.country || undefined
|
||||||
|
)
|
||||||
|
).providers;
|
||||||
|
|
||||||
|
if (!this.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedEngine = this._engines.find(
|
||||||
|
(engine) => engine.engine_id === this.value
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent(this, "supported-languages-changed", {
|
||||||
|
value: selectedEngine?.supported_languages,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!selectedEngine || selectedEngine.supported_languages?.length === 0) {
|
||||||
|
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 });
|
||||||
|
fireEvent(this, "supported-languages-changed", {
|
||||||
|
value: this._engines!.find((engine) => engine.engine_id === this.value)
|
||||||
|
?.supported_languages,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-stt-picker": HaSTTPicker;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
@ -1,43 +1,19 @@
|
|||||||
import { TopAppBarFixedBase } from "@material/mwc-top-app-bar-fixed/mwc-top-app-bar-fixed-base";
|
import { TopAppBarFixedBase } from "@material/mwc-top-app-bar-fixed/mwc-top-app-bar-fixed-base";
|
||||||
import { styles } from "@material/mwc-top-app-bar/mwc-top-app-bar.css";
|
import { styles } from "@material/mwc-top-app-bar/mwc-top-app-bar.css";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
let drawerContent: HTMLElement | undefined;
|
|
||||||
|
|
||||||
@customElement("ha-top-app-bar-fixed")
|
@customElement("ha-top-app-bar-fixed")
|
||||||
export class HaTopAppBarFixed extends TopAppBarFixedBase {
|
export class HaTopAppBarFixed extends TopAppBarFixedBase {
|
||||||
private get _drawerContent() {
|
|
||||||
if (!drawerContent) {
|
|
||||||
drawerContent = document
|
|
||||||
.querySelector("home-assistant")!
|
|
||||||
.renderRoot.querySelector("home-assistant-main")!
|
|
||||||
.renderRoot.querySelector("ha-drawer")!
|
|
||||||
.renderRoot.querySelector(".mdc-drawer-app-content") as HTMLElement;
|
|
||||||
}
|
|
||||||
return drawerContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@property({ type: Object })
|
|
||||||
get scrollTarget() {
|
|
||||||
return this._scrollTarget || this._drawerContent || window;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updateRootPosition() {}
|
|
||||||
|
|
||||||
static override styles = [
|
static override styles = [
|
||||||
styles,
|
styles,
|
||||||
css`
|
css`
|
||||||
.mdc-top-app-bar {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.mdc-top-app-bar__row {
|
.mdc-top-app-bar__row {
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
border-bottom: var(--app-header-border-bottom);
|
border-bottom: var(--app-header-border-bottom);
|
||||||
}
|
}
|
||||||
.mdc-top-app-bar--fixed-adjust {
|
.mdc-top-app-bar--fixed-adjust {
|
||||||
padding-top: 0;
|
padding-top: var(--header-height);
|
||||||
}
|
}
|
||||||
.mdc-top-app-bar {
|
.mdc-top-app-bar {
|
||||||
--mdc-typography-headline6-font-weight: 400;
|
--mdc-typography-headline6-font-weight: 400;
|
||||||
|
156
src/components/ha-tts-picker.ts
Normal file
156
src/components/ha-tts-picker.ts
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
PropertyValues,
|
||||||
|
} 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 { 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__";
|
||||||
|
|
||||||
|
const NAME_MAP = { cloud: "Home Assistant Cloud" };
|
||||||
|
|
||||||
|
@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() {
|
||||||
|
if (!this._engines) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const value =
|
||||||
|
this.value ??
|
||||||
|
(this.required
|
||||||
|
? this._engines.find(
|
||||||
|
(engine) => engine.supported_languages?.length !== 0
|
||||||
|
)
|
||||||
|
: 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) => {
|
||||||
|
let label = engine.engine_id;
|
||||||
|
if (engine.engine_id.includes(".")) {
|
||||||
|
const stateObj = this.hass!.states[engine.engine_id];
|
||||||
|
label = stateObj ? computeStateName(stateObj) : engine.engine_id;
|
||||||
|
} else if (engine.engine_id in NAME_MAP) {
|
||||||
|
label = NAME_MAP[engine.engine_id];
|
||||||
|
}
|
||||||
|
return html`<ha-list-item
|
||||||
|
.value=${engine.engine_id}
|
||||||
|
.disabled=${engine.supported_languages?.length === 0}
|
||||||
|
>
|
||||||
|
${label}
|
||||||
|
</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,
|
||||||
|
this.hass.config.country || undefined
|
||||||
|
)
|
||||||
|
).providers;
|
||||||
|
|
||||||
|
if (!this.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedEngine = this._engines.find(
|
||||||
|
(engine) => engine.engine_id === this.value
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent(this, "supported-languages-changed", {
|
||||||
|
value: selectedEngine?.supported_languages,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!selectedEngine || selectedEngine.supported_languages?.length === 0) {
|
||||||
|
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 });
|
||||||
|
fireEvent(this, "supported-languages-changed", {
|
||||||
|
value: this._engines!.find((engine) => engine.engine_id === this.value)
|
||||||
|
?.supported_languages,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-tts-picker": HaTTSPicker;
|
||||||
|
}
|
||||||
|
}
|
147
src/components/ha-tts-voice-picker.ts
Normal file
147
src/components/ha-tts-voice-picker.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
|
import { debounce } from "../common/util/debounce";
|
||||||
|
import { listTTSVoices, TTSVoice } 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-voice-picker")
|
||||||
|
export class HaTTSVoicePicker extends LitElement {
|
||||||
|
@property() public value?: string;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property() public engineId?: 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() _voices?: TTSVoice[] | null;
|
||||||
|
|
||||||
|
@query("ha-select") private _select?: HaSelect;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
if (!this._voices) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
const value =
|
||||||
|
this.value ?? (this.required ? this._voices[0]?.voice_id : NONE);
|
||||||
|
return html`
|
||||||
|
<ha-select
|
||||||
|
.label=${this.label ||
|
||||||
|
this.hass!.localize("ui.components.tts-voice-picker.voice")}
|
||||||
|
.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-voice-picker.none")}
|
||||||
|
</ha-list-item>`
|
||||||
|
: nothing}
|
||||||
|
${this._voices.map(
|
||||||
|
(voice) => html`<ha-list-item .value=${voice.voice_id}>
|
||||||
|
${voice.name}
|
||||||
|
</ha-list-item>`
|
||||||
|
)}
|
||||||
|
</ha-select>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
this._updateVoices();
|
||||||
|
} else if (
|
||||||
|
changedProperties.has("language") ||
|
||||||
|
changedProperties.has("engineId")
|
||||||
|
) {
|
||||||
|
this._debouncedUpdateVoices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _debouncedUpdateVoices = debounce(() => this._updateVoices(), 500);
|
||||||
|
|
||||||
|
private async _updateVoices() {
|
||||||
|
if (!this.engineId || !this.language) {
|
||||||
|
this._voices = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._voices = (
|
||||||
|
await listTTSVoices(this.hass, this.engineId, this.language)
|
||||||
|
).voices;
|
||||||
|
|
||||||
|
if (!this.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!this._voices ||
|
||||||
|
!this._voices.find((voice) => voice.voice_id === this.value)
|
||||||
|
) {
|
||||||
|
this.value = undefined;
|
||||||
|
fireEvent(this, "value-changed", { value: this.value });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues<this>) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
if (
|
||||||
|
changedProperties.has("_voices") &&
|
||||||
|
this._select?.value !== this.value
|
||||||
|
) {
|
||||||
|
this._select?.layoutOptions();
|
||||||
|
fireEvent(this, "value-changed", { value: this._select?.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-voice-picker": HaTTSVoicePicker;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { DEFAULT_SCHEMA, dump, load, Schema } from "js-yaml";
|
import { DEFAULT_SCHEMA, dump, load, Schema } from "js-yaml";
|
||||||
import { html, LitElement, nothing } from "lit";
|
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
@ -31,6 +31,8 @@ export class HaYamlEditor extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public autoUpdate = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public readOnly = false;
|
@property({ type: Boolean }) public readOnly = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = false;
|
@property({ type: Boolean }) public required = false;
|
||||||
@ -41,7 +43,11 @@ export class HaYamlEditor extends LitElement {
|
|||||||
try {
|
try {
|
||||||
this._yaml =
|
this._yaml =
|
||||||
value && !isEmpty(value)
|
value && !isEmpty(value)
|
||||||
? dump(value, { schema: this.yamlSchema, quotingType: '"' })
|
? dump(value, {
|
||||||
|
schema: this.yamlSchema,
|
||||||
|
quotingType: '"',
|
||||||
|
noRefs: true,
|
||||||
|
})
|
||||||
: "";
|
: "";
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@ -56,6 +62,13 @@ export class HaYamlEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues<this>): void {
|
||||||
|
super.willUpdate(changedProperties);
|
||||||
|
if (this.autoUpdate && changedProperties.has("value")) {
|
||||||
|
this.setValue(this.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (this._yaml === undefined) {
|
if (this._yaml === undefined) {
|
||||||
return nothing;
|
return nothing;
|
||||||
|
328
src/data/assist_pipeline.ts
Normal file
328
src/data/assist_pipeline.ts
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
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;
|
||||||
|
name: string;
|
||||||
|
language: string;
|
||||||
|
conversation_engine: string;
|
||||||
|
conversation_language: string | null;
|
||||||
|
stt_engine: string | null;
|
||||||
|
stt_language: string | null;
|
||||||
|
tts_engine: string | null;
|
||||||
|
tts_language: string | null;
|
||||||
|
tts_voice: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssistPipelineMutableParams {
|
||||||
|
name: string;
|
||||||
|
language: string;
|
||||||
|
conversation_engine: string;
|
||||||
|
conversation_language: string | null;
|
||||||
|
stt_engine: string | null;
|
||||||
|
stt_language: string | null;
|
||||||
|
tts_engine: string | null;
|
||||||
|
tts_language: string | null;
|
||||||
|
tts_voice: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface assistRunListing {
|
||||||
|
pipeline_run_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;
|
||||||
|
language: 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;
|
||||||
|
language: string;
|
||||||
|
voice: 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 runDebugAssistPipeline = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
callback: (run: PipelineRun) => void,
|
||||||
|
options: PipelineRunOptions
|
||||||
|
) => {
|
||||||
|
let run: PipelineRun | undefined;
|
||||||
|
|
||||||
|
const unsubProm = runAssistPipeline(
|
||||||
|
hass,
|
||||||
|
(updateEvent) => {
|
||||||
|
run = processEvent(run, updateEvent, options);
|
||||||
|
|
||||||
|
if (updateEvent.type === "run-end" || updateEvent.type === "error") {
|
||||||
|
unsubProm.then((unsub) => unsub());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (run) {
|
||||||
|
callback(run);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
return unsubProm;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const runAssistPipeline = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
callback: (event: PipelineRunEvent) => void,
|
||||||
|
options: PipelineRunOptions
|
||||||
|
) =>
|
||||||
|
hass.connection.subscribeMessage<PipelineRunEvent>(callback, {
|
||||||
|
...options,
|
||||||
|
type: "assist_pipeline/run",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const listAssistPipelineRuns = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
pipeline_id: string
|
||||||
|
) =>
|
||||||
|
hass.callWS<{
|
||||||
|
pipeline_runs: assistRunListing[];
|
||||||
|
}>({
|
||||||
|
type: "assist_pipeline/pipeline_debug/list",
|
||||||
|
pipeline_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getAssistPipelineRun = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
pipeline_id: string,
|
||||||
|
pipeline_run_id: string
|
||||||
|
) =>
|
||||||
|
hass.callWS<{
|
||||||
|
events: PipelineRunEvent[];
|
||||||
|
}>({
|
||||||
|
type: "assist_pipeline/pipeline_debug/get",
|
||||||
|
pipeline_id,
|
||||||
|
pipeline_run_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const listAssistPipelines = (hass: HomeAssistant) =>
|
||||||
|
hass.callWS<{
|
||||||
|
pipelines: AssistPipeline[];
|
||||||
|
preferred_pipeline: string | null;
|
||||||
|
}>({
|
||||||
|
type: "assist_pipeline/pipeline/list",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getAssistPipeline = (hass: HomeAssistant, pipeline_id?: string) =>
|
||||||
|
hass.callWS<AssistPipeline>({
|
||||||
|
type: "assist_pipeline/pipeline/get",
|
||||||
|
pipeline_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
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: 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,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchAssistPipelineLanguages = (hass: HomeAssistant) =>
|
||||||
|
hass.callWS<{ languages: string[] }>({
|
||||||
|
type: "assist_pipeline/language/list",
|
||||||
|
});
|
@ -123,6 +123,8 @@ export interface TimePatternTrigger extends BaseTrigger {
|
|||||||
export interface WebhookTrigger extends BaseTrigger {
|
export interface WebhookTrigger extends BaseTrigger {
|
||||||
platform: "webhook";
|
platform: "webhook";
|
||||||
webhook_id: string;
|
webhook_id: string;
|
||||||
|
allowed_methods?: string[];
|
||||||
|
local_only?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZoneTrigger extends BaseTrigger {
|
export interface ZoneTrigger extends BaseTrigger {
|
||||||
|
@ -9,15 +9,6 @@ interface CloudStatusNotLoggedIn {
|
|||||||
http_use_ssl: boolean;
|
http_use_ssl: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GoogleEntityConfig {
|
|
||||||
should_expose?: boolean | null;
|
|
||||||
disable_2fa?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AlexaEntityConfig {
|
|
||||||
should_expose?: boolean | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CertificateInformation {
|
export interface CertificateInformation {
|
||||||
common_name: string;
|
common_name: string;
|
||||||
expire_date: string;
|
expire_date: string;
|
||||||
@ -30,14 +21,6 @@ export interface CloudPreferences {
|
|||||||
remote_enabled: boolean;
|
remote_enabled: boolean;
|
||||||
google_secure_devices_pin: string | undefined;
|
google_secure_devices_pin: string | undefined;
|
||||||
cloudhooks: { [webhookId: string]: CloudWebhook };
|
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;
|
alexa_report_state: boolean;
|
||||||
google_report_state: boolean;
|
google_report_state: boolean;
|
||||||
tts_default_voice: [string, string];
|
tts_default_voice: [string, string];
|
||||||
@ -57,6 +40,13 @@ export interface CloudStatusLoggedIn {
|
|||||||
remote_domain: string | undefined;
|
remote_domain: string | undefined;
|
||||||
remote_connected: boolean;
|
remote_connected: boolean;
|
||||||
remote_certificate: undefined | CertificateInformation;
|
remote_certificate: undefined | CertificateInformation;
|
||||||
|
remote_certificate_status:
|
||||||
|
| null
|
||||||
|
| "error"
|
||||||
|
| "generating"
|
||||||
|
| "loaded"
|
||||||
|
| "loading"
|
||||||
|
| "ready";
|
||||||
http_use_ssl: boolean;
|
http_use_ssl: boolean;
|
||||||
active_subscription: boolean;
|
active_subscription: boolean;
|
||||||
}
|
}
|
||||||
@ -86,10 +76,14 @@ export const cloudLogin = (
|
|||||||
email: string,
|
email: string,
|
||||||
password: string
|
password: string
|
||||||
) =>
|
) =>
|
||||||
hass.callApi("POST", "cloud/login", {
|
hass.callApi<{ success: boolean; cloud_pipeline?: string }>(
|
||||||
email,
|
"POST",
|
||||||
password,
|
"cloud/login",
|
||||||
});
|
{
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export const cloudLogout = (hass: HomeAssistant) =>
|
export const cloudLogout = (hass: HomeAssistant) =>
|
||||||
hass.callApi("POST", "cloud/logout");
|
hass.callApi("POST", "cloud/logout");
|
||||||
@ -150,10 +144,8 @@ export const updateCloudPref = (
|
|||||||
prefs: {
|
prefs: {
|
||||||
google_enabled?: CloudPreferences["google_enabled"];
|
google_enabled?: CloudPreferences["google_enabled"];
|
||||||
alexa_enabled?: CloudPreferences["alexa_enabled"];
|
alexa_enabled?: CloudPreferences["alexa_enabled"];
|
||||||
alexa_default_expose?: CloudPreferences["alexa_default_expose"];
|
|
||||||
alexa_report_state?: CloudPreferences["alexa_report_state"];
|
alexa_report_state?: CloudPreferences["alexa_report_state"];
|
||||||
google_report_state?: CloudPreferences["google_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"];
|
google_secure_devices_pin?: CloudPreferences["google_secure_devices_pin"];
|
||||||
tts_default_voice?: CloudPreferences["tts_default_voice"];
|
tts_default_voice?: CloudPreferences["tts_default_voice"];
|
||||||
}
|
}
|
||||||
@ -165,25 +157,14 @@ export const updateCloudPref = (
|
|||||||
|
|
||||||
export const updateCloudGoogleEntityConfig = (
|
export const updateCloudGoogleEntityConfig = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityId: string,
|
entity_id: string,
|
||||||
values: GoogleEntityConfig
|
disable_2fa: boolean
|
||||||
) =>
|
) =>
|
||||||
hass.callWS<GoogleEntityConfig>({
|
hass.callWS({
|
||||||
type: "cloud/google_assistant/entities/update",
|
type: "cloud/google_assistant/entities/update",
|
||||||
entity_id: entityId,
|
entity_id,
|
||||||
...values,
|
disable_2fa,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const cloudSyncGoogleAssistant = (hass: HomeAssistant) =>
|
export const cloudSyncGoogleAssistant = (hass: HomeAssistant) =>
|
||||||
hass.callApi("POST", "cloud/google_actions/sync");
|
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,6 +56,12 @@ export interface AgentInfo {
|
|||||||
attribution?: { name: string; url: string };
|
attribution?: { name: string; url: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Agent {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
supported_languages: "*" | string[];
|
||||||
|
}
|
||||||
|
|
||||||
export const processConversationInput = (
|
export const processConversationInput = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
text: string,
|
text: string,
|
||||||
@ -70,9 +76,24 @@ export const processConversationInput = (
|
|||||||
language,
|
language,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getAgentInfo = (hass: HomeAssistant): Promise<AgentInfo> =>
|
export const listAgents = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
language?: string,
|
||||||
|
country?: string
|
||||||
|
): Promise<{ agents: Agent[] }> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "conversation/agent/list",
|
||||||
|
language,
|
||||||
|
country,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getAgentInfo = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
agent_id?: string
|
||||||
|
): Promise<AgentInfo> =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "conversation/agent/info",
|
type: "conversation/agent/info",
|
||||||
|
agent_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const prepareConversation = (
|
export const prepareConversation = (
|
||||||
|
@ -90,6 +90,9 @@ export interface EntityRegistryOptions {
|
|||||||
number?: NumberEntityOptions;
|
number?: NumberEntityOptions;
|
||||||
sensor?: SensorEntityOptions;
|
sensor?: SensorEntityOptions;
|
||||||
weather?: WeatherEntityOptions;
|
weather?: WeatherEntityOptions;
|
||||||
|
conversation?: Record<string, unknown>;
|
||||||
|
"cloud.alexa"?: Record<string, unknown>;
|
||||||
|
"cloud.google_assistant"?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityRegistryEntryUpdateParams {
|
export interface EntityRegistryEntryUpdateParams {
|
||||||
|
@ -9,5 +9,14 @@ export interface GoogleEntity {
|
|||||||
export const fetchCloudGoogleEntities = (hass: HomeAssistant) =>
|
export const fetchCloudGoogleEntities = (hass: HomeAssistant) =>
|
||||||
hass.callWS<GoogleEntity[]>({ type: "cloud/google_assistant/entities" });
|
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) =>
|
export const syncCloudGoogleEntities = (hass: HomeAssistant) =>
|
||||||
hass.callApi("POST", "cloud/google_actions/sync");
|
hass.callApi("POST", "cloud/google_actions/sync");
|
||||||
|
@ -381,3 +381,23 @@ export const fetchAddonInfo = (
|
|||||||
? `/store/addons/${addonSlug}` // Use /store/addons when add-on is not installed
|
? `/store/addons/${addonSlug}` // Use /store/addons when add-on is not installed
|
||||||
: `/addons/${addonSlug}/info` // Use /addons when add-on is 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;
|
||||||
|
};
|
||||||
|
@ -14,6 +14,7 @@ export type Selector =
|
|||||||
| BooleanSelector
|
| BooleanSelector
|
||||||
| ColorRGBSelector
|
| ColorRGBSelector
|
||||||
| ColorTempSelector
|
| ColorTempSelector
|
||||||
|
| ConversationAgentSelector
|
||||||
| ConfigEntrySelector
|
| ConfigEntrySelector
|
||||||
| ConstantSelector
|
| ConstantSelector
|
||||||
| DateSelector
|
| DateSelector
|
||||||
@ -25,19 +26,24 @@ export type Selector =
|
|||||||
| LegacyEntitySelector
|
| LegacyEntitySelector
|
||||||
| FileSelector
|
| FileSelector
|
||||||
| IconSelector
|
| IconSelector
|
||||||
|
| LanguageSelector
|
||||||
| LocationSelector
|
| LocationSelector
|
||||||
| MediaSelector
|
| MediaSelector
|
||||||
| NavigationSelector
|
| NavigationSelector
|
||||||
| NumberSelector
|
| NumberSelector
|
||||||
| ObjectSelector
|
| ObjectSelector
|
||||||
|
| AssistPipelineSelector
|
||||||
| SelectSelector
|
| SelectSelector
|
||||||
| StateSelector
|
| StateSelector
|
||||||
| StatisticSelector
|
| StatisticSelector
|
||||||
| StringSelector
|
| StringSelector
|
||||||
|
| STTSelector
|
||||||
| TargetSelector
|
| TargetSelector
|
||||||
| TemplateSelector
|
| TemplateSelector
|
||||||
| ThemeSelector
|
| ThemeSelector
|
||||||
| TimeSelector
|
| TimeSelector
|
||||||
|
| TTSSelector
|
||||||
|
| TTSVoiceSelector
|
||||||
| UiActionSelector
|
| UiActionSelector
|
||||||
| UiColorSelector;
|
| UiColorSelector;
|
||||||
|
|
||||||
@ -85,6 +91,10 @@ export interface ColorTempSelector {
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ConversationAgentSelector {
|
||||||
|
conversation_agent: { language?: string } | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ConfigEntrySelector {
|
export interface ConfigEntrySelector {
|
||||||
config_entry: {
|
config_entry: {
|
||||||
integration?: string;
|
integration?: string;
|
||||||
@ -201,6 +211,14 @@ export interface IconSelector {
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LanguageSelector {
|
||||||
|
language: {
|
||||||
|
languages?: string[];
|
||||||
|
native_name?: boolean;
|
||||||
|
no_sort?: boolean;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LocationSelector {
|
export interface LocationSelector {
|
||||||
location: { radius?: boolean; icon?: string } | null;
|
location: { radius?: boolean; icon?: string } | null;
|
||||||
}
|
}
|
||||||
@ -249,6 +267,11 @@ export interface ObjectSelector {
|
|||||||
object: {} | null;
|
object: {} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AssistPipelineSelector {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
|
assist_pipeline: {} | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SelectOption {
|
export interface SelectOption {
|
||||||
value: any;
|
value: any;
|
||||||
label: string;
|
label: string;
|
||||||
@ -294,6 +317,10 @@ export interface StringSelector {
|
|||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface STTSelector {
|
||||||
|
stt: { language?: string } | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TargetSelector {
|
export interface TargetSelector {
|
||||||
target: {
|
target: {
|
||||||
entity?: EntitySelectorFilter | readonly EntitySelectorFilter[];
|
entity?: EntitySelectorFilter | readonly EntitySelectorFilter[];
|
||||||
@ -315,15 +342,23 @@ export interface TimeSelector {
|
|||||||
time: {} | null;
|
time: {} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TTSSelector {
|
||||||
|
tts: { language?: string } | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TTSVoiceSelector {
|
||||||
|
tts_voice: { engineId?: string; language?: string } | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UiActionSelector {
|
export interface UiActionSelector {
|
||||||
"ui-action": {
|
ui_action: {
|
||||||
actions?: UiAction[];
|
actions?: UiAction[];
|
||||||
} | null;
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UiColorSelector {
|
export interface UiColorSelector {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
"ui-color": {} | null;
|
ui_color: {} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filterSelectorDevices = (
|
export const filterSelectorDevices = (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
export interface SpeechMetadata {
|
export interface SpeechMetadata {
|
||||||
language: string;
|
language: string;
|
||||||
format: "wav" | "ogg";
|
format: "wav" | "ogg";
|
||||||
@ -15,3 +17,19 @@ export interface SpeechMetadata {
|
|||||||
| 48000;
|
| 48000;
|
||||||
channel: 1 | 2;
|
channel: 1 | 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface STTEngine {
|
||||||
|
engine_id: string;
|
||||||
|
supported_languages?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listSTTEngines = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
language?: string,
|
||||||
|
country?: string
|
||||||
|
): Promise<{ providers: STTEngine[] }> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "stt/engine/list",
|
||||||
|
language,
|
||||||
|
country,
|
||||||
|
});
|
||||||
|
@ -1,5 +1,15 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface TTSEngine {
|
||||||
|
engine_id: string;
|
||||||
|
supported_languages?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TTSVoice {
|
||||||
|
voice_id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const convertTextToSpeech = (
|
export const convertTextToSpeech = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
data: {
|
data: {
|
||||||
@ -18,3 +28,25 @@ export const isTTSMediaSource = (mediaContentId: string) =>
|
|||||||
|
|
||||||
export const getProviderFromTTSMediaSource = (mediaContentId: string) =>
|
export const getProviderFromTTSMediaSource = (mediaContentId: string) =>
|
||||||
mediaContentId.substring(TTS_MEDIA_SOURCE_PREFIX.length);
|
mediaContentId.substring(TTS_MEDIA_SOURCE_PREFIX.length);
|
||||||
|
|
||||||
|
export const listTTSEngines = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
language?: string,
|
||||||
|
country?: string
|
||||||
|
): Promise<{ providers: TTSEngine[] }> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "tts/engine/list",
|
||||||
|
language,
|
||||||
|
country,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const listTTSVoices = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
engine_id: string,
|
||||||
|
language: string
|
||||||
|
): Promise<{ voices: TTSVoice[] | null }> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "tts/engine/voices",
|
||||||
|
engine_id,
|
||||||
|
language,
|
||||||
|
});
|
||||||
|
43
src/data/voice.ts
Normal file
43
src/data/voice.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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 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,
|
||||||
|
});
|
@ -1,197 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
export 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"]> & { done: boolean };
|
|
||||||
intent?: PipelineIntentStartEvent["data"] &
|
|
||||||
Partial<PipelineIntentEndEvent["data"]> & { done: boolean };
|
|
||||||
tts?: PipelineTTSStartEvent["data"] &
|
|
||||||
Partial<PipelineTTSEndEvent["data"]> & { done: boolean };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const runVoiceAssistantPipeline = (
|
|
||||||
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, done: false },
|
|
||||||
};
|
|
||||||
} else if (updateEvent.type === "stt-end") {
|
|
||||||
run = {
|
|
||||||
...run,
|
|
||||||
stt: { ...run.stt!, ...updateEvent.data, done: true },
|
|
||||||
};
|
|
||||||
} else if (updateEvent.type === "intent-start") {
|
|
||||||
run = {
|
|
||||||
...run,
|
|
||||||
stage: "intent",
|
|
||||||
intent: { ...updateEvent.data, done: false },
|
|
||||||
};
|
|
||||||
} else if (updateEvent.type === "intent-end") {
|
|
||||||
run = {
|
|
||||||
...run,
|
|
||||||
intent: { ...run.intent!, ...updateEvent.data, done: true },
|
|
||||||
};
|
|
||||||
} else if (updateEvent.type === "tts-start") {
|
|
||||||
run = {
|
|
||||||
...run,
|
|
||||||
stage: "tts",
|
|
||||||
tts: { ...updateEvent.data, done: false },
|
|
||||||
};
|
|
||||||
} else if (updateEvent.type === "tts-end") {
|
|
||||||
run = {
|
|
||||||
...run,
|
|
||||||
tts: { ...run.tts!, ...updateEvent.data, done: true },
|
|
||||||
};
|
|
||||||
} 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,16 +1,13 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { mdiDeleteOutline, mdiPlus } from "@mdi/js";
|
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../components/ha-alert";
|
import "../../components/ha-alert";
|
||||||
import "../../components/ha-area-picker";
|
|
||||||
import "../../components/ha-dialog";
|
import "../../components/ha-dialog";
|
||||||
import "../../components/ha-textfield";
|
|
||||||
import type { HaTextField } from "../../components/ha-textfield";
|
|
||||||
import { haStyle, haStyleDialog } from "../../resources/styles";
|
import { haStyle, haStyleDialog } from "../../resources/styles";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { AliasesDialogParams } from "./show-dialog-aliases";
|
import { AliasesDialogParams } from "./show-dialog-aliases";
|
||||||
|
import "../../components/ha-aliases-editor";
|
||||||
|
|
||||||
@customElement("dialog-aliases")
|
@customElement("dialog-aliases")
|
||||||
class DialogAliases extends LitElement {
|
class DialogAliases extends LitElement {
|
||||||
@ -57,43 +54,11 @@ class DialogAliases extends LitElement {
|
|||||||
${this._error
|
${this._error
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="form">
|
<ha-aliases-editor
|
||||||
${this._aliases.map(
|
.hass=${this.hass}
|
||||||
(alias, index) => html`
|
.aliases=${this._aliases}
|
||||||
<div class="layout horizontal center-center row">
|
@value-changed=${this._aliasesChanged}
|
||||||
<ha-textfield
|
></ha-aliases-editor>
|
||||||
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>
|
</div>
|
||||||
<mwc-button
|
<mwc-button
|
||||||
slot="secondaryAction"
|
slot="secondaryAction"
|
||||||
@ -113,32 +78,8 @@ class DialogAliases extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _addAlias() {
|
private _aliasesChanged(ev: CustomEvent): void {
|
||||||
this._aliases = [...this._aliases, ""];
|
this._aliases = ev.detail.value;
|
||||||
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> {
|
private async _updateAliases(): Promise<void> {
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
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,
|
|
||||||
});
|
|
||||||
};
|
|
@ -161,8 +161,6 @@ class DialogBox extends LitElement {
|
|||||||
--mdc-theme-primary: var(--error-color);
|
--mdc-theme-primary: var(--error-color);
|
||||||
}
|
}
|
||||||
ha-dialog {
|
ha-dialog {
|
||||||
--mdc-dialog-heading-ink-color: var(--primary-text-color);
|
|
||||||
--mdc-dialog-content-ink-color: var(--primary-text-color);
|
|
||||||
/* Place above other dialogs */
|
/* Place above other dialogs */
|
||||||
--dialog-z-index: 104;
|
--dialog-z-index: 104;
|
||||||
}
|
}
|
||||||
|
@ -176,8 +176,6 @@ export class DialogEnterCode
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-dialog {
|
ha-dialog {
|
||||||
--mdc-dialog-heading-ink-color: var(--primary-text-color);
|
|
||||||
--mdc-dialog-content-ink-color: var(--primary-text-color);
|
|
||||||
/* Place above other dialogs */
|
/* Place above other dialogs */
|
||||||
--dialog-z-index: 104;
|
--dialog-z-index: 104;
|
||||||
}
|
}
|
||||||
|
@ -569,6 +569,7 @@ class MoreInfoViewLightColorPicker extends LitElement {
|
|||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
letter-spacing: 0.1px;
|
letter-spacing: 0.1px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-temp {
|
.color-temp {
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
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: {},
|
||||||
|
});
|
||||||
|
};
|
@ -181,10 +181,10 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
this.setView("settings");
|
this.setView("settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _showChildView(ev: CustomEvent): Promise<void> {
|
private _showChildView(ev: CustomEvent): void {
|
||||||
const view = ev.detail as ChildView;
|
const view = ev.detail as ChildView;
|
||||||
if (view.viewImport) {
|
if (view.viewImport) {
|
||||||
await view.viewImport();
|
view.viewImport();
|
||||||
}
|
}
|
||||||
this._childView = view;
|
this._childView = view;
|
||||||
}
|
}
|
||||||
@ -369,12 +369,14 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
@show-child-view=${this._showChildView}
|
@show-child-view=${this._showChildView}
|
||||||
|
@entity-entry-updated=${this._entryUpdated}
|
||||||
>
|
>
|
||||||
${this._childView
|
${this._childView
|
||||||
? html`
|
? html`
|
||||||
<div class="child-view">
|
<div class="child-view">
|
||||||
${dynamicElement(this._childView.viewTag, {
|
${dynamicElement(this._childView.viewTag, {
|
||||||
hass: this.hass,
|
hass: this.hass,
|
||||||
|
entry: this._entry,
|
||||||
params: this._childView.viewParams,
|
params: this._childView.viewParams,
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
@ -401,7 +403,6 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.entityId=${this._entityId}
|
.entityId=${this._entityId}
|
||||||
.entry=${this._entry}
|
.entry=${this._entry}
|
||||||
@entity-entry-updated=${this._entryUpdated}
|
|
||||||
></ha-more-info-settings>
|
></ha-more-info-settings>
|
||||||
`
|
`
|
||||||
: this._currView === "related"
|
: this._currView === "related"
|
||||||
|
@ -27,7 +27,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const statTypes: StatisticsTypes = ["state", "min", "mean", "max"];
|
const statTypes: StatisticsTypes = ["min", "mean", "max"];
|
||||||
|
|
||||||
@customElement("ha-more-info-history")
|
@customElement("ha-more-info-history")
|
||||||
export class MoreInfoHistory extends LitElement {
|
export class MoreInfoHistory extends LitElement {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user