mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-20 17:20:07 +00:00
Compare commits
29 Commits
20230329.0
...
allow-part
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a548d13931 | ||
![]() |
ddfe02eb70 | ||
![]() |
acaaf25500 | ||
![]() |
c6c3e63101 | ||
![]() |
e0fe4631f9 | ||
![]() |
232f70d44c | ||
![]() |
273904a6eb | ||
![]() |
91caffc4e1 | ||
![]() |
abcb904def | ||
![]() |
36c5d70597 | ||
![]() |
b0b7998757 | ||
![]() |
33ec1e15a9 | ||
![]() |
d97ddcd31a | ||
![]() |
73c286a493 | ||
![]() |
3e954eef02 | ||
![]() |
a94b211d3e | ||
![]() |
1293e5f61f | ||
![]() |
287b0b9235 | ||
![]() |
3d6743ae3e | ||
![]() |
5193f2c6a4 | ||
![]() |
e6772e8b89 | ||
![]() |
dcac853b71 | ||
![]() |
0df096d68b | ||
![]() |
ef10cc77f7 | ||
![]() |
e52b2c49a6 | ||
![]() |
78cc75c57c | ||
![]() |
2b38a1ce33 | ||
![]() |
1f1898fa46 | ||
![]() |
fcc95825e3 |
@@ -20,7 +20,7 @@
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"webpack": {
|
||||
"config": "./webpack.config.js"
|
||||
"config": "./webpack.config.cjs"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||
|
||||
- name: Bump version
|
||||
run: script/version_bump.js nightly
|
||||
run: script/version_bump.cjs nightly
|
||||
|
||||
- name: Build nightly Python wheels
|
||||
run: |
|
||||
|
@@ -1,6 +1,6 @@
|
||||
const path = require("path");
|
||||
const env = require("./env.js");
|
||||
const paths = require("./paths.js");
|
||||
const env = require("./env.cjs");
|
||||
const paths = require("./paths.cjs");
|
||||
|
||||
// GitHub base URL to use for production source maps
|
||||
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
|
||||
@@ -99,7 +99,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||
[
|
||||
path.resolve(
|
||||
paths.polymer_dir,
|
||||
"build-scripts/babel-plugins/inline-constants-plugin.js"
|
||||
"build-scripts/babel-plugins/inline-constants-plugin.cjs"
|
||||
),
|
||||
{
|
||||
modules: ["@mdi/js"],
|
@@ -1,6 +1,6 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const paths = require("./paths.js");
|
||||
const paths = require("./paths.cjs");
|
||||
|
||||
module.exports = {
|
||||
useRollup() {
|
@@ -1,18 +1,18 @@
|
||||
// Run HA develop mode
|
||||
|
||||
const gulp = require("gulp");
|
||||
const env = require("../env");
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./locale-data.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./gather-static.js");
|
||||
require("./compress.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
require("./wds.js");
|
||||
const env = require("../env.cjs");
|
||||
require("./clean.cjs");
|
||||
require("./translations.cjs");
|
||||
require("./locale-data.cjs");
|
||||
require("./gen-icons-json.cjs");
|
||||
require("./gather-static.cjs");
|
||||
require("./compress.cjs");
|
||||
require("./webpack.cjs");
|
||||
require("./service-worker.cjs");
|
||||
require("./entry-html.cjs");
|
||||
require("./rollup.cjs");
|
||||
require("./wds.cjs");
|
||||
|
||||
gulp.task(
|
||||
"develop-app",
|
@@ -1,14 +1,13 @@
|
||||
const gulp = require("gulp");
|
||||
const env = require("../env.cjs");
|
||||
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
require("./clean.cjs");
|
||||
require("./translations.cjs");
|
||||
require("./gather-static.cjs");
|
||||
require("./webpack.cjs");
|
||||
require("./service-worker.cjs");
|
||||
require("./entry-html.cjs");
|
||||
require("./rollup.cjs");
|
||||
|
||||
gulp.task(
|
||||
"develop-cast",
|
@@ -1,7 +1,7 @@
|
||||
const del = import("del");
|
||||
const gulp = require("gulp");
|
||||
const paths = require("../paths");
|
||||
require("./translations");
|
||||
const paths = require("../paths.cjs");
|
||||
require("./translations.cjs");
|
||||
|
||||
gulp.task(
|
||||
"clean",
|
@@ -4,7 +4,7 @@ const gulp = require("gulp");
|
||||
const zopfli = require("gulp-zopfli-green");
|
||||
const merge = require("merge-stream");
|
||||
const path = require("path");
|
||||
const paths = require("../paths");
|
||||
const paths = require("../paths.cjs");
|
||||
|
||||
const zopfliOptions = { threshold: 150 };
|
||||
|
@@ -1,16 +1,15 @@
|
||||
// Run demo develop mode
|
||||
const gulp = require("gulp");
|
||||
const env = require("../env.cjs");
|
||||
|
||||
const env = require("../env");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
require("./clean.cjs");
|
||||
require("./translations.cjs");
|
||||
require("./gen-icons-json.cjs");
|
||||
require("./gather-static.cjs");
|
||||
require("./webpack.cjs");
|
||||
require("./service-worker.cjs");
|
||||
require("./entry-html.cjs");
|
||||
require("./rollup.cjs");
|
||||
|
||||
gulp.task(
|
||||
"develop-demo",
|
@@ -4,9 +4,9 @@ const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const template = require("lodash.template");
|
||||
const { minify } = require("html-minifier-terser");
|
||||
const paths = require("../paths.js");
|
||||
const env = require("../env.js");
|
||||
const { htmlMinifierOptions, terserOptions } = require("../bundle.js");
|
||||
const paths = require("../paths.cjs");
|
||||
const env = require("../env.cjs");
|
||||
const { htmlMinifierOptions, terserOptions } = require("../bundle.cjs");
|
||||
|
||||
const templatePath = (tpl) =>
|
||||
path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`);
|
@@ -6,17 +6,17 @@ const { marked } = require("marked");
|
||||
const glob = require("glob");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
const env = require("../env.cjs");
|
||||
const paths = require("../paths.cjs");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
require("./clean.cjs");
|
||||
require("./translations.cjs");
|
||||
require("./gen-icons-json.cjs");
|
||||
require("./gather-static.cjs");
|
||||
require("./webpack.cjs");
|
||||
require("./service-worker.cjs");
|
||||
require("./entry-html.cjs");
|
||||
require("./rollup.cjs");
|
||||
|
||||
gulp.task("gather-gallery-pages", async function gatherPages() {
|
||||
const pageDir = path.resolve(paths.gallery_dir, "src/pages");
|
||||
@@ -89,9 +89,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
|
||||
|
||||
// Generate sidebar
|
||||
const sidebarPath = path.resolve(paths.gallery_dir, "sidebar.js");
|
||||
// To make watch work during development
|
||||
delete require.cache[sidebarPath];
|
||||
const sidebar = require(sidebarPath);
|
||||
const sidebar = (await import(sidebarPath)).default;
|
||||
|
||||
const pagesToProcess = {};
|
||||
for (const key of processed) {
|
@@ -3,7 +3,7 @@
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const paths = require("../paths");
|
||||
const paths = require("../paths.cjs");
|
||||
|
||||
const npmPath = (...parts) =>
|
||||
path.resolve(paths.polymer_dir, "node_modules", ...parts);
|
@@ -134,11 +134,11 @@ gulp.task("gen-icons-json", (done) => {
|
||||
});
|
||||
|
||||
const file = fs.readFileSync(PACKAGE_PATH, { encoding });
|
||||
const package = JSON.parse(file);
|
||||
const packageMeta = JSON.parse(file);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.resolve(OUTPUT_DIR, "iconMetadata.json"),
|
||||
JSON.stringify({ version: package.version, parts })
|
||||
JSON.stringify({ version: packageMeta.version, parts })
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
@@ -1,13 +1,13 @@
|
||||
const gulp = require("gulp");
|
||||
const env = require("../env");
|
||||
require("./clean.js");
|
||||
require("./gen-icons-json.js");
|
||||
require("./webpack.js");
|
||||
require("./compress.js");
|
||||
require("./rollup.js");
|
||||
require("./gather-static.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons-json.js");
|
||||
const env = require("../env.cjs");
|
||||
require("./clean.cjs");
|
||||
require("./compress.cjs");
|
||||
require("./entry-html.cjs");
|
||||
require("./gather-static.cjs");
|
||||
require("./gen-icons-json.cjs");
|
||||
require("./rollup.cjs");
|
||||
require("./translations.cjs");
|
||||
require("./webpack.cjs");
|
||||
|
||||
gulp.task(
|
||||
"develop-hassio",
|
@@ -2,7 +2,7 @@ const del = import("del");
|
||||
const path = require("path");
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const paths = require("../paths");
|
||||
const paths = require("../paths.cjs");
|
||||
|
||||
const outDir = "build/locale-data";
|
||||
|
@@ -6,8 +6,8 @@ const handler = require("serve-handler");
|
||||
const http = require("http");
|
||||
const log = require("fancy-log");
|
||||
const open = require("open");
|
||||
const rollupConfig = require("../rollup");
|
||||
const paths = require("../paths");
|
||||
const rollupConfig = require("../rollup.cjs");
|
||||
const paths = require("../paths.cjs");
|
||||
|
||||
const bothBuilds = (createConfigFunc, params) =>
|
||||
gulp.series(
|
||||
@@ -46,7 +46,7 @@ function createServer(serveOptions) {
|
||||
);
|
||||
}
|
||||
|
||||
function watchRollup(createConfig, extraWatchSrc = [], serveOptions) {
|
||||
function watchRollup(createConfig, extraWatchSrc = [], serveOptions = null) {
|
||||
const { inputOptions, outputOptions } = createConfig({
|
||||
isProdBuild: false,
|
||||
latestBuild: true,
|
@@ -5,7 +5,7 @@ const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const workboxBuild = require("workbox-build");
|
||||
const sourceMapUrl = require("source-map-url");
|
||||
const paths = require("../paths.js");
|
||||
const paths = require("../paths.cjs");
|
||||
|
||||
const swDest = path.resolve(paths.app_output_root, "service_worker.js");
|
||||
|
@@ -9,11 +9,11 @@ const flatmap = require("gulp-flatmap");
|
||||
const merge = require("gulp-merge-json");
|
||||
const rename = require("gulp-rename");
|
||||
const transform = require("gulp-json-transform");
|
||||
const { mapFiles } = require("../util");
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
const { mapFiles } = require("../util.cjs");
|
||||
const env = require("../env.cjs");
|
||||
const paths = require("../paths.cjs");
|
||||
|
||||
require("./fetch-nightly-translations");
|
||||
require("./fetch-nightly-translations.cjs");
|
||||
|
||||
const inFrontendDir = "translations/frontend";
|
||||
const inBackendDir = "translations/backend";
|
@@ -5,15 +5,15 @@ const webpack = require("webpack");
|
||||
const WebpackDevServer = require("webpack-dev-server");
|
||||
const log = require("fancy-log");
|
||||
const path = require("path");
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
const env = require("../env.cjs");
|
||||
const paths = require("../paths.cjs");
|
||||
const {
|
||||
createAppConfig,
|
||||
createDemoConfig,
|
||||
createCastConfig,
|
||||
createHassioConfig,
|
||||
createGalleryConfig,
|
||||
} = require("../webpack");
|
||||
} = require("../webpack.cjs");
|
||||
|
||||
const bothBuilds = (createConfigFunc, params) => [
|
||||
createConfigFunc({ ...params, latestBuild: true }),
|
@@ -103,7 +103,7 @@ module.exports = function (opts = {}) {
|
||||
}
|
||||
delete optionsObject.type;
|
||||
|
||||
if (!new RegExp("^.*/").test(workerFile)) {
|
||||
if (!/^.*\//.test(workerFile)) {
|
||||
this.warn(
|
||||
`Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`
|
||||
);
|
@@ -3,18 +3,18 @@ const path = require("path");
|
||||
const commonjs = require("@rollup/plugin-commonjs");
|
||||
const resolve = require("@rollup/plugin-node-resolve");
|
||||
const json = require("@rollup/plugin-json");
|
||||
const babel = require("@rollup/plugin-babel").babel;
|
||||
const { babel } = require("@rollup/plugin-babel");
|
||||
const replace = require("@rollup/plugin-replace");
|
||||
const visualizer = require("rollup-plugin-visualizer");
|
||||
const { string } = require("rollup-plugin-string");
|
||||
const { terser } = require("rollup-plugin-terser");
|
||||
const manifest = require("./rollup-plugins/manifest-plugin");
|
||||
const worker = require("./rollup-plugins/worker-plugin");
|
||||
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin");
|
||||
const ignore = require("./rollup-plugins/ignore-plugin");
|
||||
const manifest = require("./rollup-plugins/manifest-plugin.cjs");
|
||||
const worker = require("./rollup-plugins/worker-plugin.cjs");
|
||||
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin.cjs");
|
||||
const ignore = require("./rollup-plugins/ignore-plugin.cjs");
|
||||
|
||||
const bundle = require("./bundle");
|
||||
const paths = require("./paths");
|
||||
const bundle = require("./bundle.cjs");
|
||||
const paths = require("./paths.cjs");
|
||||
|
||||
const extensions = [".js", ".ts"];
|
||||
|
@@ -4,8 +4,8 @@ const TerserPlugin = require("terser-webpack-plugin");
|
||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||
const log = require("fancy-log");
|
||||
const WebpackBar = require("webpackbar");
|
||||
const paths = require("./paths.js");
|
||||
const bundle = require("./bundle.js");
|
||||
const paths = require("./paths.cjs");
|
||||
const bundle = require("./bundle.cjs");
|
||||
|
||||
class LogStartCompilePlugin {
|
||||
ignoredFirst = false;
|
||||
@@ -64,6 +64,9 @@ const createWebpackConfig = ({
|
||||
cacheCompression: false,
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
@@ -149,14 +152,17 @@ const createWebpackConfig = ({
|
||||
},
|
||||
},
|
||||
output: {
|
||||
filename: ({ chunk }) => {
|
||||
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
|
||||
return `${chunk.name}.js`;
|
||||
}
|
||||
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
||||
},
|
||||
filename: ({ chunk }) =>
|
||||
!isProdBuild || isStatsBuild || dontHash.has(chunk.name)
|
||||
? "[name].js"
|
||||
: "[name]-[contenthash].js",
|
||||
chunkFilename:
|
||||
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
|
||||
isProdBuild && !isStatsBuild ? "[id]-[contenthash].js" : "[name].js",
|
||||
assetModuleFilename:
|
||||
isProdBuild && !isStatsBuild ? "[id]-[contenthash][ext]" : "[id][ext]",
|
||||
hashFunction: "xxhash64",
|
||||
hashDigest: "base64url",
|
||||
hashDigestLength: 11, // full length of 64 bit base64url
|
||||
path: outputPath,
|
||||
publicPath,
|
||||
// To silence warning in worker plugin
|
@@ -1,5 +1,5 @@
|
||||
const rollup = require("../build-scripts/rollup.js");
|
||||
const env = require("../build-scripts/env.js");
|
||||
import rollup from "../build-scripts/rollup.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
const config = rollup.createCastConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
@@ -7,4 +7,4 @@ const config = rollup.createCastConfig({
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
||||
export default { ...config.inputOptions, output: config.outputOptions };
|
||||
|
@@ -1,8 +1,8 @@
|
||||
const { createCastConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
import webpack from "../build-scripts/webpack.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
module.exports = createCastConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
isStatsBuild: isStatsBuild(),
|
||||
export default webpack.createCastConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const rollup = require("../build-scripts/rollup.js");
|
||||
const env = require("../build-scripts/env.js");
|
||||
import rollup from "../build-scripts/rollup.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
const config = rollup.createDemoConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
@@ -7,4 +7,4 @@ const config = rollup.createDemoConfig({
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
||||
export default { ...config.inputOptions, output: config.outputOptions };
|
||||
|
@@ -1,12 +1,11 @@
|
||||
const { createDemoConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
import webpack from "../build-scripts/webpack.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
// File just used for stats builds
|
||||
|
||||
const latestBuild = true;
|
||||
|
||||
module.exports = createDemoConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
isStatsBuild: isStatsBuild(),
|
||||
export default webpack.createDemoConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
latestBuild,
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const rollup = require("../build-scripts/rollup.js");
|
||||
const env = require("../build-scripts/env.js");
|
||||
import rollup from "../build-scripts/rollup.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
const config = rollup.createGalleryConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
@@ -7,4 +7,4 @@ const config = rollup.createGalleryConfig({
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
||||
export default { ...config.inputOptions, output: config.outputOptions };
|
||||
|
@@ -1,4 +1,4 @@
|
||||
module.exports = [
|
||||
export default [
|
||||
{
|
||||
// This section has no header and so all page links are shown directly in the sidebar
|
||||
category: "concepts",
|
||||
|
@@ -1,8 +1,8 @@
|
||||
const { createGalleryConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
import webpack from "../build-scripts/webpack.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
module.exports = createGalleryConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
isStatsBuild: isStatsBuild(),
|
||||
export default webpack.createGalleryConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
||||
|
14
gulpfile.js
14
gulpfile.js
@@ -1,3 +1,13 @@
|
||||
var requireDir = require("require-dir");
|
||||
import { globIterate } from "glob";
|
||||
|
||||
requireDir("./build-scripts/gulp/");
|
||||
const gulpImports = [];
|
||||
|
||||
for await (const gulpModule of globIterate("build-scripts/gulp/*.?(c|m)js", {
|
||||
dotRelative: true,
|
||||
})) {
|
||||
gulpImports.push(import(gulpModule));
|
||||
}
|
||||
|
||||
// Since all tasks are currently registered with gulp.task(), this is enough
|
||||
// If any are converted to named exports, need to loop and aggregate exports here
|
||||
await Promise.all(gulpImports);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const rollup = require("../build-scripts/rollup.js");
|
||||
const env = require("../build-scripts/env.js");
|
||||
import rollup from "../build-scripts/rollup.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
const config = rollup.createHassioConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
@@ -7,4 +7,4 @@ const config = rollup.createHassioConfig({
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
||||
export default { ...config.inputOptions, output: config.outputOptions };
|
||||
|
@@ -1,8 +1,8 @@
|
||||
const { createHassioConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
import webpack from "../build-scripts/webpack.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
module.exports = createHassioConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
isStatsBuild: isStatsBuild(),
|
||||
export default webpack.createHassioConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
"*.{js,ts}": ["prettier --write", "eslint --fix"],
|
||||
export default {
|
||||
"*.?(c|m){js,ts}": ["eslint --fix", "prettier --write"],
|
||||
"!(/translations)*.{json,css,md,html}": "prettier --write",
|
||||
"translations/*/*.json": (files) =>
|
||||
'printf "%s\n" "Translation files should not be added or modified here. Instead, make the necessary modifications in src/translations/en.json. Other languages are managed externally. Please see https://developers.home-assistant.io/docs/translations/ for details." ' +
|
||||
|
15
package.json
15
package.json
@@ -19,10 +19,11 @@
|
||||
"postinstall": "husky install",
|
||||
"prepack": "pinst --disable",
|
||||
"postpack": "pinst --enable",
|
||||
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\""
|
||||
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\""
|
||||
},
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.2",
|
||||
"@codemirror/autocomplete": "6.4.2",
|
||||
@@ -183,15 +184,15 @@
|
||||
"@types/sortablejs": "1.15.1",
|
||||
"@types/tar": "6.1.4",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "5.56.0",
|
||||
"@typescript-eslint/parser": "5.56.0",
|
||||
"@web/dev-server": "0.1.36",
|
||||
"@typescript-eslint/eslint-plugin": "5.57.0",
|
||||
"@typescript-eslint/parser": "5.57.0",
|
||||
"@web/dev-server": "0.1.37",
|
||||
"@web/dev-server-rollup": "0.4.0",
|
||||
"babel-loader": "9.1.2",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"chai": "4.3.7",
|
||||
"del": "7.0.0",
|
||||
"eslint": "8.36.0",
|
||||
"eslint": "8.37.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.0.0",
|
||||
"eslint-config-prettier": "8.8.0",
|
||||
@@ -227,13 +228,12 @@
|
||||
"open": "8.4.2",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "2.8.7",
|
||||
"require-dir": "1.2.0",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-visualizer": "5.9.0",
|
||||
"serve-handler": "6.1.5",
|
||||
"sinon": "15.0.2",
|
||||
"sinon": "15.0.3",
|
||||
"source-map-url": "0.4.1",
|
||||
"systemjs": "6.14.1",
|
||||
"tar": "6.1.13",
|
||||
@@ -254,7 +254,6 @@
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@material/mwc-button@^0.25.3": "^0.27.0"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "always"
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20230329.0"
|
||||
version = "20230401.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const rollup = require("./build-scripts/rollup.js");
|
||||
const env = require("./build-scripts/env.js");
|
||||
import rollup from "../build-scripts/rollup.cjs";
|
||||
import env from "../build-scripts/env.cjs";
|
||||
|
||||
const config = rollup.createAppConfig({
|
||||
isProdBuild: env.isProdBuild(),
|
||||
@@ -7,4 +7,4 @@ const config = rollup.createAppConfig({
|
||||
isStatsBuild: env.isStatsBuild(),
|
||||
});
|
||||
|
||||
module.exports = { ...config.inputOptions, output: config.outputOptions };
|
||||
export default { ...config.inputOptions, output: config.outputOptions };
|
||||
|
@@ -118,24 +118,40 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
|
||||
"window",
|
||||
],
|
||||
},
|
||||
device_tracker: {
|
||||
source_type: ["bluetooth", "bluetooth_le", "gps", "router"],
|
||||
},
|
||||
fan: {
|
||||
direction: ["forward", "reverse"],
|
||||
},
|
||||
humidifier: {
|
||||
device_class: ["humidifier", "dehumidifier"],
|
||||
},
|
||||
media_player: {
|
||||
device_class: ["tv", "speaker", "receiver"],
|
||||
media_content_type: [
|
||||
"album",
|
||||
"app",
|
||||
"artist",
|
||||
"channel",
|
||||
"channels",
|
||||
"composer",
|
||||
"contibuting_artist",
|
||||
"episode",
|
||||
"game",
|
||||
"genre",
|
||||
"image",
|
||||
"movie",
|
||||
"music",
|
||||
"playlist",
|
||||
"podcast",
|
||||
"season",
|
||||
"track",
|
||||
"tvshow",
|
||||
"url",
|
||||
"video",
|
||||
],
|
||||
repeat: ["off", "one", "all"],
|
||||
},
|
||||
number: {
|
||||
device_class: ["temperature"],
|
||||
|
@@ -302,6 +302,7 @@ export default class HaChartBase extends LitElement {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.chartContainer {
|
||||
overflow: hidden;
|
||||
|
@@ -61,6 +61,10 @@ class StateHistoryChartLine extends LitElement {
|
||||
this._chartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
interaction: {
|
||||
mode: "nearest",
|
||||
axis: "x",
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
@@ -108,7 +112,6 @@ class StateHistoryChartLine extends LitElement {
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "nearest",
|
||||
callbacks: {
|
||||
label: (context) =>
|
||||
`${context.dataset.label}: ${formatNumber(
|
||||
@@ -127,16 +130,13 @@ class StateHistoryChartLine extends LitElement {
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
mode: "nearest",
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.1,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
point: {
|
||||
hitRadius: 5,
|
||||
hitRadius: 50,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
|
@@ -102,6 +102,7 @@ class StatisticsChart extends LitElement {
|
||||
if (
|
||||
changedProps.has("statisticsData") ||
|
||||
changedProps.has("statTypes") ||
|
||||
changedProps.has("chartType") ||
|
||||
changedProps.has("hideLegend")
|
||||
) {
|
||||
this._generateData();
|
||||
@@ -149,6 +150,10 @@ class StatisticsChart extends LitElement {
|
||||
this._chartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
interaction: {
|
||||
mode: "nearest",
|
||||
axis: "x",
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
@@ -186,7 +191,6 @@ class StatisticsChart extends LitElement {
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "nearest",
|
||||
callbacks: {
|
||||
label: (context) =>
|
||||
`${context.dataset.label}: ${formatNumber(
|
||||
@@ -208,9 +212,6 @@ class StatisticsChart extends LitElement {
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
mode: "nearest",
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.4,
|
||||
@@ -219,7 +220,7 @@ class StatisticsChart extends LitElement {
|
||||
},
|
||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
||||
point: {
|
||||
hitRadius: 5,
|
||||
hitRadius: 50,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
@@ -316,6 +317,7 @@ class StatisticsChart extends LitElement {
|
||||
}
|
||||
statDataSets.forEach((d, i) => {
|
||||
if (
|
||||
this.chartType === "line" &&
|
||||
prevEndTime &&
|
||||
prevValues &&
|
||||
prevEndTime.getTime() !== start.getTime()
|
||||
|
@@ -25,8 +25,6 @@ export type ControlSelectOption = {
|
||||
export class HaControlSelect extends LitElement {
|
||||
@property({ type: Boolean, reflect: true }) disabled = false;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public options?: ControlSelectOption[];
|
||||
|
||||
@property() public value?: string;
|
||||
@@ -305,6 +303,14 @@ export class HaControlSelect extends LitElement {
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.option .content span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
hyphens: auto;
|
||||
}
|
||||
:host([vertical]) {
|
||||
width: var(--control-select-thickness);
|
||||
|
@@ -41,7 +41,9 @@ export class HaDialog extends DialogBase {
|
||||
SUPPRESS_DEFAULT_PRESS_SELECTOR,
|
||||
].join(", ");
|
||||
this._updateScrolledAttribute();
|
||||
this.contentElement?.addEventListener("scroll", this._onScroll);
|
||||
this.contentElement?.addEventListener("scroll", this._onScroll, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
|
@@ -11,6 +11,9 @@ export class HaDrawer extends DrawerBase {
|
||||
.mdc-drawer {
|
||||
top: 0;
|
||||
}
|
||||
.mdc-drawer--modal.mdc-drawer--open {
|
||||
left: min(0px, var(--drawer-modal-left-offset));
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -191,6 +191,9 @@ export class HaFileUpload extends LitElement {
|
||||
inset-inline-end: initial !important;
|
||||
direction: var(--direction);
|
||||
}
|
||||
.mdc-text-field__icon--trailing {
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
.dragged:before {
|
||||
position: var(--layout-fit_-_position);
|
||||
top: var(--layout-fit_-_top);
|
||||
|
@@ -33,7 +33,7 @@ export class HaHeaderBar extends LitElement {
|
||||
unsafeCSS(topAppBarStyles),
|
||||
css`
|
||||
.mdc-top-app-bar__row {
|
||||
height: var(--header-bar-height, 64px);
|
||||
height: var(--header-height);
|
||||
}
|
||||
.mdc-top-app-bar {
|
||||
position: static;
|
||||
|
@@ -92,7 +92,7 @@ export class HaSettingsRow extends LitElement {
|
||||
::slotted(ha-switch) {
|
||||
padding: 16px 0;
|
||||
}
|
||||
div[secondary] {
|
||||
.secondary {
|
||||
white-space: normal;
|
||||
}
|
||||
.prefix-wrap {
|
||||
|
@@ -10,6 +10,7 @@ export class HaTopAppBarFixed extends TopAppBarFixedBase {
|
||||
css`
|
||||
.mdc-top-app-bar__row {
|
||||
height: var(--header-height);
|
||||
border-bottom: var(--app-header-border-bottom);
|
||||
}
|
||||
.mdc-top-app-bar--fixed-adjust {
|
||||
padding-top: var(--header-height);
|
||||
@@ -21,7 +22,6 @@ export class HaTopAppBarFixed extends TopAppBarFixedBase {
|
||||
--app-header-background-color,
|
||||
var(--mdc-theme-primary)
|
||||
);
|
||||
border-bottom: var(--app-header-border-bottom);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -10,6 +10,7 @@ export class HaTopAppBar extends TopAppBarBase {
|
||||
css`
|
||||
.mdc-top-app-bar__row {
|
||||
height: var(--header-height);
|
||||
border-bottom: var(--app-header-border-bottom);
|
||||
}
|
||||
.mdc-top-app-bar--fixed-adjust {
|
||||
padding-top: var(--header-height);
|
||||
@@ -21,7 +22,6 @@ export class HaTopAppBar extends TopAppBarBase {
|
||||
--app-header-background-color,
|
||||
var(--mdc-theme-primary)
|
||||
);
|
||||
border-bottom: var(--app-header-border-bottom);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -3,7 +3,9 @@ import {
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import { blankBeforePercent } from "../common/translations/blank_before_percent";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
import { FrontendLocaleData } from "./translation";
|
||||
|
||||
export const enum CoverEntityFeature {
|
||||
OPEN = 1,
|
||||
@@ -106,3 +108,18 @@ interface CoverEntityAttributes extends HassEntityAttributeBase {
|
||||
export interface CoverEntity extends HassEntityBase {
|
||||
attributes: CoverEntityAttributes;
|
||||
}
|
||||
|
||||
export function computeCoverPositionStateDisplay(
|
||||
stateObj: CoverEntity,
|
||||
locale: FrontendLocaleData,
|
||||
position?: number
|
||||
) {
|
||||
const currentPosition =
|
||||
position ??
|
||||
stateObj.attributes.current_position ??
|
||||
stateObj.attributes.current_tilt_position;
|
||||
|
||||
return currentPosition && currentPosition !== 100
|
||||
? `${Math.round(currentPosition)}${blankBeforePercent(locale)}%`
|
||||
: "";
|
||||
}
|
||||
|
@@ -9,6 +9,8 @@ import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { blankBeforePercent } from "../common/translations/blank_before_percent";
|
||||
import { FrontendLocaleData } from "./translation";
|
||||
|
||||
export const enum FanEntityFeature {
|
||||
SET_SPEED = 1,
|
||||
@@ -91,3 +93,15 @@ export function computeFanSpeedIcon(
|
||||
: [mdiFanSpeed1, mdiFanSpeed2, mdiFanSpeed3][index - 1];
|
||||
}
|
||||
export const FAN_SPEED_COUNT_MAX_FOR_BUTTONS = 4;
|
||||
|
||||
export function computeFanSpeedStateDisplay(
|
||||
stateObj: FanEntity,
|
||||
locale: FrontendLocaleData,
|
||||
speed?: number
|
||||
) {
|
||||
const currentSpeed = speed ?? stateObj.attributes.percentage;
|
||||
|
||||
return currentSpeed
|
||||
? `${Math.round(currentSpeed)}${blankBeforePercent(locale)}%`
|
||||
: "";
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import { UiAction } from "../panels/lovelace/components/hui-action-editor";
|
||||
import type { DeviceRegistryEntry } from "./device_registry";
|
||||
import type { EntitySources } from "./entity_sources";
|
||||
@@ -149,6 +151,7 @@ interface EntitySelectorFilter {
|
||||
integration?: string;
|
||||
domain?: string | readonly string[];
|
||||
device_class?: string | readonly string[];
|
||||
supported_features?: number | [number];
|
||||
}
|
||||
|
||||
export interface EntitySelector {
|
||||
@@ -358,6 +361,7 @@ export const filterSelectorEntities = (
|
||||
const {
|
||||
domain: filterDomain,
|
||||
device_class: filterDeviceClass,
|
||||
supported_features: filterSupportedFeature,
|
||||
integration: filterIntegration,
|
||||
} = filterEntity;
|
||||
|
||||
@@ -383,6 +387,16 @@ export const filterSelectorEntities = (
|
||||
}
|
||||
}
|
||||
|
||||
if (filterSupportedFeature) {
|
||||
if (
|
||||
ensureArray(filterSupportedFeature).some(
|
||||
(feature) => !supportsFeature(entity, feature)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
filterIntegration &&
|
||||
entitySources?.[entity.entity_id]?.domain !== filterIntegration
|
||||
|
@@ -14,6 +14,7 @@ interface PipelineRunStartEvent extends PipelineEventBase {
|
||||
language: string;
|
||||
runner_data: {
|
||||
stt_binary_handler_id: number | null;
|
||||
timeout: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -40,7 +41,7 @@ interface PipelineSTTStartEvent extends PipelineEventBase {
|
||||
interface PipelineSTTEndEvent extends PipelineEventBase {
|
||||
type: "stt-end";
|
||||
data: {
|
||||
text: string;
|
||||
stt_output: { text: string };
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -1,9 +1,7 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeAttributeNameDisplay } from "../../../../common/entity/compute_attribute_display";
|
||||
import { stateColorCss } from "../../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../../common/entity/supports-feature";
|
||||
import "../../../../components/ha-control-select";
|
||||
@@ -14,6 +12,7 @@ import {
|
||||
AlarmMode,
|
||||
ALARM_MODES,
|
||||
} from "../../../../data/alarm_control_panel";
|
||||
import { UNAVAILABLE } from "../../../../data/entity";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { showEnterCodeDialogDialog } from "./show-enter-code-dialog";
|
||||
|
||||
@@ -33,14 +32,10 @@ export class HaMoreInfoAlarmControlPanelModes extends LitElement {
|
||||
});
|
||||
});
|
||||
|
||||
protected updated(changedProp: Map<string | number | symbol, unknown>): void {
|
||||
super.updated(changedProp);
|
||||
if (changedProp.has("stateObj") && this.stateObj) {
|
||||
const oldStateObj = changedProp.get("stateObj") as HassEntity | undefined;
|
||||
|
||||
if (!oldStateObj || this.stateObj.state !== oldStateObj.state) {
|
||||
this._currentMode = this._getCurrentMode(this.stateObj);
|
||||
}
|
||||
protected willUpdate(changedProp: PropertyValues): void {
|
||||
super.willUpdate(changedProp);
|
||||
if (changedProp.has("stateObj")) {
|
||||
this._currentMode = this._getCurrentMode(this.stateObj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,53 +45,59 @@ export class HaMoreInfoAlarmControlPanelModes extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private async _valueChanged(ev: CustomEvent) {
|
||||
const mode = (ev.detail as any).value as AlarmMode;
|
||||
|
||||
const { state: modeState, service } = ALARM_MODES[mode];
|
||||
|
||||
if (modeState === this.stateObj.state) return;
|
||||
|
||||
// Force ha-control-select to previous mode because we don't known if the service call will succeed due to code check
|
||||
this._currentMode = mode;
|
||||
await this.requestUpdate("_currentMode");
|
||||
this._currentMode = this._getCurrentMode(this.stateObj!);
|
||||
private async _setMode(mode: AlarmMode) {
|
||||
const { service } = ALARM_MODES[mode];
|
||||
|
||||
let code: string | undefined;
|
||||
|
||||
if (
|
||||
(mode !== "disarmed" &&
|
||||
this.stateObj.attributes.code_arm_required &&
|
||||
this.stateObj.attributes.code_format) ||
|
||||
(mode === "disarmed" && this.stateObj.attributes.code_format)
|
||||
this.stateObj!.attributes.code_arm_required &&
|
||||
this.stateObj!.attributes.code_format) ||
|
||||
(mode === "disarmed" && this.stateObj!.attributes.code_format)
|
||||
) {
|
||||
const disarm = mode === "disarmed";
|
||||
|
||||
const response = await showEnterCodeDialogDialog(this, {
|
||||
codeFormat: this.stateObj.attributes.code_format,
|
||||
title: this.hass.localize(
|
||||
codeFormat: this.stateObj!.attributes.code_format,
|
||||
title: this.hass!.localize(
|
||||
`ui.dialogs.more_info_control.alarm_control_panel.${
|
||||
disarm ? "disarm_title" : "arm_title"
|
||||
}`
|
||||
),
|
||||
submitText: this.hass.localize(
|
||||
submitText: this.hass!.localize(
|
||||
`ui.dialogs.more_info_control.alarm_control_panel.${
|
||||
disarm ? "disarm_action" : "arm_action"
|
||||
}`
|
||||
),
|
||||
});
|
||||
if (!response) {
|
||||
return;
|
||||
if (response == null) {
|
||||
throw new Error("cancel");
|
||||
}
|
||||
code = response;
|
||||
}
|
||||
|
||||
await this.hass.callService("alarm_control_panel", service, {
|
||||
await this.hass!.callService("alarm_control_panel", service, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
code,
|
||||
});
|
||||
}
|
||||
|
||||
private async _valueChanged(ev: CustomEvent) {
|
||||
const mode = (ev.detail as any).value as AlarmMode;
|
||||
|
||||
if (ALARM_MODES[mode].state === this.stateObj!.state) return;
|
||||
|
||||
const oldMode = this._getCurrentMode(this.stateObj!);
|
||||
this._currentMode = mode;
|
||||
|
||||
try {
|
||||
await this._setMode(mode);
|
||||
} catch (err) {
|
||||
this._currentMode = oldMode;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const color = stateColorCss(this.stateObj);
|
||||
|
||||
@@ -116,16 +117,14 @@ export class HaMoreInfoAlarmControlPanelModes extends LitElement {
|
||||
.options=${options}
|
||||
.value=${this._currentMode}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${computeAttributeNameDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj,
|
||||
this.hass.entities,
|
||||
"percentage"
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.alarm_control_panel.modes_label"
|
||||
)}
|
||||
style=${styleMap({
|
||||
"--control-select-color": color,
|
||||
"--modes-count": modes.length.toString(),
|
||||
})}
|
||||
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||
>
|
||||
</ha-control-select>
|
||||
`;
|
||||
|
@@ -108,6 +108,7 @@ export class HaMoreInfoFanSpeed extends LitElement {
|
||||
style=${styleMap({
|
||||
"--control-select-color": color,
|
||||
})}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
</ha-control-select>
|
||||
`;
|
||||
@@ -116,9 +117,9 @@ export class HaMoreInfoFanSpeed extends LitElement {
|
||||
return html`
|
||||
<ha-control-slider
|
||||
vertical
|
||||
.value=${this.value}
|
||||
min="0"
|
||||
max="100"
|
||||
.value=${this.value}
|
||||
.step=${this.stateObj.attributes.percentage_step ?? 1}
|
||||
@value-changed=${this._valueChanged}
|
||||
.ariaLabel=${computeAttributeNameDisplay(
|
||||
|
@@ -31,7 +31,7 @@ class MoreInfoAlarmControlPanel extends LitElement {
|
||||
"ui.dialogs.more_info_control.alarm_control_panel.disarm_action"
|
||||
),
|
||||
});
|
||||
if (!response) {
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
code = response;
|
||||
|
@@ -10,9 +10,12 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
import "../../../components/ha-attributes";
|
||||
import { CoverEntity, CoverEntityFeature } from "../../../data/cover";
|
||||
import {
|
||||
computeCoverPositionStateDisplay,
|
||||
CoverEntity,
|
||||
CoverEntityFeature,
|
||||
} from "../../../data/cover";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../components/cover/ha-more-info-cover-buttons";
|
||||
import "../components/cover/ha-more-info-cover-position";
|
||||
@@ -27,7 +30,9 @@ class MoreInfoCover extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public stateObj?: CoverEntity;
|
||||
|
||||
@state() private _displayedPosition?: number;
|
||||
@state() private _livePosition?: number;
|
||||
|
||||
@state() private _liveTilt?: number;
|
||||
|
||||
@state() private _mode?: "position" | "button";
|
||||
|
||||
@@ -35,20 +40,29 @@ class MoreInfoCover extends LitElement {
|
||||
this._mode = this._mode === "position" ? "button" : "position";
|
||||
}
|
||||
|
||||
private _positionChanged(ev) {
|
||||
private _positionSliderMoved(ev) {
|
||||
const value = (ev.detail as any).value;
|
||||
if (isNaN(value)) return;
|
||||
this._displayedPosition = value;
|
||||
this._livePosition = value;
|
||||
}
|
||||
|
||||
private _positionValueChanged() {
|
||||
this._livePosition = undefined;
|
||||
}
|
||||
|
||||
private _tiltSliderMoved(ev) {
|
||||
const value = (ev.detail as any).value;
|
||||
if (isNaN(value)) return;
|
||||
this._liveTilt = value;
|
||||
}
|
||||
|
||||
private _tiltValueChanged() {
|
||||
this._liveTilt = undefined;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("stateObj") && this.stateObj) {
|
||||
if (supportsFeature(this.stateObj, CoverEntityFeature.SET_POSITION)) {
|
||||
const currentPosition = this.stateObj?.attributes.current_position;
|
||||
this._displayedPosition =
|
||||
currentPosition != null ? Math.round(currentPosition) : undefined;
|
||||
}
|
||||
if (!this._mode) {
|
||||
this._mode =
|
||||
supportsFeature(this.stateObj, CoverEntityFeature.SET_POSITION) ||
|
||||
@@ -60,29 +74,29 @@ class MoreInfoCover extends LitElement {
|
||||
}
|
||||
|
||||
private get _stateOverride() {
|
||||
if (this._displayedPosition == null) return undefined;
|
||||
const liveValue = this._livePosition ?? this._liveTilt;
|
||||
|
||||
const tempState = {
|
||||
...this.stateObj,
|
||||
state: this._displayedPosition ? "open" : "closed",
|
||||
attributes: {
|
||||
...this.stateObj!.attributes,
|
||||
current_position: this._displayedPosition,
|
||||
},
|
||||
} as CoverEntity;
|
||||
const forcedState =
|
||||
liveValue != null ? (liveValue ? "open" : "closed") : undefined;
|
||||
|
||||
const stateDisplay = computeStateDisplay(
|
||||
this.hass.localize,
|
||||
tempState!,
|
||||
this.stateObj!,
|
||||
this.hass.locale,
|
||||
this.hass.entities
|
||||
this.hass.entities,
|
||||
forcedState
|
||||
);
|
||||
|
||||
return this._displayedPosition && this._displayedPosition !== 100
|
||||
? `${stateDisplay} - ${Math.round(
|
||||
this._displayedPosition
|
||||
)}${blankBeforePercent(this.hass!.locale)}%`
|
||||
: stateDisplay;
|
||||
const positionStateDisplay = computeCoverPositionStateDisplay(
|
||||
this.stateObj!,
|
||||
this.hass.locale,
|
||||
liveValue
|
||||
);
|
||||
|
||||
if (positionStateDisplay) {
|
||||
return `${stateDisplay} ⸱ ${positionStateDisplay}`;
|
||||
}
|
||||
return stateDisplay;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -133,7 +147,8 @@ class MoreInfoCover extends LitElement {
|
||||
<ha-more-info-cover-position
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
@slider-moved=${this._positionChanged}
|
||||
@slider-moved=${this._positionSliderMoved}
|
||||
@value-changed=${this._positionValueChanged}
|
||||
></ha-more-info-cover-position>
|
||||
`
|
||||
: nothing}
|
||||
@@ -142,6 +157,8 @@ class MoreInfoCover extends LitElement {
|
||||
<ha-more-info-cover-tilt-position
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
@slider-moved=${this._tiltSliderMoved}
|
||||
@value-changed=${this._tiltValueChanged}
|
||||
></ha-more-info-cover-tilt-position>
|
||||
`
|
||||
: nothing}
|
||||
|
@@ -22,11 +22,12 @@ import {
|
||||
computeAttributeNameDisplay,
|
||||
computeAttributeValueDisplay,
|
||||
} from "../../../common/entity/compute_attribute_display";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
import "../../../components/ha-attributes";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import {
|
||||
computeFanSpeedStateDisplay,
|
||||
computeFanSpeedCount,
|
||||
FanEntity,
|
||||
FanEntityFeature,
|
||||
@@ -49,12 +50,16 @@ class MoreInfoFan extends LitElement {
|
||||
|
||||
@state() public _presetMode?: string;
|
||||
|
||||
@state() private _selectedPercentage?: number;
|
||||
@state() private _liveSpeed?: number;
|
||||
|
||||
private _percentageChanged(ev) {
|
||||
private _speedSliderMoved(ev) {
|
||||
const value = (ev.detail as any).value;
|
||||
if (isNaN(value)) return;
|
||||
this._selectedPercentage = value;
|
||||
this._liveSpeed = value;
|
||||
}
|
||||
|
||||
private _speedValueChanged() {
|
||||
this._liveSpeed = undefined;
|
||||
}
|
||||
|
||||
private _toggle = () => {
|
||||
@@ -107,12 +112,35 @@ class MoreInfoFan extends LitElement {
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("stateObj")) {
|
||||
this._presetMode = this.stateObj?.attributes.preset_mode;
|
||||
this._selectedPercentage = this.stateObj?.attributes.percentage
|
||||
? Math.round(this.stateObj.attributes.percentage)
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private get _stateOverride() {
|
||||
const liveValue = this._liveSpeed;
|
||||
|
||||
const forcedState =
|
||||
this._liveSpeed != null ? (this._liveSpeed ? "on" : "off") : undefined;
|
||||
|
||||
const stateDisplay = computeStateDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj!,
|
||||
this.hass.locale,
|
||||
this.hass.entities,
|
||||
forcedState
|
||||
);
|
||||
|
||||
const positionStateDisplay = computeFanSpeedStateDisplay(
|
||||
this.stateObj!,
|
||||
this.hass.locale,
|
||||
liveValue
|
||||
);
|
||||
|
||||
if (positionStateDisplay) {
|
||||
return positionStateDisplay;
|
||||
}
|
||||
return stateDisplay;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return nothing;
|
||||
@@ -140,17 +168,11 @@ class MoreInfoFan extends LitElement {
|
||||
supportsSpeed &&
|
||||
computeFanSpeedCount(this.stateObj) > FAN_SPEED_COUNT_MAX_FOR_BUTTONS;
|
||||
|
||||
const stateOverride = this._selectedPercentage
|
||||
? `${Math.round(this._selectedPercentage)}${blankBeforePercent(
|
||||
this.hass!.locale
|
||||
)}%`
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<ha-more-info-state-header
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
.stateOverride=${stateOverride}
|
||||
.stateOverride=${this._stateOverride}
|
||||
></ha-more-info-state-header>
|
||||
<div class="controls">
|
||||
${
|
||||
@@ -159,7 +181,8 @@ class MoreInfoFan extends LitElement {
|
||||
<ha-more-info-fan-speed
|
||||
.stateObj=${this.stateObj}
|
||||
.hass=${this.hass}
|
||||
@slider-moved=${this._percentageChanged}
|
||||
@slider-moved=${this._speedSliderMoved}
|
||||
@value-changed=${this._speedValueChanged}
|
||||
>
|
||||
</ha-more-info-fan-speed>
|
||||
`
|
||||
|
@@ -478,7 +478,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
@media all and (max-width: 450px) {
|
||||
.child-view > * {
|
||||
min-height: calc(100vh - 56px);
|
||||
min-height: calc(100vh - var(--header-height));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -90,7 +90,7 @@ export class MoreInfoInfo extends LitElement {
|
||||
|
||||
@media all and (max-width: 450px) {
|
||||
.container {
|
||||
min-height: calc(100vh - 56px);
|
||||
min-height: calc(100vh - var(--header-height));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -152,7 +152,6 @@ export class HuiNotificationDrawer extends LitElement {
|
||||
--mdc-theme-primary: var(--primary-background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
display: block;
|
||||
--header-bar-height: var(--header-height);
|
||||
}
|
||||
|
||||
.notifications {
|
||||
|
@@ -755,6 +755,9 @@ export class QuickBar extends LitElement {
|
||||
haStyleScrollbar,
|
||||
haStyleDialog,
|
||||
css`
|
||||
mwc-list {
|
||||
--mdc-list-vertical-padding: 0;
|
||||
}
|
||||
.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@@ -44,6 +44,25 @@ const handleExternalMessage = (
|
||||
success: true,
|
||||
result: null,
|
||||
});
|
||||
} else if (msg.command === "sidebar/toggle") {
|
||||
fireEvent(hassMainEl, "hass-toggle-menu");
|
||||
bus.fireMessage({
|
||||
id: msg.id,
|
||||
type: "result",
|
||||
success: true,
|
||||
result: null,
|
||||
});
|
||||
} else if (msg.command === "sidebar/show") {
|
||||
fireEvent(hassMainEl, "hass-toggle-menu", {
|
||||
open: true,
|
||||
screenPercentage: msg.data?.screenPercentage,
|
||||
});
|
||||
bus.fireMessage({
|
||||
id: msg.id,
|
||||
type: "result",
|
||||
success: true,
|
||||
result: null,
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@@ -121,9 +121,24 @@ interface EMIncomingMessageShowNotifications {
|
||||
command: "notifications/show";
|
||||
}
|
||||
|
||||
interface EMIncomingMessageToggleSidebar {
|
||||
id: number;
|
||||
type: "command";
|
||||
command: "sidebar/toggle";
|
||||
}
|
||||
|
||||
interface EMIncomingMessageShowSidebar {
|
||||
id: number;
|
||||
type: "command";
|
||||
command: "sidebar/show";
|
||||
data?: { screenPercentage: number };
|
||||
}
|
||||
|
||||
export type EMIncomingMessageCommands =
|
||||
| EMIncomingMessageRestart
|
||||
| EMIncomingMessageShowNotifications;
|
||||
| EMIncomingMessageShowNotifications
|
||||
| EMIncomingMessageToggleSidebar
|
||||
| EMIncomingMessageShowSidebar;
|
||||
|
||||
type EMIncomingMessage =
|
||||
| EMMessageResultSuccess
|
||||
|
@@ -1,8 +1,6 @@
|
||||
<meta name='viewport' content='width=device-width, user-scalable=no, viewport-fit=cover, initial-scale=1'>
|
||||
<style>
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Roboto, sans-serif;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
@@ -65,7 +65,7 @@ class HassErrorScreen extends LitElement {
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
height: var(--header-height);
|
||||
padding: 0 16px;
|
||||
padding: 8px 12px;
|
||||
pointer-events: none;
|
||||
background-color: var(--app-header-background-color);
|
||||
font-weight: 400;
|
||||
@@ -73,6 +73,11 @@ class HassErrorScreen extends LitElement {
|
||||
border-bottom: var(--app-header-border-bottom, none);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@media (max-width: 599px) {
|
||||
.toolbar {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
ha-icon-button-arrow-prev {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
@@ -60,7 +60,7 @@ class HassLoadingScreen extends LitElement {
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
height: var(--header-height);
|
||||
padding: 0 16px;
|
||||
padding: 8px 12px;
|
||||
pointer-events: none;
|
||||
background-color: var(--app-header-background-color);
|
||||
font-weight: 400;
|
||||
@@ -68,6 +68,11 @@ class HassLoadingScreen extends LitElement {
|
||||
border-bottom: var(--app-header-border-bottom, none);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@media (max-width: 599px) {
|
||||
.toolbar {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
ha-menu-button,
|
||||
ha-icon-button-arrow-prev {
|
||||
pointer-events: auto;
|
||||
|
@@ -111,7 +111,7 @@ class HassSubpage extends LitElement {
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
height: var(--header-height);
|
||||
padding: 0 16px;
|
||||
padding: 8px 12px;
|
||||
pointer-events: none;
|
||||
background-color: var(--app-header-background-color);
|
||||
font-weight: 400;
|
||||
@@ -119,6 +119,11 @@ class HassSubpage extends LitElement {
|
||||
border-bottom: var(--app-header-border-bottom, none);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@media (max-width: 599px) {
|
||||
.toolbar {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
.toolbar a {
|
||||
color: var(--sidebar-text-color);
|
||||
text-decoration: none;
|
||||
|
@@ -323,7 +323,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
||||
--text-field-overflow: initial;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-right: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.active-filters {
|
||||
|
@@ -235,9 +235,14 @@ class HassTabsSubpage extends LitElement {
|
||||
background-color: var(--sidebar-background-color);
|
||||
font-weight: 400;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
padding: 0 16px;
|
||||
padding: 8px 12px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@media (max-width: 599px) {
|
||||
.toolbar {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
.toolbar a {
|
||||
color: var(--sidebar-text-color);
|
||||
text-decoration: none;
|
||||
|
@@ -7,11 +7,11 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import "@material/mwc-drawer/mwc-drawer";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { listenMediaQuery } from "../common/dom/media_query";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import "../components/ha-drawer";
|
||||
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
|
||||
import type { HomeAssistant, Route } from "../types";
|
||||
import "./partial-panel-resolver";
|
||||
@@ -19,12 +19,15 @@ import "./partial-panel-resolver";
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"hass-toggle-menu": undefined;
|
||||
"hass-toggle-menu":
|
||||
| undefined
|
||||
| { open?: boolean; screenPercentage?: number };
|
||||
"hass-edit-sidebar": EditSideBarEvent;
|
||||
"hass-show-notifications": undefined;
|
||||
}
|
||||
interface HTMLElementEventMap {
|
||||
"hass-edit-sidebar": HASSDomEvent<EditSideBarEvent>;
|
||||
"hass-toggle-menu": HASSDomEvent<HASSDomEvents["hass-toggle-menu"]>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +60,7 @@ export class HomeAssistantMain extends LitElement {
|
||||
const sidebarNarrow = this._sidebarNarrow || this._externalSidebar;
|
||||
|
||||
return html`
|
||||
<mwc-drawer
|
||||
<ha-drawer
|
||||
.type=${sidebarNarrow ? "modal" : ""}
|
||||
.open=${sidebarNarrow ? this._drawerOpen : undefined}
|
||||
@MDCDrawer:closed=${this._drawerClosed}
|
||||
@@ -75,7 +78,7 @@ export class HomeAssistantMain extends LitElement {
|
||||
.route=${this.route}
|
||||
slot="appContent"
|
||||
></partial-panel-resolver>
|
||||
</mwc-drawer>
|
||||
</ha-drawer>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -107,7 +110,7 @@ export class HomeAssistantMain extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
this.addEventListener("hass-toggle-menu", () => {
|
||||
this.addEventListener("hass-toggle-menu", (ev) => {
|
||||
if (this._sidebarEditMode) {
|
||||
return;
|
||||
}
|
||||
@@ -118,10 +121,20 @@ export class HomeAssistantMain extends LitElement {
|
||||
return;
|
||||
}
|
||||
if (this._sidebarNarrow) {
|
||||
this._drawerOpen = !this._drawerOpen;
|
||||
this._drawerOpen = ev.detail?.open ?? !this._drawerOpen;
|
||||
const offset = ev.detail?.screenPercentage
|
||||
? -256 + screen.width * (ev.detail.screenPercentage / 100)
|
||||
: 0;
|
||||
this.style.setProperty("--drawer-modal-left-offset", `${offset}px`);
|
||||
} else {
|
||||
fireEvent(this, "hass-dock-sidebar", {
|
||||
dock: this.hass.dockedSidebar === "auto" ? "docked" : "auto",
|
||||
dock: ev.detail?.open
|
||||
? "docked"
|
||||
: ev.detail?.open === false
|
||||
? "auto"
|
||||
: this.hass.dockedSidebar === "auto"
|
||||
? "docked"
|
||||
: "auto",
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -142,10 +155,12 @@ export class HomeAssistantMain extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
toggleAttribute(this, "expanded", this.hass.dockedSidebar === "docked");
|
||||
|
||||
toggleAttribute(
|
||||
this,
|
||||
"expanded",
|
||||
this.narrow || this.hass.dockedSidebar !== "auto"
|
||||
"modal",
|
||||
this._sidebarNarrow || this._externalSidebar
|
||||
);
|
||||
}
|
||||
|
||||
@@ -165,20 +180,20 @@ export class HomeAssistantMain extends LitElement {
|
||||
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
--mdc-drawer-width: 56px;
|
||||
--mdc-top-app-bar-width: calc(100% - var(--mdc-drawer-width));
|
||||
}
|
||||
:host([expanded]) {
|
||||
--mdc-drawer-width: calc(256px + env(safe-area-inset-left));
|
||||
}
|
||||
:host([modal]) {
|
||||
--mdc-drawer-width: unset;
|
||||
--mdc-top-app-bar-width: unset;
|
||||
}
|
||||
partial-panel-resolver,
|
||||
ha-sidebar {
|
||||
/* allow a light tap highlight on the actual interface elements */
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@media (min-width: 870px) {
|
||||
partial-panel-resolver {
|
||||
--mdc-top-app-bar-width: calc(100% - var(--mdc-drawer-width));
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -511,7 +511,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
height: var(--header-height);
|
||||
padding: 0 16px;
|
||||
padding: 4px;
|
||||
background-color: var(--primary-background-color);
|
||||
font-weight: 400;
|
||||
color: var(--app-header-text-color, white);
|
||||
@@ -520,7 +520,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
}
|
||||
|
||||
.main {
|
||||
height: calc(100% - 56px);
|
||||
height: calc(100% - var(--header-height));
|
||||
display: flex;
|
||||
background-color: var(--card-background-color);
|
||||
direction: ltr;
|
||||
|
@@ -376,7 +376,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
|
||||
const filterMenu = html`
|
||||
<div slot=${ifDefined(this.narrow ? "toolbar-icon" : "suffix")}>
|
||||
<div slot=${ifDefined(this.narrow ? "toolbar-icon" : undefined)}>
|
||||
<div class="menu-badge-container">
|
||||
${!this._showDisabled && this.narrow && disabledCount
|
||||
? html`<span class="badge">${disabledCount}</span>`
|
||||
@@ -455,24 +455,25 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
>
|
||||
${!this._showDisabled && disabledCount
|
||||
? html`<div
|
||||
class="active-filters"
|
||||
slot="suffix"
|
||||
@click=${this._preventDefault}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.disabled_integrations",
|
||||
{ number: disabledCount }
|
||||
)}
|
||||
<mwc-button
|
||||
@click=${this._toggleShowDisabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.show"
|
||||
? html`<div class="filters" slot="suffix">
|
||||
<div
|
||||
class="active-filters"
|
||||
@click=${this._preventDefault}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.disabled_integrations",
|
||||
{ number: disabledCount }
|
||||
)}
|
||||
></mwc-button>
|
||||
<mwc-button
|
||||
@click=${this._toggleShowDisabled}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.disable.show"
|
||||
)}
|
||||
></mwc-button>
|
||||
</div>
|
||||
${filterMenu}
|
||||
</div>`
|
||||
: ""}
|
||||
${filterMenu}
|
||||
</search-input>
|
||||
</div>
|
||||
`}
|
||||
@@ -845,7 +846,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
.container > * {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
@@ -884,6 +884,15 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
.filters {
|
||||
--mdc-text-field-fill-color: var(--input-fill-color);
|
||||
--mdc-text-field-idle-line-color: var(--input-idle-line-color);
|
||||
--mdc-shape-small: 4px;
|
||||
--text-field-overflow: initial;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.active-filters {
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
|
@@ -1,317 +1,131 @@
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../../../components/ha-card";
|
||||
import "../../../../../../components/ha-alert";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import "../../../../../../components/ha-button";
|
||||
import "../../../../../../components/ha-circular-progress";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import "../../../../../../components/ha-textfield";
|
||||
import {
|
||||
PipelineRun,
|
||||
runPipelineFromText,
|
||||
} from "../../../../../../data/voice_assistant";
|
||||
import "../../../../../../layouts/hass-subpage";
|
||||
import { SubscribeMixin } from "../../../../../../mixins/subscribe-mixin";
|
||||
import "../../../../../../components/ha-formfield";
|
||||
import "../../../../../../components/ha-checkbox";
|
||||
import { haStyle } from "../../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { formatNumber } from "../../../../../../common/number/format_number";
|
||||
import { showPromptDialog } from "../../../../../../dialogs/generic/show-dialog-box";
|
||||
|
||||
const RUN_DATA = {
|
||||
pipeline: "Pipeline",
|
||||
language: "Language",
|
||||
};
|
||||
|
||||
const STT_DATA = {
|
||||
engine: "Engine",
|
||||
};
|
||||
|
||||
const INTENT_DATA = {
|
||||
engine: "Engine",
|
||||
intent_input: "Input",
|
||||
};
|
||||
|
||||
const TTS_DATA = {
|
||||
engine: "Engine",
|
||||
tts_input: "Input",
|
||||
};
|
||||
|
||||
const STAGES: Record<PipelineRun["stage"], number> = {
|
||||
ready: 0,
|
||||
stt: 1,
|
||||
intent: 2,
|
||||
tts: 3,
|
||||
done: 4,
|
||||
error: 5,
|
||||
};
|
||||
|
||||
const hasStage = (run: PipelineRun, stage: PipelineRun["stage"]) =>
|
||||
STAGES[run.init_options.start_stage] <= STAGES[stage] &&
|
||||
STAGES[stage] <= STAGES[run.init_options.end_stage];
|
||||
|
||||
const maybeRenderError = (
|
||||
run: PipelineRun,
|
||||
stage: string,
|
||||
lastRunStage: string
|
||||
) => {
|
||||
if (run.stage !== "error" || lastRunStage !== stage) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return html`<ha-alert alert-type="error">
|
||||
${run.error!.message} (${run.error!.code})
|
||||
</ha-alert>`;
|
||||
};
|
||||
|
||||
const renderProgress = (
|
||||
hass: HomeAssistant,
|
||||
pipelineRun: PipelineRun,
|
||||
stage: PipelineRun["stage"]
|
||||
) => {
|
||||
const startEvent = pipelineRun.events.find(
|
||||
(ev) => ev.type === `${stage}-start`
|
||||
);
|
||||
const finishEvent = pipelineRun.events.find(
|
||||
(ev) => ev.type === `${stage}-end`
|
||||
);
|
||||
|
||||
if (!startEvent) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (pipelineRun.stage === "error") {
|
||||
return html`❌`;
|
||||
}
|
||||
|
||||
if (!finishEvent) {
|
||||
return html`<ha-circular-progress
|
||||
size="tiny"
|
||||
active
|
||||
></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
const duration =
|
||||
new Date(finishEvent.timestamp).getTime() -
|
||||
new Date(startEvent.timestamp).getTime();
|
||||
const durationString = formatNumber(duration / 1000, hass.locale, {
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
return html`${durationString}s ✅`;
|
||||
};
|
||||
|
||||
const renderData = (data: Record<string, any>, keys: Record<string, string>) =>
|
||||
Object.entries(keys).map(
|
||||
([key, label]) =>
|
||||
html`
|
||||
<div class="row">
|
||||
<div>${label}</div>
|
||||
<div>${data[key]}</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
const dataMinusKeysRender = (
|
||||
data: Record<string, any>,
|
||||
keys: Record<string, string>
|
||||
) => {
|
||||
const result = {};
|
||||
let render = false;
|
||||
for (const key in data) {
|
||||
if (key in keys) {
|
||||
continue;
|
||||
}
|
||||
render = true;
|
||||
result[key] = data[key];
|
||||
}
|
||||
return render ? html`<pre>${JSON.stringify(result, null, 2)}</pre>` : "";
|
||||
};
|
||||
import "./assist-render-pipeline-run";
|
||||
import type { HaCheckbox } from "../../../../../../components/ha-checkbox";
|
||||
import type { HaTextField } from "../../../../../../components/ha-textfield";
|
||||
import "../../../../../../components/ha-textfield";
|
||||
|
||||
@customElement("assist-pipeline-debug")
|
||||
export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
|
||||
export class AssistPipelineDebug extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@state() private _pipelineRun?: PipelineRun;
|
||||
@state() private _pipelineRuns: PipelineRun[] = [];
|
||||
|
||||
@state() private _stopRecording?: () => void;
|
||||
|
||||
@query("#continue-conversation")
|
||||
private _continueConversationCheckbox!: HaCheckbox;
|
||||
|
||||
@query("#continue-conversation-text")
|
||||
private _continueConversationTextField?: HaTextField;
|
||||
|
||||
private _audioBuffer?: Int16Array[];
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const lastRunStage: string = this._pipelineRun
|
||||
? ["tts", "intent", "stt"].find(
|
||||
(stage) => this._pipelineRun![stage] !== undefined
|
||||
) || "ready"
|
||||
: "ready";
|
||||
@state() private _finished = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-subpage
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
header="Assist Pipeline"
|
||||
>
|
||||
${this._pipelineRuns.length > 0
|
||||
? html`
|
||||
<ha-button
|
||||
slot="toolbar-icon"
|
||||
@click=${this._clearConversation}
|
||||
.disabled=${!this._finished}
|
||||
>
|
||||
Clear
|
||||
</ha-button>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<div class="content">
|
||||
<div class="start-row">
|
||||
<ha-button
|
||||
raised
|
||||
@click=${this._runTextPipeline}
|
||||
.disabled=${this._pipelineRun &&
|
||||
!["error", "done"].includes(this._pipelineRun.stage)}
|
||||
>
|
||||
Run Text Pipeline
|
||||
</ha-button>
|
||||
<ha-button
|
||||
raised
|
||||
@click=${this._runAudioPipeline}
|
||||
.disabled=${this._pipelineRun &&
|
||||
!["error", "done"].includes(this._pipelineRun.stage)}
|
||||
>
|
||||
Run Audio Pipeline
|
||||
</ha-button>
|
||||
${this._pipelineRuns.length === 0
|
||||
? html`
|
||||
<ha-button raised @click=${this._runTextPipeline}>
|
||||
Run Text Pipeline
|
||||
</ha-button>
|
||||
<ha-button raised @click=${this._runAudioPipeline}>
|
||||
Run Audio Pipeline
|
||||
</ha-button>
|
||||
`
|
||||
: this._pipelineRuns[0].init_options.start_stage === "intent"
|
||||
? html`
|
||||
<ha-textfield
|
||||
id="continue-conversation-text"
|
||||
label="Response"
|
||||
.disabled=${!this._finished}
|
||||
@keydown=${this._handleContinueKeyDown}
|
||||
></ha-textfield>
|
||||
<ha-button
|
||||
@click=${this._runTextPipeline}
|
||||
.disabled=${!this._finished}
|
||||
>
|
||||
Send
|
||||
</ha-button>
|
||||
`
|
||||
: html`
|
||||
<ha-formfield label="Continue conversation">
|
||||
<ha-checkbox
|
||||
id="continue-conversation"
|
||||
checked
|
||||
></ha-checkbox>
|
||||
</ha-formfield>
|
||||
`}
|
||||
</div>
|
||||
|
||||
${this._pipelineRun
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="row heading">
|
||||
<div>Run</div>
|
||||
<div>${this._pipelineRun.stage}</div>
|
||||
</div>
|
||||
|
||||
${renderData(this._pipelineRun.run, RUN_DATA)}
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
${maybeRenderError(this._pipelineRun, "ready", lastRunStage)}
|
||||
${hasStage(this._pipelineRun, "stt")
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="row heading">
|
||||
<span>Speech-to-Text</span>
|
||||
${renderProgress(
|
||||
this.hass,
|
||||
this._pipelineRun,
|
||||
"stt"
|
||||
)}
|
||||
</div>
|
||||
${this._pipelineRun.stt
|
||||
? html`
|
||||
<div class="card-content">
|
||||
${renderData(this._pipelineRun.stt, STT_DATA)}
|
||||
${dataMinusKeysRender(
|
||||
this._pipelineRun.stt,
|
||||
STT_DATA
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${this._pipelineRun.stage === "stt" &&
|
||||
this._stopRecording
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
<ha-button @click=${this._stopRecording}>
|
||||
Stop Recording
|
||||
</ha-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${maybeRenderError(this._pipelineRun, "stt", lastRunStage)}
|
||||
${hasStage(this._pipelineRun, "intent")
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="row heading">
|
||||
<span>Natural Language Processing</span>
|
||||
${renderProgress(
|
||||
this.hass,
|
||||
this._pipelineRun,
|
||||
"intent"
|
||||
)}
|
||||
</div>
|
||||
${this._pipelineRun.intent
|
||||
? html`
|
||||
<div class="card-content">
|
||||
${renderData(
|
||||
this._pipelineRun.intent,
|
||||
INTENT_DATA
|
||||
)}
|
||||
${dataMinusKeysRender(
|
||||
this._pipelineRun.intent,
|
||||
INTENT_DATA
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${maybeRenderError(this._pipelineRun, "intent", lastRunStage)}
|
||||
${hasStage(this._pipelineRun, "tts")
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="row heading">
|
||||
<span>Text-to-Speech</span>
|
||||
${renderProgress(
|
||||
this.hass,
|
||||
this._pipelineRun,
|
||||
"tts"
|
||||
)}
|
||||
</div>
|
||||
${this._pipelineRun.tts
|
||||
? html`
|
||||
<div class="card-content">
|
||||
${renderData(this._pipelineRun.tts, TTS_DATA)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${this._pipelineRun?.tts?.tts_output
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
<ha-button @click=${this._playTTS}>
|
||||
Play Audio
|
||||
</ha-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${maybeRenderError(this._pipelineRun, "tts", lastRunStage)}
|
||||
<ha-card>
|
||||
<ha-expansion-panel>
|
||||
<span slot="header">Raw</span>
|
||||
<pre>${JSON.stringify(this._pipelineRun, null, 2)}</pre>
|
||||
</ha-expansion-panel>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${this._pipelineRuns.map((run) =>
|
||||
run === null
|
||||
? ""
|
||||
: html`
|
||||
<assist-render-pipeline-run
|
||||
.hass=${this.hass}
|
||||
.pipelineRun=${run}
|
||||
></assist-render-pipeline-run>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
|
||||
if (
|
||||
!changedProperties.has("_pipelineRun") ||
|
||||
!this._pipelineRun ||
|
||||
this._pipelineRun.init_options.start_stage !== "stt"
|
||||
!changedProperties.has("_pipelineRuns") ||
|
||||
this._pipelineRuns.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._pipelineRun.stage === "stt" && this._audioBuffer) {
|
||||
const currentRun = this._pipelineRuns[0];
|
||||
|
||||
if (currentRun.init_options.start_stage !== "stt") {
|
||||
if (["error", "done"].includes(currentRun.stage)) {
|
||||
this._finished = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentRun.stage === "stt" && this._audioBuffer) {
|
||||
// Send the buffer over the WS to the STT engine.
|
||||
for (const buffer of this._audioBuffer) {
|
||||
this._sendAudioChunk(buffer);
|
||||
@@ -319,31 +133,70 @@ export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
|
||||
this._audioBuffer = undefined;
|
||||
}
|
||||
|
||||
if (this._pipelineRun.stage !== "stt" && this._stopRecording) {
|
||||
if (currentRun.stage !== "stt" && this._stopRecording) {
|
||||
this._stopRecording();
|
||||
}
|
||||
|
||||
if (currentRun.stage === "done") {
|
||||
const url = currentRun.tts!.tts_output!.url;
|
||||
const audio = new Audio(url);
|
||||
audio.addEventListener("ended", () => {
|
||||
if (this._continueConversationCheckbox.checked) {
|
||||
this._runAudioPipeline();
|
||||
} else {
|
||||
this._finished = true;
|
||||
}
|
||||
});
|
||||
audio.play();
|
||||
} else if (currentRun.stage === "error") {
|
||||
this._finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private get conversationId(): string | null {
|
||||
return this._pipelineRuns.length === 0
|
||||
? null
|
||||
: this._pipelineRuns[0].intent?.intent_output?.conversation_id || null;
|
||||
}
|
||||
|
||||
private async _runTextPipeline() {
|
||||
const text = await showPromptDialog(this, {
|
||||
title: "Input text",
|
||||
confirmText: "Run",
|
||||
});
|
||||
const textfield = this._continueConversationTextField;
|
||||
|
||||
let text: string | null;
|
||||
|
||||
if (textfield) {
|
||||
text = textfield.value;
|
||||
} else {
|
||||
text = await showPromptDialog(this, {
|
||||
title: "Input text",
|
||||
confirmText: "Run",
|
||||
});
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._pipelineRun = undefined;
|
||||
let added = false;
|
||||
runPipelineFromText(
|
||||
this.hass,
|
||||
(run) => {
|
||||
this._pipelineRun = run;
|
||||
if (textfield && ["done", "error"].includes(run.stage)) {
|
||||
textfield.value = "";
|
||||
}
|
||||
|
||||
if (added) {
|
||||
this._pipelineRuns = [run, ...this._pipelineRuns.slice(1)];
|
||||
} else {
|
||||
this._pipelineRuns = [run, ...this._pipelineRuns];
|
||||
added = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
start_stage: "intent",
|
||||
end_stage: "intent",
|
||||
input: { text },
|
||||
conversation_id: this.conversationId,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -375,21 +228,28 @@ export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
|
||||
this._audioBuffer.push(e.data);
|
||||
return;
|
||||
}
|
||||
if (this._pipelineRun?.stage !== "stt") {
|
||||
if (this._pipelineRuns[0].stage !== "stt") {
|
||||
return;
|
||||
}
|
||||
this._sendAudioChunk(e.data);
|
||||
};
|
||||
|
||||
this._pipelineRun = undefined;
|
||||
this._finished = false;
|
||||
let added = false;
|
||||
runPipelineFromText(
|
||||
this.hass,
|
||||
(run) => {
|
||||
this._pipelineRun = run;
|
||||
if (added) {
|
||||
this._pipelineRuns = [run, ...this._pipelineRuns.slice(1)];
|
||||
} else {
|
||||
this._pipelineRuns = [run, ...this._pipelineRuns];
|
||||
added = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
start_stage: "stt",
|
||||
end_stage: "tts",
|
||||
conversation_id: this.conversationId,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -397,16 +257,20 @@ export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
|
||||
private _sendAudioChunk(chunk: Int16Array) {
|
||||
// Turn into 8 bit so we can prefix our handler ID.
|
||||
const data = new Uint8Array(1 + chunk.length * 2);
|
||||
data[0] = this._pipelineRun!.run.runner_data.stt_binary_handler_id!;
|
||||
data[0] = this._pipelineRuns[0].run.runner_data.stt_binary_handler_id!;
|
||||
data.set(new Uint8Array(chunk.buffer), 1);
|
||||
|
||||
this.hass.connection.socket!.send(data);
|
||||
}
|
||||
|
||||
private _playTTS(): void {
|
||||
const url = this._pipelineRun!.tts!.tts_output!.url;
|
||||
const audio = new Audio(url);
|
||||
audio.play();
|
||||
private _handleContinueKeyDown(ev) {
|
||||
if (ev.keyCode === 13) {
|
||||
this._runTextPipeline();
|
||||
}
|
||||
}
|
||||
|
||||
private _clearConversation() {
|
||||
this._pipelineRuns = [];
|
||||
}
|
||||
|
||||
static styles = [
|
||||
@@ -419,32 +283,19 @@ export class AssistPipelineDebug extends SubscribeMixin(LitElement) {
|
||||
direction: ltr;
|
||||
}
|
||||
.start-row {
|
||||
text-align: center;
|
||||
}
|
||||
.start-row ha-button {
|
||||
margin: 16px;
|
||||
}
|
||||
ha-card,
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.run-pipeline-card ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
margin: 0 16px 16px;
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
.start-row ha-textfield {
|
||||
flex: 1;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
padding-left: 8px;
|
||||
assist-render-pipeline-run {
|
||||
padding-top: 16px;
|
||||
}
|
||||
.heading {
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
assist-render-pipeline-run + assist-render-pipeline-run {
|
||||
border-top: 3px solid black;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -0,0 +1,336 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../../../components/ha-card";
|
||||
import "../../../../../../components/ha-alert";
|
||||
import "../../../../../../components/ha-button";
|
||||
import "../../../../../../components/ha-circular-progress";
|
||||
import "../../../../../../components/ha-expansion-panel";
|
||||
import type { PipelineRun } from "../../../../../../data/voice_assistant";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { formatNumber } from "../../../../../../common/number/format_number";
|
||||
|
||||
const RUN_DATA = {
|
||||
pipeline: "Pipeline",
|
||||
language: "Language",
|
||||
};
|
||||
|
||||
const STT_DATA = {
|
||||
engine: "Engine",
|
||||
};
|
||||
|
||||
const INTENT_DATA = {
|
||||
engine: "Engine",
|
||||
intent_input: "Input",
|
||||
};
|
||||
|
||||
const TTS_DATA = {
|
||||
engine: "Engine",
|
||||
tts_input: "Input",
|
||||
};
|
||||
|
||||
const STAGES: Record<PipelineRun["stage"], number> = {
|
||||
ready: 0,
|
||||
stt: 1,
|
||||
intent: 2,
|
||||
tts: 3,
|
||||
done: 4,
|
||||
error: 5,
|
||||
};
|
||||
|
||||
const hasStage = (run: PipelineRun, stage: PipelineRun["stage"]) =>
|
||||
STAGES[run.init_options.start_stage] <= STAGES[stage] &&
|
||||
STAGES[stage] <= STAGES[run.init_options.end_stage];
|
||||
|
||||
const maybeRenderError = (
|
||||
run: PipelineRun,
|
||||
stage: string,
|
||||
lastRunStage: string
|
||||
) => {
|
||||
if (run.stage !== "error" || lastRunStage !== stage) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return html`<ha-alert alert-type="error">
|
||||
${run.error!.message} (${run.error!.code})
|
||||
</ha-alert>`;
|
||||
};
|
||||
|
||||
const renderProgress = (
|
||||
hass: HomeAssistant,
|
||||
pipelineRun: PipelineRun,
|
||||
stage: PipelineRun["stage"]
|
||||
) => {
|
||||
const startEvent = pipelineRun.events.find(
|
||||
(ev) => ev.type === `${stage}-start`
|
||||
);
|
||||
const finishEvent = pipelineRun.events.find(
|
||||
(ev) => ev.type === `${stage}-end`
|
||||
);
|
||||
|
||||
if (!startEvent) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (pipelineRun.stage === "error") {
|
||||
return html`❌`;
|
||||
}
|
||||
|
||||
if (!finishEvent) {
|
||||
return html`<ha-circular-progress
|
||||
size="tiny"
|
||||
active
|
||||
></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
const duration =
|
||||
new Date(finishEvent.timestamp).getTime() -
|
||||
new Date(startEvent.timestamp).getTime();
|
||||
const durationString = formatNumber(duration / 1000, hass.locale, {
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
return html`${durationString}s ✅`;
|
||||
};
|
||||
|
||||
const renderData = (data: Record<string, any>, keys: Record<string, string>) =>
|
||||
Object.entries(keys).map(
|
||||
([key, label]) =>
|
||||
html`
|
||||
<div class="row">
|
||||
<div>${label}</div>
|
||||
<div>${data[key]}</div>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
|
||||
const dataMinusKeysRender = (
|
||||
data: Record<string, any>,
|
||||
keys: Record<string, string>
|
||||
) => {
|
||||
const result = {};
|
||||
let render = false;
|
||||
for (const key in data) {
|
||||
if (key in keys) {
|
||||
continue;
|
||||
}
|
||||
render = true;
|
||||
result[key] = data[key];
|
||||
}
|
||||
return render ? html`<pre>${JSON.stringify(result, null, 2)}</pre>` : "";
|
||||
};
|
||||
|
||||
@customElement("assist-render-pipeline-run")
|
||||
export class AssistPipelineDebug extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() private pipelineRun!: PipelineRun;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const lastRunStage: string = this.pipelineRun
|
||||
? ["tts", "intent", "stt"].find(
|
||||
(stage) => this.pipelineRun![stage] !== undefined
|
||||
) || "ready"
|
||||
: "ready";
|
||||
|
||||
const messages: Array<{ from: string; text: string }> = [];
|
||||
|
||||
const userMessage =
|
||||
this.pipelineRun.init_options.input?.text ||
|
||||
this.pipelineRun?.stt?.stt_output?.text;
|
||||
|
||||
if (userMessage) {
|
||||
messages.push({
|
||||
from: "user",
|
||||
text: userMessage,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
this.pipelineRun?.intent?.intent_output?.response?.speech?.plain?.speech
|
||||
) {
|
||||
messages.push({
|
||||
from: "hass",
|
||||
text: this.pipelineRun.intent.intent_output.response.speech.plain
|
||||
.speech,
|
||||
});
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="row heading">
|
||||
<div>Run</div>
|
||||
<div>${this.pipelineRun.stage}</div>
|
||||
</div>
|
||||
|
||||
${renderData(this.pipelineRun.run, RUN_DATA)}
|
||||
${messages.length > 0
|
||||
? html`
|
||||
<div class="messages">
|
||||
${messages.map(
|
||||
({ from, text }) => html`
|
||||
<div class=${`message ${from}`}>${text}</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<div style="clear:both"></div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
${maybeRenderError(this.pipelineRun, "ready", lastRunStage)}
|
||||
${hasStage(this.pipelineRun, "stt")
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="row heading">
|
||||
<span>Speech-to-Text</span>
|
||||
${renderProgress(this.hass, this.pipelineRun, "stt")}
|
||||
</div>
|
||||
${this.pipelineRun.stt
|
||||
? html`
|
||||
<div class="card-content">
|
||||
${renderData(this.pipelineRun.stt, STT_DATA)}
|
||||
${dataMinusKeysRender(this.pipelineRun.stt, STT_DATA)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${maybeRenderError(this.pipelineRun, "stt", lastRunStage)}
|
||||
${hasStage(this.pipelineRun, "intent")
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="row heading">
|
||||
<span>Natural Language Processing</span>
|
||||
${renderProgress(this.hass, this.pipelineRun, "intent")}
|
||||
</div>
|
||||
${this.pipelineRun.intent
|
||||
? html`
|
||||
<div class="card-content">
|
||||
${renderData(this.pipelineRun.intent, INTENT_DATA)}
|
||||
${dataMinusKeysRender(
|
||||
this.pipelineRun.intent,
|
||||
INTENT_DATA
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${maybeRenderError(this.pipelineRun, "intent", lastRunStage)}
|
||||
${hasStage(this.pipelineRun, "tts")
|
||||
? html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="row heading">
|
||||
<span>Text-to-Speech</span>
|
||||
${renderProgress(this.hass, this.pipelineRun, "tts")}
|
||||
</div>
|
||||
${this.pipelineRun.tts
|
||||
? html`
|
||||
<div class="card-content">
|
||||
${renderData(this.pipelineRun.tts, TTS_DATA)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${this.pipelineRun?.tts?.tts_output
|
||||
? html`
|
||||
<div class="card-actions">
|
||||
<ha-button @click=${this._playTTS}>
|
||||
Play Audio
|
||||
</ha-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${maybeRenderError(this.pipelineRun, "tts", lastRunStage)}
|
||||
<ha-card>
|
||||
<ha-expansion-panel>
|
||||
<span slot="header">Raw</span>
|
||||
<pre>${JSON.stringify(this.pipelineRun, null, 2)}</pre>
|
||||
</ha-expansion-panel>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _playTTS(): void {
|
||||
const url = this.pipelineRun!.tts!.tts_output!.url;
|
||||
const audio = new Audio(url);
|
||||
audio.play();
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card,
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
padding-left: 8px;
|
||||
}
|
||||
.heading {
|
||||
font-weight: 500;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: 18px;
|
||||
margin: 8px 0;
|
||||
padding: 8px;
|
||||
border-radius: 15px;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.message.user {
|
||||
margin-left: 24px;
|
||||
margin-inline-start: 24px;
|
||||
margin-inline-end: initial;
|
||||
float: var(--float-end);
|
||||
text-align: right;
|
||||
border-bottom-right-radius: 0px;
|
||||
background-color: var(--light-primary-color);
|
||||
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
.message.hass {
|
||||
margin-right: 24px;
|
||||
margin-inline-end: 24px;
|
||||
margin-inline-start: initial;
|
||||
float: var(--float-start);
|
||||
border-bottom-left-radius: 0px;
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-primary-color);
|
||||
direction: var(--direction);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"assist-render-pipeline-run": AssistPipelineDebug;
|
||||
}
|
||||
}
|
@@ -508,7 +508,7 @@ export class HaScriptTrace extends LitElement {
|
||||
}
|
||||
|
||||
.main {
|
||||
height: calc(100% - 56px);
|
||||
height: calc(100% - var(--header-height));
|
||||
display: flex;
|
||||
background-color: var(--card-background-color);
|
||||
}
|
||||
|
@@ -108,10 +108,15 @@ class PanelDeveloperTools extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
padding: 0 16px;
|
||||
padding: 8px 12px;
|
||||
font-weight: 400;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@media (max-width: 599px) {
|
||||
.toolbar {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
.main-title {
|
||||
margin: 0 0 0 24px;
|
||||
line-height: 20px;
|
||||
|
@@ -157,6 +157,9 @@ export class HuiEnergyGasGraphCard
|
||||
const options: ChartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
interaction: {
|
||||
mode: "x",
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
@@ -171,9 +174,6 @@ export class HuiEnergyGasGraphCard
|
||||
maxRotation: 0,
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 20,
|
||||
major: {
|
||||
enabled: true,
|
||||
},
|
||||
font: (context) =>
|
||||
context.tick && context.tick.major
|
||||
? ({ weight: "bold" } as any)
|
||||
@@ -213,7 +213,7 @@ export class HuiEnergyGasGraphCard
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "nearest",
|
||||
position: "nearest",
|
||||
callbacks: {
|
||||
title: (datasets) => {
|
||||
if (dayDifference > 0) {
|
||||
@@ -244,13 +244,10 @@ export class HuiEnergyGasGraphCard
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
mode: "nearest",
|
||||
},
|
||||
elements: {
|
||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
||||
point: {
|
||||
hitRadius: 5,
|
||||
hitRadius: 50,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
|
@@ -154,6 +154,9 @@ export class HuiEnergySolarGraphCard
|
||||
const options: ChartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
interaction: {
|
||||
mode: "x",
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
@@ -168,9 +171,6 @@ export class HuiEnergySolarGraphCard
|
||||
maxRotation: 0,
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 20,
|
||||
major: {
|
||||
enabled: true,
|
||||
},
|
||||
font: (context) =>
|
||||
context.tick && context.tick.major
|
||||
? ({ weight: "bold" } as any)
|
||||
@@ -209,7 +209,7 @@ export class HuiEnergySolarGraphCard
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "nearest",
|
||||
position: "nearest",
|
||||
callbacks: {
|
||||
title: (datasets) => {
|
||||
if (dayDifference > 0) {
|
||||
@@ -240,9 +240,6 @@ export class HuiEnergySolarGraphCard
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
mode: "nearest",
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.3,
|
||||
|
@@ -148,6 +148,9 @@ export class HuiEnergyUsageGraphCard
|
||||
const options: ChartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
interaction: {
|
||||
mode: "x",
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
@@ -162,9 +165,6 @@ export class HuiEnergyUsageGraphCard
|
||||
maxRotation: 0,
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 20,
|
||||
major: {
|
||||
enabled: true,
|
||||
},
|
||||
font: (context) =>
|
||||
context.tick && context.tick.major
|
||||
? ({ weight: "bold" } as any)
|
||||
@@ -204,8 +204,6 @@ export class HuiEnergyUsageGraphCard
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "x",
|
||||
intersect: true,
|
||||
position: "nearest",
|
||||
filter: (val) => val.formattedValue !== "0",
|
||||
callbacks: {
|
||||
@@ -265,13 +263,10 @@ export class HuiEnergyUsageGraphCard
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
mode: "nearest",
|
||||
},
|
||||
elements: {
|
||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
||||
point: {
|
||||
hitRadius: 5,
|
||||
hitRadius: 50,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
|
@@ -157,6 +157,9 @@ export class HuiEnergyWaterGraphCard
|
||||
const options: ChartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
interaction: {
|
||||
mode: "x",
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
@@ -171,9 +174,6 @@ export class HuiEnergyWaterGraphCard
|
||||
maxRotation: 0,
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 20,
|
||||
major: {
|
||||
enabled: true,
|
||||
},
|
||||
font: (context) =>
|
||||
context.tick && context.tick.major
|
||||
? ({ weight: "bold" } as any)
|
||||
@@ -213,7 +213,7 @@ export class HuiEnergyWaterGraphCard
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "nearest",
|
||||
position: "nearest",
|
||||
callbacks: {
|
||||
title: (datasets) => {
|
||||
if (dayDifference > 0) {
|
||||
@@ -244,13 +244,10 @@ export class HuiEnergyWaterGraphCard
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
mode: "nearest",
|
||||
},
|
||||
elements: {
|
||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
||||
point: {
|
||||
hitRadius: 5,
|
||||
hitRadius: 50,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
|
@@ -36,9 +36,12 @@ import "../../../components/tile/ha-tile-icon";
|
||||
import "../../../components/tile/ha-tile-image";
|
||||
import "../../../components/tile/ha-tile-info";
|
||||
import { cameraUrlWithWidthHeight } from "../../../data/camera";
|
||||
import { CoverEntity } from "../../../data/cover";
|
||||
import { isUnavailableState, ON } from "../../../data/entity";
|
||||
import { FanEntity } from "../../../data/fan";
|
||||
import {
|
||||
computeCoverPositionStateDisplay,
|
||||
CoverEntity,
|
||||
} from "../../../data/cover";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { computeFanSpeedStateDisplay, FanEntity } from "../../../data/fan";
|
||||
import { LightEntity } from "../../../data/light";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { SENSOR_DEVICE_CLASS_TIMESTAMP } from "../../../data/sensor";
|
||||
@@ -202,7 +205,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
if (domain === "light" && stateObj.state === ON) {
|
||||
if (domain === "light" && stateActive(stateObj)) {
|
||||
const brightness = (stateObj as LightEntity).attributes.brightness;
|
||||
if (brightness) {
|
||||
return `${Math.round((brightness * 100) / 255)}${blankBeforePercent(
|
||||
@@ -211,10 +214,13 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
}
|
||||
|
||||
if (domain === "fan" && stateObj.state === ON) {
|
||||
const speed = (stateObj as FanEntity).attributes.percentage;
|
||||
if (speed) {
|
||||
return `${Math.round(speed)}${blankBeforePercent(this.hass!.locale)}%`;
|
||||
if (domain === "fan" && stateActive(stateObj)) {
|
||||
const speedStateDisplay = computeFanSpeedStateDisplay(
|
||||
stateObj as FanEntity,
|
||||
this.hass!.locale
|
||||
);
|
||||
if (speedStateDisplay) {
|
||||
return speedStateDisplay;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,15 +231,14 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
this.hass!.entities
|
||||
);
|
||||
|
||||
if (
|
||||
domain === "cover" &&
|
||||
["open", "opening", "closing"].includes(stateObj.state)
|
||||
) {
|
||||
const position = (stateObj as CoverEntity).attributes.current_position;
|
||||
if (position && position !== 100) {
|
||||
return `${stateDisplay} - ${Math.round(position)}${blankBeforePercent(
|
||||
this.hass!.locale
|
||||
)}%`;
|
||||
if (domain === "cover" && stateActive(stateObj)) {
|
||||
const positionStateDisplay = computeCoverPositionStateDisplay(
|
||||
stateObj as CoverEntity,
|
||||
this.hass!.locale
|
||||
);
|
||||
|
||||
if (positionStateDisplay) {
|
||||
return `${stateDisplay} ⸱ ${positionStateDisplay}`;
|
||||
}
|
||||
}
|
||||
return stateDisplay;
|
||||
|
@@ -63,7 +63,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
|
||||
public connectedCallback() {
|
||||
Object.assign(this.style, {
|
||||
position: "absolute",
|
||||
position: "fixed",
|
||||
width: isTouch ? "100px" : "50px",
|
||||
height: isTouch ? "100px" : "50px",
|
||||
transform: "translate(-50%, -50%)",
|
||||
@@ -147,11 +147,11 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
let x;
|
||||
let y;
|
||||
if ((ev as TouchEvent).touches) {
|
||||
x = (ev as TouchEvent).touches[0].pageX;
|
||||
y = (ev as TouchEvent).touches[0].pageY;
|
||||
x = (ev as TouchEvent).touches[0].clientX;
|
||||
y = (ev as TouchEvent).touches[0].clientY;
|
||||
} else {
|
||||
x = (ev as MouseEvent).pageX;
|
||||
y = (ev as MouseEvent).pageY;
|
||||
x = (ev as MouseEvent).clientX;
|
||||
y = (ev as MouseEvent).clientY;
|
||||
}
|
||||
|
||||
if (options.hasHold) {
|
||||
|
@@ -164,7 +164,7 @@ export class HuiCreateDialogCard
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 845px;
|
||||
--dialog-content-padding: 2px 24px 20px 24px;
|
||||
--dialog-z-index: 5;
|
||||
--dialog-z-index: 6;
|
||||
}
|
||||
|
||||
ha-dialog.table {
|
||||
|
@@ -394,7 +394,7 @@ export class HuiDialogEditCard
|
||||
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 845px;
|
||||
--dialog-z-index: 5;
|
||||
--dialog-z-index: 6;
|
||||
}
|
||||
|
||||
@media all and (min-width: 451px) and (min-height: 501px) {
|
||||
|
@@ -143,7 +143,7 @@ export class HuiDialogSuggestCard extends LitElement {
|
||||
}
|
||||
ha-dialog {
|
||||
max-width: 845px;
|
||||
--dialog-z-index: 5;
|
||||
--dialog-z-index: 6;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
|
@@ -145,7 +145,7 @@ export class HuiCreateDialogHeaderFooter
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 550px;
|
||||
--dialog-content-padding: 2px 24px 20px 24px;
|
||||
--dialog-z-index: 5;
|
||||
--dialog-z-index: 6;
|
||||
}
|
||||
|
||||
.elements {
|
||||
|
@@ -26,7 +26,13 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import {
|
||||
customElement,
|
||||
eventOptions,
|
||||
property,
|
||||
query,
|
||||
state,
|
||||
} from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -116,9 +122,9 @@ class HUIRoot extends LitElement {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=" ${classMap({
|
||||
class=${classMap({
|
||||
"edit-mode": this._editMode,
|
||||
})}"
|
||||
})}
|
||||
>
|
||||
<div class="header">
|
||||
<div class="toolbar">
|
||||
@@ -547,11 +553,20 @@ class HUIRoot extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div id="view" @ll-rebuild=${this._debouncedConfigChanged}></div>
|
||||
<div
|
||||
id="view"
|
||||
@ll-rebuild=${this._debouncedConfigChanged}
|
||||
@scroll=${this._viewScrolled}
|
||||
></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _viewScrolled(ev) {
|
||||
this.toggleAttribute("scrolled", ev.currentTarget.scrollTop !== 0);
|
||||
}
|
||||
|
||||
private _isVisible = (view: LovelaceViewConfig) =>
|
||||
Boolean(
|
||||
this._editMode ||
|
||||
@@ -947,6 +962,10 @@ class HUIRoot extends LitElement {
|
||||
top: 0;
|
||||
width: var(--mdc-top-app-bar-width, 100%);
|
||||
z-index: 2;
|
||||
transition: box-shadow 0.3s ease-out;
|
||||
}
|
||||
:host([scrolled]) .header {
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
.edit-mode .header {
|
||||
background-color: var(--app-header-edit-background-color, #455a64);
|
||||
@@ -957,10 +976,15 @@ class HUIRoot extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
padding: 0 16px;
|
||||
padding: 0px 12px;
|
||||
font-weight: 400;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@media (max-width: 599px) {
|
||||
.toolbar {
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
.main-title {
|
||||
margin: 0 0 0 24px;
|
||||
line-height: 20px;
|
||||
@@ -1025,14 +1049,10 @@ class HUIRoot extends LitElement {
|
||||
100vh - var(--header-height) - env(safe-area-inset-top) -
|
||||
env(safe-area-inset-bottom)
|
||||
);
|
||||
/**
|
||||
* Since we only set min-height, if child nodes need percentage
|
||||
* heights they must use absolute positioning so we need relative
|
||||
* positioning here.
|
||||
*
|
||||
* https://www.w3.org/TR/CSS2/visudet.html#the-height-property
|
||||
*/
|
||||
position: relative;
|
||||
background: var(
|
||||
--lovelace-background,
|
||||
var(--primary-background-color)
|
||||
);
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -1064,12 +1084,6 @@ class HUIRoot extends LitElement {
|
||||
.menu-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
hui-view {
|
||||
background: var(
|
||||
--lovelace-background,
|
||||
var(--primary-background-color)
|
||||
);
|
||||
}
|
||||
.exit-edit-mode {
|
||||
--mdc-theme-primary: var(--app-header-edit-text-color, #fff);
|
||||
--mdc-button-outline-color: var(--app-header-edit-text-color, #fff);
|
||||
|
@@ -1,23 +1,23 @@
|
||||
import { mdiShieldOff } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { computeAttributeNameDisplay } from "../../../common/entity/compute_attribute_display";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-control-select";
|
||||
import type { ControlSelectOption } from "../../../components/ha-control-select";
|
||||
import "../../../components/ha-control-slider";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import {
|
||||
AlarmControlPanelEntity,
|
||||
AlarmMode,
|
||||
ALARM_MODES,
|
||||
} from "../../../data/alarm_control_panel";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { showEnterCodeDialogDialog } from "../../../dialogs/more-info/components/alarm_control_panel/show-enter-code-dialog";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceTileFeature, LovelaceTileFeatureEditor } from "../types";
|
||||
@@ -67,14 +67,10 @@ class HuiAlarmModeTileFeature
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected updated(changedProp: Map<string | number | symbol, unknown>): void {
|
||||
super.updated(changedProp);
|
||||
protected willUpdate(changedProp: PropertyValues): void {
|
||||
super.willUpdate(changedProp);
|
||||
if (changedProp.has("stateObj") && this.stateObj) {
|
||||
const oldStateObj = changedProp.get("stateObj") as HassEntity | undefined;
|
||||
|
||||
if (!oldStateObj || this.stateObj.state !== oldStateObj.state) {
|
||||
this._currentMode = this._getCurrentMode(this.stateObj);
|
||||
}
|
||||
this._currentMode = this._getCurrentMode(this.stateObj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,12 +104,14 @@ class HuiAlarmModeTileFeature
|
||||
|
||||
if (ALARM_MODES[mode].state === this.stateObj!.state) return;
|
||||
|
||||
// Force ha-control-select to previous mode because we don't known if the service call will succeed due to code check
|
||||
const oldMode = this._getCurrentMode(this.stateObj!);
|
||||
this._currentMode = mode;
|
||||
await this.requestUpdate("_currentMode");
|
||||
this._currentMode = this._getCurrentMode(this.stateObj!);
|
||||
|
||||
this._setMode(mode);
|
||||
try {
|
||||
await this._setMode(mode);
|
||||
} catch (err) {
|
||||
this._currentMode = oldMode;
|
||||
}
|
||||
}
|
||||
|
||||
private async _disarm() {
|
||||
@@ -146,13 +144,13 @@ class HuiAlarmModeTileFeature
|
||||
}`
|
||||
),
|
||||
});
|
||||
if (!response) {
|
||||
return;
|
||||
if (response == null) {
|
||||
throw new Error("cancel");
|
||||
}
|
||||
code = response;
|
||||
}
|
||||
|
||||
this.hass!.callService("alarm_control_panel", service, {
|
||||
await this.hass!.callService("alarm_control_panel", service, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
code,
|
||||
});
|
||||
@@ -201,16 +199,14 @@ class HuiAlarmModeTileFeature
|
||||
.value=${this._currentMode}
|
||||
@value-changed=${this._valueChanged}
|
||||
hide-label
|
||||
.label=${computeAttributeNameDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj,
|
||||
this.hass.entities,
|
||||
"percentage"
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.alarm_control_panel.modes_label"
|
||||
)}
|
||||
style=${styleMap({
|
||||
"--control-select-color": color,
|
||||
"--modes-count": modes.length.toString(),
|
||||
})}
|
||||
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||
>
|
||||
</ha-control-select>
|
||||
</div>
|
||||
|
@@ -100,12 +100,13 @@ class HuiFanSpeedTileFeature extends LitElement implements LovelaceTileFeature {
|
||||
.value=${speed}
|
||||
@value-changed=${this._speedValueChanged}
|
||||
hide-label
|
||||
.label=${computeAttributeNameDisplay(
|
||||
.ariaLabel=${computeAttributeNameDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj,
|
||||
this.hass.entities,
|
||||
"percentage"
|
||||
)}
|
||||
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||
>
|
||||
</ha-control-select>
|
||||
</div>
|
||||
@@ -124,14 +125,14 @@ class HuiFanSpeedTileFeature extends LitElement implements LovelaceTileFeature {
|
||||
min="0"
|
||||
max="100"
|
||||
.step=${this.stateObj.attributes.percentage_step ?? 1}
|
||||
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${computeAttributeNameDisplay(
|
||||
.ariaLabel=${computeAttributeNameDisplay(
|
||||
this.hass.localize,
|
||||
this.stateObj,
|
||||
this.hass.entities,
|
||||
"percentage"
|
||||
)}
|
||||
.disabled=${this.stateObj!.state === UNAVAILABLE}
|
||||
></ha-control-slider>
|
||||
</div>
|
||||
`;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user