mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-16 12:49:26 +00:00
Compare commits
1 Commits
20240806.1
...
dashboard_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ebe5207b6e |
@@ -1,25 +1,28 @@
|
||||
[modern]
|
||||
# Modern builds target recent browsers supporting the latest features to minimize transpilation, polyfills, etc.
|
||||
# It is served to browsers meeting the following requirements:
|
||||
# - released in the last year + current alpha/beta versions
|
||||
# - Firefox extended support release (ESR)
|
||||
# - with global utilization at or above 0.5%
|
||||
# - must support dynamic import of ES modules
|
||||
# - exclude browsers no longer being maintained
|
||||
# - exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data
|
||||
unreleased versions
|
||||
last 1 year
|
||||
Firefox ESR
|
||||
>= 0.5% and supports es6-module-dynamic-import
|
||||
not dead
|
||||
# Support for dynamic import is the main litmus test for serving modern builds.
|
||||
# Although officially a ES2020 feature, browsers implemented it early, so this
|
||||
# enables all of ES2017 and some features in ES2018.
|
||||
supports es6-module-dynamic-import
|
||||
|
||||
# Exclude Safari 11-12 because of a bug in tagged template literals
|
||||
# https://bugs.webkit.org/show_bug.cgi?id=190756
|
||||
# Note: Dropping version 11 also enables several more ES2018 features
|
||||
not Safari < 13
|
||||
not iOS < 13
|
||||
|
||||
# Exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data
|
||||
# Babel ignores these automatically, but we need here for Webpack to output ESM with dynamic imports
|
||||
not KaiOS > 0
|
||||
not QQAndroid > 0
|
||||
not UCAndroid > 0
|
||||
|
||||
# Exclude unsupported browsers
|
||||
not dead
|
||||
|
||||
[legacy]
|
||||
# Legacy builds are served when modern requirements are not met and support browsers:
|
||||
# - released in the last 7 years + current alpha/beta versionss
|
||||
# - with global utilization at or above 0.05%
|
||||
# - with global utilization above 0.05%
|
||||
# The lattermost query ensures that support for popular old browsers is not dropped too early
|
||||
# (e.g. IE 11, Android 4.4, or Samsung 4).
|
||||
#
|
||||
@@ -33,10 +36,4 @@ not UCAndroid > 0
|
||||
# As of May 2023, only web sockets must be added to the query.
|
||||
unreleased versions
|
||||
last 7 years
|
||||
>= 0.05% and supports websockets
|
||||
|
||||
[legacy-sw]
|
||||
# Same as legacy plus supports service workers
|
||||
unreleased versions
|
||||
last 7 years
|
||||
>= 0.05% and supports websockets and supports serviceworkers
|
||||
> 0.05% and supports websockets
|
||||
|
@@ -8,7 +8,6 @@
|
||||
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
|
||||
"postStartCommand": "script/bootstrap",
|
||||
"containerEnv": {
|
||||
"DEV_CONTAINER": "1",
|
||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||
},
|
||||
"customizations": {
|
||||
|
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
12
.github/workflows/ci.yaml
vendored
12
.github/workflows/ci.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.3.5
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
with:
|
||||
name: frontend-bundle-stats
|
||||
path: build/stats/*.json
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.7
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.3.5
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
with:
|
||||
name: supervisor-bundle-stats
|
||||
path: build/stats/*.json
|
||||
|
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.7
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
6
.github/workflows/nightly.yaml
vendored
6
.github/workflows/nightly.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -57,14 +57,14 @@ jobs:
|
||||
run: tar -czvf translations.tar.gz translations
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4.3.5
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/home_assistant_frontend*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@v4.3.5
|
||||
uses: actions/upload-artifact@v4.3.3
|
||||
with:
|
||||
name: translations
|
||||
path: translations.tar.gz
|
||||
|
8
.github/workflows/release.yaml
vendored
8
.github/workflows/release.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4.0.3
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
script/release
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@v2.0.8
|
||||
uses: softprops/action-gh-release@v2.0.6
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
@@ -74,9 +74,9 @@ jobs:
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2024.07.1
|
||||
uses: home-assistant/wheels@2024.01.0
|
||||
with:
|
||||
abi: cp312
|
||||
abi: cp311
|
||||
tag: musllinux_1_2
|
||||
arch: amd64
|
||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||
|
@@ -1 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
yarn run lint-staged --relative --shell "/bin/bash"
|
||||
|
@@ -1,55 +0,0 @@
|
||||
diff --git a/build/inject-manifest.js b/build/inject-manifest.js
|
||||
index 60e3d2bb51c11a19fbbedbad65e101082ec41c36..fed6026630f43f86e25446383982cf6fb694313b 100644
|
||||
--- a/build/inject-manifest.js
|
||||
+++ b/build/inject-manifest.js
|
||||
@@ -104,7 +104,7 @@ async function injectManifest(config) {
|
||||
replaceString: manifestString,
|
||||
searchString: options.injectionPoint,
|
||||
});
|
||||
- filesToWrite[options.swDest] = source;
|
||||
+ filesToWrite[options.swDest] = source.replace(url, encodeURI(upath_1.default.basename(destPath)));
|
||||
filesToWrite[destPath] = map;
|
||||
}
|
||||
else {
|
||||
diff --git a/build/lib/translate-url-to-sourcemap-paths.js b/build/lib/translate-url-to-sourcemap-paths.js
|
||||
index 3220c5474eeac6e8a56ca9b2ac2bd9be48529e43..5f003879a904d4840529a42dd056d288fd213771 100644
|
||||
--- a/build/lib/translate-url-to-sourcemap-paths.js
|
||||
+++ b/build/lib/translate-url-to-sourcemap-paths.js
|
||||
@@ -22,7 +22,7 @@ function translateURLToSourcemapPaths(url, swSrc, swDest) {
|
||||
const possibleSrcPath = upath_1.default.resolve(upath_1.default.dirname(swSrc), url);
|
||||
if (fs_extra_1.default.existsSync(possibleSrcPath)) {
|
||||
srcPath = possibleSrcPath;
|
||||
- destPath = upath_1.default.resolve(upath_1.default.dirname(swDest), url);
|
||||
+ destPath = `${swDest}.map`;
|
||||
}
|
||||
else {
|
||||
warning = `${errors_1.errors['cant-find-sourcemap']} ${possibleSrcPath}`;
|
||||
diff --git a/src/inject-manifest.ts b/src/inject-manifest.ts
|
||||
index 8795ddcaa77aea7b0356417e4bc4b19e2b3f860c..fcdc68342d9ac53936c9ed40a9ccfc2f5070cad3 100644
|
||||
--- a/src/inject-manifest.ts
|
||||
+++ b/src/inject-manifest.ts
|
||||
@@ -129,7 +129,10 @@ export async function injectManifest(
|
||||
searchString: options.injectionPoint!,
|
||||
});
|
||||
|
||||
- filesToWrite[options.swDest] = source;
|
||||
+ filesToWrite[options.swDest] = source.replace(
|
||||
+ url!,
|
||||
+ encodeURI(upath.basename(destPath)),
|
||||
+ );
|
||||
filesToWrite[destPath] = map;
|
||||
} else {
|
||||
// If there's no sourcemap associated with swSrc, a simple string
|
||||
diff --git a/src/lib/translate-url-to-sourcemap-paths.ts b/src/lib/translate-url-to-sourcemap-paths.ts
|
||||
index 072eac40d4ef5d095a01cb7f7e392a9e034853bd..f0bbe69e88ef3a415de18a7e9cb264daea273d71 100644
|
||||
--- a/src/lib/translate-url-to-sourcemap-paths.ts
|
||||
+++ b/src/lib/translate-url-to-sourcemap-paths.ts
|
||||
@@ -28,7 +28,7 @@ export function translateURLToSourcemapPaths(
|
||||
const possibleSrcPath = upath.resolve(upath.dirname(swSrc), url);
|
||||
if (fse.existsSync(possibleSrcPath)) {
|
||||
srcPath = possibleSrcPath;
|
||||
- destPath = upath.resolve(upath.dirname(swDest), url);
|
||||
+ destPath = `${swDest}.map`;
|
||||
} else {
|
||||
warning = `${errors['cant-find-sourcemap']} ${possibleSrcPath}`;
|
||||
}
|
894
.yarn/releases/yarn-4.3.1.cjs
vendored
Executable file
894
.yarn/releases/yarn-4.3.1.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
925
.yarn/releases/yarn-4.4.0.cjs
vendored
925
.yarn/releases/yarn-4.4.0.cjs
vendored
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.4.0.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.3.1.cjs
|
||||
|
@@ -47,7 +47,7 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
||||
|
||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||
__DEV__: !isProdBuild,
|
||||
__BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"),
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify(env.version()),
|
||||
__DEMO__: false,
|
||||
__SUPERVISOR__: false,
|
||||
@@ -79,12 +79,7 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
|
||||
sourceMap: !isTestBuild,
|
||||
});
|
||||
|
||||
module.exports.babelOptions = ({
|
||||
latestBuild,
|
||||
isProdBuild,
|
||||
isTestBuild,
|
||||
sw,
|
||||
}) => ({
|
||||
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||
babelrc: false,
|
||||
compact: false,
|
||||
assumptions: {
|
||||
@@ -92,7 +87,7 @@ module.exports.babelOptions = ({
|
||||
setPublicClassFields: true,
|
||||
setSpreadProperties: true,
|
||||
},
|
||||
browserslistEnv: latestBuild ? "modern" : `legacy${sw ? "-sw" : ""}`,
|
||||
browserslistEnv: latestBuild ? "modern" : "legacy",
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
@@ -140,14 +135,8 @@ module.exports.babelOptions = ({
|
||||
"@babel/plugin-transform-runtime",
|
||||
{ version: dependencies["@babel/runtime"] },
|
||||
],
|
||||
// Transpile decorators (still in TC39 process)
|
||||
// Modern browsers support class fields and private methods, but transform is required with the older decorator version dictated by Lit
|
||||
[
|
||||
"@babel/plugin-proposal-decorators",
|
||||
{ version: "2018-09", decoratorsBeforeExport: true },
|
||||
],
|
||||
"@babel/plugin-transform-class-properties",
|
||||
"@babel/plugin-transform-private-methods",
|
||||
// Support some proposals still in TC39 process
|
||||
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
||||
].filter(Boolean),
|
||||
exclude: [
|
||||
// \\ for Windows, / for Mac OS and Linux
|
||||
@@ -226,13 +215,7 @@ module.exports.config = {
|
||||
return {
|
||||
name: "frontend" + nameSuffix(latestBuild),
|
||||
entry: {
|
||||
"service-worker":
|
||||
!env.useRollup() && !latestBuild
|
||||
? {
|
||||
import: "./src/entrypoints/service-worker.ts",
|
||||
layer: "sw",
|
||||
}
|
||||
: "./src/entrypoints/service-worker.ts",
|
||||
service_worker: "./src/entrypoints/service_worker.ts",
|
||||
app: "./src/entrypoints/app.ts",
|
||||
authorize: "./src/entrypoints/authorize.ts",
|
||||
onboarding: "./src/entrypoints/onboarding.ts",
|
||||
|
@@ -32,7 +32,4 @@ module.exports = {
|
||||
}
|
||||
return version[1];
|
||||
},
|
||||
isDevContainer() {
|
||||
return process.env.DEV_CONTAINER === "1";
|
||||
},
|
||||
};
|
||||
|
@@ -1,54 +1,19 @@
|
||||
// Tasks to compress
|
||||
|
||||
import { constants } from "node:zlib";
|
||||
import gulp from "gulp";
|
||||
import brotli from "gulp-brotli";
|
||||
import zopfli from "gulp-zopfli-green";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
const filesGlob = "*.{js,json,css,svg,xml}";
|
||||
const brotliOptions = {
|
||||
skipLarger: true,
|
||||
params: {
|
||||
[constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,
|
||||
},
|
||||
};
|
||||
const zopfliOptions = { threshold: 150 };
|
||||
|
||||
const compressDistBrotli = (rootDir, modernDir) =>
|
||||
const compressDist = (rootDir) =>
|
||||
gulp
|
||||
.src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
|
||||
base: rootDir,
|
||||
})
|
||||
.pipe(brotli(brotliOptions))
|
||||
.pipe(gulp.dest(rootDir));
|
||||
|
||||
const compressDistZopfli = (rootDir, modernDir) =>
|
||||
gulp
|
||||
.src(
|
||||
[
|
||||
`${rootDir}/**/${filesGlob}`,
|
||||
`!${modernDir}/**/${filesGlob}`,
|
||||
`!${rootDir}/{sw-modern,service_worker}.js`,
|
||||
`${rootDir}/{authorize,onboarding}.html`,
|
||||
],
|
||||
{ base: rootDir }
|
||||
)
|
||||
.src([
|
||||
`${rootDir}/**/*.{js,json,css,svg,xml}`,
|
||||
`${rootDir}/{authorize,onboarding}.html`,
|
||||
])
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(gulp.dest(rootDir));
|
||||
|
||||
const compressAppBrotli = () =>
|
||||
compressDistBrotli(paths.app_output_root, paths.app_output_latest);
|
||||
const compressHassioBrotli = () =>
|
||||
compressDistBrotli(paths.hassio_output_root, paths.hassio_output_latest);
|
||||
|
||||
const compressAppZopfli = () =>
|
||||
compressDistZopfli(paths.app_output_root, paths.app_output_latest);
|
||||
const compressHassioZopfli = () =>
|
||||
compressDistZopfli(paths.hassio_output_root, paths.hassio_output_latest);
|
||||
|
||||
gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli));
|
||||
gulp.task(
|
||||
"compress-hassio",
|
||||
gulp.parallel(compressHassioBrotli, compressHassioZopfli)
|
||||
);
|
||||
gulp.task("compress-app", () => compressDist(paths.app_output_root));
|
||||
gulp.task("compress-hassio", () => compressDist(paths.hassio_output_root));
|
||||
|
@@ -1,6 +1,5 @@
|
||||
// Tasks to generate entry HTML
|
||||
|
||||
import { getUserAgentRegex } from "browserslist-useragent-regexp";
|
||||
import fs from "fs-extra";
|
||||
import gulp from "gulp";
|
||||
import { minify } from "html-minifier-terser";
|
||||
@@ -18,12 +17,6 @@ const renderTemplate = (templateFile, data = {}) => {
|
||||
...data,
|
||||
useRollup: env.useRollup(),
|
||||
useWDS: env.useWDS(),
|
||||
modernRegex: getUserAgentRegex({
|
||||
env: "modern",
|
||||
allowHigherVersions: true,
|
||||
mobileToDesktop: true,
|
||||
throwOnMissing: true,
|
||||
}).toString(),
|
||||
// Resolve any child/nested templates relative to the parent and pass the same data
|
||||
renderTemplate: (childTemplate) =>
|
||||
renderTemplate(
|
||||
|
@@ -1,19 +1,20 @@
|
||||
// Generate service workers
|
||||
// Generate service worker.
|
||||
// Based on manifest, create a file with the content as service_worker.js
|
||||
|
||||
import { deleteAsync } from "del";
|
||||
import fs from "fs-extra";
|
||||
import gulp from "gulp";
|
||||
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
|
||||
import { basename, join, relative } from "node:path";
|
||||
import { injectManifest } from "workbox-build";
|
||||
import path from "path";
|
||||
import sourceMapUrl from "source-map-url";
|
||||
import workboxBuild from "workbox-build";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
const SW_MAP = {
|
||||
[paths.app_output_latest]: "modern",
|
||||
[paths.app_output_es5]: "legacy",
|
||||
};
|
||||
const swDest = path.resolve(paths.app_output_root, "service_worker.js");
|
||||
|
||||
const SW_DEV =
|
||||
`
|
||||
const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n");
|
||||
|
||||
gulp.task("gen-service-worker-app-dev", (done) => {
|
||||
writeSW(
|
||||
`
|
||||
console.debug('Service worker disabled in development');
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
@@ -21,67 +22,72 @@ self.addEventListener('install', (event) => {
|
||||
// removing any prod service worker the dev might have running
|
||||
self.skipWaiting();
|
||||
});
|
||||
`.trim() + "\n";
|
||||
|
||||
gulp.task("gen-service-worker-app-dev", async () => {
|
||||
await mkdir(paths.app_output_root, { recursive: true });
|
||||
await Promise.all(
|
||||
Object.values(SW_MAP).map((build) =>
|
||||
writeFile(join(paths.app_output_root, `sw-${build}.js`), SW_DEV, {
|
||||
encoding: "utf-8",
|
||||
})
|
||||
)
|
||||
`
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("gen-service-worker-app-prod", () =>
|
||||
Promise.all(
|
||||
Object.entries(SW_MAP).map(async ([outPath, build]) => {
|
||||
const manifest = JSON.parse(
|
||||
await readFile(join(outPath, "manifest.json"), "utf-8")
|
||||
);
|
||||
const swSrc = join(paths.app_output_root, manifest["service-worker.js"]);
|
||||
const swDest = join(paths.app_output_root, `sw-${build}.js`);
|
||||
const buildDir = relative(paths.app_output_root, outPath);
|
||||
const { warnings } = await injectManifest({
|
||||
swSrc,
|
||||
swDest,
|
||||
injectionPoint: "__WB_MANIFEST__",
|
||||
// Files that mach this pattern will be considered unique and skip revision check
|
||||
// ignore JS files + translation files
|
||||
dontCacheBustURLsMatching: new RegExp(
|
||||
`(?:${buildDir}/.+|static/translations/.+)`
|
||||
),
|
||||
globDirectory: paths.app_output_root,
|
||||
globPatterns: [
|
||||
`${buildDir}/*.js`,
|
||||
// Cache all English translations because we catch them as fallback
|
||||
// Using pattern to match hash instead of * to avoid caching en-GB
|
||||
// 'v' added as valid hash letter because in dev we hash with 'dev'
|
||||
"static/translations/**/en-+([a-fv0-9]).json",
|
||||
// Icon shown on splash screen
|
||||
"static/icons/favicon-192x192.png",
|
||||
"static/icons/favicon.ico",
|
||||
// Common fonts
|
||||
"static/fonts/roboto/Roboto-Light.woff2",
|
||||
"static/fonts/roboto/Roboto-Medium.woff2",
|
||||
"static/fonts/roboto/Roboto-Regular.woff2",
|
||||
"static/fonts/roboto/Roboto-Bold.woff2",
|
||||
],
|
||||
globIgnores: [`${buildDir}/service-worker*`],
|
||||
});
|
||||
if (warnings.length > 0) {
|
||||
console.warn(
|
||||
`Problems while injecting ${build} service worker:\n`,
|
||||
warnings.join("\n")
|
||||
);
|
||||
}
|
||||
await deleteAsync(`${swSrc}?(.map)`);
|
||||
// Needed to install new SW from a cached HTML
|
||||
if (build === "modern") {
|
||||
const swOld = join(paths.app_output_root, "service_worker.js");
|
||||
await symlink(basename(swDest), swOld);
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
gulp.task("gen-service-worker-app-prod", async () => {
|
||||
// Read bundled source file
|
||||
const bundleManifestLatest = fs.readJsonSync(
|
||||
path.resolve(paths.app_output_latest, "manifest.json")
|
||||
);
|
||||
let serviceWorkerContent = fs.readFileSync(
|
||||
paths.app_output_root + bundleManifestLatest["service_worker.js"],
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
// Delete old file from frontend_latest so manifest won't pick it up
|
||||
fs.removeSync(
|
||||
paths.app_output_root + bundleManifestLatest["service_worker.js"]
|
||||
);
|
||||
fs.removeSync(
|
||||
paths.app_output_root + bundleManifestLatest["service_worker.js.map"]
|
||||
);
|
||||
|
||||
// Remove ES5
|
||||
const bundleManifestES5 = fs.readJsonSync(
|
||||
path.resolve(paths.app_output_es5, "manifest.json")
|
||||
);
|
||||
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]);
|
||||
fs.removeSync(
|
||||
paths.app_output_root + bundleManifestES5["service_worker.js.map"]
|
||||
);
|
||||
|
||||
const workboxManifest = await workboxBuild.getManifest({
|
||||
// Files that mach this pattern will be considered unique and skip revision check
|
||||
// ignore JS files + translation files
|
||||
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/,
|
||||
|
||||
globDirectory: paths.app_output_root,
|
||||
globPatterns: [
|
||||
"frontend_latest/*.js",
|
||||
// Cache all English translations because we catch them as fallback
|
||||
// Using pattern to match hash instead of * to avoid caching en-GB
|
||||
// 'v' added as valid hash letter because in dev we hash with 'dev'
|
||||
"static/translations/**/en-+([a-fv0-9]).json",
|
||||
// Icon shown on splash screen
|
||||
"static/icons/favicon-192x192.png",
|
||||
"static/icons/favicon.ico",
|
||||
// Common fonts
|
||||
"static/fonts/roboto/Roboto-Light.woff2",
|
||||
"static/fonts/roboto/Roboto-Medium.woff2",
|
||||
"static/fonts/roboto/Roboto-Regular.woff2",
|
||||
"static/fonts/roboto/Roboto-Bold.woff2",
|
||||
],
|
||||
});
|
||||
|
||||
for (const warning of workboxManifest.warnings) {
|
||||
console.warn(warning);
|
||||
}
|
||||
|
||||
// remove source map and add WB manifest
|
||||
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
|
||||
serviceWorkerContent = serviceWorkerContent.replace(
|
||||
"WB_MANIFEST",
|
||||
JSON.stringify(workboxManifest.manifestEntries)
|
||||
);
|
||||
|
||||
// Write new file to root
|
||||
fs.writeFileSync(swDest, serviceWorkerContent);
|
||||
});
|
||||
|
@@ -244,11 +244,11 @@ const createTranslations = async () => {
|
||||
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
||||
// Will be OK for now as long as we don't have anything more complicated
|
||||
// than a base translation + region.
|
||||
const masterStream = gulp
|
||||
gulp
|
||||
.src(`${workDir}/en.json`)
|
||||
.pipe(new PassThrough({ objectMode: true }));
|
||||
masterStream.pipe(hashStream, { end: false });
|
||||
const mergesFinished = [finished(masterStream)];
|
||||
.pipe(new PassThrough({ objectMode: true }))
|
||||
.pipe(hashStream, { end: false });
|
||||
const mergesFinished = [];
|
||||
for (const translationFile of translationFiles) {
|
||||
const locale = basename(translationFile, ".json");
|
||||
const subtags = locale.split("-");
|
||||
|
@@ -40,12 +40,8 @@ const runDevServer = async ({
|
||||
compiler,
|
||||
contentBase,
|
||||
port,
|
||||
listenHost = undefined,
|
||||
listenHost = "localhost",
|
||||
}) => {
|
||||
if (listenHost === undefined) {
|
||||
// For dev container, we need to listen on all hosts
|
||||
listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost";
|
||||
}
|
||||
const server = new WebpackDevServer(
|
||||
{
|
||||
hot: false,
|
||||
|
@@ -63,25 +63,17 @@ const createWebpackConfig = ({
|
||||
rules: [
|
||||
{
|
||||
test: /\.m?js$|\.ts$/,
|
||||
use: (info) => ({
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
...bundle.babelOptions({
|
||||
latestBuild,
|
||||
isProdBuild,
|
||||
isTestBuild,
|
||||
sw: info.issuerLayer === "sw",
|
||||
}),
|
||||
...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }),
|
||||
cacheDirectory: !isProdBuild,
|
||||
cacheCompression: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
parser: {
|
||||
worker: ["*context.audioWorklet.addModule()", "..."],
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
@@ -100,15 +92,11 @@ const createWebpackConfig = ({
|
||||
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||
splitChunks: {
|
||||
// Disable splitting for web workers and worklets because imports of
|
||||
// external chunks are broken for:
|
||||
// - ESM output: https://github.com/webpack/webpack/issues/17014
|
||||
// - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543
|
||||
chunks: (chunk) =>
|
||||
!chunk.canBeInitial() &&
|
||||
!new RegExp(`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`).test(
|
||||
chunk.name
|
||||
),
|
||||
// Disable splitting for web workers with ESM output
|
||||
// Imports of external chunks are broken
|
||||
chunks: latestBuild
|
||||
? (chunk) => !chunk.canBeInitial() && !/^.+-worker$/.test(chunk.name)
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
@@ -240,7 +228,6 @@ const createWebpackConfig = ({
|
||||
),
|
||||
},
|
||||
experiments: {
|
||||
layers: true,
|
||||
outputModule: true,
|
||||
},
|
||||
};
|
||||
|
@@ -1,5 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
event.respondWith(fetch(event.request));
|
||||
});
|
@@ -36,7 +36,13 @@
|
||||
</head>
|
||||
<body>
|
||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||
<%= renderTemplate("../../../src/html/_script_loader.html.template") %>
|
||||
<script>
|
||||
<% for (const entry of latestEntryJS) { %>
|
||||
import("<%= entry %>");
|
||||
<% } %>
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
||||
<hc-layout subtitle="FAQ">
|
||||
<style>
|
||||
a {
|
||||
@@ -226,5 +232,17 @@ http:
|
||||
</p>
|
||||
</div>
|
||||
</hc-layout>
|
||||
|
||||
<script>
|
||||
var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]];
|
||||
(function (d, t) {
|
||||
var g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src =
|
||||
("https:" == location.protocol ? "//ssl" : "//www") +
|
||||
".google-analytics.com/ga.js";
|
||||
s.parentNode.insertBefore(g, s);
|
||||
})(document, "script");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -13,9 +13,15 @@
|
||||
<%= renderTemplate("_social_meta.html.template") %>
|
||||
</head>
|
||||
<body>
|
||||
<hc-connect></hc-connect>
|
||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||
<%= renderTemplate("../../../src/html/_script_loader.html.template") %>
|
||||
<hc-connect></hc-connect>
|
||||
<script>
|
||||
<% for (const entry of latestEntryJS) { %>
|
||||
import("<%= entry %>");
|
||||
<% } %>
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
|
@@ -14,10 +14,22 @@
|
||||
--background-color: #41bdf5;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
|
||||
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
||||
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
|
||||
s.parentNode.insertBefore(g,s)}(document,'script'));
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<cast-media-player></cast-media-player>
|
||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||
<%= renderTemplate("../../../src/html/_script_loader.html.template") %>
|
||||
<cast-media-player></cast-media-player>
|
||||
<script>
|
||||
<% for (const entry of latestEntryJS) { %>
|
||||
import("<%= entry %>");
|
||||
<% } %>
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -11,4 +11,10 @@
|
||||
font-size: initial;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
|
||||
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
||||
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
|
||||
s.parentNode.insertBefore(g,s)}(document,'script'));
|
||||
</script>
|
||||
</html>
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "../../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./layout/hc-connect";
|
||||
|
||||
import("../../../src/resources/ha-style");
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 30 KiB |
@@ -1,5 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
event.respondWith(fetch(event.request));
|
||||
});
|
@@ -1,7 +1,7 @@
|
||||
import { convertEntities } from "../../../../src/fake_data/entity";
|
||||
import { DemoConfig } from "../types";
|
||||
|
||||
export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
||||
export const demoEntitiesSections: DemoConfig["entities"] = () =>
|
||||
convertEntities({
|
||||
"cover.living_room_garden_shutter": {
|
||||
entity_id: "cover.living_room_garden_shutter",
|
||||
@@ -113,30 +113,11 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
||||
},
|
||||
"media_player.living_room_nest_mini": {
|
||||
entity_id: "media_player.living_room_nest_mini",
|
||||
state: "on",
|
||||
state: "off",
|
||||
attributes: {
|
||||
device_class: "speaker",
|
||||
volume_level: 0.18,
|
||||
is_volume_muted: false,
|
||||
media_content_type: "music",
|
||||
media_duration: 300,
|
||||
media_position: 0,
|
||||
media_position_updated_at: new Date(
|
||||
// 23 seconds in
|
||||
new Date().getTime() - 23000
|
||||
).toISOString(),
|
||||
media_title: "I Wasn't Born To Follow",
|
||||
media_artist: "The Byrds",
|
||||
media_album_name: "The Notorious Byrd Brothers",
|
||||
source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"],
|
||||
shuffle: false,
|
||||
night_sound: false,
|
||||
speech_enhance: false,
|
||||
friendly_name: localize(
|
||||
"ui.panel.page-demo.config.sections.entities.media_player.living_room_nest_mini"
|
||||
),
|
||||
entity_picture: "/assets/sections/images/media_player_family_room.jpg",
|
||||
supported_features: 64063,
|
||||
friendly_name: "Living room Nest Mini",
|
||||
supported_features: 152461,
|
||||
},
|
||||
},
|
||||
"cover.kitchen_shutter": {
|
||||
@@ -187,27 +168,8 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
||||
state: "on",
|
||||
attributes: {
|
||||
device_class: "speaker",
|
||||
volume_level: 0.18,
|
||||
is_volume_muted: false,
|
||||
media_content_type: "music",
|
||||
media_duration: 300,
|
||||
media_position: 0,
|
||||
media_position_updated_at: new Date(
|
||||
// 23 seconds in
|
||||
new Date().getTime() - 23000
|
||||
).toISOString(),
|
||||
media_title: "I Wasn't Born To Follow",
|
||||
media_artist: "The Byrds",
|
||||
media_album_name: "The Notorious Byrd Brothers",
|
||||
source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"],
|
||||
shuffle: false,
|
||||
night_sound: false,
|
||||
speech_enhance: false,
|
||||
friendly_name: localize(
|
||||
"ui.panel.page-demo.config.sections.entities.media_player.kitchen_nest_audio"
|
||||
),
|
||||
entity_picture: "/assets/sections/images/media_player_family_room.jpg",
|
||||
supported_features: 64063,
|
||||
friendly_name: "Kitchen Nest Audio",
|
||||
supported_features: 152461,
|
||||
},
|
||||
},
|
||||
"binary_sensor.tesla_wall_connector_vehicle_connected": {
|
||||
@@ -371,28 +333,8 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
||||
entity_id: "media_player.study_nest_hub",
|
||||
state: "off",
|
||||
attributes: {
|
||||
device_class: "speaker",
|
||||
volume_level: 0.18,
|
||||
is_volume_muted: false,
|
||||
media_content_type: "music",
|
||||
media_duration: 300,
|
||||
media_position: 0,
|
||||
media_position_updated_at: new Date(
|
||||
// 23 seconds in
|
||||
new Date().getTime() - 23000
|
||||
).toISOString(),
|
||||
media_title: "I Wasn't Born To Follow",
|
||||
media_artist: "The Byrds",
|
||||
media_album_name: "The Notorious Byrd Brothers",
|
||||
source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"],
|
||||
shuffle: false,
|
||||
night_sound: false,
|
||||
speech_enhance: false,
|
||||
friendly_name: localize(
|
||||
"ui.panel.page-demo.config.sections.entities.media_player.study_nest_hub"
|
||||
),
|
||||
entity_picture: "/assets/sections/images/media_player_family_room.jpg",
|
||||
supported_features: 64063,
|
||||
friendly_name: "Study Nest Hub",
|
||||
supported_features: 152461,
|
||||
},
|
||||
},
|
||||
"sensor.standing_desk_height": {
|
||||
|
@@ -1,25 +1,40 @@
|
||||
import { isFrontpageEmbed } from "../../util/is_frontpage";
|
||||
import { DemoConfig } from "../types";
|
||||
|
||||
export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
||||
title: "Home Assistant Demo",
|
||||
views: [
|
||||
{
|
||||
type: "sections",
|
||||
title: isFrontpageEmbed ? "Home Assistant" : "Demo",
|
||||
title: "Demo",
|
||||
path: "home",
|
||||
icon: "mdi:home-assistant",
|
||||
sections: [
|
||||
...(isFrontpageEmbed
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`,
|
||||
cards: [{ type: "custom:ha-demo-card" }],
|
||||
},
|
||||
]),
|
||||
{
|
||||
title: "Welcome 👋",
|
||||
cards: [{ type: "custom:ha-demo-card" }],
|
||||
},
|
||||
{
|
||||
cards: [
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_garden_shutter",
|
||||
name: "Garden",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_graveyard_shutter",
|
||||
name: "Rear",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_left_shutter",
|
||||
name: "Left",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_right_shutter",
|
||||
name: "Right",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "light.floor_lamp",
|
||||
@@ -45,17 +60,13 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
detail: 1,
|
||||
name: "Temperature",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "cover.living_room_garden_shutter",
|
||||
name: "Blinds",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
entity: "media_player.living_room_nest_mini",
|
||||
name: "Nest Mini",
|
||||
},
|
||||
],
|
||||
title: `🛋️ ${localize("ui.panel.page-demo.config.sections.titles.living_room")} `,
|
||||
title: "🛋️ Living room ",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
@@ -88,9 +99,10 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
{
|
||||
type: "tile",
|
||||
entity: "media_player.kitchen_nest_audio",
|
||||
name: "Nest Audio",
|
||||
},
|
||||
],
|
||||
title: `👩🍳 ${localize("ui.panel.page-demo.config.sections.titles.kitchen")}`,
|
||||
title: "👩🍳 Kitchen",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
@@ -132,7 +144,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
color: "dark-grey",
|
||||
},
|
||||
],
|
||||
title: `⚡️ ${localize("ui.panel.page-demo.config.sections.titles.energy")}`,
|
||||
title: "⚡️ Energy",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
@@ -169,7 +181,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
state_content: ["preset_mode", "current_temperature"],
|
||||
},
|
||||
],
|
||||
title: `🌤️ ${localize("ui.panel.page-demo.config.sections.titles.climate")}`,
|
||||
title: "🌤️ Climate",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
@@ -187,6 +199,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
{
|
||||
type: "tile",
|
||||
entity: "media_player.study_nest_hub",
|
||||
name: "Nest Hub",
|
||||
},
|
||||
{
|
||||
type: "tile",
|
||||
@@ -196,7 +209,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
icon: "mdi:desk",
|
||||
},
|
||||
],
|
||||
title: `🧑💻 ${localize("ui.panel.page-demo.config.sections.titles.study")}`,
|
||||
title: "🧑💻 Study",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
@@ -230,7 +243,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
name: "Illuminance",
|
||||
},
|
||||
],
|
||||
title: `🌳 ${localize("ui.panel.page-demo.config.sections.titles.outdoor")}`,
|
||||
title: "🌳 Outdoor",
|
||||
},
|
||||
{
|
||||
type: "grid",
|
||||
@@ -260,7 +273,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
||||
icon: "mdi:home-assistant",
|
||||
},
|
||||
],
|
||||
title: `🎉 ${localize("ui.panel.page-demo.config.sections.titles.updates")}`,
|
||||
title: "🎉 Updates",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import "./util/is_frontpage";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./ha-demo";
|
||||
|
||||
import("../../src/resources/ha-style");
|
||||
|
@@ -82,8 +82,6 @@ export class HaDemo extends HomeAssistantAppEl {
|
||||
has_entity_name: false,
|
||||
unique_id: "co2_intensity",
|
||||
options: null,
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
config_entry_id: "co2signal",
|
||||
@@ -102,8 +100,6 @@ export class HaDemo extends HomeAssistantAppEl {
|
||||
has_entity_name: false,
|
||||
unique_id: "grid_fossil_fuel_percentage",
|
||||
options: null,
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
|
@@ -69,14 +69,6 @@
|
||||
#ha-launch-screen .ha-launch-screen-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.ohf-logo {
|
||||
color: grey;
|
||||
font-size: 12px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -87,14 +79,30 @@
|
||||
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
|
||||
</svg>
|
||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
|
||||
<div class="ohf-logo">
|
||||
a project from
|
||||
<img src="/static/icons/ohf.svg" alt="Open Home Foundation" height="32">
|
||||
</div>
|
||||
</div>
|
||||
<ha-demo></ha-demo>
|
||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %>
|
||||
<%= renderTemplate("../../../src/html/_script_loader.html.template") %>
|
||||
<script>
|
||||
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
|
||||
if (!isS11_12) {
|
||||
<% for (const entry of latestEntryJS) { %>
|
||||
import("<%= entry %>");
|
||||
<% } %>
|
||||
window.latestJS = true;
|
||||
}
|
||||
</script>
|
||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
||||
<script>
|
||||
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
|
||||
(function (d, t) {
|
||||
var g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src =
|
||||
("https:" == location.protocol ? "//ssl" : "//www") +
|
||||
".google-analytics.com/ga.js";
|
||||
s.parentNode.insertBefore(g, s);
|
||||
})(document, "script");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -1,55 +1,5 @@
|
||||
import { convertEntities } from "../../../src/fake_data/entity";
|
||||
|
||||
export const mapEntities = () =>
|
||||
convertEntities({
|
||||
"zone.home": {
|
||||
entity_id: "zone.home",
|
||||
state: "zoning",
|
||||
attributes: {
|
||||
hidden: true,
|
||||
latitude: 52.3631339,
|
||||
longitude: 4.8903147,
|
||||
radius: 200,
|
||||
friendly_name: "Home",
|
||||
icon: "hademo:home",
|
||||
},
|
||||
},
|
||||
"zone.uva": {
|
||||
entity_id: "zone.buckhead",
|
||||
state: "zoning",
|
||||
attributes: {
|
||||
hidden: true,
|
||||
radius: 400,
|
||||
friendly_name: "UvA",
|
||||
icon: "hademo:school",
|
||||
latitude: 52.3558182,
|
||||
longitude: 4.9535376,
|
||||
},
|
||||
},
|
||||
"person.arsaboo": {
|
||||
entity_id: "person.arsaboo",
|
||||
state: "not_home",
|
||||
attributes: {
|
||||
radius: 50,
|
||||
friendly_name: "Arsaboo",
|
||||
latitude: 52.3579946,
|
||||
longitude: 4.8664597,
|
||||
entity_picture: "/assets/arsaboo/images/arsaboo.jpg",
|
||||
},
|
||||
},
|
||||
"person.melody": {
|
||||
entity_id: "person.melody",
|
||||
state: "not_home",
|
||||
attributes: {
|
||||
radius: 50,
|
||||
friendly_name: "Melody",
|
||||
latitude: 52.3408927,
|
||||
longitude: 4.8711073,
|
||||
entity_picture: "/assets/arsaboo/images/melody.jpg",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const energyEntities = () =>
|
||||
convertEntities({
|
||||
"sensor.grid_fossil_fuel_percentage": {
|
||||
|
@@ -7,25 +7,16 @@ import {
|
||||
} from "../configs/demo-configs";
|
||||
import "../custom-cards/cast-demo-row";
|
||||
import "../custom-cards/ha-demo-card";
|
||||
import { mapEntities } from "./entities";
|
||||
|
||||
export const mockLovelace = (
|
||||
hass: MockHomeAssistant,
|
||||
localizePromise: Promise<LocalizeFunc>
|
||||
) => {
|
||||
hass.mockWS("lovelace/config", ({ url_path }) => {
|
||||
if (url_path === "map") {
|
||||
hass.addEntities(mapEntities());
|
||||
return {
|
||||
strategy: {
|
||||
type: "map",
|
||||
},
|
||||
};
|
||||
}
|
||||
return Promise.all([selectedDemoConfig, localizePromise]).then(
|
||||
hass.mockWS("lovelace/config", () =>
|
||||
Promise.all([selectedDemoConfig, localizePromise]).then(
|
||||
([config, localize]) => config.lovelace(localize)
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
|
||||
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
||||
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
|
||||
|
@@ -1 +0,0 @@
|
||||
export const isFrontpageEmbed = document.location.search === "?frontpage";
|
Binary file not shown.
Before Width: | Height: | Size: 110 KiB |
@@ -3,16 +3,13 @@ title: When to use remove, delete, add and create
|
||||
subtitle: The difference between remove/delete and add/create.
|
||||
---
|
||||
|
||||
# Removing or deleting content
|
||||
# Remove vs Delete
|
||||
|
||||
_Remove_ and _Delete_ are quite similar, but can be frustrating if used inconsistently.
|
||||
|
||||
- Remove refers to an action that can be restored or reapplied.
|
||||
- Delete refers to a permanent, non-recoverable action.
|
||||
Remove and Delete are quite similar, but can be frustrating if used inconsistently.
|
||||
|
||||
## Remove
|
||||
|
||||
The term _Remove_ should always be used when an item/setting or content is to be removed or disassociated, but the action can be reversed or reapplied.
|
||||
Take away and set aside, but kept in existence.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -25,7 +22,7 @@ For example:
|
||||
|
||||
## Delete
|
||||
|
||||
The term _Delete_ should always be used to refer to any action that will cause the permanent deletion of an item/setting or content.
|
||||
Erase, rendered nonexistent or nonrecoverable.
|
||||
|
||||
For example:
|
||||
|
||||
|
@@ -15,7 +15,6 @@ import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/demo-black-white-row";
|
||||
import { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
@@ -42,7 +41,7 @@ const ENTITIES = [
|
||||
}),
|
||||
];
|
||||
|
||||
const DEVICES: DeviceRegistryEntry[] = [
|
||||
const DEVICES = [
|
||||
{
|
||||
area_id: "bedroom",
|
||||
configuration_url: null,
|
||||
@@ -54,7 +53,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
identifiers: [["demo", "volume1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
model_id: null,
|
||||
name_by_user: null,
|
||||
name: "Dishwasher",
|
||||
sw_version: null,
|
||||
@@ -62,8 +60,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
via_device_id: null,
|
||||
serial_number: null,
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
area_id: "backyard",
|
||||
@@ -76,7 +72,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
model_id: null,
|
||||
name_by_user: null,
|
||||
name: "Lamp",
|
||||
sw_version: null,
|
||||
@@ -84,8 +79,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
via_device_id: null,
|
||||
serial_number: null,
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
area_id: null,
|
||||
@@ -98,7 +91,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
model_id: null,
|
||||
name_by_user: "User name",
|
||||
name: "Technical name",
|
||||
sw_version: null,
|
||||
@@ -106,8 +98,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
via_device_id: null,
|
||||
serial_number: null,
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -120,8 +110,6 @@ const AREAS: AreaRegistryEntry[] = [
|
||||
picture: null,
|
||||
aliases: [],
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
@@ -131,8 +119,6 @@ const AREAS: AreaRegistryEntry[] = [
|
||||
picture: null,
|
||||
aliases: [],
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
@@ -142,8 +128,6 @@ const AREAS: AreaRegistryEntry[] = [
|
||||
picture: null,
|
||||
aliases: [],
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -21,7 +21,6 @@ import { FloorRegistryEntry } from "../../../../src/data/floor_registry";
|
||||
import { LabelRegistryEntry } from "../../../../src/data/label_registry";
|
||||
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
|
||||
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
|
||||
import { DeviceRegistryEntry } from "../../../../src/data/device_registry";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
@@ -42,7 +41,7 @@ const ENTITIES = [
|
||||
}),
|
||||
];
|
||||
|
||||
const DEVICES: DeviceRegistryEntry[] = [
|
||||
const DEVICES = [
|
||||
{
|
||||
area_id: "bedroom",
|
||||
configuration_url: null,
|
||||
@@ -54,7 +53,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
identifiers: [["demo", "volume1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
model_id: null,
|
||||
name_by_user: null,
|
||||
name: "Dishwasher",
|
||||
sw_version: null,
|
||||
@@ -62,8 +60,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
via_device_id: null,
|
||||
serial_number: null,
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
area_id: "backyard",
|
||||
@@ -76,7 +72,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
model_id: null,
|
||||
name_by_user: null,
|
||||
name: "Lamp",
|
||||
sw_version: null,
|
||||
@@ -84,8 +79,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
via_device_id: null,
|
||||
serial_number: null,
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
area_id: null,
|
||||
@@ -98,7 +91,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||
manufacturer: null,
|
||||
model: null,
|
||||
model_id: null,
|
||||
name_by_user: "User name",
|
||||
name: "Technical name",
|
||||
sw_version: null,
|
||||
@@ -106,8 +98,6 @@ const DEVICES: DeviceRegistryEntry[] = [
|
||||
via_device_id: null,
|
||||
serial_number: null,
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -120,8 +110,6 @@ const AREAS: AreaRegistryEntry[] = [
|
||||
picture: null,
|
||||
aliases: [],
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
area_id: "bedroom",
|
||||
@@ -131,8 +119,6 @@ const AREAS: AreaRegistryEntry[] = [
|
||||
picture: null,
|
||||
aliases: [],
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
area_id: "livingroom",
|
||||
@@ -142,8 +128,6 @@ const AREAS: AreaRegistryEntry[] = [
|
||||
picture: null,
|
||||
aliases: [],
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -154,8 +138,6 @@ const FLOORS: FloorRegistryEntry[] = [
|
||||
level: 0,
|
||||
icon: null,
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
floor_id: "first",
|
||||
@@ -163,8 +145,6 @@ const FLOORS: FloorRegistryEntry[] = [
|
||||
level: 1,
|
||||
icon: "mdi:numeric-1",
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
floor_id: "second",
|
||||
@@ -172,8 +152,6 @@ const FLOORS: FloorRegistryEntry[] = [
|
||||
level: 2,
|
||||
icon: "mdi:numeric-2",
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -184,8 +162,6 @@ const LABELS: LabelRegistryEntry[] = [
|
||||
icon: null,
|
||||
color: "yellow",
|
||||
description: null,
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
{
|
||||
label_id: "entertainment",
|
||||
@@ -193,8 +169,6 @@ const LABELS: LabelRegistryEntry[] = [
|
||||
icon: "mdi:popcorn",
|
||||
color: "blue",
|
||||
description: null,
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -287,11 +287,11 @@ const CONFIGS = [
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- type: perform-action
|
||||
- type: call-service
|
||||
icon: mdi:power
|
||||
name: Bed light
|
||||
action_name: Toggle light
|
||||
action: light.toggle
|
||||
service: light.toggle
|
||||
data:
|
||||
entity_id: light.bed_light
|
||||
- type: section
|
||||
|
@@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Picture Card
|
||||
---
|
@@ -1,61 +0,0 @@
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, query } from "lit/decorators";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("person", "paulus", "home", {
|
||||
friendly_name: "Paulus",
|
||||
entity_picture: "/images/paulus.jpg",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Image URL",
|
||||
config: `
|
||||
- type: picture
|
||||
image: /images/living_room.png
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Person entity",
|
||||
config: `
|
||||
- type: picture
|
||||
image_entity: person.paulus
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Error: Image required",
|
||||
config: `
|
||||
- type: picture
|
||||
entity: person.paulus
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-lovelace-picture-card")
|
||||
class DemoPicture extends LitElement {
|
||||
@query("#demos") private _demoRoot!: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
const hass = provideHass(this._demoRoot);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("lovelace", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockIcons(hass);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-lovelace-picture-card": DemoPicture;
|
||||
}
|
||||
}
|
@@ -25,15 +25,6 @@ const ENTITIES = [
|
||||
friendly_name: "Movement Backyard",
|
||||
device_class: "motion",
|
||||
}),
|
||||
getEntity("person", "paulus", "home", {
|
||||
friendly_name: "Paulus",
|
||||
entity_picture: "/images/paulus.jpg",
|
||||
}),
|
||||
getEntity("sensor", "battery", 35, {
|
||||
device_class: "battery",
|
||||
friendly_name: "Battery",
|
||||
unit_of_measurement: "%",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
@@ -132,19 +123,6 @@ const CONFIGS = [
|
||||
left: 35%
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Person entity",
|
||||
config: `
|
||||
- type: picture-elements
|
||||
image_entity: person.paulus
|
||||
elements:
|
||||
- type: state-icon
|
||||
entity: sensor.battery
|
||||
style:
|
||||
top: 8%
|
||||
left: 8%
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-lovelace-picture-elements-card")
|
||||
|
@@ -12,10 +12,6 @@ const ENTITIES = [
|
||||
getEntity("light", "bed_light", "off", {
|
||||
friendly_name: "Bed Light",
|
||||
}),
|
||||
getEntity("person", "paulus", "home", {
|
||||
friendly_name: "Paulus",
|
||||
entity_picture: "/images/paulus.jpg",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
@@ -54,13 +50,6 @@ const CONFIGS = [
|
||||
entity: camera.demo_camera
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Person entity",
|
||||
config: `
|
||||
- type: picture-entity
|
||||
entity: person.paulus
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Hidden name",
|
||||
config: `
|
||||
|
@@ -20,15 +20,6 @@ const ENTITIES = [
|
||||
friendly_name: "Basement Floor Wet",
|
||||
device_class: "moisture",
|
||||
}),
|
||||
getEntity("person", "paulus", "home", {
|
||||
friendly_name: "Paulus",
|
||||
entity_picture: "/images/paulus.jpg",
|
||||
}),
|
||||
getEntity("sensor", "battery", 35, {
|
||||
device_class: "battery",
|
||||
friendly_name: "Battery",
|
||||
unit_of_measurement: "%",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
@@ -99,15 +90,6 @@ const CONFIGS = [
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Person entity",
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image_entity: person.paulus
|
||||
entities:
|
||||
- sensor.battery
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom icon",
|
||||
config: `
|
||||
|
@@ -8,7 +8,6 @@ import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
import { ClimateEntityFeature } from "../../../../src/data/climate";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("switch", "tv_outlet", "on", {
|
||||
@@ -61,36 +60,6 @@ const ENTITIES = [
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT,
|
||||
}),
|
||||
getEntity("input_number", "counter", "1.0", {
|
||||
friendly_name: "Counter",
|
||||
initial: 0,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
mode: "slider",
|
||||
}),
|
||||
getEntity("climate", "dual_thermostat", "heat/cool", {
|
||||
friendly_name: "Dual thermostat",
|
||||
hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"],
|
||||
min_temp: 7,
|
||||
max_temp: 35,
|
||||
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
|
||||
preset_modes: ["home", "eco", "away"],
|
||||
swing_modes: ["auto", "1", "2", "3", "off"],
|
||||
current_temperature: 23,
|
||||
target_temp_high: 24,
|
||||
target_temp_low: 21,
|
||||
fan_mode: "auto_low",
|
||||
preset_mode: "home",
|
||||
swing_mode: "auto",
|
||||
supported_features:
|
||||
ClimateEntityFeature.TURN_ON +
|
||||
ClimateEntityFeature.TURN_OFF +
|
||||
ClimateEntityFeature.SWING_MODE +
|
||||
ClimateEntityFeature.PRESET_MODE +
|
||||
ClimateEntityFeature.FAN_MODE +
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
@@ -224,25 +193,6 @@ const CONFIGS = [
|
||||
- type: "cover-tilt"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Number buttons feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: input_number.counter
|
||||
features:
|
||||
- type: numeric-input
|
||||
style: buttons
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Dual thermostat feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: climate.dual_thermostat
|
||||
features:
|
||||
- type: target-temperature
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-lovelace-tile-card")
|
||||
|
@@ -140,9 +140,6 @@ const ENTITIES: HassEntity[] = [
|
||||
createEntity("climate.auto_preheating", "auto", undefined, {
|
||||
hvac_action: "preheating",
|
||||
}),
|
||||
createEntity("climate.auto_defrosting", "auto", undefined, {
|
||||
hvac_action: "defrosting",
|
||||
}),
|
||||
createEntity("climate.auto_heating", "auto", undefined, {
|
||||
hvac_action: "heating",
|
||||
}),
|
||||
@@ -358,11 +355,13 @@ export class DemoEntityState extends LitElement {
|
||||
},
|
||||
entity_id: {
|
||||
title: "Entity ID",
|
||||
width: "30%",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
},
|
||||
state: {
|
||||
title: "State",
|
||||
width: "20%",
|
||||
sortable: true,
|
||||
template: (entry) =>
|
||||
html`${computeStateDisplay(
|
||||
@@ -377,12 +376,14 @@ export class DemoEntityState extends LitElement {
|
||||
device_class: {
|
||||
title: "Device class",
|
||||
template: (entry) => html`${entry.device_class ?? "-"}`,
|
||||
width: "20%",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
},
|
||||
domain: {
|
||||
title: "Domain",
|
||||
template: (entry) => html`${computeDomain(entry.entity_id)}`,
|
||||
width: "20%",
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
},
|
||||
|
@@ -203,8 +203,6 @@ const createEntityRegistryEntries = (
|
||||
options: null,
|
||||
labels: [],
|
||||
categories: {},
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -217,7 +215,6 @@ const createDeviceRegistryEntries = (
|
||||
connections: [],
|
||||
manufacturer: "ESPHome",
|
||||
model: "Mock Device",
|
||||
model_id: "ABC-001",
|
||||
name: "Tag Reader",
|
||||
sw_version: null,
|
||||
hw_version: "1.0.0",
|
||||
@@ -230,8 +227,6 @@ const createDeviceRegistryEntries = (
|
||||
disabled_by: null,
|
||||
configuration_url: null,
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -127,13 +127,14 @@ export class HassioBackups extends LitElement {
|
||||
main: true,
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
flex: 2,
|
||||
grows: true,
|
||||
template: (backup) =>
|
||||
html`${backup.name || backup.slug}
|
||||
<div class="secondary">${backup.secondary}</div>`,
|
||||
},
|
||||
size: {
|
||||
title: this.supervisor.localize("backup.size"),
|
||||
width: "15%",
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
@@ -141,6 +142,7 @@ export class HassioBackups extends LitElement {
|
||||
},
|
||||
location: {
|
||||
title: this.supervisor.localize("backup.location"),
|
||||
width: "15%",
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
@@ -149,6 +151,7 @@ export class HassioBackups extends LitElement {
|
||||
},
|
||||
date: {
|
||||
title: this.supervisor.localize("backup.created"),
|
||||
width: "15%",
|
||||
direction: "desc",
|
||||
hidden: narrow,
|
||||
filterable: true,
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import { stripDiacritics } from "../../../src/common/string/strip-diacritics";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import { StoreAddon } from "../../../src/data/supervisor/store";
|
||||
import { getStripDiacriticsFn } from "../../../src/util/fuse";
|
||||
|
||||
export function filterAndSort(addons: StoreAddon[], filter: string) {
|
||||
const options: IFuseOptions<StoreAddon> = {
|
||||
@@ -10,8 +8,7 @@ export function filterAndSort(addons: StoreAddon[], filter: string) {
|
||||
isCaseSensitive: false,
|
||||
minMatchCharLength: Math.min(filter.length, 2),
|
||||
threshold: 0.2,
|
||||
getFn: getStripDiacriticsFn,
|
||||
};
|
||||
const fuse = new Fuse(addons, options);
|
||||
return fuse.search(stripDiacritics(filter)).map((result) => result.item);
|
||||
return fuse.search(filter).map((result) => result.item);
|
||||
}
|
||||
|
@@ -66,8 +66,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
repo.slug !== "core" && // The core add-ons repository
|
||||
repo.slug !== "local" && // Locally managed add-ons
|
||||
repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons
|
||||
repo.slug !== "5c53de3b" && // The ESPHome repository
|
||||
repo.slug !== "d5369777" // Music Assistant repository
|
||||
repo.slug !== "5c53de3b" // The ESPHome repository
|
||||
)
|
||||
.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language)
|
||||
|
@@ -4,7 +4,11 @@
|
||||
el.src = src;
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
if (<%= modernRegex %>.test(navigator.userAgent)) {
|
||||
if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) {
|
||||
<% for (const entry of es5EntryJS) { %>
|
||||
loadES5("<%= entry %>");
|
||||
<% } %>
|
||||
} else {
|
||||
try {
|
||||
<% for (const entry of latestEntryJS) { %>
|
||||
new Function("import('<%= entry %>')")();
|
||||
@@ -13,10 +17,6 @@
|
||||
<% for (const entry of es5EntryJS) { %>
|
||||
loadES5("<%= entry %>");
|
||||
<% } %>
|
||||
} else {
|
||||
<% for (const entry of es5EntryJS) { %>
|
||||
loadES5("<%= entry %>");
|
||||
<% } %>
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
@@ -1,5 +1,6 @@
|
||||
// Compat needs to be first import
|
||||
import "../../src/resources/compatibility";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./hassio-main";
|
||||
|
||||
import("../../src/resources/ha-style");
|
||||
|
83
package.json
83
package.json
@@ -16,7 +16,7 @@
|
||||
"lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"",
|
||||
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types && yarn run lint:lit",
|
||||
"format": "yarn run format:eslint && yarn run format:prettier",
|
||||
"postinstall": "husky",
|
||||
"postinstall": "husky install",
|
||||
"prepack": "pinst --disable",
|
||||
"postpack": "pinst --enable",
|
||||
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\""
|
||||
@@ -25,15 +25,15 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.25.0",
|
||||
"@braintree/sanitize-url": "7.1.0",
|
||||
"@codemirror/autocomplete": "6.17.0",
|
||||
"@babel/runtime": "7.24.7",
|
||||
"@braintree/sanitize-url": "7.0.3",
|
||||
"@codemirror/autocomplete": "6.16.3",
|
||||
"@codemirror/commands": "6.6.0",
|
||||
"@codemirror/language": "6.10.2",
|
||||
"@codemirror/legacy-modes": "6.4.0",
|
||||
"@codemirror/search": "6.5.6",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@codemirror/view": "6.29.1",
|
||||
"@codemirror/view": "6.28.2",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||
"@formatjs/intl-displaynames": "6.6.8",
|
||||
@@ -43,12 +43,12 @@
|
||||
"@formatjs/intl-numberformat": "8.10.3",
|
||||
"@formatjs/intl-pluralrules": "5.2.14",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.14",
|
||||
"@fullcalendar/core": "6.1.15",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"@fullcalendar/interaction": "6.1.15",
|
||||
"@fullcalendar/list": "6.1.15",
|
||||
"@fullcalendar/luxon3": "6.1.15",
|
||||
"@fullcalendar/timegrid": "6.1.15",
|
||||
"@fullcalendar/core": "6.1.11",
|
||||
"@fullcalendar/daygrid": "6.1.11",
|
||||
"@fullcalendar/interaction": "6.1.11",
|
||||
"@fullcalendar/list": "6.1.11",
|
||||
"@fullcalendar/luxon3": "6.1.11",
|
||||
"@fullcalendar/timegrid": "6.1.11",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.7",
|
||||
@@ -80,7 +80,7 @@
|
||||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "2.0.0",
|
||||
"@material/web": "1.5.0",
|
||||
"@mdi/js": "7.4.47",
|
||||
"@mdi/svg": "7.4.47",
|
||||
"@polymer/paper-item": "3.0.1",
|
||||
@@ -88,8 +88,8 @@
|
||||
"@polymer/paper-tabs": "3.1.0",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.4.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.5",
|
||||
"@vaadin/combo-box": "24.4.0",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.0",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -118,7 +118,7 @@
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.8.0",
|
||||
"luxon": "3.4.4",
|
||||
"marked": "13.0.3",
|
||||
"marked": "12.0.2",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
@@ -129,7 +129,7 @@
|
||||
"rrule": "2.8.1",
|
||||
"sortablejs": "1.15.2",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"superstruct": "1.0.4",
|
||||
"tinykeys": "2.1.0",
|
||||
"tsparticles-engine": "2.12.0",
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
@@ -149,18 +149,18 @@
|
||||
"xss": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.25.2",
|
||||
"@babel/core": "7.24.7",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||
"@babel/plugin-proposal-decorators": "7.24.7",
|
||||
"@babel/plugin-transform-runtime": "7.24.7",
|
||||
"@babel/preset-env": "7.25.3",
|
||||
"@babel/preset-env": "7.24.7",
|
||||
"@babel/preset-typescript": "7.24.7",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.13.4",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.13.2",
|
||||
"@koa/cors": "5.0.0",
|
||||
"@lokalise/node-api": "12.7.0",
|
||||
"@lokalise/node-api": "12.5.0",
|
||||
"@octokit/auth-oauth-device": "7.1.1",
|
||||
"@octokit/plugin-retry": "7.1.1",
|
||||
"@octokit/rest": "21.0.1",
|
||||
"@octokit/rest": "21.0.0",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.4",
|
||||
"@rollup/plugin-commonjs": "26.0.1",
|
||||
@@ -168,7 +168,7 @@
|
||||
"@rollup/plugin-node-resolve": "15.2.3",
|
||||
"@rollup/plugin-replace": "5.0.7",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.16",
|
||||
"@types/chromecast-caf-receiver": "6.0.15",
|
||||
"@types/chromecast-caf-sender": "1.0.10",
|
||||
"@types/color-name": "1.1.4",
|
||||
"@types/glob": "8.1.0",
|
||||
@@ -178,20 +178,19 @@
|
||||
"@types/leaflet-draw": "1.0.11",
|
||||
"@types/lodash.merge": "4.6.9",
|
||||
"@types/luxon": "3.4.2",
|
||||
"@types/mocha": "10.0.7",
|
||||
"@types/mocha": "10.0.6",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/serve-handler": "6.1.4",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@types/tar": "6.1.13",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||
"@typescript-eslint/parser": "7.18.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.13.1",
|
||||
"@typescript-eslint/parser": "7.13.1",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.3",
|
||||
"chai": "5.1.1",
|
||||
"del": "7.1.0",
|
||||
"eslint": "8.57.0",
|
||||
@@ -201,51 +200,51 @@
|
||||
"eslint-import-resolver-webpack": "0.13.8",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-lit": "1.14.0",
|
||||
"eslint-plugin-lit-a11y": "4.1.4",
|
||||
"eslint-plugin-unused-imports": "4.0.1",
|
||||
"eslint-plugin-lit-a11y": "4.1.2",
|
||||
"eslint-plugin-unused-imports": "4.0.0",
|
||||
"eslint-plugin-wc": "2.1.0",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "11.0.0",
|
||||
"glob": "10.4.2",
|
||||
"gulp": "5.0.0",
|
||||
"gulp-brotli": "3.0.0",
|
||||
"gulp-json-transform": "0.5.0",
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"gulp-zopfli-green": "6.0.1",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.4",
|
||||
"husky": "9.0.11",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.2.8",
|
||||
"lint-staged": "15.2.7",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.11",
|
||||
"magic-string": "0.30.10",
|
||||
"map-stream": "0.0.7",
|
||||
"mocha": "10.5.0",
|
||||
"mocha": "10.4.0",
|
||||
"object-hash": "3.0.0",
|
||||
"open": "10.1.0",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.3.3",
|
||||
"prettier": "3.3.2",
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"serve-handler": "6.1.5",
|
||||
"sinon": "18.0.0",
|
||||
"source-map-url": "0.4.1",
|
||||
"systemjs": "6.15.1",
|
||||
"tar": "7.4.3",
|
||||
"tar": "7.4.0",
|
||||
"terser-webpack-plugin": "5.3.10",
|
||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.5.4",
|
||||
"webpack": "5.93.0",
|
||||
"typescript": "5.4.5",
|
||||
"webpack": "5.92.1",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "6.0.1",
|
||||
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
||||
"workbox-build": "7.1.1"
|
||||
},
|
||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||
"resolutions": {
|
||||
@@ -254,9 +253,9 @@
|
||||
"lit": "2.8.0",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"@fullcalendar/daygrid": "6.1.15",
|
||||
"@fullcalendar/daygrid": "6.1.11",
|
||||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
},
|
||||
"packageManager": "yarn@4.4.0"
|
||||
"packageManager": "yarn@4.3.1"
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.2 KiB |
BIN
public/static/images/logo_twitter.png
Normal file
BIN
public/static/images/logo_twitter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 430 B |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20240806.1"
|
||||
version = "20240610.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -37,7 +37,8 @@
|
||||
{
|
||||
"description": "Group tsparticles engine and presets",
|
||||
"groupName": "tsparticles",
|
||||
"matchPackageNames": ["tsparticles-engine", "tsparticles-preset-{/,}**"]
|
||||
"matchPackageNames": ["tsparticles-engine"],
|
||||
"matchPackagePrefixes": ["tsparticles-preset-"]
|
||||
},
|
||||
{
|
||||
"description": "Group date-fns with dependent timezone package",
|
||||
@@ -47,8 +48,8 @@
|
||||
{
|
||||
"description": "Group and temporarily disable WDS packages",
|
||||
"groupName": "Web Dev Server",
|
||||
"enabled": false,
|
||||
"matchPackageNames": ["@web/dev-server{/,}**"]
|
||||
"matchPackagePrefixes": ["@web/dev-server"],
|
||||
"enabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@@ -125,7 +125,6 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
|
||||
"off",
|
||||
"idle",
|
||||
"preheating",
|
||||
"defrosting",
|
||||
"heating",
|
||||
"cooling",
|
||||
"drying",
|
||||
|
@@ -25,9 +25,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
||||
if (__DEMO__) {
|
||||
if (replace) {
|
||||
mainWindow.history.replaceState(
|
||||
mainWindow.history.state?.root
|
||||
? { root: true }
|
||||
: (options?.data ?? null),
|
||||
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
||||
"",
|
||||
`${mainWindow.location.pathname}#${path}`
|
||||
);
|
||||
@@ -36,7 +34,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
||||
}
|
||||
} else if (replace) {
|
||||
mainWindow.history.replaceState(
|
||||
mainWindow.history.state?.root ? { root: true } : (options?.data ?? null),
|
||||
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
||||
"",
|
||||
path
|
||||
);
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { stripDiacritics } from "../strip-diacritics";
|
||||
import { fuzzyScore } from "./filter";
|
||||
|
||||
/**
|
||||
@@ -20,10 +19,10 @@ export const fuzzySequentialMatch = (
|
||||
for (const word of item.strings) {
|
||||
const scores = fuzzyScore(
|
||||
filter,
|
||||
stripDiacritics(filter.toLowerCase()),
|
||||
filter.toLowerCase(),
|
||||
0,
|
||||
word,
|
||||
stripDiacritics(word.toLowerCase()),
|
||||
word.toLowerCase(),
|
||||
0,
|
||||
true
|
||||
);
|
||||
|
@@ -1,2 +0,0 @@
|
||||
export const stripDiacritics = (str: string) =>
|
||||
str.normalize("NFD").replace(/[\u0300-\u036F]/g, "");
|
@@ -1,6 +1,5 @@
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import "./ha-progress-button";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@@ -18,9 +17,7 @@ class HaCallServiceButton extends LitElement {
|
||||
|
||||
@property() public service!: string;
|
||||
|
||||
@property({ type: Object }) public target!: HassServiceTarget;
|
||||
|
||||
@property({ type: Object }) public data = {};
|
||||
@property({ type: Object }) public serviceData = {};
|
||||
|
||||
@property() public confirmation?;
|
||||
|
||||
@@ -42,8 +39,7 @@ class HaCallServiceButton extends LitElement {
|
||||
const eventData = {
|
||||
domain: this.domain,
|
||||
service: this.service,
|
||||
data: this.data,
|
||||
target: this.target,
|
||||
serviceData: this.serviceData,
|
||||
success: false,
|
||||
};
|
||||
|
||||
@@ -51,12 +47,7 @@ class HaCallServiceButton extends LitElement {
|
||||
this.shadowRoot!.querySelector("ha-progress-button")!;
|
||||
|
||||
try {
|
||||
await this.hass.callService(
|
||||
this.domain,
|
||||
this.service,
|
||||
this.data,
|
||||
this.target
|
||||
);
|
||||
await this.hass.callService(this.domain, this.service, this.serviceData);
|
||||
this.progress = false;
|
||||
progressElement.actionSuccess();
|
||||
eventData.success = true;
|
||||
@@ -94,8 +85,7 @@ declare global {
|
||||
"hass-service-called": {
|
||||
domain: string;
|
||||
service: string;
|
||||
target: HassServiceTarget;
|
||||
data: object;
|
||||
serviceData: object;
|
||||
success: boolean;
|
||||
};
|
||||
}
|
||||
|
@@ -159,10 +159,10 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
},
|
||||
afterUpdate: (y) => {
|
||||
const yWidth = this.showNames
|
||||
? (y.width ?? 0)
|
||||
? y.width ?? 0
|
||||
: computeRTL(this.hass)
|
||||
? 0
|
||||
: (y.left ?? 0);
|
||||
: y.left ?? 0;
|
||||
if (
|
||||
this._yWidth !== Math.floor(yWidth) &&
|
||||
y.ticks.length === this.data.length
|
||||
|
@@ -1,319 +0,0 @@
|
||||
import "@material/mwc-list";
|
||||
import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { createCloseHeading } from "../ha-dialog";
|
||||
import "../ha-list-item";
|
||||
import "../ha-sortable";
|
||||
import "../ha-button";
|
||||
import { DataTableColumnContainer, DataTableColumnData } from "./ha-data-table";
|
||||
import { DataTableSettingsDialogParams } from "./show-dialog-data-table-settings";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
@customElement("dialog-data-table-settings")
|
||||
export class DialogDataTableSettings extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: DataTableSettingsDialogParams;
|
||||
|
||||
@state() private _columnOrder?: string[];
|
||||
|
||||
@state() private _hiddenColumns?: string[];
|
||||
|
||||
public showDialog(params: DataTableSettingsDialogParams) {
|
||||
this._params = params;
|
||||
this._columnOrder = params.columnOrder;
|
||||
this._hiddenColumns = params.hiddenColumns;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _sortedColumns = memoizeOne(
|
||||
(
|
||||
columns: DataTableColumnContainer,
|
||||
columnOrder: string[] | undefined,
|
||||
hiddenColumns: string[] | undefined
|
||||
) =>
|
||||
Object.keys(columns)
|
||||
.filter((col) => !columns[col].hidden)
|
||||
.sort((a, b) => {
|
||||
const orderA = columnOrder?.indexOf(a) ?? -1;
|
||||
const orderB = columnOrder?.indexOf(b) ?? -1;
|
||||
const hiddenA =
|
||||
hiddenColumns?.includes(a) ?? Boolean(columns[a].defaultHidden);
|
||||
const hiddenB =
|
||||
hiddenColumns?.includes(b) ?? Boolean(columns[b].defaultHidden);
|
||||
if (hiddenA !== hiddenB) {
|
||||
return hiddenA ? 1 : -1;
|
||||
}
|
||||
if (orderA !== orderB) {
|
||||
if (orderA === -1) {
|
||||
return 1;
|
||||
}
|
||||
if (orderB === -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return orderA - orderB;
|
||||
})
|
||||
.reduce(
|
||||
(arr, key) => {
|
||||
arr.push({ key, ...columns[key] });
|
||||
return arr;
|
||||
},
|
||||
[] as (DataTableColumnData & { key: string })[]
|
||||
)
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const localize = this._params.localizeFunc || this.hass.localize;
|
||||
|
||||
const columns = this._sortedColumns(
|
||||
this._params.columns,
|
||||
this._columnOrder,
|
||||
this._hiddenColumns
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
localize("ui.components.data-table.settings.header")
|
||||
)}
|
||||
>
|
||||
<ha-sortable
|
||||
@item-moved=${this._columnMoved}
|
||||
draggable-selector=".draggable"
|
||||
handle-selector=".handle"
|
||||
>
|
||||
<mwc-list>
|
||||
${repeat(
|
||||
columns,
|
||||
(col) => col.key,
|
||||
(col, _idx) => {
|
||||
const canMove = !col.main && col.moveable !== false;
|
||||
const canHide = !col.main && col.hideable !== false;
|
||||
const isVisible = !(this._columnOrder &&
|
||||
this._columnOrder.includes(col.key)
|
||||
? (this._hiddenColumns?.includes(col.key) ??
|
||||
col.defaultHidden)
|
||||
: col.defaultHidden);
|
||||
|
||||
return html`<ha-list-item
|
||||
hasMeta
|
||||
class=${classMap({
|
||||
hidden: !isVisible,
|
||||
draggable: canMove && isVisible,
|
||||
})}
|
||||
graphic="icon"
|
||||
noninteractive
|
||||
>${col.title || col.label || col.key}
|
||||
${canMove && isVisible
|
||||
? html`<ha-svg-icon
|
||||
class="handle"
|
||||
.path=${mdiDrag}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
<ha-icon-button
|
||||
tabindex="0"
|
||||
class="action"
|
||||
.disabled=${!canHide}
|
||||
.hidden=${!isVisible}
|
||||
.path=${isVisible ? mdiEye : mdiEyeOff}
|
||||
slot="meta"
|
||||
.label=${this.hass!.localize(
|
||||
`ui.components.data-table.settings.${isVisible ? "hide" : "show"}`,
|
||||
{ title: typeof col.title === "string" ? col.title : "" }
|
||||
)}
|
||||
.column=${col.key}
|
||||
@click=${this._toggle}
|
||||
></ha-icon-button>
|
||||
</ha-list-item>`;
|
||||
}
|
||||
)}
|
||||
</mwc-list>
|
||||
</ha-sortable>
|
||||
<ha-button slot="secondaryAction" @click=${this._reset}
|
||||
>${localize("ui.components.data-table.settings.restore")}</ha-button
|
||||
>
|
||||
<ha-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${localize("ui.components.data-table.settings.done")}
|
||||
</ha-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _columnMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
|
||||
const columns = this._sortedColumns(
|
||||
this._params.columns,
|
||||
this._columnOrder,
|
||||
this._hiddenColumns
|
||||
);
|
||||
|
||||
const columnOrder = columns.map((column) => column.key);
|
||||
|
||||
const option = columnOrder.splice(oldIndex, 1)[0];
|
||||
columnOrder.splice(newIndex, 0, option);
|
||||
|
||||
this._columnOrder = columnOrder;
|
||||
|
||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||
}
|
||||
|
||||
_toggle(ev) {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
const column = ev.target.column;
|
||||
const wasHidden = ev.target.hidden;
|
||||
|
||||
const hidden = [
|
||||
...(this._hiddenColumns ??
|
||||
Object.entries(this._params.columns)
|
||||
.filter(([_key, col]) => col.defaultHidden)
|
||||
.map(([key]) => key)),
|
||||
];
|
||||
|
||||
if (wasHidden && hidden.includes(column)) {
|
||||
hidden.splice(hidden.indexOf(column), 1);
|
||||
} else if (!wasHidden) {
|
||||
hidden.push(column);
|
||||
}
|
||||
|
||||
const columns = this._sortedColumns(
|
||||
this._params.columns,
|
||||
this._columnOrder,
|
||||
hidden
|
||||
);
|
||||
|
||||
if (!this._columnOrder) {
|
||||
this._columnOrder = columns.map((col) => col.key);
|
||||
} else {
|
||||
const newOrder = this._columnOrder.filter((col) => col !== column);
|
||||
|
||||
// Array.findLastIndex when supported or core-js polyfill
|
||||
const findLastIndex = (
|
||||
arr: Array<any>,
|
||||
fn: (item: any, index: number, arr: Array<any>) => boolean
|
||||
) => {
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
if (fn(arr[i], i, arr)) return i;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
let lastMoveable = findLastIndex(
|
||||
newOrder,
|
||||
(col) =>
|
||||
col !== column &&
|
||||
!hidden.includes(col) &&
|
||||
!this._params!.columns[col].main &&
|
||||
this._params!.columns[col].moveable !== false
|
||||
);
|
||||
|
||||
if (lastMoveable === -1) {
|
||||
lastMoveable = newOrder.length - 1;
|
||||
}
|
||||
|
||||
columns.forEach((col) => {
|
||||
if (!newOrder.includes(col.key)) {
|
||||
if (col.moveable === false) {
|
||||
newOrder.unshift(col.key);
|
||||
} else {
|
||||
newOrder.splice(lastMoveable + 1, 0, col.key);
|
||||
}
|
||||
|
||||
if (
|
||||
col.key !== column &&
|
||||
col.defaultHidden &&
|
||||
!hidden.includes(col.key)
|
||||
) {
|
||||
hidden.push(col.key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._columnOrder = newOrder;
|
||||
}
|
||||
|
||||
this._hiddenColumns = hidden;
|
||||
|
||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||
}
|
||||
|
||||
_reset() {
|
||||
this._columnOrder = undefined;
|
||||
this._hiddenColumns = undefined;
|
||||
|
||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 500px;
|
||||
--dialog-z-index: 10;
|
||||
--dialog-content-padding: 0 8px;
|
||||
}
|
||||
@media all and (max-width: 451px) {
|
||||
ha-dialog {
|
||||
--vertical-align-dialog: flex-start;
|
||||
--dialog-surface-margin-top: 250px;
|
||||
--ha-dialog-border-radius: 28px 28px 0 0;
|
||||
--mdc-dialog-min-height: calc(100% - 250px);
|
||||
--mdc-dialog-max-height: calc(100% - 250px);
|
||||
}
|
||||
}
|
||||
ha-list-item {
|
||||
--mdc-list-side-padding: 12px;
|
||||
overflow: visible;
|
||||
}
|
||||
.hidden {
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
.handle {
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
ha-icon-button {
|
||||
display: block;
|
||||
margin: -12px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-data-table-settings": DialogDataTableSettings;
|
||||
}
|
||||
}
|
@@ -34,7 +34,6 @@ import type { HaCheckbox } from "../ha-checkbox";
|
||||
import "../ha-svg-icon";
|
||||
import "../search-input";
|
||||
import { filterData, sortData } from "./sort-filter";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
|
||||
export interface RowClickedEvent {
|
||||
id: string;
|
||||
@@ -66,10 +65,6 @@ export interface DataTableSortColumnData {
|
||||
valueColumn?: string;
|
||||
direction?: SortingDirection;
|
||||
groupable?: boolean;
|
||||
moveable?: boolean;
|
||||
hideable?: boolean;
|
||||
defaultHidden?: boolean;
|
||||
showNarrow?: boolean;
|
||||
}
|
||||
|
||||
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||
@@ -84,10 +79,9 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||
| "overflow-menu"
|
||||
| "flex";
|
||||
template?: (row: T) => TemplateResult | string | typeof nothing;
|
||||
extraTemplate?: (row: T) => TemplateResult | string | typeof nothing;
|
||||
minWidth?: string;
|
||||
width?: string;
|
||||
maxWidth?: string;
|
||||
flex?: number;
|
||||
grows?: boolean;
|
||||
forceLTR?: boolean;
|
||||
hidden?: boolean;
|
||||
}
|
||||
@@ -111,10 +105,6 @@ const UNDEFINED_GROUP_KEY = "zzzzz_undefined";
|
||||
export class HaDataTable extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public localizeFunc?: LocalizeFunc;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
||||
|
||||
@property({ type: Array }) public data: DataTableRowData[] = [];
|
||||
@@ -155,10 +145,6 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public initialCollapsedGroups?: string[];
|
||||
|
||||
@property({ attribute: false }) public hiddenColumns?: string[];
|
||||
|
||||
@property({ attribute: false }) public columnOrder?: string[];
|
||||
|
||||
@state() private _filterable = false;
|
||||
|
||||
@state() private _filter = "";
|
||||
@@ -216,18 +202,6 @@ export class HaDataTable extends LitElement {
|
||||
this.updateComplete.then(() => this._calcTableHeight());
|
||||
}
|
||||
|
||||
protected updated() {
|
||||
const header = this.renderRoot.querySelector(".mdc-data-table__header-row");
|
||||
if (!header) {
|
||||
return;
|
||||
}
|
||||
if (header.scrollWidth > header.clientWidth) {
|
||||
this.style.setProperty("--table-row-width", `${header.scrollWidth}px`);
|
||||
} else {
|
||||
this.style.removeProperty("--table-row-width");
|
||||
}
|
||||
}
|
||||
|
||||
public willUpdate(properties: PropertyValues) {
|
||||
super.willUpdate(properties);
|
||||
|
||||
@@ -261,7 +235,6 @@ export class HaDataTable extends LitElement {
|
||||
(column: ClonedDataTableColumnData) => {
|
||||
delete column.title;
|
||||
delete column.template;
|
||||
delete column.extraTemplate;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -299,46 +272,12 @@ export class HaDataTable extends LitElement {
|
||||
this._sortFilterData();
|
||||
}
|
||||
|
||||
if (properties.has("selectable") || properties.has("hiddenColumns")) {
|
||||
if (properties.has("selectable")) {
|
||||
this._items = [...this._items];
|
||||
}
|
||||
}
|
||||
|
||||
private _sortedColumns = memoizeOne(
|
||||
(columns: DataTableColumnContainer, columnOrder?: string[]) => {
|
||||
if (!columnOrder || !columnOrder.length) {
|
||||
return columns;
|
||||
}
|
||||
|
||||
return Object.keys(columns)
|
||||
.sort((a, b) => {
|
||||
const orderA = columnOrder!.indexOf(a);
|
||||
const orderB = columnOrder!.indexOf(b);
|
||||
if (orderA !== orderB) {
|
||||
if (orderA === -1) {
|
||||
return 1;
|
||||
}
|
||||
if (orderB === -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return orderA - orderB;
|
||||
})
|
||||
.reduce((obj, key) => {
|
||||
obj[key] = columns[key];
|
||||
return obj;
|
||||
}, {}) as DataTableColumnContainer;
|
||||
}
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const localize = this.localizeFunc || this.hass.localize;
|
||||
|
||||
const columns = this._sortedColumns(this.columns, this.columnOrder);
|
||||
|
||||
const renderRow = (row: DataTableRowData, index: number) =>
|
||||
this._renderRow(columns, this.narrow, row, index);
|
||||
|
||||
return html`
|
||||
<div class="mdc-data-table">
|
||||
<slot name="header" @slotchange=${this._calcTableHeight}>
|
||||
@@ -367,12 +306,7 @@ export class HaDataTable extends LitElement {
|
||||
: `calc(100% - ${this._headerHeight}px)`,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
class="mdc-data-table__header-row"
|
||||
role="row"
|
||||
aria-rowindex="1"
|
||||
@scroll=${this._scrollContent}
|
||||
>
|
||||
<div class="mdc-data-table__header-row" role="row" aria-rowindex="1">
|
||||
<slot name="header-row">
|
||||
${this.selectable
|
||||
? html`
|
||||
@@ -392,15 +326,9 @@ export class HaDataTable extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${Object.entries(columns).map(([key, column]) => {
|
||||
if (
|
||||
column.hidden ||
|
||||
(this.columnOrder && this.columnOrder.includes(key)
|
||||
? (this.hiddenColumns?.includes(key) ??
|
||||
column.defaultHidden)
|
||||
: column.defaultHidden)
|
||||
) {
|
||||
return nothing;
|
||||
${Object.entries(this.columns).map(([key, column]) => {
|
||||
if (column.hidden) {
|
||||
return "";
|
||||
}
|
||||
const sorted = key === this.sortColumn;
|
||||
const classes = {
|
||||
@@ -415,16 +343,18 @@ export class HaDataTable extends LitElement {
|
||||
column.type === "overflow",
|
||||
sortable: Boolean(column.sortable),
|
||||
"not-sorted": Boolean(column.sortable && !sorted),
|
||||
grows: Boolean(column.grows),
|
||||
};
|
||||
return html`
|
||||
<div
|
||||
aria-label=${ifDefined(column.label)}
|
||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||
style=${styleMap({
|
||||
minWidth: column.minWidth,
|
||||
maxWidth: column.maxWidth,
|
||||
flex: column.flex || 1,
|
||||
})}
|
||||
style=${column.width
|
||||
? styleMap({
|
||||
[column.grows ? "minWidth" : "width"]: column.width,
|
||||
maxWidth: column.maxWidth || "",
|
||||
})
|
||||
: ""}
|
||||
role="columnheader"
|
||||
aria-sort=${ifDefined(
|
||||
sorted
|
||||
@@ -457,7 +387,7 @@ export class HaDataTable extends LitElement {
|
||||
<div class="mdc-data-table__row" role="row">
|
||||
<div class="mdc-data-table__cell grows center" role="cell">
|
||||
${this.noDataText ||
|
||||
localize("ui.components.data-table.no-data")}
|
||||
this.hass.localize("ui.components.data-table.no-data")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -469,7 +399,7 @@ export class HaDataTable extends LitElement {
|
||||
@scroll=${this._saveScrollPos}
|
||||
.items=${this._items}
|
||||
.keyFunction=${this._keyFunction}
|
||||
.renderItem=${renderRow}
|
||||
.renderItem=${this._renderRow}
|
||||
></lit-virtualizer>
|
||||
`}
|
||||
</div>
|
||||
@@ -479,12 +409,7 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
|
||||
|
||||
private _renderRow = (
|
||||
columns: DataTableColumnContainer,
|
||||
narrow: boolean,
|
||||
row: DataTableRowData,
|
||||
index: number
|
||||
) => {
|
||||
private _renderRow = (row: DataTableRowData, index: number) => {
|
||||
// not sure how this happens...
|
||||
if (!row) {
|
||||
return nothing;
|
||||
@@ -529,14 +454,8 @@ export class HaDataTable extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${Object.entries(columns).map(([key, column]) => {
|
||||
if (
|
||||
(narrow && !column.main && !column.showNarrow) ||
|
||||
column.hidden ||
|
||||
(this.columnOrder && this.columnOrder.includes(key)
|
||||
? (this.hiddenColumns?.includes(key) ?? column.defaultHidden)
|
||||
: column.defaultHidden)
|
||||
) {
|
||||
${Object.entries(this.columns).map(([key, column]) => {
|
||||
if (column.hidden) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
@@ -553,46 +472,17 @@ export class HaDataTable extends LitElement {
|
||||
"mdc-data-table__cell--overflow-menu":
|
||||
column.type === "overflow-menu",
|
||||
"mdc-data-table__cell--overflow": column.type === "overflow",
|
||||
grows: Boolean(column.grows),
|
||||
forceLTR: Boolean(column.forceLTR),
|
||||
})}"
|
||||
style=${styleMap({
|
||||
minWidth: column.minWidth,
|
||||
maxWidth: column.maxWidth,
|
||||
flex: column.flex || 1,
|
||||
})}
|
||||
style=${column.width
|
||||
? styleMap({
|
||||
[column.grows ? "minWidth" : "width"]: column.width,
|
||||
maxWidth: column.maxWidth ? column.maxWidth : "",
|
||||
})
|
||||
: ""}
|
||||
>
|
||||
${column.template
|
||||
? column.template(row)
|
||||
: narrow && column.main
|
||||
? html`<div class="primary">${row[key]}</div>
|
||||
<div class="secondary">
|
||||
${Object.entries(columns)
|
||||
.filter(
|
||||
([key2, column2]) =>
|
||||
!column2.hidden &&
|
||||
!column2.main &&
|
||||
!column2.showNarrow &&
|
||||
!(this.columnOrder &&
|
||||
this.columnOrder.includes(key2)
|
||||
? (this.hiddenColumns?.includes(key2) ??
|
||||
column2.defaultHidden)
|
||||
: column2.defaultHidden)
|
||||
)
|
||||
.map(
|
||||
([key2, column2], i) =>
|
||||
html`${i !== 0
|
||||
? " ⸱ "
|
||||
: nothing}${column2.template
|
||||
? column2.template(row)
|
||||
: row[key2]}`
|
||||
)}
|
||||
</div>
|
||||
${column.extraTemplate
|
||||
? column.extraTemplate(row)
|
||||
: nothing}`
|
||||
: html`${row[key]}${column.extraTemplate
|
||||
? column.extraTemplate(row)
|
||||
: nothing}`}
|
||||
${column.template ? column.template(row) : row[key]}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
@@ -610,7 +500,7 @@ export class HaDataTable extends LitElement {
|
||||
filteredData = await this._memFilterData(
|
||||
this.data,
|
||||
this._sortColumns,
|
||||
this._filter.trim()
|
||||
this._filter
|
||||
);
|
||||
}
|
||||
|
||||
@@ -638,8 +528,6 @@ export class HaDataTable extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const localize = this.localizeFunc || this.hass.localize;
|
||||
|
||||
if (this.appendRow || this.hasFab || this.groupColumn) {
|
||||
let items = [...data];
|
||||
|
||||
@@ -693,7 +581,7 @@ export class HaDataTable extends LitElement {
|
||||
>
|
||||
</ha-icon-button>
|
||||
${groupName === UNDEFINED_GROUP_KEY
|
||||
? localize("ui.components.data-table.ungrouped")
|
||||
? this.hass.localize("ui.components.data-table.ungrouped")
|
||||
: groupName || ""}
|
||||
</div>`,
|
||||
});
|
||||
@@ -828,17 +716,6 @@ export class HaDataTable extends LitElement {
|
||||
@eventOptions({ passive: true })
|
||||
private _saveScrollPos(e: Event) {
|
||||
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
|
||||
|
||||
this.renderRoot.querySelector(".mdc-data-table__header-row")!.scrollLeft = (
|
||||
e.target as HTMLDivElement
|
||||
).scrollLeft;
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _scrollContent(e: Event) {
|
||||
this.renderRoot.querySelector("lit-virtualizer")!.scrollLeft = (
|
||||
e.target as HTMLDivElement
|
||||
).scrollLeft;
|
||||
}
|
||||
|
||||
private _collapseGroup = (ev: Event) => {
|
||||
@@ -913,8 +790,8 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
.mdc-data-table__row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: var(--data-table-row-height, 52px);
|
||||
width: var(--table-row-width, 100%);
|
||||
}
|
||||
|
||||
.mdc-data-table__row ~ .mdc-data-table__row {
|
||||
@@ -938,26 +815,18 @@ export class HaDataTable extends LitElement {
|
||||
.mdc-data-table__header-row {
|
||||
height: 56px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.mdc-data-table__header-row::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.mdc-data-table__header-row {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.mdc-data-table__cell,
|
||||
.mdc-data-table__header-cell {
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
min-width: 150px;
|
||||
align-self: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -992,7 +861,6 @@ export class HaDataTable extends LitElement {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell {
|
||||
@@ -1005,8 +873,6 @@ export class HaDataTable extends LitElement {
|
||||
letter-spacing: 0.0178571429em;
|
||||
text-decoration: inherit;
|
||||
text-transform: inherit;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell a {
|
||||
@@ -1025,8 +891,7 @@ export class HaDataTable extends LitElement {
|
||||
|
||||
.mdc-data-table__header-cell--icon,
|
||||
.mdc-data-table__cell--icon {
|
||||
min-width: 64px;
|
||||
flex: 0 0 64px !important;
|
||||
width: 54px;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon img {
|
||||
@@ -1066,14 +931,11 @@ export class HaDataTable extends LitElement {
|
||||
.mdc-data-table__header-cell--overflow-menu,
|
||||
.mdc-data-table__header-cell--icon-button,
|
||||
.mdc-data-table__cell--icon-button {
|
||||
min-width: 64px;
|
||||
flex: 0 0 64px !important;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell--icon-button,
|
||||
.mdc-data-table__cell--icon-button {
|
||||
min-width: 56px;
|
||||
width: 56px;
|
||||
}
|
||||
|
||||
|
@@ -1,28 +0,0 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { DataTableColumnContainer } from "./ha-data-table";
|
||||
|
||||
export interface DataTableSettingsDialogParams {
|
||||
columns: DataTableColumnContainer;
|
||||
onUpdate: (
|
||||
columnOrder: string[] | undefined,
|
||||
hiddenColumns: string[] | undefined
|
||||
) => void;
|
||||
hiddenColumns?: string[];
|
||||
columnOrder?: string[];
|
||||
localizeFunc?: LocalizeFunc;
|
||||
}
|
||||
|
||||
export const loadDataTableSettingsDialog = () =>
|
||||
import("./dialog-data-table-settings");
|
||||
|
||||
export const showDataTableSettingsDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: DataTableSettingsDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-data-table-settings",
|
||||
dialogImport: loadDataTableSettingsDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -1,6 +1,5 @@
|
||||
import { expose } from "comlink";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import { stripDiacritics } from "../../common/string/strip-diacritics";
|
||||
import type {
|
||||
ClonedDataTableColumnData,
|
||||
DataTableRowData,
|
||||
@@ -13,18 +12,20 @@ const filterData = (
|
||||
columns: SortableColumnContainer,
|
||||
filter: string
|
||||
) => {
|
||||
filter = stripDiacritics(filter.toLowerCase());
|
||||
filter = filter.toUpperCase();
|
||||
return data.filter((row) =>
|
||||
Object.entries(columns).some((columnEntry) => {
|
||||
const [key, column] = columnEntry;
|
||||
if (column.filterable) {
|
||||
const value = String(
|
||||
column.filterKey
|
||||
? row[column.valueColumn || key][column.filterKey]
|
||||
: row[column.valueColumn || key]
|
||||
);
|
||||
|
||||
if (stripDiacritics(value).toLowerCase().includes(filter)) {
|
||||
if (
|
||||
String(
|
||||
column.filterKey
|
||||
? row[column.valueColumn || key][column.filterKey]
|
||||
: row[column.valueColumn || key]
|
||||
)
|
||||
.toUpperCase()
|
||||
.includes(filter)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -1,330 +0,0 @@
|
||||
import { mdiDrag } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { LitElement, PropertyValues, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import {
|
||||
STATE_DISPLAY_SPECIAL_CONTENT,
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS,
|
||||
} from "../../state-display/state-display";
|
||||
import { HomeAssistant, ValueChangedEvent } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
|
||||
const HIDDEN_ATTRIBUTES = [
|
||||
"access_token",
|
||||
"available_modes",
|
||||
"battery_icon",
|
||||
"battery_level",
|
||||
"code_arm_required",
|
||||
"code_format",
|
||||
"color_modes",
|
||||
"device_class",
|
||||
"editable",
|
||||
"effect_list",
|
||||
"entity_id",
|
||||
"entity_picture",
|
||||
"event_types",
|
||||
"fan_modes",
|
||||
"fan_speed_list",
|
||||
"friendly_name",
|
||||
"frontend_stream_type",
|
||||
"has_date",
|
||||
"has_time",
|
||||
"hvac_modes",
|
||||
"icon",
|
||||
"id",
|
||||
"max_color_temp_kelvin",
|
||||
"max_mireds",
|
||||
"max_temp",
|
||||
"max",
|
||||
"min_color_temp_kelvin",
|
||||
"min_mireds",
|
||||
"min_temp",
|
||||
"min",
|
||||
"mode",
|
||||
"operation_list",
|
||||
"options",
|
||||
"percentage_step",
|
||||
"precipitation_unit",
|
||||
"preset_modes",
|
||||
"pressure_unit",
|
||||
"remaining",
|
||||
"sound_mode_list",
|
||||
"source_list",
|
||||
"state_class",
|
||||
"step",
|
||||
"supported_color_modes",
|
||||
"supported_features",
|
||||
"swing_modes",
|
||||
"target_temp_step",
|
||||
"temperature_unit",
|
||||
"token",
|
||||
"unit_of_measurement",
|
||||
"visibility_unit",
|
||||
"wind_speed_unit",
|
||||
];
|
||||
|
||||
@customElement("ha-entity-state-content-picker")
|
||||
class HaEntityStatePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId?: string;
|
||||
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-name" }) public allowName =
|
||||
false;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string[] | string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
}
|
||||
|
||||
private options = memoizeOne(
|
||||
(entityId?: string, stateObj?: HassEntity, allowName?: boolean) => {
|
||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||
return [
|
||||
{
|
||||
label: this.hass.localize("ui.components.state-content-picker.state"),
|
||||
value: "state",
|
||||
},
|
||||
...(allowName
|
||||
? [
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.name"
|
||||
),
|
||||
value: "name",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_changed"
|
||||
),
|
||||
value: "last_changed",
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_updated"
|
||||
),
|
||||
value: "last_updated",
|
||||
},
|
||||
...(domain
|
||||
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
|
||||
).map((content) => ({
|
||||
label: this.hass.localize(
|
||||
`ui.components.state-content-picker.${content}`
|
||||
),
|
||||
value: content,
|
||||
}))
|
||||
: []),
|
||||
...Object.keys(stateObj?.attributes ?? {})
|
||||
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
||||
.map((attribute) => ({
|
||||
value: attribute,
|
||||
label: this.hass.formatEntityAttributeName(stateObj!, attribute),
|
||||
})),
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
private _filter = "";
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const value = this._value;
|
||||
|
||||
const stateObj = this.entityId
|
||||
? this.hass.states[this.entityId]
|
||||
: undefined;
|
||||
|
||||
const options = this.options(this.entityId, stateObj, this.allowName);
|
||||
const optionItems = options.filter(
|
||||
(option) => !this._value.includes(option.value)
|
||||
);
|
||||
|
||||
return html`
|
||||
${value?.length
|
||||
? html`
|
||||
<ha-sortable
|
||||
no-style
|
||||
@item-moved=${this._moveItem}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-chip-set>
|
||||
${repeat(
|
||||
this._value,
|
||||
(item) => item,
|
||||
(item, idx) => {
|
||||
const label =
|
||||
options.find((option) => option.value === item)?.label ||
|
||||
item;
|
||||
return html`
|
||||
<ha-input-chip
|
||||
.idx=${idx}
|
||||
@remove=${this._removeItem}
|
||||
.label=${label}
|
||||
selected
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiDrag}
|
||||
data-handle
|
||||
></ha-svg-icon>
|
||||
|
||||
${label}
|
||||
</ha-input-chip>
|
||||
`;
|
||||
}
|
||||
)}
|
||||
</ha-chip-set>
|
||||
</ha-sortable>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-combo-box
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !value.length}
|
||||
.value=${""}
|
||||
.items=${optionItems}
|
||||
allow-custom-value
|
||||
@filter-changed=${this._filterChanged}
|
||||
@value-changed=${this._comboBoxValueChanged}
|
||||
@opened-changed=${this._openedChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return !this.value ? [] : ensureArray(this.value);
|
||||
}
|
||||
|
||||
private _openedChanged(ev: ValueChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
this._comboBox.filteredItems = this._comboBox.items;
|
||||
}
|
||||
|
||||
private _filterChanged(ev?: CustomEvent): void {
|
||||
this._filter = ev?.detail.value || "";
|
||||
|
||||
const filteredItems = this._comboBox.items?.filter((item) => {
|
||||
const label = item.label || item.value;
|
||||
return label.toLowerCase().includes(this._filter?.toLowerCase());
|
||||
});
|
||||
|
||||
if (this._filter) {
|
||||
filteredItems?.unshift({ label: this._filter, value: this._filter });
|
||||
}
|
||||
|
||||
this._comboBox.filteredItems = filteredItems;
|
||||
}
|
||||
|
||||
private async _moveItem(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
const value = this._value;
|
||||
const newValue = value.concat();
|
||||
const element = newValue.splice(oldIndex, 1)[0];
|
||||
newValue.splice(newIndex, 0, element);
|
||||
this._setValue(newValue);
|
||||
await this.updateComplete;
|
||||
this._filterChanged();
|
||||
}
|
||||
|
||||
private async _removeItem(ev) {
|
||||
ev.stopPropagation();
|
||||
const value: string[] = [...this._value];
|
||||
value.splice(ev.target.idx, 1);
|
||||
this._setValue(value);
|
||||
await this.updateComplete;
|
||||
this._filterChanged();
|
||||
}
|
||||
|
||||
private _comboBoxValueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
|
||||
if (this.disabled || newValue === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentValue = this._value;
|
||||
|
||||
if (currentValue.includes(newValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this._filterChanged();
|
||||
this._comboBox.setInputValue("");
|
||||
}, 0);
|
||||
|
||||
this._setValue([...currentValue, newValue]);
|
||||
}
|
||||
|
||||
private _setValue(value: string[]) {
|
||||
const newValue =
|
||||
value.length === 0 ? undefined : value.length === 1 ? value[0] : value;
|
||||
this.value = newValue;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ha-chip-set {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.sortable-fallback {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.sortable-drag {
|
||||
cursor: grabbing;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-state-content-picker": HaEntityStatePicker;
|
||||
}
|
||||
}
|
@@ -134,7 +134,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
this._timerTimeRemaining
|
||||
)}
|
||||
.description=${this.showName
|
||||
? (this.name ?? computeStateName(entityState))
|
||||
? this.name ?? computeStateName(entityState)
|
||||
: undefined}
|
||||
>
|
||||
${!image && showIcon
|
||||
|
@@ -90,8 +90,7 @@ class HaAnsiToHtml extends LitElement {
|
||||
|
||||
private _parseTextToColoredPre(text) {
|
||||
const pre = document.createElement("pre");
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const re = /\x1b(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1b\\))/g;
|
||||
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
|
||||
let i = 0;
|
||||
|
||||
const state: State = {
|
||||
|
@@ -279,8 +279,6 @@ export class HaAreaPicker extends LitElement {
|
||||
icon: null,
|
||||
aliases: [],
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -297,8 +295,6 @@ export class HaAreaPicker extends LitElement {
|
||||
icon: "mdi:plus",
|
||||
aliases: [],
|
||||
labels: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -381,8 +377,6 @@ export class HaAreaPicker extends LitElement {
|
||||
picture: null,
|
||||
labels: [],
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
] as AreaRegistryEntry[];
|
||||
} else {
|
||||
@@ -399,8 +393,6 @@ export class HaAreaPicker extends LitElement {
|
||||
picture: null,
|
||||
labels: [],
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
] as AreaRegistryEntry[];
|
||||
}
|
||||
|
@@ -1,12 +1,10 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { css, html, LitElement, TemplateResult, nothing } from "lit";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import "./ha-select";
|
||||
import "./ha-icon-button";
|
||||
import { HaTextField } from "./ha-textfield";
|
||||
import "./ha-input-helper-text";
|
||||
|
||||
@@ -126,128 +124,116 @@ export class HaBaseTimeInput extends LitElement {
|
||||
*/
|
||||
@property() amPm: "AM" | "PM" = "AM";
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public clearable?: boolean;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.label
|
||||
? html`<label>${this.label}${this.required ? " *" : ""}</label>`
|
||||
: ""}
|
||||
<div class="time-input-wrap-wrap">
|
||||
<div class="time-input-wrap">
|
||||
${this.enableDay
|
||||
? html`
|
||||
<ha-textfield
|
||||
id="day"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
.value=${this.days.toFixed()}
|
||||
.label=${this.dayLabel}
|
||||
name="days"
|
||||
@change=${this._valueChanged}
|
||||
@focusin=${this._onFocus}
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
suffix=":"
|
||||
class="hasSuffix"
|
||||
>
|
||||
</ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-textfield
|
||||
id="hour"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
.value=${this.hours.toFixed()}
|
||||
.label=${this.hourLabel}
|
||||
name="hours"
|
||||
@change=${this._valueChanged}
|
||||
@focusin=${this._onFocus}
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
maxlength="2"
|
||||
max=${ifDefined(this._hourMax)}
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
suffix=":"
|
||||
class="hasSuffix"
|
||||
>
|
||||
</ha-textfield>
|
||||
<ha-textfield
|
||||
id="min"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
.value=${this._formatValue(this.minutes)}
|
||||
.label=${this.minLabel}
|
||||
@change=${this._valueChanged}
|
||||
@focusin=${this._onFocus}
|
||||
name="minutes"
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
maxlength="2"
|
||||
max="59"
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
.suffix=${this.enableSecond ? ":" : ""}
|
||||
class=${this.enableSecond ? "has-suffix" : ""}
|
||||
>
|
||||
</ha-textfield>
|
||||
${this.enableSecond
|
||||
? html`<ha-textfield
|
||||
id="sec"
|
||||
<div class="time-input-wrap">
|
||||
${this.enableDay
|
||||
? html`
|
||||
<ha-textfield
|
||||
id="day"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
.value=${this._formatValue(this.seconds)}
|
||||
.label=${this.secLabel}
|
||||
.value=${this.days.toFixed()}
|
||||
.label=${this.dayLabel}
|
||||
name="days"
|
||||
@change=${this._valueChanged}
|
||||
@focusin=${this._onFocus}
|
||||
name="seconds"
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
maxlength="2"
|
||||
max="59"
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
.suffix=${this.enableMillisecond ? ":" : ""}
|
||||
class=${this.enableMillisecond ? "has-suffix" : ""}
|
||||
suffix=":"
|
||||
class="hasSuffix"
|
||||
>
|
||||
</ha-textfield>`
|
||||
: ""}
|
||||
${this.enableMillisecond
|
||||
? html`<ha-textfield
|
||||
id="millisec"
|
||||
type="number"
|
||||
.value=${this._formatValue(this.milliseconds, 3)}
|
||||
.label=${this.millisecLabel}
|
||||
@change=${this._valueChanged}
|
||||
@focusin=${this._onFocus}
|
||||
name="milliseconds"
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
maxlength="3"
|
||||
max="999"
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
</ha-textfield>`
|
||||
: ""}
|
||||
${this.clearable && !this.required && !this.disabled
|
||||
? html`<ha-icon-button
|
||||
label="clear"
|
||||
@click=${this._clearValue}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-textfield
|
||||
id="hour"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
.value=${this.hours.toFixed()}
|
||||
.label=${this.hourLabel}
|
||||
name="hours"
|
||||
@change=${this._valueChanged}
|
||||
@focusin=${this._onFocus}
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
maxlength="2"
|
||||
max=${ifDefined(this._hourMax)}
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
suffix=":"
|
||||
class="hasSuffix"
|
||||
>
|
||||
</ha-textfield>
|
||||
<ha-textfield
|
||||
id="min"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
.value=${this._formatValue(this.minutes)}
|
||||
.label=${this.minLabel}
|
||||
@change=${this._valueChanged}
|
||||
@focusin=${this._onFocus}
|
||||
name="minutes"
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
maxlength="2"
|
||||
max="59"
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
.suffix=${this.enableSecond ? ":" : ""}
|
||||
class=${this.enableSecond ? "has-suffix" : ""}
|
||||
>
|
||||
</ha-textfield>
|
||||
${this.enableSecond
|
||||
? html`<ha-textfield
|
||||
id="sec"
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
.value=${this._formatValue(this.seconds)}
|
||||
.label=${this.secLabel}
|
||||
@change=${this._valueChanged}
|
||||
@focusin=${this._onFocus}
|
||||
name="seconds"
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
maxlength="2"
|
||||
max="59"
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
.suffix=${this.enableMillisecond ? ":" : ""}
|
||||
class=${this.enableMillisecond ? "has-suffix" : ""}
|
||||
>
|
||||
</ha-textfield>`
|
||||
: ""}
|
||||
${this.enableMillisecond
|
||||
? html`<ha-textfield
|
||||
id="millisec"
|
||||
type="number"
|
||||
.value=${this._formatValue(this.milliseconds, 3)}
|
||||
.label=${this.millisecLabel}
|
||||
@change=${this._valueChanged}
|
||||
@focusin=${this._onFocus}
|
||||
name="milliseconds"
|
||||
no-spinner
|
||||
.required=${this.required}
|
||||
.autoValidate=${this.autoValidate}
|
||||
maxlength="3"
|
||||
max="999"
|
||||
min="0"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
</ha-textfield>`
|
||||
: ""}
|
||||
${this.format === 24
|
||||
? ""
|
||||
: html`<ha-select
|
||||
@@ -263,17 +249,13 @@ export class HaBaseTimeInput extends LitElement {
|
||||
<mwc-list-item value="AM">AM</mwc-list-item>
|
||||
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||
</ha-select>`}
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
</div>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private _clearValue(): void {
|
||||
fireEvent(this, "value-changed");
|
||||
}
|
||||
|
||||
private _valueChanged(ev: InputEvent) {
|
||||
const textField = ev.currentTarget as HaTextField;
|
||||
this[textField.name] =
|
||||
@@ -320,25 +302,18 @@ export class HaBaseTimeInput extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host([clearable]) {
|
||||
position: relative;
|
||||
}
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.time-input-wrap-wrap {
|
||||
display: flex;
|
||||
}
|
||||
.time-input-wrap {
|
||||
display: flex;
|
||||
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
direction: ltr;
|
||||
padding-right: 3px;
|
||||
}
|
||||
ha-textfield {
|
||||
width: 55px;
|
||||
width: 40px;
|
||||
text-align: center;
|
||||
--mdc-shape-small: 0;
|
||||
--text-field-appearance: none;
|
||||
@@ -360,21 +335,6 @@ export class HaBaseTimeInput extends LitElement {
|
||||
--mdc-shape-small: 0;
|
||||
width: 85px;
|
||||
}
|
||||
:host([clearable]) .mdc-select__anchor {
|
||||
padding-inline-end: var(--select-selected-text-padding-end, 12px);
|
||||
}
|
||||
ha-icon-button {
|
||||
position: relative
|
||||
--mdc-icon-button-size: 36px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--secondary-text-color);
|
||||
direction: var(--direction);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color:var(--mdc-text-field-fill-color, whitesmoke);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
label {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
@@ -196,8 +196,8 @@ export class HaControlNumberButton extends LitElement {
|
||||
--control-number-buttons-background-opacity: 0.2;
|
||||
--control-number-buttons-border-radius: 10px;
|
||||
--mdc-icon-size: 16px;
|
||||
height: var(--feature-height);
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
width: 200px;
|
||||
color: var(--primary-text-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
font-style: normal;
|
||||
|
@@ -295,8 +295,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
icon: null,
|
||||
level: null,
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -311,8 +309,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
icon: "mdi:plus",
|
||||
level: null,
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -395,8 +391,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
icon: null,
|
||||
level: null,
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
] as FloorRegistryEntry[];
|
||||
} else {
|
||||
@@ -411,8 +405,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
|
||||
icon: "mdi:plus",
|
||||
level: null,
|
||||
aliases: [],
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
] as FloorRegistryEntry[];
|
||||
}
|
||||
|
@@ -94,8 +94,6 @@ export const computeInitialHaFormData = (
|
||||
data[field.name] = selector.color_temp?.min_mireds ?? 153;
|
||||
} else if (
|
||||
"action" in selector ||
|
||||
"trigger" in selector ||
|
||||
"condition" in selector ||
|
||||
"media" in selector ||
|
||||
"target" in selector
|
||||
) {
|
||||
|
@@ -7,10 +7,9 @@ import { mdiRestore } from "@mdi/js";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { conditionalClamp } from "../common/number/clamp";
|
||||
|
||||
type GridSizeValue = {
|
||||
rows?: number | "auto";
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
};
|
||||
|
||||
@@ -20,7 +19,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public value?: GridSizeValue;
|
||||
|
||||
@property({ attribute: false }) public rows = 8;
|
||||
@property({ attribute: false }) public rows = 6;
|
||||
|
||||
@property({ attribute: false }) public columns = 4;
|
||||
|
||||
@@ -43,20 +42,6 @@ export class HaGridSizeEditor extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const disabledColumns =
|
||||
this.columnMin !== undefined && this.columnMin === this.columnMax;
|
||||
const disabledRows =
|
||||
this.rowMin !== undefined && this.rowMin === this.rowMax;
|
||||
|
||||
const autoHeight = this._localValue?.rows === "auto";
|
||||
|
||||
const rowMin = this.rowMin ?? 1;
|
||||
const rowMax = this.rowMax ?? this.rows;
|
||||
const columnMin = this.columnMin ?? 1;
|
||||
const columnMax = this.columnMax ?? this.columns;
|
||||
const rowValue = autoHeight ? rowMin : this._localValue?.rows;
|
||||
const columnValue = this._localValue?.columns;
|
||||
|
||||
return html`
|
||||
<div class="grid">
|
||||
<ha-grid-layout-slider
|
||||
@@ -64,28 +49,25 @@ export class HaGridSizeEditor extends LitElement {
|
||||
"ui.components.grid-size-picker.columns"
|
||||
)}
|
||||
id="columns"
|
||||
.min=${columnMin}
|
||||
.max=${columnMax}
|
||||
.min=${this.columnMin ?? 1}
|
||||
.max=${this.columnMax ?? this.columns}
|
||||
.range=${this.columns}
|
||||
.value=${columnValue}
|
||||
.value=${this.value?.columns}
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
.disabled=${disabledColumns}
|
||||
></ha-grid-layout-slider>
|
||||
|
||||
<ha-grid-layout-slider
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.grid-size-picker.rows"
|
||||
)}
|
||||
id="rows"
|
||||
.min=${rowMin}
|
||||
.max=${rowMax}
|
||||
.min=${this.rowMin ?? 1}
|
||||
.max=${this.rowMax ?? this.rows}
|
||||
.range=${this.rows}
|
||||
vertical
|
||||
.value=${rowValue}
|
||||
.value=${this.value?.rows}
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
.disabled=${disabledRows}
|
||||
></ha-grid-layout-slider>
|
||||
${!this.isDefault
|
||||
? html`
|
||||
@@ -108,8 +90,8 @@ export class HaGridSizeEditor extends LitElement {
|
||||
style=${styleMap({
|
||||
"--total-rows": this.rows,
|
||||
"--total-columns": this.columns,
|
||||
"--rows": rowValue,
|
||||
"--columns": columnValue,
|
||||
"--rows": this._localValue?.rows,
|
||||
"--columns": this._localValue?.columns,
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
@@ -118,11 +100,17 @@ export class HaGridSizeEditor extends LitElement {
|
||||
.map((_, index) => {
|
||||
const row = Math.floor(index / this.columns) + 1;
|
||||
const column = (index % this.columns) + 1;
|
||||
const disabled =
|
||||
(this.rowMin !== undefined && row < this.rowMin) ||
|
||||
(this.rowMax !== undefined && row > this.rowMax) ||
|
||||
(this.columnMin !== undefined && column < this.columnMin) ||
|
||||
(this.columnMax !== undefined && column > this.columnMax);
|
||||
return html`
|
||||
<div
|
||||
class="cell"
|
||||
data-row=${row}
|
||||
data-column=${column}
|
||||
?disabled=${disabled}
|
||||
@click=${this._cellClick}
|
||||
></div>
|
||||
`;
|
||||
@@ -138,16 +126,11 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
_cellClick(ev) {
|
||||
const cell = ev.currentTarget as HTMLElement;
|
||||
if (cell.getAttribute("disabled") !== null) return;
|
||||
const rows = Number(cell.getAttribute("data-row"));
|
||||
const columns = Number(cell.getAttribute("data-column"));
|
||||
const clampedRow = conditionalClamp(rows, this.rowMin, this.rowMax);
|
||||
const clampedColumn = conditionalClamp(
|
||||
columns,
|
||||
this.columnMin,
|
||||
this.columnMax
|
||||
);
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { rows: clampedRow, columns: clampedColumn },
|
||||
value: { rows, columns },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,7 +188,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
.preview {
|
||||
position: relative;
|
||||
grid-area: preview;
|
||||
aspect-ratio: 1 / 1.2;
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
.preview > div {
|
||||
position: absolute;
|
||||
@@ -226,6 +209,10 @@ export class HaGridSizeEditor extends LitElement {
|
||||
opacity: 0.2;
|
||||
cursor: pointer;
|
||||
}
|
||||
.preview .cell[disabled] {
|
||||
opacity: 0.05;
|
||||
cursor: initial;
|
||||
}
|
||||
.selected {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@@ -303,8 +303,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
icon: null,
|
||||
color: null,
|
||||
description: null,
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -319,8 +317,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
icon: "mdi:plus",
|
||||
color: null,
|
||||
description: null,
|
||||
created_at: 0,
|
||||
modified_at: 0,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import { MdMenuItem } from "@material/web/menu/menu-item";
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-menu-item")
|
||||
export class HaMenuItem extends MdMenuItem {
|
||||
@property({ attribute: false }) clickAction?: (item?: HTMLElement) => void;
|
||||
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
|
@@ -1,30 +1,9 @@
|
||||
import { MdMenu } from "@material/web/menu/menu";
|
||||
import type { CloseMenuEvent } from "@material/web/menu/menu";
|
||||
import {
|
||||
CloseReason,
|
||||
KeydownCloseKey,
|
||||
} from "@material/web/menu/internal/controllers/shared";
|
||||
import { css } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import type { HaMenuItem } from "./ha-menu-item";
|
||||
|
||||
@customElement("ha-menu")
|
||||
export class HaMenu extends MdMenu {
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.addEventListener("close-menu", this._handleCloseMenu);
|
||||
}
|
||||
|
||||
private _handleCloseMenu(ev: CloseMenuEvent) {
|
||||
if (
|
||||
ev.detail.reason.kind === CloseReason.KEYDOWN &&
|
||||
ev.detail.reason.key === KeydownCloseKey.ESCAPE
|
||||
) {
|
||||
return;
|
||||
}
|
||||
(ev.detail.initiator as HaMenuItem).clickAction?.(ev.detail.initiator);
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
...super.styles,
|
||||
css`
|
||||
@@ -39,8 +18,4 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-menu": HaMenu;
|
||||
}
|
||||
|
||||
interface HTMLElementEventMap {
|
||||
"close-menu": CloseMenuEvent;
|
||||
}
|
||||
}
|
||||
|
@@ -1,92 +1,72 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiCamera } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import type QrScanner from "qr-scanner";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { addExternalBarCodeListener } from "../external_app/external_app_entrypoint";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-alert";
|
||||
import "./ha-button-menu";
|
||||
import "./ha-list-item";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
@customElement("ha-qr-scanner")
|
||||
class HaQrScanner extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||
|
||||
@property() public description?: string;
|
||||
|
||||
@property({ attribute: "alternative_option_label" })
|
||||
public alternativeOptionLabel?: string;
|
||||
|
||||
@property() public error?: string;
|
||||
|
||||
@state() private _cameras?: QrScanner.Camera[];
|
||||
|
||||
@state() private _manual = false;
|
||||
@state() private _error?: string;
|
||||
|
||||
private _qrScanner?: QrScanner;
|
||||
|
||||
private _qrNotFoundCount = 0;
|
||||
|
||||
private _removeListener?: UnsubscribeFunc;
|
||||
@query("video", true) private _video!: HTMLVideoElement;
|
||||
|
||||
@query("video", true) private _video?: HTMLVideoElement;
|
||||
|
||||
@query("#canvas-container", true) private _canvasContainer?: HTMLDivElement;
|
||||
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
|
||||
|
||||
@query("ha-textfield") private _manualInput?: HaTextField;
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._qrNotFoundCount = 0;
|
||||
if (this._nativeBarcodeScanner) {
|
||||
this._closeExternalScanner();
|
||||
}
|
||||
if (this._qrScanner) {
|
||||
this._qrScanner.stop();
|
||||
this._qrScanner.destroy();
|
||||
this._qrScanner = undefined;
|
||||
}
|
||||
while (this._canvasContainer?.lastChild) {
|
||||
while (this._canvasContainer.lastChild) {
|
||||
this._canvasContainer.removeChild(this._canvasContainer.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
if (this.hasUpdated && navigator.mediaDevices) {
|
||||
this._loadQrScanner();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._loadQrScanner();
|
||||
if (navigator.mediaDevices) {
|
||||
this._loadQrScanner();
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("error") && this.error) {
|
||||
alert(`error: ${this.error}`);
|
||||
this._notifyExternalScanner(this.error);
|
||||
if (changedProps.has("_error") && this._error) {
|
||||
fireEvent(this, "qr-code-error", { message: this._error });
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (this._nativeBarcodeScanner && !this._manual) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`${this.error
|
||||
? html`<ha-alert alert-type="error">${this.error}</ha-alert>`
|
||||
protected render(): TemplateResult {
|
||||
return html`${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
${navigator.mediaDevices && !this._manual
|
||||
${navigator.mediaDevices
|
||||
? html`<video></video>
|
||||
<div id="canvas-container">
|
||||
${this._cameras && this._cameras.length > 1
|
||||
@@ -100,26 +80,21 @@ class HaQrScanner extends LitElement {
|
||||
></ha-icon-button>
|
||||
${this._cameras!.map(
|
||||
(camera) => html`
|
||||
<ha-list-item
|
||||
<mwc-list-item
|
||||
.value=${camera.id}
|
||||
@click=${this._cameraChanged}
|
||||
>${camera.label}</mwc-list-item
|
||||
>
|
||||
${camera.label}
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>`
|
||||
: nothing}
|
||||
: ""}
|
||||
</div>`
|
||||
: html`${this._manual
|
||||
? nothing
|
||||
: html`<ha-alert alert-type="warning">
|
||||
${!window.isSecureContext
|
||||
? this.localize(
|
||||
"ui.components.qr-scanner.only_https_supported"
|
||||
)
|
||||
: this.localize("ui.components.qr-scanner.not_supported")}
|
||||
</ha-alert>`}
|
||||
: html`<ha-alert alert-type="warning">
|
||||
${!window.isSecureContext
|
||||
? this.localize("ui.components.qr-scanner.only_https_supported")
|
||||
: this.localize("ui.components.qr-scanner.not_supported")}
|
||||
</ha-alert>
|
||||
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
|
||||
<div class="row">
|
||||
<ha-textfield
|
||||
@@ -127,44 +102,33 @@ class HaQrScanner extends LitElement {
|
||||
@keyup=${this._manualKeyup}
|
||||
@paste=${this._manualPaste}
|
||||
></ha-textfield>
|
||||
<mwc-button @click=${this._manualSubmit}>
|
||||
${this.localize("ui.common.submit")}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._manualSubmit}
|
||||
>${this.localize("ui.common.submit")}</mwc-button
|
||||
>
|
||||
</div>`}`;
|
||||
}
|
||||
|
||||
private get _nativeBarcodeScanner(): boolean {
|
||||
return Boolean(this.hass.auth.external?.config.hasBarCodeScanner);
|
||||
}
|
||||
|
||||
private async _loadQrScanner() {
|
||||
if (this._nativeBarcodeScanner) {
|
||||
this._openExternalScanner();
|
||||
return;
|
||||
}
|
||||
if (!navigator.mediaDevices) {
|
||||
return;
|
||||
}
|
||||
const QrScanner = (await import("qr-scanner")).default;
|
||||
if (!(await QrScanner.hasCamera())) {
|
||||
this._reportError("No camera found");
|
||||
this._error = "No camera found";
|
||||
return;
|
||||
}
|
||||
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
|
||||
this._listCameras(QrScanner);
|
||||
this._qrScanner = new QrScanner(
|
||||
this._video!,
|
||||
this._video,
|
||||
this._qrCodeScanned,
|
||||
this._qrCodeError
|
||||
);
|
||||
// @ts-ignore
|
||||
const canvas = this._qrScanner.$canvas;
|
||||
this._canvasContainer!.appendChild(canvas);
|
||||
this._canvasContainer.appendChild(canvas);
|
||||
canvas.style.display = "block";
|
||||
try {
|
||||
await this._qrScanner.start();
|
||||
} catch (err: any) {
|
||||
this._reportError(err);
|
||||
this._error = err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,16 +140,16 @@ class HaQrScanner extends LitElement {
|
||||
if (err === "No QR code found") {
|
||||
this._qrNotFoundCount++;
|
||||
if (this._qrNotFoundCount === 250) {
|
||||
this._reportError(err);
|
||||
this._error = err;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this._reportError(err.message || err);
|
||||
this._error = err.message || err;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err);
|
||||
};
|
||||
|
||||
private _qrCodeScanned = (qrCodeString: string): void => {
|
||||
private _qrCodeScanned = async (qrCodeString: string): Promise<void> => {
|
||||
this._qrNotFoundCount = 0;
|
||||
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
||||
};
|
||||
@@ -211,62 +175,6 @@ class HaQrScanner extends LitElement {
|
||||
this._qrScanner?.setCamera((ev.target as any).value);
|
||||
}
|
||||
|
||||
private _openExternalScanner() {
|
||||
this._removeListener = addExternalBarCodeListener((msg) => {
|
||||
if (msg.command === "bar_code/scan_result") {
|
||||
if (msg.payload.format !== "qr_code") {
|
||||
this._notifyExternalScanner(
|
||||
`Wrong barcode scanned! ${msg.payload.format}: ${msg.payload.rawValue}, we need a QR code.`
|
||||
);
|
||||
} else {
|
||||
this._qrCodeScanned(msg.payload.rawValue);
|
||||
}
|
||||
} else if (msg.command === "bar_code/aborted") {
|
||||
this._closeExternalScanner();
|
||||
if (msg.payload.reason === "canceled") {
|
||||
fireEvent(this, "qr-code-closed");
|
||||
} else {
|
||||
this._manual = true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "bar_code/scan",
|
||||
payload: {
|
||||
title: this.title || "Scan QR code",
|
||||
description: this.description || "Scan a barcode.",
|
||||
alternative_option_label:
|
||||
this.alternativeOptionLabel || "Click to manually enter the barcode",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _closeExternalScanner() {
|
||||
this._removeListener?.();
|
||||
this._removeListener = undefined;
|
||||
this.hass.auth.external!.fireMessage({
|
||||
type: "bar_code/close",
|
||||
});
|
||||
}
|
||||
|
||||
private _notifyExternalScanner(message: string) {
|
||||
if (!this.hass.auth.external) {
|
||||
return;
|
||||
}
|
||||
this.hass.auth.external.fireMessage({
|
||||
type: "bar_code/notify",
|
||||
payload: {
|
||||
message,
|
||||
},
|
||||
});
|
||||
this.error = undefined;
|
||||
}
|
||||
|
||||
private _reportError(message: string) {
|
||||
fireEvent(this, "qr-code-error", { message });
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
canvas {
|
||||
width: 100%;
|
||||
@@ -302,7 +210,6 @@ declare global {
|
||||
interface HASSDomEvents {
|
||||
"qr-code-scanned": { value: string };
|
||||
"qr-code-error": { message: string };
|
||||
"qr-code-closed": undefined;
|
||||
}
|
||||
|
||||
interface HTMLElementTagNameMap {
|
||||
|
@@ -6,12 +6,12 @@ import {
|
||||
mdiSofa,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
@@ -20,7 +20,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { Blueprints, fetchBlueprints } from "../data/blueprint";
|
||||
import { ConfigEntry, getConfigEntries } from "../data/config_entries";
|
||||
import { ItemType, RelatedResult, findRelated } from "../data/search";
|
||||
import { findRelated, ItemType, RelatedResult } from "../data/search";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
@@ -109,26 +109,6 @@ export class HaRelatedItems extends LitElement {
|
||||
)
|
||||
);
|
||||
|
||||
private _getConfigEntries = memoizeOne(
|
||||
(
|
||||
relatedConfigEntries: string[] | undefined,
|
||||
entries: ConfigEntry[] | undefined
|
||||
) => {
|
||||
const configEntries =
|
||||
relatedConfigEntries && entries
|
||||
? relatedConfigEntries.map((entryId) =>
|
||||
entries!.find((configEntry) => configEntry.entry_id === entryId)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
const configEntryDomains = new Set(
|
||||
configEntries?.map((entry) => entry?.domain)
|
||||
);
|
||||
|
||||
return { configEntries, configEntryDomains };
|
||||
}
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this._related) {
|
||||
return nothing;
|
||||
@@ -148,25 +128,22 @@ export class HaRelatedItems extends LitElement {
|
||||
</mwc-list>
|
||||
`;
|
||||
}
|
||||
|
||||
const { configEntries, configEntryDomains } = this._getConfigEntries(
|
||||
this._related.config_entry,
|
||||
this._entries
|
||||
);
|
||||
|
||||
return html`
|
||||
${configEntries || this._related.integration
|
||||
${this._related.config_entry && this._entries
|
||||
? html`<h3>
|
||||
${this.hass.localize("ui.components.related-items.integration")}
|
||||
</h3>
|
||||
<mwc-list
|
||||
>${configEntries?.map((entry) => {
|
||||
>${this._related.config_entry.map((relatedConfigEntryId) => {
|
||||
const entry: ConfigEntry | undefined = this._entries!.find(
|
||||
(configEntry) => configEntry.entry_id === relatedConfigEntryId
|
||||
);
|
||||
if (!entry) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<a
|
||||
href=${`/config/integrations/integration/${entry.domain}#config_entry=${entry.entry_id}`}
|
||||
href=${`/config/integrations/integration/${entry.domain}#config_entry=${relatedConfigEntryId}`}
|
||||
@click=${this._navigateAwayClose}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
@@ -187,34 +164,8 @@ export class HaRelatedItems extends LitElement {
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`;
|
||||
})}
|
||||
${this._related.integration
|
||||
?.filter((integration) => !configEntryDomains.has(integration))
|
||||
.map(
|
||||
(integration) =>
|
||||
html`<a
|
||||
href=${`/config/integrations/integration/${integration}`}
|
||||
@click=${this._navigateAwayClose}
|
||||
>
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<img
|
||||
.src=${brandsUrl({
|
||||
domain: integration,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
alt=${integration}
|
||||
slot="graphic"
|
||||
/>
|
||||
${this.hass.localize(`component.${integration}.title`)}
|
||||
<ha-icon-next slot="meta"></ha-icon-next>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
)}
|
||||
</mwc-list>`
|
||||
})}</mwc-list
|
||||
>`
|
||||
: nothing}
|
||||
${this._related.device
|
||||
? html`<h3>
|
||||
|
@@ -35,6 +35,10 @@ export class HaActionSelector extends LitElement {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
:host([disabled]) ha-automation-action {
|
||||
opacity: var(--light-disabled-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
|
@@ -11,7 +11,6 @@ import {
|
||||
fetchEntitySourcesWithCache,
|
||||
} from "../../data/entity_sources";
|
||||
import type { AreaSelector } from "../../data/selector";
|
||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
||||
import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
@@ -38,8 +37,6 @@ export class HaAreaSelector extends LitElement {
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _configEntries?: ConfigEntry[];
|
||||
|
||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||
|
||||
private _hasIntegration(selector: AreaSelector) {
|
||||
@@ -75,12 +72,6 @@ export class HaAreaSelector extends LitElement {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
}
|
||||
if (!this._configEntries && this._hasIntegration(this.selector)) {
|
||||
this._configEntries = [];
|
||||
getConfigEntries(this.hass).then((entries) => {
|
||||
this._configEntries = entries;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -145,9 +136,7 @@ export class HaAreaSelector extends LitElement {
|
||||
const deviceIntegrations = this._entitySources
|
||||
? this._deviceIntegrationLookup(
|
||||
this._entitySources,
|
||||
Object.values(this.hass.entities),
|
||||
Object.values(this.hass.devices),
|
||||
this._configEntries
|
||||
Object.values(this.hass.entities)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
|
@@ -12,8 +12,6 @@ export class HaBooleanSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public value = false;
|
||||
|
||||
@property() public placeholder?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
@@ -24,7 +22,7 @@ export class HaBooleanSelector extends LitElement {
|
||||
return html`
|
||||
<ha-formfield alignEnd spaceBetween .label=${this.label}>
|
||||
<ha-switch
|
||||
.checked=${this.value ?? this.placeholder === true}
|
||||
.checked=${this.value}
|
||||
@change=${this._handleChange}
|
||||
.disabled=${this.disabled}
|
||||
></ha-switch>
|
||||
|
@@ -35,6 +35,10 @@ export class HaConditionSelector extends LitElement {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
:host([disabled]) ha-automation-condition {
|
||||
opacity: var(--light-disabled-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
|
@@ -11,7 +11,6 @@ import {
|
||||
fetchEntitySourcesWithCache,
|
||||
} from "../../data/entity_sources";
|
||||
import type { DeviceSelector } from "../../data/selector";
|
||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
||||
import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
@@ -28,8 +27,6 @@ export class HaDeviceSelector extends LitElement {
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _configEntries?: ConfigEntry[];
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property() public label?: string;
|
||||
@@ -78,12 +75,6 @@ export class HaDeviceSelector extends LitElement {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
}
|
||||
if (!this._configEntries && this._hasIntegration(this.selector)) {
|
||||
this._configEntries = [];
|
||||
getConfigEntries(this.hass).then((entries) => {
|
||||
this._configEntries = entries;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -132,9 +123,7 @@ export class HaDeviceSelector extends LitElement {
|
||||
const deviceIntegrations = this._entitySources
|
||||
? this._deviceIntegrationLookup(
|
||||
this._entitySources,
|
||||
Object.values(this.hass.entities),
|
||||
Object.values(this.hass.devices),
|
||||
this._configEntries
|
||||
Object.values(this.hass.entities)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
|
@@ -30,7 +30,6 @@ export class HaTimeDuration extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
?enableDay=${this.selector.duration?.enable_day}
|
||||
?enableMillisecond=${this.selector.duration?.enable_millisecond}
|
||||
></ha-duration-input>
|
||||
`;
|
||||
}
|
||||
|
@@ -11,7 +11,6 @@ import {
|
||||
fetchEntitySourcesWithCache,
|
||||
} from "../../data/entity_sources";
|
||||
import type { FloorSelector } from "../../data/selector";
|
||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
||||
import {
|
||||
filterSelectorDevices,
|
||||
filterSelectorEntities,
|
||||
@@ -38,8 +37,6 @@ export class HaFloorSelector extends LitElement {
|
||||
|
||||
@state() private _entitySources?: EntitySources;
|
||||
|
||||
@state() private _configEntries?: ConfigEntry[];
|
||||
|
||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||
|
||||
private _hasIntegration(selector: FloorSelector) {
|
||||
@@ -75,12 +72,6 @@ export class HaFloorSelector extends LitElement {
|
||||
this._entitySources = sources;
|
||||
});
|
||||
}
|
||||
if (!this._configEntries && this._hasIntegration(this.selector)) {
|
||||
this._configEntries = [];
|
||||
getConfigEntries(this.hass).then((entries) => {
|
||||
this._configEntries = entries;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -145,9 +136,7 @@ export class HaFloorSelector extends LitElement {
|
||||
const deviceIntegrations = this._entitySources
|
||||
? this._deviceIntegrationLookup(
|
||||
this._entitySources,
|
||||
Object.values(this.hass.entities),
|
||||
Object.values(this.hass.devices),
|
||||
this._configEntries
|
||||
Object.values(this.hass.entities)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
|
@@ -45,14 +45,7 @@ export class HaImageSelector extends LitElement {
|
||||
return html`
|
||||
<div>
|
||||
<label>
|
||||
${this.hass.localize(
|
||||
"ui.components.selectors.image.select_image_with_label",
|
||||
{
|
||||
label:
|
||||
this.label ||
|
||||
this.hass.localize("ui.components.selectors.image.image"),
|
||||
}
|
||||
)}
|
||||
${this.hass.localize("ui.components.selectors.image.select_image")}
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize("ui.components.selectors.image.upload")}
|
||||
>
|
||||
|
@@ -57,10 +57,6 @@ const SELECTOR_SCHEMAS = {
|
||||
name: "enable_day",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
{
|
||||
name: "enable_millisecond",
|
||||
selector: { boolean: {} },
|
||||
},
|
||||
] as const,
|
||||
entity: [
|
||||
{
|
||||
|
@@ -1,18 +1,9 @@
|
||||
import { css, html, nothing, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import "../ha-code-editor";
|
||||
import "../ha-input-helper-text";
|
||||
import "../ha-alert";
|
||||
|
||||
const WARNING_STRINGS = [
|
||||
"template:",
|
||||
"sensor:",
|
||||
"state:",
|
||||
"platform: template",
|
||||
];
|
||||
|
||||
@customElement("ha-selector-template")
|
||||
export class HaTemplateSelector extends LitElement {
|
||||
@@ -28,33 +19,9 @@ export class HaTemplateSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
|
||||
@state() private warn: string | undefined = undefined;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.warn
|
||||
? html`<ha-alert alert-type="warning"
|
||||
>${this.hass.localize(
|
||||
"ui.components.selectors.template.yaml_warning",
|
||||
{ string: this.warn }
|
||||
)}
|
||||
<br />
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
"/docs/configuration/templating/"
|
||||
)}
|
||||
>${this.hass.localize(
|
||||
"ui.components.selectors.template.learn_more"
|
||||
)}</a
|
||||
></ha-alert
|
||||
>`
|
||||
: nothing}
|
||||
${this.label
|
||||
? html`<p>${this.label}${this.required ? "*" : ""}</p>`
|
||||
: nothing}
|
||||
${this.label ? html`<p>${this.label}${this.required ? "*" : ""}</p>` : ""}
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
.hass=${this.hass}
|
||||
@@ -69,7 +36,7 @@ export class HaTemplateSelector extends LitElement {
|
||||
></ha-code-editor>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: nothing}
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -78,7 +45,6 @@ export class HaTemplateSelector extends LitElement {
|
||||
if (this.value === value) {
|
||||
return;
|
||||
}
|
||||
this.warn = WARNING_STRINGS.find((str) => value.includes(str));
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,6 @@ export class HaTimeSelector extends LitElement {
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
clearable
|
||||
.helper=${this.helper}
|
||||
.label=${this.label}
|
||||
enable-second
|
||||
|
@@ -35,6 +35,10 @@ export class HaTriggerSelector extends LitElement {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
:host([disabled]) ha-automation-trigger {
|
||||
opacity: var(--light-disabled-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user