mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-16 04:39:28 +00:00
Compare commits
1 Commits
20240719.0
...
dashboard_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ebe5207b6e |
@@ -37,9 +37,3 @@ not dead
|
|||||||
unreleased versions
|
unreleased versions
|
||||||
last 7 years
|
last 7 years
|
||||||
> 0.05% and supports websockets
|
> 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
|
|
||||||
|
@@ -8,7 +8,6 @@
|
|||||||
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
|
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
|
||||||
"postStartCommand": "script/bootstrap",
|
"postStartCommand": "script/bootstrap",
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
"DEV_CONTAINER": "1",
|
|
||||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
ref: dev
|
ref: dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
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
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@v4.3.4
|
uses: actions/upload-artifact@v4.3.3
|
||||||
with:
|
with:
|
||||||
name: frontend-bundle-stats
|
name: frontend-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
@@ -102,7 +102,7 @@ jobs:
|
|||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v4.1.7
|
uses: actions/checkout@v4.1.7
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@v4.3.4
|
uses: actions/upload-artifact@v4.3.3
|
||||||
with:
|
with:
|
||||||
name: supervisor-bundle-stats
|
name: supervisor-bundle-stats
|
||||||
path: build/stats/*.json
|
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
|
ref: dev
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -63,7 +63,7 @@ jobs:
|
|||||||
ref: master
|
ref: master
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
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
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
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
|
uses: actions/checkout@v4.1.7
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
6
.github/workflows/nightly.yaml
vendored
6
.github/workflows/nightly.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -57,14 +57,14 @@ jobs:
|
|||||||
run: tar -czvf translations.tar.gz translations
|
run: tar -czvf translations.tar.gz translations
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4.3.4
|
uses: actions/upload-artifact@v4.3.3
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels
|
||||||
path: dist/home_assistant_frontend*.whl
|
path: dist/home_assistant_frontend*.whl
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload translations
|
- name: Upload translations
|
||||||
uses: actions/upload-artifact@v4.3.4
|
uses: actions/upload-artifact@v4.3.3
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
|
4
.github/workflows/release.yaml
vendored
4
.github/workflows/release.yaml
vendored
@@ -34,7 +34,7 @@ jobs:
|
|||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4.0.3
|
uses: actions/setup-node@v4.0.2
|
||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@2024.07.1
|
uses: home-assistant/wheels@2024.01.0
|
||||||
with:
|
with:
|
||||||
abi: cp311
|
abi: cp311
|
||||||
tag: musllinux_1_2
|
tag: musllinux_1_2
|
||||||
|
@@ -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}`;
|
|
||||||
}
|
|
@@ -47,7 +47,7 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
|||||||
|
|
||||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||||
__DEV__: !isProdBuild,
|
__DEV__: !isProdBuild,
|
||||||
__BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"),
|
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||||
__VERSION__: JSON.stringify(env.version()),
|
__VERSION__: JSON.stringify(env.version()),
|
||||||
__DEMO__: false,
|
__DEMO__: false,
|
||||||
__SUPERVISOR__: false,
|
__SUPERVISOR__: false,
|
||||||
@@ -79,12 +79,7 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
|
|||||||
sourceMap: !isTestBuild,
|
sourceMap: !isTestBuild,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports.babelOptions = ({
|
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
||||||
latestBuild,
|
|
||||||
isProdBuild,
|
|
||||||
isTestBuild,
|
|
||||||
sw,
|
|
||||||
}) => ({
|
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
compact: false,
|
compact: false,
|
||||||
assumptions: {
|
assumptions: {
|
||||||
@@ -92,7 +87,7 @@ module.exports.babelOptions = ({
|
|||||||
setPublicClassFields: true,
|
setPublicClassFields: true,
|
||||||
setSpreadProperties: true,
|
setSpreadProperties: true,
|
||||||
},
|
},
|
||||||
browserslistEnv: latestBuild ? "modern" : `legacy${sw ? "-sw" : ""}`,
|
browserslistEnv: latestBuild ? "modern" : "legacy",
|
||||||
presets: [
|
presets: [
|
||||||
[
|
[
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
@@ -220,13 +215,7 @@ module.exports.config = {
|
|||||||
return {
|
return {
|
||||||
name: "frontend" + nameSuffix(latestBuild),
|
name: "frontend" + nameSuffix(latestBuild),
|
||||||
entry: {
|
entry: {
|
||||||
"service-worker":
|
service_worker: "./src/entrypoints/service_worker.ts",
|
||||||
!env.useRollup() && !latestBuild
|
|
||||||
? {
|
|
||||||
import: "./src/entrypoints/service-worker.ts",
|
|
||||||
layer: "sw",
|
|
||||||
}
|
|
||||||
: "./src/entrypoints/service-worker.ts",
|
|
||||||
app: "./src/entrypoints/app.ts",
|
app: "./src/entrypoints/app.ts",
|
||||||
authorize: "./src/entrypoints/authorize.ts",
|
authorize: "./src/entrypoints/authorize.ts",
|
||||||
onboarding: "./src/entrypoints/onboarding.ts",
|
onboarding: "./src/entrypoints/onboarding.ts",
|
||||||
|
@@ -32,7 +32,4 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
return version[1];
|
return version[1];
|
||||||
},
|
},
|
||||||
isDevContainer() {
|
|
||||||
return process.env.DEV_CONTAINER === "1";
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
@@ -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 gulp from "gulp";
|
||||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
import path from "path";
|
||||||
import { join, relative } from "node:path";
|
import sourceMapUrl from "source-map-url";
|
||||||
import { injectManifest } from "workbox-build";
|
import workboxBuild from "workbox-build";
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
const SW_MAP = {
|
const swDest = path.resolve(paths.app_output_root, "service_worker.js");
|
||||||
[paths.app_output_latest]: "modern",
|
|
||||||
[paths.app_output_es5]: "legacy",
|
|
||||||
};
|
|
||||||
|
|
||||||
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');
|
console.debug('Service worker disabled in development');
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener('install', (event) => {
|
||||||
@@ -21,61 +22,72 @@ self.addEventListener('install', (event) => {
|
|||||||
// removing any prod service worker the dev might have running
|
// removing any prod service worker the dev might have running
|
||||||
self.skipWaiting();
|
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", () =>
|
gulp.task("gen-service-worker-app-prod", async () => {
|
||||||
Promise.all(
|
// Read bundled source file
|
||||||
Object.entries(SW_MAP).map(async ([outPath, build]) => {
|
const bundleManifestLatest = fs.readJsonSync(
|
||||||
const manifest = JSON.parse(
|
path.resolve(paths.app_output_latest, "manifest.json")
|
||||||
await readFile(join(outPath, "manifest.json"), "utf-8")
|
);
|
||||||
);
|
let serviceWorkerContent = fs.readFileSync(
|
||||||
const swSrc = join(paths.app_output_root, manifest["service-worker.js"]);
|
paths.app_output_root + bundleManifestLatest["service_worker.js"],
|
||||||
const buildDir = relative(paths.app_output_root, outPath);
|
"utf-8"
|
||||||
const { warnings } = await injectManifest({
|
);
|
||||||
swSrc,
|
|
||||||
swDest: join(paths.app_output_root, `sw-${build}.js`),
|
// Delete old file from frontend_latest so manifest won't pick it up
|
||||||
injectionPoint: "__WB_MANIFEST__",
|
fs.removeSync(
|
||||||
// Files that mach this pattern will be considered unique and skip revision check
|
paths.app_output_root + bundleManifestLatest["service_worker.js"]
|
||||||
// ignore JS files + translation files
|
);
|
||||||
dontCacheBustURLsMatching: new RegExp(
|
fs.removeSync(
|
||||||
`(?:${buildDir}/.+|static/translations/.+)`
|
paths.app_output_root + bundleManifestLatest["service_worker.js.map"]
|
||||||
),
|
);
|
||||||
globDirectory: paths.app_output_root,
|
|
||||||
globPatterns: [
|
// Remove ES5
|
||||||
`${buildDir}/*.js`,
|
const bundleManifestES5 = fs.readJsonSync(
|
||||||
// Cache all English translations because we catch them as fallback
|
path.resolve(paths.app_output_es5, "manifest.json")
|
||||||
// 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'
|
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]);
|
||||||
"static/translations/**/en-+([a-fv0-9]).json",
|
fs.removeSync(
|
||||||
// Icon shown on splash screen
|
paths.app_output_root + bundleManifestES5["service_worker.js.map"]
|
||||||
"static/icons/favicon-192x192.png",
|
);
|
||||||
"static/icons/favicon.ico",
|
|
||||||
// Common fonts
|
const workboxManifest = await workboxBuild.getManifest({
|
||||||
"static/fonts/roboto/Roboto-Light.woff2",
|
// Files that mach this pattern will be considered unique and skip revision check
|
||||||
"static/fonts/roboto/Roboto-Medium.woff2",
|
// ignore JS files + translation files
|
||||||
"static/fonts/roboto/Roboto-Regular.woff2",
|
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/,
|
||||||
"static/fonts/roboto/Roboto-Bold.woff2",
|
|
||||||
],
|
globDirectory: paths.app_output_root,
|
||||||
globIgnores: [`${buildDir}/service-worker*`],
|
globPatterns: [
|
||||||
});
|
"frontend_latest/*.js",
|
||||||
if (warnings.length > 0) {
|
// Cache all English translations because we catch them as fallback
|
||||||
console.warn(
|
// Using pattern to match hash instead of * to avoid caching en-GB
|
||||||
`Problems while injecting ${build} service worker:\n`,
|
// 'v' added as valid hash letter because in dev we hash with 'dev'
|
||||||
warnings.join("\n")
|
"static/translations/**/en-+([a-fv0-9]).json",
|
||||||
);
|
// Icon shown on splash screen
|
||||||
}
|
"static/icons/favicon-192x192.png",
|
||||||
await deleteAsync(`${swSrc}?(.map)`);
|
"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.
|
// 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
|
// Will be OK for now as long as we don't have anything more complicated
|
||||||
// than a base translation + region.
|
// than a base translation + region.
|
||||||
const masterStream = gulp
|
gulp
|
||||||
.src(`${workDir}/en.json`)
|
.src(`${workDir}/en.json`)
|
||||||
.pipe(new PassThrough({ objectMode: true }));
|
.pipe(new PassThrough({ objectMode: true }))
|
||||||
masterStream.pipe(hashStream, { end: false });
|
.pipe(hashStream, { end: false });
|
||||||
const mergesFinished = [finished(masterStream)];
|
const mergesFinished = [];
|
||||||
for (const translationFile of translationFiles) {
|
for (const translationFile of translationFiles) {
|
||||||
const locale = basename(translationFile, ".json");
|
const locale = basename(translationFile, ".json");
|
||||||
const subtags = locale.split("-");
|
const subtags = locale.split("-");
|
||||||
|
@@ -40,12 +40,8 @@ const runDevServer = async ({
|
|||||||
compiler,
|
compiler,
|
||||||
contentBase,
|
contentBase,
|
||||||
port,
|
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(
|
const server = new WebpackDevServer(
|
||||||
{
|
{
|
||||||
hot: false,
|
hot: false,
|
||||||
|
@@ -63,25 +63,17 @@ const createWebpackConfig = ({
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.m?js$|\.ts$/,
|
test: /\.m?js$|\.ts$/,
|
||||||
use: (info) => ({
|
use: {
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
options: {
|
options: {
|
||||||
...bundle.babelOptions({
|
...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }),
|
||||||
latestBuild,
|
|
||||||
isProdBuild,
|
|
||||||
isTestBuild,
|
|
||||||
sw: info.issuerLayer === "sw",
|
|
||||||
}),
|
|
||||||
cacheDirectory: !isProdBuild,
|
cacheDirectory: !isProdBuild,
|
||||||
cacheCompression: false,
|
cacheCompression: false,
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
fullySpecified: false,
|
fullySpecified: false,
|
||||||
},
|
},
|
||||||
parser: {
|
|
||||||
worker: ["*context.audioWorklet.addModule()", "..."],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
@@ -100,15 +92,11 @@ const createWebpackConfig = ({
|
|||||||
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||||
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
// Disable splitting for web workers and worklets because imports of
|
// Disable splitting for web workers with ESM output
|
||||||
// external chunks are broken for:
|
// Imports of external chunks are broken
|
||||||
// - ESM output: https://github.com/webpack/webpack/issues/17014
|
chunks: latestBuild
|
||||||
// - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543
|
? (chunk) => !chunk.canBeInitial() && !/^.+-worker$/.test(chunk.name)
|
||||||
chunks: (chunk) =>
|
: undefined,
|
||||||
!chunk.canBeInitial() &&
|
|
||||||
!new RegExp(`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`).test(
|
|
||||||
chunk.name
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
@@ -240,7 +228,6 @@ const createWebpackConfig = ({
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
experiments: {
|
experiments: {
|
||||||
layers: true,
|
|
||||||
outputModule: true,
|
outputModule: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -232,5 +232,17 @@ http:
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</hc-layout>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -14,6 +14,12 @@
|
|||||||
--background-color: #41bdf5;
|
--background-color: #41bdf5;
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
|
@@ -11,4 +11,10 @@
|
|||||||
font-size: initial;
|
font-size: initial;
|
||||||
}
|
}
|
||||||
</style>
|
</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>
|
</html>
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import "../../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./layout/hc-connect";
|
import "./layout/hc-connect";
|
||||||
|
|
||||||
import("../../../src/resources/ha-style");
|
import("../../../src/resources/ha-style");
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 30 KiB |
@@ -1,7 +1,7 @@
|
|||||||
import { convertEntities } from "../../../../src/fake_data/entity";
|
import { convertEntities } from "../../../../src/fake_data/entity";
|
||||||
import { DemoConfig } from "../types";
|
import { DemoConfig } from "../types";
|
||||||
|
|
||||||
export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
export const demoEntitiesSections: DemoConfig["entities"] = () =>
|
||||||
convertEntities({
|
convertEntities({
|
||||||
"cover.living_room_garden_shutter": {
|
"cover.living_room_garden_shutter": {
|
||||||
entity_id: "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": {
|
"media_player.living_room_nest_mini": {
|
||||||
entity_id: "media_player.living_room_nest_mini",
|
entity_id: "media_player.living_room_nest_mini",
|
||||||
state: "on",
|
state: "off",
|
||||||
attributes: {
|
attributes: {
|
||||||
device_class: "speaker",
|
device_class: "speaker",
|
||||||
volume_level: 0.18,
|
friendly_name: "Living room Nest Mini",
|
||||||
is_volume_muted: false,
|
supported_features: 152461,
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"cover.kitchen_shutter": {
|
"cover.kitchen_shutter": {
|
||||||
@@ -187,27 +168,8 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
|||||||
state: "on",
|
state: "on",
|
||||||
attributes: {
|
attributes: {
|
||||||
device_class: "speaker",
|
device_class: "speaker",
|
||||||
volume_level: 0.18,
|
friendly_name: "Kitchen Nest Audio",
|
||||||
is_volume_muted: false,
|
supported_features: 152461,
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"binary_sensor.tesla_wall_connector_vehicle_connected": {
|
"binary_sensor.tesla_wall_connector_vehicle_connected": {
|
||||||
@@ -371,28 +333,8 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
|
|||||||
entity_id: "media_player.study_nest_hub",
|
entity_id: "media_player.study_nest_hub",
|
||||||
state: "off",
|
state: "off",
|
||||||
attributes: {
|
attributes: {
|
||||||
device_class: "speaker",
|
friendly_name: "Study Nest Hub",
|
||||||
volume_level: 0.18,
|
supported_features: 152461,
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"sensor.standing_desk_height": {
|
"sensor.standing_desk_height": {
|
||||||
|
@@ -1,25 +1,40 @@
|
|||||||
import { isFrontpageEmbed } from "../../util/is_frontpage";
|
|
||||||
import { DemoConfig } from "../types";
|
import { DemoConfig } from "../types";
|
||||||
|
|
||||||
export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
||||||
title: "Home Assistant Demo",
|
title: "Home Assistant Demo",
|
||||||
views: [
|
views: [
|
||||||
{
|
{
|
||||||
type: "sections",
|
type: "sections",
|
||||||
title: isFrontpageEmbed ? "Home Assistant" : "Demo",
|
title: "Demo",
|
||||||
path: "home",
|
path: "home",
|
||||||
icon: "mdi:home-assistant",
|
icon: "mdi:home-assistant",
|
||||||
sections: [
|
sections: [
|
||||||
...(isFrontpageEmbed
|
{
|
||||||
? []
|
title: "Welcome 👋",
|
||||||
: [
|
cards: [{ type: "custom:ha-demo-card" }],
|
||||||
{
|
},
|
||||||
title: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`,
|
|
||||||
cards: [{ type: "custom:ha-demo-card" }],
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
{
|
{
|
||||||
cards: [
|
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",
|
type: "tile",
|
||||||
entity: "light.floor_lamp",
|
entity: "light.floor_lamp",
|
||||||
@@ -45,17 +60,13 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
detail: 1,
|
detail: 1,
|
||||||
name: "Temperature",
|
name: "Temperature",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: "tile",
|
|
||||||
entity: "cover.living_room_garden_shutter",
|
|
||||||
name: "Blinds",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
entity: "media_player.living_room_nest_mini",
|
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",
|
type: "grid",
|
||||||
@@ -88,9 +99,10 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
entity: "media_player.kitchen_nest_audio",
|
entity: "media_player.kitchen_nest_audio",
|
||||||
|
name: "Nest Audio",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: `👩🍳 ${localize("ui.panel.page-demo.config.sections.titles.kitchen")}`,
|
title: "👩🍳 Kitchen",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -132,7 +144,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
color: "dark-grey",
|
color: "dark-grey",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: `⚡️ ${localize("ui.panel.page-demo.config.sections.titles.energy")}`,
|
title: "⚡️ Energy",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -169,7 +181,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
state_content: ["preset_mode", "current_temperature"],
|
state_content: ["preset_mode", "current_temperature"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: `🌤️ ${localize("ui.panel.page-demo.config.sections.titles.climate")}`,
|
title: "🌤️ Climate",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -187,6 +199,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
entity: "media_player.study_nest_hub",
|
entity: "media_player.study_nest_hub",
|
||||||
|
name: "Nest Hub",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
@@ -196,7 +209,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
icon: "mdi:desk",
|
icon: "mdi:desk",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: `🧑💻 ${localize("ui.panel.page-demo.config.sections.titles.study")}`,
|
title: "🧑💻 Study",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -230,7 +243,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
name: "Illuminance",
|
name: "Illuminance",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: `🌳 ${localize("ui.panel.page-demo.config.sections.titles.outdoor")}`,
|
title: "🌳 Outdoor",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "grid",
|
type: "grid",
|
||||||
@@ -260,7 +273,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
icon: "mdi:home-assistant",
|
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 "./ha-demo";
|
||||||
|
|
||||||
import("../../src/resources/ha-style");
|
import("../../src/resources/ha-style");
|
||||||
|
@@ -93,5 +93,16 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
<%= 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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1,55 +1,5 @@
|
|||||||
import { convertEntities } from "../../../src/fake_data/entity";
|
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 = () =>
|
export const energyEntities = () =>
|
||||||
convertEntities({
|
convertEntities({
|
||||||
"sensor.grid_fossil_fuel_percentage": {
|
"sensor.grid_fossil_fuel_percentage": {
|
||||||
|
@@ -7,25 +7,16 @@ import {
|
|||||||
} from "../configs/demo-configs";
|
} from "../configs/demo-configs";
|
||||||
import "../custom-cards/cast-demo-row";
|
import "../custom-cards/cast-demo-row";
|
||||||
import "../custom-cards/ha-demo-card";
|
import "../custom-cards/ha-demo-card";
|
||||||
import { mapEntities } from "./entities";
|
|
||||||
|
|
||||||
export const mockLovelace = (
|
export const mockLovelace = (
|
||||||
hass: MockHomeAssistant,
|
hass: MockHomeAssistant,
|
||||||
localizePromise: Promise<LocalizeFunc>
|
localizePromise: Promise<LocalizeFunc>
|
||||||
) => {
|
) => {
|
||||||
hass.mockWS("lovelace/config", ({ url_path }) => {
|
hass.mockWS("lovelace/config", () =>
|
||||||
if (url_path === "map") {
|
Promise.all([selectedDemoConfig, localizePromise]).then(
|
||||||
hass.addEntities(mapEntities());
|
|
||||||
return {
|
|
||||||
strategy: {
|
|
||||||
type: "map",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return Promise.all([selectedDemoConfig, localizePromise]).then(
|
|
||||||
([config, localize]) => config.lovelace(localize)
|
([config, localize]) => config.lovelace(localize)
|
||||||
);
|
)
|
||||||
});
|
);
|
||||||
|
|
||||||
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
||||||
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
|
hass.mockWS("lovelace/resources", () => Promise.resolve([]));
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
export const isFrontpageEmbed = document.location.search === "?frontpage";
|
|
@@ -3,16 +3,13 @@ title: When to use remove, delete, add and create
|
|||||||
subtitle: The difference between remove/delete and add/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 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
|
## 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:
|
For example:
|
||||||
|
|
||||||
@@ -25,7 +22,7 @@ For example:
|
|||||||
|
|
||||||
## Delete
|
## 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:
|
For example:
|
||||||
|
|
||||||
|
@@ -53,7 +53,6 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "volume1"] as [string, string]],
|
identifiers: [["demo", "volume1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
model_id: null,
|
|
||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
name: "Dishwasher",
|
name: "Dishwasher",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -73,7 +72,6 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
model_id: null,
|
|
||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
name: "Lamp",
|
name: "Lamp",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -93,7 +91,6 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
model_id: null,
|
|
||||||
name_by_user: "User name",
|
name_by_user: "User name",
|
||||||
name: "Technical name",
|
name: "Technical name",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
|
@@ -53,7 +53,6 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "volume1"] as [string, string]],
|
identifiers: [["demo", "volume1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
model_id: null,
|
|
||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
name: "Dishwasher",
|
name: "Dishwasher",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -73,7 +72,6 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
model_id: null,
|
|
||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
name: "Lamp",
|
name: "Lamp",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
@@ -93,7 +91,6 @@ const DEVICES = [
|
|||||||
identifiers: [["demo", "pwm1"] as [string, string]],
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
manufacturer: null,
|
manufacturer: null,
|
||||||
model: null,
|
model: null,
|
||||||
model_id: null,
|
|
||||||
name_by_user: "User name",
|
name_by_user: "User name",
|
||||||
name: "Technical name",
|
name: "Technical name",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
|
@@ -140,9 +140,6 @@ const ENTITIES: HassEntity[] = [
|
|||||||
createEntity("climate.auto_preheating", "auto", undefined, {
|
createEntity("climate.auto_preheating", "auto", undefined, {
|
||||||
hvac_action: "preheating",
|
hvac_action: "preheating",
|
||||||
}),
|
}),
|
||||||
createEntity("climate.auto_defrosting", "auto", undefined, {
|
|
||||||
hvac_action: "defrosting",
|
|
||||||
}),
|
|
||||||
createEntity("climate.auto_heating", "auto", undefined, {
|
createEntity("climate.auto_heating", "auto", undefined, {
|
||||||
hvac_action: "heating",
|
hvac_action: "heating",
|
||||||
}),
|
}),
|
||||||
|
@@ -215,7 +215,6 @@ const createDeviceRegistryEntries = (
|
|||||||
connections: [],
|
connections: [],
|
||||||
manufacturer: "ESPHome",
|
manufacturer: "ESPHome",
|
||||||
model: "Mock Device",
|
model: "Mock Device",
|
||||||
model_id: "ABC-001",
|
|
||||||
name: "Tag Reader",
|
name: "Tag Reader",
|
||||||
sw_version: null,
|
sw_version: null,
|
||||||
hw_version: "1.0.0",
|
hw_version: "1.0.0",
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
import type { IFuseOptions } from "fuse.js";
|
|
||||||
import Fuse 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 { StoreAddon } from "../../../src/data/supervisor/store";
|
||||||
import { getStripDiacriticsFn } from "../../../src/util/fuse";
|
|
||||||
|
|
||||||
export function filterAndSort(addons: StoreAddon[], filter: string) {
|
export function filterAndSort(addons: StoreAddon[], filter: string) {
|
||||||
const options: IFuseOptions<StoreAddon> = {
|
const options: IFuseOptions<StoreAddon> = {
|
||||||
@@ -10,8 +8,7 @@ export function filterAndSort(addons: StoreAddon[], filter: string) {
|
|||||||
isCaseSensitive: false,
|
isCaseSensitive: false,
|
||||||
minMatchCharLength: Math.min(filter.length, 2),
|
minMatchCharLength: Math.min(filter.length, 2),
|
||||||
threshold: 0.2,
|
threshold: 0.2,
|
||||||
getFn: getStripDiacriticsFn,
|
|
||||||
};
|
};
|
||||||
const fuse = new Fuse(addons, options);
|
const fuse = new Fuse(addons, options);
|
||||||
return fuse.search(stripDiacritics(filter)).map((result) => result.item);
|
return fuse.search(filter).map((result) => result.item);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../../src/resources/compatibility";
|
import "../../src/resources/compatibility";
|
||||||
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./hassio-main";
|
import "./hassio-main";
|
||||||
|
|
||||||
import("../../src/resources/ha-style");
|
import("../../src/resources/ha-style");
|
||||||
|
65
package.json
65
package.json
@@ -25,15 +25,15 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.24.8",
|
"@babel/runtime": "7.24.7",
|
||||||
"@braintree/sanitize-url": "7.0.4",
|
"@braintree/sanitize-url": "7.0.3",
|
||||||
"@codemirror/autocomplete": "6.17.0",
|
"@codemirror/autocomplete": "6.16.3",
|
||||||
"@codemirror/commands": "6.6.0",
|
"@codemirror/commands": "6.6.0",
|
||||||
"@codemirror/language": "6.10.2",
|
"@codemirror/language": "6.10.2",
|
||||||
"@codemirror/legacy-modes": "6.4.0",
|
"@codemirror/legacy-modes": "6.4.0",
|
||||||
"@codemirror/search": "6.5.6",
|
"@codemirror/search": "6.5.6",
|
||||||
"@codemirror/state": "6.4.1",
|
"@codemirror/state": "6.4.1",
|
||||||
"@codemirror/view": "6.28.4",
|
"@codemirror/view": "6.28.2",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.12.5",
|
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||||
"@formatjs/intl-displaynames": "6.6.8",
|
"@formatjs/intl-displaynames": "6.6.8",
|
||||||
@@ -43,12 +43,12 @@
|
|||||||
"@formatjs/intl-numberformat": "8.10.3",
|
"@formatjs/intl-numberformat": "8.10.3",
|
||||||
"@formatjs/intl-pluralrules": "5.2.14",
|
"@formatjs/intl-pluralrules": "5.2.14",
|
||||||
"@formatjs/intl-relativetimeformat": "11.2.14",
|
"@formatjs/intl-relativetimeformat": "11.2.14",
|
||||||
"@fullcalendar/core": "6.1.15",
|
"@fullcalendar/core": "6.1.11",
|
||||||
"@fullcalendar/daygrid": "6.1.15",
|
"@fullcalendar/daygrid": "6.1.11",
|
||||||
"@fullcalendar/interaction": "6.1.15",
|
"@fullcalendar/interaction": "6.1.11",
|
||||||
"@fullcalendar/list": "6.1.15",
|
"@fullcalendar/list": "6.1.11",
|
||||||
"@fullcalendar/luxon3": "6.1.15",
|
"@fullcalendar/luxon3": "6.1.11",
|
||||||
"@fullcalendar/timegrid": "6.1.15",
|
"@fullcalendar/timegrid": "6.1.11",
|
||||||
"@lezer/highlight": "1.2.0",
|
"@lezer/highlight": "1.2.0",
|
||||||
"@lit-labs/context": "0.4.1",
|
"@lit-labs/context": "0.4.1",
|
||||||
"@lit-labs/motion": "1.0.7",
|
"@lit-labs/motion": "1.0.7",
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"@material/mwc-top-app-bar": "0.27.0",
|
"@material/mwc-top-app-bar": "0.27.0",
|
||||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/web": "1.5.1",
|
"@material/web": "1.5.0",
|
||||||
"@mdi/js": "7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@mdi/svg": "7.4.47",
|
"@mdi/svg": "7.4.47",
|
||||||
"@polymer/paper-item": "3.0.1",
|
"@polymer/paper-item": "3.0.1",
|
||||||
@@ -88,8 +88,8 @@
|
|||||||
"@polymer/paper-tabs": "3.1.0",
|
"@polymer/paper-tabs": "3.1.0",
|
||||||
"@polymer/polymer": "3.5.1",
|
"@polymer/polymer": "3.5.1",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@vaadin/combo-box": "24.4.3",
|
"@vaadin/combo-box": "24.4.0",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.4.3",
|
"@vaadin/vaadin-themable-mixin": "24.4.0",
|
||||||
"@vibrant/color": "3.2.1-alpha.1",
|
"@vibrant/color": "3.2.1-alpha.1",
|
||||||
"@vibrant/core": "3.2.1-alpha.1",
|
"@vibrant/core": "3.2.1-alpha.1",
|
||||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
"leaflet-draw": "1.0.4",
|
"leaflet-draw": "1.0.4",
|
||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"luxon": "3.4.4",
|
"luxon": "3.4.4",
|
||||||
"marked": "13.0.2",
|
"marked": "12.0.2",
|
||||||
"memoize-one": "6.0.0",
|
"memoize-one": "6.0.0",
|
||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
"proxy-polyfill": "0.3.2",
|
"proxy-polyfill": "0.3.2",
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
"rrule": "2.8.1",
|
"rrule": "2.8.1",
|
||||||
"sortablejs": "1.15.2",
|
"sortablejs": "1.15.2",
|
||||||
"stacktrace-js": "2.0.2",
|
"stacktrace-js": "2.0.2",
|
||||||
"superstruct": "2.0.2",
|
"superstruct": "1.0.4",
|
||||||
"tinykeys": "2.1.0",
|
"tinykeys": "2.1.0",
|
||||||
"tsparticles-engine": "2.12.0",
|
"tsparticles-engine": "2.12.0",
|
||||||
"tsparticles-preset-links": "2.12.0",
|
"tsparticles-preset-links": "2.12.0",
|
||||||
@@ -149,15 +149,15 @@
|
|||||||
"xss": "1.0.15"
|
"xss": "1.0.15"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.24.9",
|
"@babel/core": "7.24.7",
|
||||||
"@babel/helper-define-polyfill-provider": "0.6.2",
|
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||||
"@babel/plugin-proposal-decorators": "7.24.7",
|
"@babel/plugin-proposal-decorators": "7.24.7",
|
||||||
"@babel/plugin-transform-runtime": "7.24.7",
|
"@babel/plugin-transform-runtime": "7.24.7",
|
||||||
"@babel/preset-env": "7.24.8",
|
"@babel/preset-env": "7.24.7",
|
||||||
"@babel/preset-typescript": "7.24.7",
|
"@babel/preset-typescript": "7.24.7",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.13.3",
|
"@bundle-stats/plugin-webpack-filter": "4.13.2",
|
||||||
"@koa/cors": "5.0.0",
|
"@koa/cors": "5.0.0",
|
||||||
"@lokalise/node-api": "12.6.0",
|
"@lokalise/node-api": "12.5.0",
|
||||||
"@octokit/auth-oauth-device": "7.1.1",
|
"@octokit/auth-oauth-device": "7.1.1",
|
||||||
"@octokit/plugin-retry": "7.1.1",
|
"@octokit/plugin-retry": "7.1.1",
|
||||||
"@octokit/rest": "21.0.0",
|
"@octokit/rest": "21.0.0",
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"@rollup/plugin-node-resolve": "15.2.3",
|
"@rollup/plugin-node-resolve": "15.2.3",
|
||||||
"@rollup/plugin-replace": "5.0.7",
|
"@rollup/plugin-replace": "5.0.7",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@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/chromecast-caf-sender": "1.0.10",
|
||||||
"@types/color-name": "1.1.4",
|
"@types/color-name": "1.1.4",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
@@ -178,15 +178,15 @@
|
|||||||
"@types/leaflet-draw": "1.0.11",
|
"@types/leaflet-draw": "1.0.11",
|
||||||
"@types/lodash.merge": "4.6.9",
|
"@types/lodash.merge": "4.6.9",
|
||||||
"@types/luxon": "3.4.2",
|
"@types/luxon": "3.4.2",
|
||||||
"@types/mocha": "10.0.7",
|
"@types/mocha": "10.0.6",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
"@types/serve-handler": "6.1.4",
|
"@types/serve-handler": "6.1.4",
|
||||||
"@types/sortablejs": "1.15.8",
|
"@types/sortablejs": "1.15.8",
|
||||||
"@types/tar": "6.1.13",
|
"@types/tar": "6.1.13",
|
||||||
"@types/ua-parser-js": "0.7.39",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "7.16.1",
|
"@typescript-eslint/eslint-plugin": "7.13.1",
|
||||||
"@typescript-eslint/parser": "7.16.1",
|
"@typescript-eslint/parser": "7.13.1",
|
||||||
"@web/dev-server": "0.1.38",
|
"@web/dev-server": "0.1.38",
|
||||||
"@web/dev-server-rollup": "0.4.1",
|
"@web/dev-server-rollup": "0.4.1",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
@@ -200,16 +200,16 @@
|
|||||||
"eslint-import-resolver-webpack": "0.13.8",
|
"eslint-import-resolver-webpack": "0.13.8",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-lit": "1.14.0",
|
"eslint-plugin-lit": "1.14.0",
|
||||||
"eslint-plugin-lit-a11y": "4.1.4",
|
"eslint-plugin-lit-a11y": "4.1.2",
|
||||||
"eslint-plugin-unused-imports": "4.0.0",
|
"eslint-plugin-unused-imports": "4.0.0",
|
||||||
"eslint-plugin-wc": "2.1.0",
|
"eslint-plugin-wc": "2.1.0",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"glob": "11.0.0",
|
"glob": "10.4.2",
|
||||||
"gulp": "5.0.0",
|
"gulp": "5.0.0",
|
||||||
"gulp-json-transform": "0.5.0",
|
"gulp-json-transform": "0.5.0",
|
||||||
"gulp-rename": "2.0.0",
|
"gulp-rename": "2.0.0",
|
||||||
"gulp-zopfli-green": "6.0.2",
|
"gulp-zopfli-green": "6.0.1",
|
||||||
"html-minifier-terser": "7.2.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "9.0.11",
|
"husky": "9.0.11",
|
||||||
"instant-mocha": "1.5.2",
|
"instant-mocha": "1.5.2",
|
||||||
@@ -220,30 +220,31 @@
|
|||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"magic-string": "0.30.10",
|
"magic-string": "0.30.10",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"mocha": "10.5.0",
|
"mocha": "10.4.0",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
"open": "10.1.0",
|
"open": "10.1.0",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.3.2",
|
||||||
"rollup": "2.79.1",
|
"rollup": "2.79.1",
|
||||||
"rollup-plugin-string": "3.0.0",
|
"rollup-plugin-string": "3.0.0",
|
||||||
"rollup-plugin-terser": "7.0.2",
|
"rollup-plugin-terser": "7.0.2",
|
||||||
"rollup-plugin-visualizer": "5.12.0",
|
"rollup-plugin-visualizer": "5.12.0",
|
||||||
"serve-handler": "6.1.5",
|
"serve-handler": "6.1.5",
|
||||||
"sinon": "18.0.0",
|
"sinon": "18.0.0",
|
||||||
|
"source-map-url": "0.4.1",
|
||||||
"systemjs": "6.15.1",
|
"systemjs": "6.15.1",
|
||||||
"tar": "7.4.0",
|
"tar": "7.4.0",
|
||||||
"terser-webpack-plugin": "5.3.10",
|
"terser-webpack-plugin": "5.3.10",
|
||||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.5.3",
|
"typescript": "5.4.5",
|
||||||
"webpack": "5.93.0",
|
"webpack": "5.92.1",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.0.4",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack-manifest-plugin": "5.0.0",
|
"webpack-manifest-plugin": "5.0.0",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
"webpackbar": "6.0.1",
|
"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",
|
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
@@ -252,7 +253,7 @@
|
|||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"clean-css": "5.3.3",
|
"clean-css": "5.3.3",
|
||||||
"@lit/reactive-element": "1.6.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",
|
"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"
|
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||||
},
|
},
|
||||||
|
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]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20240719.0"
|
version = "20240610.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@@ -125,7 +125,6 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
|
|||||||
"off",
|
"off",
|
||||||
"idle",
|
"idle",
|
||||||
"preheating",
|
"preheating",
|
||||||
"defrosting",
|
|
||||||
"heating",
|
"heating",
|
||||||
"cooling",
|
"cooling",
|
||||||
"drying",
|
"drying",
|
||||||
|
@@ -25,9 +25,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
|||||||
if (__DEMO__) {
|
if (__DEMO__) {
|
||||||
if (replace) {
|
if (replace) {
|
||||||
mainWindow.history.replaceState(
|
mainWindow.history.replaceState(
|
||||||
mainWindow.history.state?.root
|
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
||||||
? { root: true }
|
|
||||||
: (options?.data ?? null),
|
|
||||||
"",
|
"",
|
||||||
`${mainWindow.location.pathname}#${path}`
|
`${mainWindow.location.pathname}#${path}`
|
||||||
);
|
);
|
||||||
@@ -36,7 +34,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
|
|||||||
}
|
}
|
||||||
} else if (replace) {
|
} else if (replace) {
|
||||||
mainWindow.history.replaceState(
|
mainWindow.history.replaceState(
|
||||||
mainWindow.history.state?.root ? { root: true } : (options?.data ?? null),
|
mainWindow.history.state?.root ? { root: true } : options?.data ?? null,
|
||||||
"",
|
"",
|
||||||
path
|
path
|
||||||
);
|
);
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { stripDiacritics } from "../strip-diacritics";
|
|
||||||
import { fuzzyScore } from "./filter";
|
import { fuzzyScore } from "./filter";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,10 +19,10 @@ export const fuzzySequentialMatch = (
|
|||||||
for (const word of item.strings) {
|
for (const word of item.strings) {
|
||||||
const scores = fuzzyScore(
|
const scores = fuzzyScore(
|
||||||
filter,
|
filter,
|
||||||
stripDiacritics(filter.toLowerCase()),
|
filter.toLowerCase(),
|
||||||
0,
|
0,
|
||||||
word,
|
word,
|
||||||
stripDiacritics(word.toLowerCase()),
|
word.toLowerCase(),
|
||||||
0,
|
0,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
export const stripDiacritics = (str: string) =>
|
|
||||||
str.normalize("NFD").replace(/[\u0300-\u036F]/g, "");
|
|
@@ -159,10 +159,10 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
},
|
},
|
||||||
afterUpdate: (y) => {
|
afterUpdate: (y) => {
|
||||||
const yWidth = this.showNames
|
const yWidth = this.showNames
|
||||||
? (y.width ?? 0)
|
? y.width ?? 0
|
||||||
: computeRTL(this.hass)
|
: computeRTL(this.hass)
|
||||||
? 0
|
? 0
|
||||||
: (y.left ?? 0);
|
: y.left ?? 0;
|
||||||
if (
|
if (
|
||||||
this._yWidth !== Math.floor(yWidth) &&
|
this._yWidth !== Math.floor(yWidth) &&
|
||||||
y.ticks.length === this.data.length
|
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 "../ha-svg-icon";
|
||||||
import "../search-input";
|
import "../search-input";
|
||||||
import { filterData, sortData } from "./sort-filter";
|
import { filterData, sortData } from "./sort-filter";
|
||||||
import { LocalizeFunc } from "../../common/translations/localize";
|
|
||||||
|
|
||||||
export interface RowClickedEvent {
|
export interface RowClickedEvent {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -66,10 +65,6 @@ export interface DataTableSortColumnData {
|
|||||||
valueColumn?: string;
|
valueColumn?: string;
|
||||||
direction?: SortingDirection;
|
direction?: SortingDirection;
|
||||||
groupable?: boolean;
|
groupable?: boolean;
|
||||||
moveable?: boolean;
|
|
||||||
hideable?: boolean;
|
|
||||||
defaultHidden?: boolean;
|
|
||||||
showNarrow?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||||
@@ -84,7 +79,6 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
|||||||
| "overflow-menu"
|
| "overflow-menu"
|
||||||
| "flex";
|
| "flex";
|
||||||
template?: (row: T) => TemplateResult | string | typeof nothing;
|
template?: (row: T) => TemplateResult | string | typeof nothing;
|
||||||
extraTemplate?: (row: T) => TemplateResult | string | typeof nothing;
|
|
||||||
width?: string;
|
width?: string;
|
||||||
maxWidth?: string;
|
maxWidth?: string;
|
||||||
grows?: boolean;
|
grows?: boolean;
|
||||||
@@ -111,10 +105,6 @@ const UNDEFINED_GROUP_KEY = "zzzzz_undefined";
|
|||||||
export class HaDataTable extends LitElement {
|
export class HaDataTable extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@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: Object }) public columns: DataTableColumnContainer = {};
|
||||||
|
|
||||||
@property({ type: Array }) public data: DataTableRowData[] = [];
|
@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 initialCollapsedGroups?: string[];
|
||||||
|
|
||||||
@property({ attribute: false }) public hiddenColumns?: string[];
|
|
||||||
|
|
||||||
@property({ attribute: false }) public columnOrder?: string[];
|
|
||||||
|
|
||||||
@state() private _filterable = false;
|
@state() private _filterable = false;
|
||||||
|
|
||||||
@state() private _filter = "";
|
@state() private _filter = "";
|
||||||
@@ -249,7 +235,6 @@ export class HaDataTable extends LitElement {
|
|||||||
(column: ClonedDataTableColumnData) => {
|
(column: ClonedDataTableColumnData) => {
|
||||||
delete column.title;
|
delete column.title;
|
||||||
delete column.template;
|
delete column.template;
|
||||||
delete column.extraTemplate;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -287,46 +272,12 @@ export class HaDataTable extends LitElement {
|
|||||||
this._sortFilterData();
|
this._sortFilterData();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.has("selectable") || properties.has("hiddenColumns")) {
|
if (properties.has("selectable")) {
|
||||||
this._items = [...this._items];
|
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() {
|
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`
|
return html`
|
||||||
<div class="mdc-data-table">
|
<div class="mdc-data-table">
|
||||||
<slot name="header" @slotchange=${this._calcTableHeight}>
|
<slot name="header" @slotchange=${this._calcTableHeight}>
|
||||||
@@ -375,15 +326,9 @@ export class HaDataTable extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${Object.entries(columns).map(([key, column]) => {
|
${Object.entries(this.columns).map(([key, column]) => {
|
||||||
if (
|
if (column.hidden) {
|
||||||
column.hidden ||
|
return "";
|
||||||
(this.columnOrder && this.columnOrder.includes(key)
|
|
||||||
? (this.hiddenColumns?.includes(key) ??
|
|
||||||
column.defaultHidden)
|
|
||||||
: column.defaultHidden)
|
|
||||||
) {
|
|
||||||
return nothing;
|
|
||||||
}
|
}
|
||||||
const sorted = key === this.sortColumn;
|
const sorted = key === this.sortColumn;
|
||||||
const classes = {
|
const classes = {
|
||||||
@@ -442,7 +387,7 @@ export class HaDataTable extends LitElement {
|
|||||||
<div class="mdc-data-table__row" role="row">
|
<div class="mdc-data-table__row" role="row">
|
||||||
<div class="mdc-data-table__cell grows center" role="cell">
|
<div class="mdc-data-table__cell grows center" role="cell">
|
||||||
${this.noDataText ||
|
${this.noDataText ||
|
||||||
localize("ui.components.data-table.no-data")}
|
this.hass.localize("ui.components.data-table.no-data")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -454,7 +399,7 @@ export class HaDataTable extends LitElement {
|
|||||||
@scroll=${this._saveScrollPos}
|
@scroll=${this._saveScrollPos}
|
||||||
.items=${this._items}
|
.items=${this._items}
|
||||||
.keyFunction=${this._keyFunction}
|
.keyFunction=${this._keyFunction}
|
||||||
.renderItem=${renderRow}
|
.renderItem=${this._renderRow}
|
||||||
></lit-virtualizer>
|
></lit-virtualizer>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
@@ -464,12 +409,7 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
|
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
|
||||||
|
|
||||||
private _renderRow = (
|
private _renderRow = (row: DataTableRowData, index: number) => {
|
||||||
columns: DataTableColumnContainer,
|
|
||||||
narrow: boolean,
|
|
||||||
row: DataTableRowData,
|
|
||||||
index: number
|
|
||||||
) => {
|
|
||||||
// not sure how this happens...
|
// not sure how this happens...
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -514,14 +454,8 @@ export class HaDataTable extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${Object.entries(columns).map(([key, column]) => {
|
${Object.entries(this.columns).map(([key, column]) => {
|
||||||
if (
|
if (column.hidden) {
|
||||||
(narrow && !column.main && !column.showNarrow) ||
|
|
||||||
column.hidden ||
|
|
||||||
(this.columnOrder && this.columnOrder.includes(key)
|
|
||||||
? (this.hiddenColumns?.includes(key) ?? column.defaultHidden)
|
|
||||||
: column.defaultHidden)
|
|
||||||
) {
|
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
@@ -548,38 +482,7 @@ export class HaDataTable extends LitElement {
|
|||||||
})
|
})
|
||||||
: ""}
|
: ""}
|
||||||
>
|
>
|
||||||
${column.template
|
${column.template ? column.template(row) : row[key]}
|
||||||
? 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}`}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
@@ -625,8 +528,6 @@ export class HaDataTable extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localize = this.localizeFunc || this.hass.localize;
|
|
||||||
|
|
||||||
if (this.appendRow || this.hasFab || this.groupColumn) {
|
if (this.appendRow || this.hasFab || this.groupColumn) {
|
||||||
let items = [...data];
|
let items = [...data];
|
||||||
|
|
||||||
@@ -680,7 +581,7 @@ export class HaDataTable extends LitElement {
|
|||||||
>
|
>
|
||||||
</ha-icon-button>
|
</ha-icon-button>
|
||||||
${groupName === UNDEFINED_GROUP_KEY
|
${groupName === UNDEFINED_GROUP_KEY
|
||||||
? localize("ui.components.data-table.ungrouped")
|
? this.hass.localize("ui.components.data-table.ungrouped")
|
||||||
: groupName || ""}
|
: groupName || ""}
|
||||||
</div>`,
|
</div>`,
|
||||||
});
|
});
|
||||||
@@ -960,7 +861,6 @@ export class HaDataTable extends LitElement {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__cell {
|
.mdc-data-table__cell {
|
||||||
|
@@ -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 { expose } from "comlink";
|
||||||
import { stringCompare } from "../../common/string/compare";
|
import { stringCompare } from "../../common/string/compare";
|
||||||
import { stripDiacritics } from "../../common/string/strip-diacritics";
|
|
||||||
import type {
|
import type {
|
||||||
ClonedDataTableColumnData,
|
ClonedDataTableColumnData,
|
||||||
DataTableRowData,
|
DataTableRowData,
|
||||||
@@ -13,18 +12,20 @@ const filterData = (
|
|||||||
columns: SortableColumnContainer,
|
columns: SortableColumnContainer,
|
||||||
filter: string
|
filter: string
|
||||||
) => {
|
) => {
|
||||||
filter = stripDiacritics(filter.toLowerCase());
|
filter = filter.toUpperCase();
|
||||||
return data.filter((row) =>
|
return data.filter((row) =>
|
||||||
Object.entries(columns).some((columnEntry) => {
|
Object.entries(columns).some((columnEntry) => {
|
||||||
const [key, column] = columnEntry;
|
const [key, column] = columnEntry;
|
||||||
if (column.filterable) {
|
if (column.filterable) {
|
||||||
const value = String(
|
if (
|
||||||
column.filterKey
|
String(
|
||||||
? row[column.valueColumn || key][column.filterKey]
|
column.filterKey
|
||||||
: row[column.valueColumn || key]
|
? row[column.valueColumn || key][column.filterKey]
|
||||||
);
|
: row[column.valueColumn || key]
|
||||||
|
)
|
||||||
if (stripDiacritics(value).toLowerCase().includes(filter)) {
|
.toUpperCase()
|
||||||
|
.includes(filter)
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,314 +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() 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) => {
|
|
||||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: this.hass.localize("ui.components.state-content-picker.state"),
|
|
||||||
value: "state",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
this._timerTimeRemaining
|
||||||
)}
|
)}
|
||||||
.description=${this.showName
|
.description=${this.showName
|
||||||
? (this.name ?? computeStateName(entityState))
|
? this.name ?? computeStateName(entityState)
|
||||||
: undefined}
|
: undefined}
|
||||||
>
|
>
|
||||||
${!image && showIcon
|
${!image && showIcon
|
||||||
|
@@ -90,8 +90,7 @@ class HaAnsiToHtml extends LitElement {
|
|||||||
|
|
||||||
private _parseTextToColoredPre(text) {
|
private _parseTextToColoredPre(text) {
|
||||||
const pre = document.createElement("pre");
|
const pre = document.createElement("pre");
|
||||||
// eslint-disable-next-line no-control-regex
|
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
|
||||||
const re = /\x1b(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1b\\))/g;
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
const state: State = {
|
const state: State = {
|
||||||
|
@@ -94,8 +94,6 @@ export const computeInitialHaFormData = (
|
|||||||
data[field.name] = selector.color_temp?.min_mireds ?? 153;
|
data[field.name] = selector.color_temp?.min_mireds ?? 153;
|
||||||
} else if (
|
} else if (
|
||||||
"action" in selector ||
|
"action" in selector ||
|
||||||
"trigger" in selector ||
|
|
||||||
"condition" in selector ||
|
|
||||||
"media" in selector ||
|
"media" in selector ||
|
||||||
"target" in selector
|
"target" in selector
|
||||||
) {
|
) {
|
||||||
|
@@ -7,10 +7,9 @@ import { mdiRestore } from "@mdi/js";
|
|||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { conditionalClamp } from "../common/number/clamp";
|
|
||||||
|
|
||||||
type GridSizeValue = {
|
type GridSizeValue = {
|
||||||
rows?: number | "auto";
|
rows?: number;
|
||||||
columns?: number;
|
columns?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,7 +19,7 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public value?: GridSizeValue;
|
@property({ attribute: false }) public value?: GridSizeValue;
|
||||||
|
|
||||||
@property({ attribute: false }) public rows = 8;
|
@property({ attribute: false }) public rows = 6;
|
||||||
|
|
||||||
@property({ attribute: false }) public columns = 4;
|
@property({ attribute: false }) public columns = 4;
|
||||||
|
|
||||||
@@ -43,20 +42,6 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
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`
|
return html`
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<ha-grid-layout-slider
|
<ha-grid-layout-slider
|
||||||
@@ -64,28 +49,25 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
"ui.components.grid-size-picker.columns"
|
"ui.components.grid-size-picker.columns"
|
||||||
)}
|
)}
|
||||||
id="columns"
|
id="columns"
|
||||||
.min=${columnMin}
|
.min=${this.columnMin ?? 1}
|
||||||
.max=${columnMax}
|
.max=${this.columnMax ?? this.columns}
|
||||||
.range=${this.columns}
|
.range=${this.columns}
|
||||||
.value=${columnValue}
|
.value=${this.value?.columns}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@slider-moved=${this._sliderMoved}
|
@slider-moved=${this._sliderMoved}
|
||||||
.disabled=${disabledColumns}
|
|
||||||
></ha-grid-layout-slider>
|
></ha-grid-layout-slider>
|
||||||
|
|
||||||
<ha-grid-layout-slider
|
<ha-grid-layout-slider
|
||||||
aria-label=${this.hass.localize(
|
aria-label=${this.hass.localize(
|
||||||
"ui.components.grid-size-picker.rows"
|
"ui.components.grid-size-picker.rows"
|
||||||
)}
|
)}
|
||||||
id="rows"
|
id="rows"
|
||||||
.min=${rowMin}
|
.min=${this.rowMin ?? 1}
|
||||||
.max=${rowMax}
|
.max=${this.rowMax ?? this.rows}
|
||||||
.range=${this.rows}
|
.range=${this.rows}
|
||||||
vertical
|
vertical
|
||||||
.value=${rowValue}
|
.value=${this.value?.rows}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@slider-moved=${this._sliderMoved}
|
@slider-moved=${this._sliderMoved}
|
||||||
.disabled=${disabledRows}
|
|
||||||
></ha-grid-layout-slider>
|
></ha-grid-layout-slider>
|
||||||
${!this.isDefault
|
${!this.isDefault
|
||||||
? html`
|
? html`
|
||||||
@@ -108,8 +90,8 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
"--total-rows": this.rows,
|
"--total-rows": this.rows,
|
||||||
"--total-columns": this.columns,
|
"--total-columns": this.columns,
|
||||||
"--rows": rowValue,
|
"--rows": this._localValue?.rows,
|
||||||
"--columns": columnValue,
|
"--columns": this._localValue?.columns,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
@@ -118,11 +100,17 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
.map((_, index) => {
|
.map((_, index) => {
|
||||||
const row = Math.floor(index / this.columns) + 1;
|
const row = Math.floor(index / this.columns) + 1;
|
||||||
const column = (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`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="cell"
|
class="cell"
|
||||||
data-row=${row}
|
data-row=${row}
|
||||||
data-column=${column}
|
data-column=${column}
|
||||||
|
?disabled=${disabled}
|
||||||
@click=${this._cellClick}
|
@click=${this._cellClick}
|
||||||
></div>
|
></div>
|
||||||
`;
|
`;
|
||||||
@@ -138,16 +126,11 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
|
|
||||||
_cellClick(ev) {
|
_cellClick(ev) {
|
||||||
const cell = ev.currentTarget as HTMLElement;
|
const cell = ev.currentTarget as HTMLElement;
|
||||||
|
if (cell.getAttribute("disabled") !== null) return;
|
||||||
const rows = Number(cell.getAttribute("data-row"));
|
const rows = Number(cell.getAttribute("data-row"));
|
||||||
const columns = Number(cell.getAttribute("data-column"));
|
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", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { rows: clampedRow, columns: clampedColumn },
|
value: { rows, columns },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +188,7 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
.preview {
|
.preview {
|
||||||
position: relative;
|
position: relative;
|
||||||
grid-area: preview;
|
grid-area: preview;
|
||||||
aspect-ratio: 1 / 1.2;
|
aspect-ratio: 1 / 1;
|
||||||
}
|
}
|
||||||
.preview > div {
|
.preview > div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -226,6 +209,10 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.preview .cell[disabled] {
|
||||||
|
opacity: 0.05;
|
||||||
|
cursor: initial;
|
||||||
|
}
|
||||||
.selected {
|
.selected {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
import { MdMenuItem } from "@material/web/menu/menu-item";
|
import { MdMenuItem } from "@material/web/menu/menu-item";
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("ha-menu-item")
|
@customElement("ha-menu-item")
|
||||||
export class HaMenuItem extends MdMenuItem {
|
export class HaMenuItem extends MdMenuItem {
|
||||||
@property({ attribute: false }) clickAction?: (item?: HTMLElement) => void;
|
|
||||||
|
|
||||||
static override styles = [
|
static override styles = [
|
||||||
...super.styles,
|
...super.styles,
|
||||||
css`
|
css`
|
||||||
|
@@ -1,30 +1,9 @@
|
|||||||
import { MdMenu } from "@material/web/menu/menu";
|
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 { css } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import type { HaMenuItem } from "./ha-menu-item";
|
|
||||||
|
|
||||||
@customElement("ha-menu")
|
@customElement("ha-menu")
|
||||||
export class HaMenu extends MdMenu {
|
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 = [
|
static override styles = [
|
||||||
...super.styles,
|
...super.styles,
|
||||||
css`
|
css`
|
||||||
@@ -39,8 +18,4 @@ declare global {
|
|||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-menu": HaMenu;
|
"ha-menu": HaMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HTMLElementEventMap {
|
|
||||||
"close-menu": CloseMenuEvent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,92 +1,72 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiCamera } from "@mdi/js";
|
import { mdiCamera } from "@mdi/js";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement, nothing, PropertyValues } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import type QrScanner from "qr-scanner";
|
import type QrScanner from "qr-scanner";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { addExternalBarCodeListener } from "../external_app/external_app_entrypoint";
|
|
||||||
import { HomeAssistant } from "../types";
|
|
||||||
import "./ha-alert";
|
import "./ha-alert";
|
||||||
import "./ha-button-menu";
|
import "./ha-button-menu";
|
||||||
import "./ha-list-item";
|
|
||||||
import "./ha-textfield";
|
import "./ha-textfield";
|
||||||
import type { HaTextField } from "./ha-textfield";
|
import type { HaTextField } from "./ha-textfield";
|
||||||
|
|
||||||
@customElement("ha-qr-scanner")
|
@customElement("ha-qr-scanner")
|
||||||
class HaQrScanner extends LitElement {
|
class HaQrScanner extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
@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 _cameras?: QrScanner.Camera[];
|
||||||
|
|
||||||
@state() private _manual = false;
|
@state() private _error?: string;
|
||||||
|
|
||||||
private _qrScanner?: QrScanner;
|
private _qrScanner?: QrScanner;
|
||||||
|
|
||||||
private _qrNotFoundCount = 0;
|
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;
|
@query("ha-textfield") private _manualInput?: HaTextField;
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._qrNotFoundCount = 0;
|
this._qrNotFoundCount = 0;
|
||||||
if (this._nativeBarcodeScanner) {
|
|
||||||
this._closeExternalScanner();
|
|
||||||
}
|
|
||||||
if (this._qrScanner) {
|
if (this._qrScanner) {
|
||||||
this._qrScanner.stop();
|
this._qrScanner.stop();
|
||||||
this._qrScanner.destroy();
|
this._qrScanner.destroy();
|
||||||
this._qrScanner = undefined;
|
this._qrScanner = undefined;
|
||||||
}
|
}
|
||||||
while (this._canvasContainer?.lastChild) {
|
while (this._canvasContainer.lastChild) {
|
||||||
this._canvasContainer.removeChild(this._canvasContainer.lastChild);
|
this._canvasContainer.removeChild(this._canvasContainer.lastChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (this.hasUpdated) {
|
if (this.hasUpdated && navigator.mediaDevices) {
|
||||||
this._loadQrScanner();
|
this._loadQrScanner();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
this._loadQrScanner();
|
if (navigator.mediaDevices) {
|
||||||
|
this._loadQrScanner();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
if (changedProps.has("error") && this.error) {
|
if (changedProps.has("_error") && this._error) {
|
||||||
alert(`error: ${this.error}`);
|
fireEvent(this, "qr-code-error", { message: this._error });
|
||||||
this._notifyExternalScanner(this.error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render(): TemplateResult {
|
||||||
if (this._nativeBarcodeScanner && !this._manual) {
|
return html`${this._error
|
||||||
return nothing;
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
}
|
|
||||||
|
|
||||||
return html`${this.error
|
|
||||||
? html`<ha-alert alert-type="error">${this.error}</ha-alert>`
|
|
||||||
: ""}
|
: ""}
|
||||||
${navigator.mediaDevices && !this._manual
|
${navigator.mediaDevices
|
||||||
? html`<video></video>
|
? html`<video></video>
|
||||||
<div id="canvas-container">
|
<div id="canvas-container">
|
||||||
${this._cameras && this._cameras.length > 1
|
${this._cameras && this._cameras.length > 1
|
||||||
@@ -100,26 +80,21 @@ class HaQrScanner extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
${this._cameras!.map(
|
${this._cameras!.map(
|
||||||
(camera) => html`
|
(camera) => html`
|
||||||
<ha-list-item
|
<mwc-list-item
|
||||||
.value=${camera.id}
|
.value=${camera.id}
|
||||||
@click=${this._cameraChanged}
|
@click=${this._cameraChanged}
|
||||||
|
>${camera.label}</mwc-list-item
|
||||||
>
|
>
|
||||||
${camera.label}
|
|
||||||
</ha-list-item>
|
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</ha-button-menu>`
|
</ha-button-menu>`
|
||||||
: nothing}
|
: ""}
|
||||||
</div>`
|
</div>`
|
||||||
: html`${this._manual
|
: html`<ha-alert alert-type="warning">
|
||||||
? nothing
|
${!window.isSecureContext
|
||||||
: html`<ha-alert alert-type="warning">
|
? this.localize("ui.components.qr-scanner.only_https_supported")
|
||||||
${!window.isSecureContext
|
: this.localize("ui.components.qr-scanner.not_supported")}
|
||||||
? this.localize(
|
</ha-alert>
|
||||||
"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>
|
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
@@ -127,44 +102,33 @@ class HaQrScanner extends LitElement {
|
|||||||
@keyup=${this._manualKeyup}
|
@keyup=${this._manualKeyup}
|
||||||
@paste=${this._manualPaste}
|
@paste=${this._manualPaste}
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
<mwc-button @click=${this._manualSubmit}>
|
<mwc-button @click=${this._manualSubmit}
|
||||||
${this.localize("ui.common.submit")}
|
>${this.localize("ui.common.submit")}</mwc-button
|
||||||
</mwc-button>
|
>
|
||||||
</div>`}`;
|
</div>`}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _nativeBarcodeScanner(): boolean {
|
|
||||||
return Boolean(this.hass.auth.external?.config.hasBarCodeScanner);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadQrScanner() {
|
private async _loadQrScanner() {
|
||||||
if (this._nativeBarcodeScanner) {
|
|
||||||
this._openExternalScanner();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!navigator.mediaDevices) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const QrScanner = (await import("qr-scanner")).default;
|
const QrScanner = (await import("qr-scanner")).default;
|
||||||
if (!(await QrScanner.hasCamera())) {
|
if (!(await QrScanner.hasCamera())) {
|
||||||
this._reportError("No camera found");
|
this._error = "No camera found";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
|
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
|
||||||
this._listCameras(QrScanner);
|
this._listCameras(QrScanner);
|
||||||
this._qrScanner = new QrScanner(
|
this._qrScanner = new QrScanner(
|
||||||
this._video!,
|
this._video,
|
||||||
this._qrCodeScanned,
|
this._qrCodeScanned,
|
||||||
this._qrCodeError
|
this._qrCodeError
|
||||||
);
|
);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const canvas = this._qrScanner.$canvas;
|
const canvas = this._qrScanner.$canvas;
|
||||||
this._canvasContainer!.appendChild(canvas);
|
this._canvasContainer.appendChild(canvas);
|
||||||
canvas.style.display = "block";
|
canvas.style.display = "block";
|
||||||
try {
|
try {
|
||||||
await this._qrScanner.start();
|
await this._qrScanner.start();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._reportError(err);
|
this._error = err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,16 +140,16 @@ class HaQrScanner extends LitElement {
|
|||||||
if (err === "No QR code found") {
|
if (err === "No QR code found") {
|
||||||
this._qrNotFoundCount++;
|
this._qrNotFoundCount++;
|
||||||
if (this._qrNotFoundCount === 250) {
|
if (this._qrNotFoundCount === 250) {
|
||||||
this._reportError(err);
|
this._error = err;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._reportError(err.message || err);
|
this._error = err.message || err;
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(err);
|
console.log(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _qrCodeScanned = (qrCodeString: string): void => {
|
private _qrCodeScanned = async (qrCodeString: string): Promise<void> => {
|
||||||
this._qrNotFoundCount = 0;
|
this._qrNotFoundCount = 0;
|
||||||
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
||||||
};
|
};
|
||||||
@@ -211,62 +175,6 @@ class HaQrScanner extends LitElement {
|
|||||||
this._qrScanner?.setCamera((ev.target as any).value);
|
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`
|
static styles = css`
|
||||||
canvas {
|
canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -302,7 +210,6 @@ declare global {
|
|||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"qr-code-scanned": { value: string };
|
"qr-code-scanned": { value: string };
|
||||||
"qr-code-error": { message: string };
|
"qr-code-error": { message: string };
|
||||||
"qr-code-closed": undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
@@ -35,6 +35,10 @@ export class HaActionSelector extends LitElement {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
:host([disabled]) ha-automation-action {
|
||||||
|
opacity: var(--light-disabled-opacity);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
@@ -11,7 +11,6 @@ import {
|
|||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
} from "../../data/entity_sources";
|
} from "../../data/entity_sources";
|
||||||
import type { AreaSelector } from "../../data/selector";
|
import type { AreaSelector } from "../../data/selector";
|
||||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
|
||||||
import {
|
import {
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
@@ -38,8 +37,6 @@ export class HaAreaSelector extends LitElement {
|
|||||||
|
|
||||||
@state() private _entitySources?: EntitySources;
|
@state() private _entitySources?: EntitySources;
|
||||||
|
|
||||||
@state() private _configEntries?: ConfigEntry[];
|
|
||||||
|
|
||||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||||
|
|
||||||
private _hasIntegration(selector: AreaSelector) {
|
private _hasIntegration(selector: AreaSelector) {
|
||||||
@@ -75,12 +72,6 @@ export class HaAreaSelector extends LitElement {
|
|||||||
this._entitySources = sources;
|
this._entitySources = sources;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!this._configEntries && this._hasIntegration(this.selector)) {
|
|
||||||
this._configEntries = [];
|
|
||||||
getConfigEntries(this.hass).then((entries) => {
|
|
||||||
this._configEntries = entries;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -145,9 +136,7 @@ export class HaAreaSelector extends LitElement {
|
|||||||
const deviceIntegrations = this._entitySources
|
const deviceIntegrations = this._entitySources
|
||||||
? this._deviceIntegrationLookup(
|
? this._deviceIntegrationLookup(
|
||||||
this._entitySources,
|
this._entitySources,
|
||||||
Object.values(this.hass.entities),
|
Object.values(this.hass.entities)
|
||||||
Object.values(this.hass.devices),
|
|
||||||
this._configEntries
|
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
@@ -35,6 +35,10 @@ export class HaConditionSelector extends LitElement {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
:host([disabled]) ha-automation-condition {
|
||||||
|
opacity: var(--light-disabled-opacity);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
@@ -11,7 +11,6 @@ import {
|
|||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
} from "../../data/entity_sources";
|
} from "../../data/entity_sources";
|
||||||
import type { DeviceSelector } from "../../data/selector";
|
import type { DeviceSelector } from "../../data/selector";
|
||||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
|
||||||
import {
|
import {
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
@@ -28,8 +27,6 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
|
|
||||||
@state() private _entitySources?: EntitySources;
|
@state() private _entitySources?: EntitySources;
|
||||||
|
|
||||||
@state() private _configEntries?: ConfigEntry[];
|
|
||||||
|
|
||||||
@property() public value?: any;
|
@property() public value?: any;
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
@@ -78,12 +75,6 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
this._entitySources = sources;
|
this._entitySources = sources;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!this._configEntries && this._hasIntegration(this.selector)) {
|
|
||||||
this._configEntries = [];
|
|
||||||
getConfigEntries(this.hass).then((entries) => {
|
|
||||||
this._configEntries = entries;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -132,9 +123,7 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
const deviceIntegrations = this._entitySources
|
const deviceIntegrations = this._entitySources
|
||||||
? this._deviceIntegrationLookup(
|
? this._deviceIntegrationLookup(
|
||||||
this._entitySources,
|
this._entitySources,
|
||||||
Object.values(this.hass.entities),
|
Object.values(this.hass.entities)
|
||||||
Object.values(this.hass.devices),
|
|
||||||
this._configEntries
|
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
@@ -11,7 +11,6 @@ import {
|
|||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
} from "../../data/entity_sources";
|
} from "../../data/entity_sources";
|
||||||
import type { FloorSelector } from "../../data/selector";
|
import type { FloorSelector } from "../../data/selector";
|
||||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
|
||||||
import {
|
import {
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
@@ -38,8 +37,6 @@ export class HaFloorSelector extends LitElement {
|
|||||||
|
|
||||||
@state() private _entitySources?: EntitySources;
|
@state() private _entitySources?: EntitySources;
|
||||||
|
|
||||||
@state() private _configEntries?: ConfigEntry[];
|
|
||||||
|
|
||||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||||
|
|
||||||
private _hasIntegration(selector: FloorSelector) {
|
private _hasIntegration(selector: FloorSelector) {
|
||||||
@@ -75,12 +72,6 @@ export class HaFloorSelector extends LitElement {
|
|||||||
this._entitySources = sources;
|
this._entitySources = sources;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!this._configEntries && this._hasIntegration(this.selector)) {
|
|
||||||
this._configEntries = [];
|
|
||||||
getConfigEntries(this.hass).then((entries) => {
|
|
||||||
this._configEntries = entries;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -145,9 +136,7 @@ export class HaFloorSelector extends LitElement {
|
|||||||
const deviceIntegrations = this._entitySources
|
const deviceIntegrations = this._entitySources
|
||||||
? this._deviceIntegrationLookup(
|
? this._deviceIntegrationLookup(
|
||||||
this._entitySources,
|
this._entitySources,
|
||||||
Object.values(this.hass.entities),
|
Object.values(this.hass.entities)
|
||||||
Object.values(this.hass.devices),
|
|
||||||
this._configEntries
|
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
@@ -35,6 +35,10 @@ export class HaTriggerSelector extends LitElement {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
:host([disabled]) ha-automation-trigger {
|
||||||
|
opacity: var(--light-disabled-opacity);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
import { html, LitElement } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import { UiStateContentSelector } from "../../data/selector";
|
|
||||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import "../entity/ha-entity-state-content-picker";
|
|
||||||
|
|
||||||
@customElement("ha-selector-ui_state_content")
|
|
||||||
export class HaSelectorUiStateContent extends SubscribeMixin(LitElement) {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public selector!: UiStateContentSelector;
|
|
||||||
|
|
||||||
@property() public value?: string | string[];
|
|
||||||
|
|
||||||
@property() public label?: string;
|
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public context?: {
|
|
||||||
filter_entity?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`
|
|
||||||
<ha-entity-state-content-picker
|
|
||||||
.hass=${this.hass}
|
|
||||||
.entityId=${this.selector.ui_state_content?.entity_id ||
|
|
||||||
this.context?.filter_entity}
|
|
||||||
.value=${this.value}
|
|
||||||
.label=${this.label}
|
|
||||||
.helper=${this.helper}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.required=${this.required}
|
|
||||||
></ha-entity-state-content-picker>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-selector-ui_state_content": HaSelectorUiStateContent;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -57,7 +57,6 @@ const LOAD_ELEMENTS = {
|
|||||||
color_temp: () => import("./ha-selector-color-temp"),
|
color_temp: () => import("./ha-selector-color-temp"),
|
||||||
ui_action: () => import("./ha-selector-ui-action"),
|
ui_action: () => import("./ha-selector-ui-action"),
|
||||||
ui_color: () => import("./ha-selector-ui-color"),
|
ui_color: () => import("./ha-selector-ui-color"),
|
||||||
ui_state_content: () => import("./ha-selector-ui-state-content"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const LEGACY_UI_SELECTORS = new Set(["ui-action", "ui-color"]);
|
const LEGACY_UI_SELECTORS = new Set(["ui-action", "ui-color"]);
|
||||||
|
@@ -65,8 +65,6 @@ interface ExtHassService extends Omit<HassService, "fields"> {
|
|||||||
Omit<HassService["fields"][string], "selector"> & {
|
Omit<HassService["fields"][string], "selector"> & {
|
||||||
key: string;
|
key: string;
|
||||||
selector?: Selector;
|
selector?: Selector;
|
||||||
fields?: Record<string, Omit<HassService["fields"][string], "selector">>;
|
|
||||||
collapsed?: boolean;
|
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
hasSelector: string[];
|
hasSelector: string[];
|
||||||
@@ -249,7 +247,20 @@ export class HaServiceControl extends LitElement {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
private _getTargetedEntities = memoizeOne((target, value) => {
|
private _filterFields = memoizeOne(
|
||||||
|
(serviceData: ExtHassService | undefined, value: this["value"]) =>
|
||||||
|
serviceData?.fields?.filter(
|
||||||
|
(field) =>
|
||||||
|
!field.filter ||
|
||||||
|
this._filterField(serviceData.target, field.filter, value)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
private _filterField(
|
||||||
|
target: ExtHassService["target"],
|
||||||
|
filter: ExtHassService["fields"][number]["filter"],
|
||||||
|
value: this["value"]
|
||||||
|
) {
|
||||||
const targetSelector = target ? { target } : { target: {} };
|
const targetSelector = target ? { target } : { target: {} };
|
||||||
const targetEntities =
|
const targetEntities =
|
||||||
ensureArray(
|
ensureArray(
|
||||||
@@ -319,13 +330,6 @@ export class HaServiceControl extends LitElement {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return targetEntities;
|
|
||||||
});
|
|
||||||
|
|
||||||
private _filterField(
|
|
||||||
filter: ExtHassService["fields"][number]["filter"],
|
|
||||||
targetEntities: string[]
|
|
||||||
) {
|
|
||||||
if (!targetEntities.length) {
|
if (!targetEntities.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -387,10 +391,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
serviceData?.fields.some((field) => showOptionalToggle(field))
|
serviceData?.fields.some((field) => showOptionalToggle(field))
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetEntities = this._getTargetedEntities(
|
const filteredFields = this._filterFields(serviceData, this._value);
|
||||||
serviceData?.target,
|
|
||||||
this._value
|
|
||||||
);
|
|
||||||
|
|
||||||
const domain = this._value?.service
|
const domain = this._value?.service
|
||||||
? computeDomain(this._value.service)
|
? computeDomain(this._value.service)
|
||||||
@@ -451,7 +452,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
>
|
>
|
||||||
<span slot="description"
|
<span slot="description"
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
"ui.components.service-control.target_secondary"
|
"ui.components.service-control.target_description"
|
||||||
)}</span
|
)}</span
|
||||||
><ha-selector
|
><ha-selector
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -478,123 +479,81 @@ export class HaServiceControl extends LitElement {
|
|||||||
${shouldRenderServiceDataYaml
|
${shouldRenderServiceDataYaml
|
||||||
? html`<ha-yaml-editor
|
? html`<ha-yaml-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize("ui.components.service-control.data")}
|
||||||
"ui.components.service-control.action_data"
|
|
||||||
)}
|
|
||||||
.name=${"data"}
|
.name=${"data"}
|
||||||
.readOnly=${this.disabled}
|
.readOnly=${this.disabled}
|
||||||
.defaultValue=${this._value?.data}
|
.defaultValue=${this._value?.data}
|
||||||
@value-changed=${this._dataChanged}
|
@value-changed=${this._dataChanged}
|
||||||
></ha-yaml-editor>`
|
></ha-yaml-editor>`
|
||||||
: serviceData?.fields.map((dataField) =>
|
: filteredFields?.map((dataField) => {
|
||||||
dataField.fields
|
const selector = dataField?.selector ?? { text: undefined };
|
||||||
? html`<ha-expansion-panel
|
const type = Object.keys(selector)[0];
|
||||||
leftChevron
|
const enhancedSelector = ["action", "condition", "trigger"].includes(
|
||||||
.expanded=${!dataField.collapsed}
|
type
|
||||||
.header=${this.hass.localize(
|
)
|
||||||
`component.${domain}.services.${serviceName}.sections.${dataField.key}.name`
|
? {
|
||||||
) ||
|
[type]: {
|
||||||
dataField.name ||
|
...selector[type],
|
||||||
dataField.key}
|
path: [dataField.key],
|
||||||
>
|
},
|
||||||
${Object.entries(dataField.fields).map(([key, field]) =>
|
}
|
||||||
this._renderField(
|
: selector;
|
||||||
{ key, ...field },
|
|
||||||
hasOptional,
|
const showOptional = showOptionalToggle(dataField);
|
||||||
domain,
|
|
||||||
serviceName,
|
return dataField.selector &&
|
||||||
targetEntities
|
(!dataField.advanced ||
|
||||||
)
|
this.showAdvanced ||
|
||||||
)}
|
(this._value?.data &&
|
||||||
</ha-expansion-panel>`
|
this._value.data[dataField.key] !== undefined))
|
||||||
: this._renderField(
|
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||||
dataField,
|
${!showOptional
|
||||||
hasOptional,
|
? hasOptional
|
||||||
domain,
|
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||||
serviceName,
|
: ""
|
||||||
targetEntities
|
: html`<ha-checkbox
|
||||||
)
|
.key=${dataField.key}
|
||||||
)} `;
|
.checked=${this._checkedKeys.has(dataField.key) ||
|
||||||
|
(this._value?.data &&
|
||||||
|
this._value.data[dataField.key] !== undefined)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._checkboxChanged}
|
||||||
|
slot="prefix"
|
||||||
|
></ha-checkbox>`}
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize(
|
||||||
|
`component.${domain}.services.${serviceName}.fields.${dataField.key}.name`
|
||||||
|
) ||
|
||||||
|
dataField.name ||
|
||||||
|
dataField.key}</span
|
||||||
|
>
|
||||||
|
<span slot="description"
|
||||||
|
>${this.hass.localize(
|
||||||
|
`component.${domain}.services.${serviceName}.fields.${dataField.key}.description`
|
||||||
|
) || dataField?.description}</span
|
||||||
|
>
|
||||||
|
<ha-selector
|
||||||
|
.disabled=${this.disabled ||
|
||||||
|
(showOptional &&
|
||||||
|
!this._checkedKeys.has(dataField.key) &&
|
||||||
|
(!this._value?.data ||
|
||||||
|
this._value.data[dataField.key] === undefined))}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${enhancedSelector}
|
||||||
|
.key=${dataField.key}
|
||||||
|
@value-changed=${this._serviceDataChanged}
|
||||||
|
.value=${this._value?.data
|
||||||
|
? this._value.data[dataField.key]
|
||||||
|
: undefined}
|
||||||
|
.placeholder=${dataField.default}
|
||||||
|
.localizeValue=${this._localizeValueCallback}
|
||||||
|
@item-moved=${this._itemMoved}
|
||||||
|
></ha-selector>
|
||||||
|
</ha-settings-row>`
|
||||||
|
: "";
|
||||||
|
})} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderField = (
|
|
||||||
dataField: ExtHassService["fields"][number],
|
|
||||||
hasOptional: boolean,
|
|
||||||
domain: string | undefined,
|
|
||||||
serviceName: string | undefined,
|
|
||||||
targetEntities: string[]
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
dataField.filter &&
|
|
||||||
!this._filterField(dataField.filter, targetEntities)
|
|
||||||
) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selector = dataField?.selector ?? { text: undefined };
|
|
||||||
const type = Object.keys(selector)[0];
|
|
||||||
const enhancedSelector = ["action", "condition", "trigger"].includes(type)
|
|
||||||
? {
|
|
||||||
[type]: {
|
|
||||||
...selector[type],
|
|
||||||
path: [dataField.key],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: selector;
|
|
||||||
|
|
||||||
const showOptional = showOptionalToggle(dataField);
|
|
||||||
|
|
||||||
return dataField.selector &&
|
|
||||||
(!dataField.advanced ||
|
|
||||||
this.showAdvanced ||
|
|
||||||
(this._value?.data && this._value.data[dataField.key] !== undefined))
|
|
||||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
|
||||||
${!showOptional
|
|
||||||
? hasOptional
|
|
||||||
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
|
||||||
: ""
|
|
||||||
: html`<ha-checkbox
|
|
||||||
.key=${dataField.key}
|
|
||||||
.checked=${this._checkedKeys.has(dataField.key) ||
|
|
||||||
(this._value?.data &&
|
|
||||||
this._value.data[dataField.key] !== undefined)}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
@change=${this._checkboxChanged}
|
|
||||||
slot="prefix"
|
|
||||||
></ha-checkbox>`}
|
|
||||||
<span slot="heading"
|
|
||||||
>${this.hass.localize(
|
|
||||||
`component.${domain}.services.${serviceName}.fields.${dataField.key}.name`
|
|
||||||
) ||
|
|
||||||
dataField.name ||
|
|
||||||
dataField.key}</span
|
|
||||||
>
|
|
||||||
<span slot="description"
|
|
||||||
>${this.hass.localize(
|
|
||||||
`component.${domain}.services.${serviceName}.fields.${dataField.key}.description`
|
|
||||||
) || dataField?.description}</span
|
|
||||||
>
|
|
||||||
<ha-selector
|
|
||||||
.disabled=${this.disabled ||
|
|
||||||
(showOptional &&
|
|
||||||
!this._checkedKeys.has(dataField.key) &&
|
|
||||||
(!this._value?.data ||
|
|
||||||
this._value.data[dataField.key] === undefined))}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.selector=${enhancedSelector}
|
|
||||||
.key=${dataField.key}
|
|
||||||
@value-changed=${this._serviceDataChanged}
|
|
||||||
.value=${this._value?.data
|
|
||||||
? this._value.data[dataField.key]
|
|
||||||
: undefined}
|
|
||||||
.placeholder=${dataField.default}
|
|
||||||
.localizeValue=${this._localizeValueCallback}
|
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
></ha-selector>
|
|
||||||
</ha-settings-row>`
|
|
||||||
: "";
|
|
||||||
};
|
|
||||||
|
|
||||||
private _localizeValueCallback = (key: string) => {
|
private _localizeValueCallback = (key: string) => {
|
||||||
if (!this._value?.service) {
|
if (!this._value?.service) {
|
||||||
return "";
|
return "";
|
||||||
@@ -880,11 +839,6 @@ export class HaServiceControl extends LitElement {
|
|||||||
.description p {
|
.description p {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
ha-expansion-panel {
|
|
||||||
--ha-card-border-radius: 0;
|
|
||||||
--expansion-panel-summary-padding: 0 16px;
|
|
||||||
--expansion-panel-content-padding: 0;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,7 +46,7 @@ class HaServicePicker extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-combo-box
|
<ha-combo-box
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.label=${this.hass.localize("ui.components.service-picker.action")}
|
.label=${this.hass.localize("ui.components.service-picker.service")}
|
||||||
.filteredItems=${this._filteredServices(
|
.filteredItems=${this._filteredServices(
|
||||||
this.hass.localize,
|
this.hass.localize,
|
||||||
this.hass.services,
|
this.hass.services,
|
||||||
|
@@ -210,8 +210,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _editStyleLoaded = false;
|
private _editStyleLoaded = false;
|
||||||
|
|
||||||
private _unsubPersistentNotifications: UnsubscribeFunc | undefined;
|
|
||||||
|
|
||||||
@storage({
|
@storage({
|
||||||
key: "sidebarPanelOrder",
|
key: "sidebarPanelOrder",
|
||||||
state: true,
|
state: true,
|
||||||
@@ -285,26 +283,15 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
hass.localize !== oldHass.localize ||
|
hass.localize !== oldHass.localize ||
|
||||||
hass.locale !== oldHass.locale ||
|
hass.locale !== oldHass.locale ||
|
||||||
hass.states !== oldHass.states ||
|
hass.states !== oldHass.states ||
|
||||||
hass.defaultPanel !== oldHass.defaultPanel ||
|
hass.defaultPanel !== oldHass.defaultPanel
|
||||||
hass.connected !== oldHass.connected
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this.subscribePersistentNotifications();
|
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||||
}
|
this._notifications = notifications;
|
||||||
|
});
|
||||||
private subscribePersistentNotifications(): void {
|
|
||||||
if (this._unsubPersistentNotifications) {
|
|
||||||
this._unsubPersistentNotifications();
|
|
||||||
}
|
|
||||||
this._unsubPersistentNotifications = subscribeNotifications(
|
|
||||||
this.hass.connection,
|
|
||||||
(notifications) => {
|
|
||||||
this._notifications = notifications;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps) {
|
protected updated(changedProps) {
|
||||||
@@ -319,14 +306,6 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
this.hass &&
|
|
||||||
changedProps.get("hass")?.connected === false &&
|
|
||||||
this.hass.connected === true
|
|
||||||
) {
|
|
||||||
this.subscribePersistentNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._calculateCounts();
|
this._calculateCounts();
|
||||||
|
|
||||||
if (!SUPPORT_SCROLL_IF_NEEDED) {
|
if (!SUPPORT_SCROLL_IF_NEEDED) {
|
||||||
|
@@ -15,7 +15,6 @@ export class HaSlider extends MdSlider {
|
|||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
--md-sys-color-primary: var(--primary-color);
|
--md-sys-color-primary: var(--primary-color);
|
||||||
--md-sys-color-on-primary: var(--text-primary-color);
|
|
||||||
--md-sys-color-outline: var(--outline-color);
|
--md-sys-color-outline: var(--outline-color);
|
||||||
--md-sys-color-on-surface: var(--primary-text-color);
|
--md-sys-color-on-surface: var(--primary-text-color);
|
||||||
--md-slider-handle-width: 14px;
|
--md-slider-handle-width: 14px;
|
||||||
|
@@ -483,12 +483,12 @@ export class HaMap extends ReactiveElement {
|
|||||||
const entityName =
|
const entityName =
|
||||||
typeof entity !== "string" && entity.label_mode === "state"
|
typeof entity !== "string" && entity.label_mode === "state"
|
||||||
? this.hass.formatEntityState(stateObj)
|
? this.hass.formatEntityState(stateObj)
|
||||||
: (customTitle ??
|
: customTitle ??
|
||||||
title
|
title
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.map((part) => part[0])
|
.map((part) => part[0])
|
||||||
.join("")
|
.join("")
|
||||||
.substr(0, 3));
|
.substr(0, 3);
|
||||||
|
|
||||||
// create marker with the icon
|
// create marker with the icon
|
||||||
const marker = Leaflet.marker([latitude, longitude], {
|
const marker = Leaflet.marker([latitude, longitude], {
|
||||||
|
@@ -17,7 +17,7 @@ export class HaTileIcon extends LitElement {
|
|||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
--tile-icon-color: var(--disabled-color);
|
--tile-icon-color: var(--disabled-color);
|
||||||
--mdc-icon-size: 22px;
|
--mdc-icon-size: 24px;
|
||||||
}
|
}
|
||||||
.shape::before {
|
.shape::before {
|
||||||
content: "";
|
content: "";
|
||||||
@@ -32,9 +32,9 @@ export class HaTileIcon extends LitElement {
|
|||||||
}
|
}
|
||||||
.shape {
|
.shape {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 36px;
|
width: 40px;
|
||||||
height: 36px;
|
height: 40px;
|
||||||
border-radius: 18px;
|
border-radius: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@@ -25,9 +25,9 @@ export class HaTileImage extends LitElement {
|
|||||||
return css`
|
return css`
|
||||||
.image {
|
.image {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 36px;
|
width: 40px;
|
||||||
height: 36px;
|
height: 40px;
|
||||||
border-radius: 18px;
|
border-radius: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: none;
|
flex: none;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@@ -33,7 +33,7 @@ export class HaTileInfo extends LitElement {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 36px;
|
min-height: 40px;
|
||||||
}
|
}
|
||||||
span {
|
span {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@@ -352,22 +352,6 @@ export const saveAutomationConfig = (
|
|||||||
config: AutomationConfig
|
config: AutomationConfig
|
||||||
) => hass.callApi<void>("POST", `config/automation/config/${id}`, config);
|
) => hass.callApi<void>("POST", `config/automation/config/${id}`, config);
|
||||||
|
|
||||||
export const normalizeAutomationConfig = <
|
|
||||||
T extends Partial<AutomationConfig> | AutomationConfig,
|
|
||||||
>(
|
|
||||||
config: T
|
|
||||||
): T => {
|
|
||||||
// Normalize data: ensure trigger, action and condition are lists
|
|
||||||
// Happens when people copy paste their automations into the config
|
|
||||||
for (const key of ["trigger", "condition", "action"]) {
|
|
||||||
const value = config[key];
|
|
||||||
if (value && !Array.isArray(value)) {
|
|
||||||
config[key] = [value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showAutomationEditor = (data?: Partial<AutomationConfig>) => {
|
export const showAutomationEditor = (data?: Partial<AutomationConfig>) => {
|
||||||
initialAutomationEditorData = data;
|
initialAutomationEditorData = data;
|
||||||
navigate("/config/automation/edit/new");
|
navigate("/config/automation/edit/new");
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { ManualAutomationConfig } from "./automation";
|
|
||||||
import { ManualScriptConfig } from "./script";
|
|
||||||
import { Selector } from "./selector";
|
import { Selector } from "./selector";
|
||||||
|
|
||||||
export type BlueprintDomain = "automation" | "script";
|
export type BlueprintDomain = "automation" | "script";
|
||||||
@@ -44,11 +42,6 @@ export interface BlueprintImportResult {
|
|||||||
validation_errors: string[] | null;
|
validation_errors: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlueprintSubstituteResults {
|
|
||||||
automation: { substituted_config: ManualAutomationConfig };
|
|
||||||
script: { substituted_config: ManualScriptConfig };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchBlueprints = (hass: HomeAssistant, domain: BlueprintDomain) =>
|
export const fetchBlueprints = (hass: HomeAssistant, domain: BlueprintDomain) =>
|
||||||
hass.callWS<Blueprints>({ type: "blueprint/list", domain });
|
hass.callWS<Blueprints>({ type: "blueprint/list", domain });
|
||||||
|
|
||||||
@@ -98,18 +91,3 @@ export const getBlueprintSourceType = (
|
|||||||
}
|
}
|
||||||
return "community";
|
return "community";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const substituteBlueprint = <
|
|
||||||
T extends BlueprintDomain = BlueprintDomain,
|
|
||||||
>(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
domain: T,
|
|
||||||
path: string,
|
|
||||||
input: Record<string, any>
|
|
||||||
) =>
|
|
||||||
hass.callWS<BlueprintSubstituteResults[T]>({
|
|
||||||
type: "blueprint/substitute",
|
|
||||||
domain,
|
|
||||||
path,
|
|
||||||
input,
|
|
||||||
});
|
|
||||||
|
@@ -28,14 +28,13 @@ export type HvacMode = (typeof HVAC_MODES)[number];
|
|||||||
export const CLIMATE_PRESET_NONE = "none";
|
export const CLIMATE_PRESET_NONE = "none";
|
||||||
|
|
||||||
export type HvacAction =
|
export type HvacAction =
|
||||||
| "cooling"
|
|
||||||
| "defrosting"
|
|
||||||
| "drying"
|
|
||||||
| "fan"
|
|
||||||
| "heating"
|
|
||||||
| "idle"
|
|
||||||
| "off"
|
| "off"
|
||||||
| "preheating";
|
| "preheating"
|
||||||
|
| "heating"
|
||||||
|
| "cooling"
|
||||||
|
| "drying"
|
||||||
|
| "idle"
|
||||||
|
| "fan";
|
||||||
|
|
||||||
export type ClimateEntity = HassEntityBase & {
|
export type ClimateEntity = HassEntityBase & {
|
||||||
attributes: HassEntityAttributeBase & {
|
attributes: HassEntityAttributeBase & {
|
||||||
@@ -90,13 +89,12 @@ export const compareClimateHvacModes = (mode1: HvacMode, mode2: HvacMode) =>
|
|||||||
|
|
||||||
export const CLIMATE_HVAC_ACTION_TO_MODE: Record<HvacAction, HvacMode> = {
|
export const CLIMATE_HVAC_ACTION_TO_MODE: Record<HvacAction, HvacMode> = {
|
||||||
cooling: "cool",
|
cooling: "cool",
|
||||||
defrosting: "heat",
|
|
||||||
drying: "dry",
|
drying: "dry",
|
||||||
fan: "fan_only",
|
fan: "fan_only",
|
||||||
|
preheating: "heat",
|
||||||
heating: "heat",
|
heating: "heat",
|
||||||
idle: "off",
|
idle: "off",
|
||||||
off: "off",
|
off: "off",
|
||||||
preheating: "heat",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CLIMATE_HVAC_MODE_ICONS: Record<HvacMode, string> = {
|
export const CLIMATE_HVAC_MODE_ICONS: Record<HvacMode, string> = {
|
||||||
|
@@ -115,8 +115,8 @@ export function computeCoverPositionStateDisplay(
|
|||||||
position?: number
|
position?: number
|
||||||
) {
|
) {
|
||||||
const statePosition = stateActive(stateObj)
|
const statePosition = stateActive(stateObj)
|
||||||
? (stateObj.attributes.current_position ??
|
? stateObj.attributes.current_position ??
|
||||||
stateObj.attributes.current_tilt_position)
|
stateObj.attributes.current_tilt_position
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const currentPosition = position ?? statePosition;
|
const currentPosition = position ?? statePosition;
|
||||||
|
@@ -5,7 +5,6 @@ import type {
|
|||||||
EntityRegistryDisplayEntry,
|
EntityRegistryDisplayEntry,
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
} from "./entity_registry";
|
} from "./entity_registry";
|
||||||
import { ConfigEntry } from "./config_entries";
|
|
||||||
import type { EntitySources } from "./entity_sources";
|
import type { EntitySources } from "./entity_sources";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -20,7 +19,6 @@ export interface DeviceRegistryEntry {
|
|||||||
identifiers: Array<[string, string]>;
|
identifiers: Array<[string, string]>;
|
||||||
manufacturer: string | null;
|
manufacturer: string | null;
|
||||||
model: string | null;
|
model: string | null;
|
||||||
model_id: string | null;
|
|
||||||
name: string | null;
|
name: string | null;
|
||||||
labels: string[];
|
labels: string[];
|
||||||
sw_version: string | null;
|
sw_version: string | null;
|
||||||
@@ -144,11 +142,9 @@ export const getDeviceEntityDisplayLookup = (
|
|||||||
|
|
||||||
export const getDeviceIntegrationLookup = (
|
export const getDeviceIntegrationLookup = (
|
||||||
entitySources: EntitySources,
|
entitySources: EntitySources,
|
||||||
entities: EntityRegistryDisplayEntry[] | EntityRegistryEntry[],
|
entities: EntityRegistryDisplayEntry[] | EntityRegistryEntry[]
|
||||||
devices?: DeviceRegistryEntry[],
|
): Record<string, string[]> => {
|
||||||
configEntries?: ConfigEntry[]
|
const deviceIntegrations: Record<string, string[]> = {};
|
||||||
): Record<string, Set<string>> => {
|
|
||||||
const deviceIntegrations: Record<string, Set<string>> = {};
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
const source = entitySources[entity.entity_id];
|
const source = entitySources[entity.entity_id];
|
||||||
@@ -156,22 +152,10 @@ export const getDeviceIntegrationLookup = (
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceIntegrations[entity.device_id!] =
|
if (!deviceIntegrations[entity.device_id!]) {
|
||||||
deviceIntegrations[entity.device_id!] || new Set<string>();
|
deviceIntegrations[entity.device_id!] = [];
|
||||||
deviceIntegrations[entity.device_id!].add(source.domain);
|
|
||||||
}
|
|
||||||
// Lookup devices that have no entities
|
|
||||||
if (devices && configEntries) {
|
|
||||||
for (const device of devices) {
|
|
||||||
for (const config_entry_id of device.config_entries) {
|
|
||||||
const entry = configEntries.find((e) => e.entry_id === config_entry_id);
|
|
||||||
if (entry?.domain) {
|
|
||||||
deviceIntegrations[device.id] =
|
|
||||||
deviceIntegrations[device.id] || new Set<string>();
|
|
||||||
deviceIntegrations[device.id].add(entry.domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
deviceIntegrations[entity.device_id!].push(source.domain);
|
||||||
}
|
}
|
||||||
return deviceIntegrations;
|
return deviceIntegrations;
|
||||||
};
|
};
|
||||||
|
@@ -17,8 +17,6 @@ export const enum FanEntityFeature {
|
|||||||
OSCILLATE = 2,
|
OSCILLATE = 2,
|
||||||
DIRECTION = 4,
|
DIRECTION = 4,
|
||||||
PRESET_MODE = 8,
|
PRESET_MODE = 8,
|
||||||
TURN_OFF = 16,
|
|
||||||
TURN_ON = 32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FanEntityAttributes extends HassEntityAttributeBase {
|
interface FanEntityAttributes extends HassEntityAttributeBase {
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
HassEntityAttributeBase,
|
HassEntityAttributeBase,
|
||||||
HassEntityBase,
|
HassEntityBase,
|
||||||
|
UnsubscribeFunc,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { computeDomain } from "../common/entity/compute_domain";
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
interface GroupEntityAttributes extends HassEntityAttributeBase {
|
interface GroupEntityAttributes extends HassEntityAttributeBase {
|
||||||
entity_id: string[];
|
entity_id: string[];
|
||||||
@@ -15,6 +17,11 @@ export interface GroupEntity extends HassEntityBase {
|
|||||||
attributes: GroupEntityAttributes;
|
attributes: GroupEntityAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GroupPreview {
|
||||||
|
state: string;
|
||||||
|
attributes: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
export const computeGroupDomain = (
|
export const computeGroupDomain = (
|
||||||
stateObj: GroupEntity
|
stateObj: GroupEntity
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
@@ -24,3 +31,17 @@ export const computeGroupDomain = (
|
|||||||
];
|
];
|
||||||
return uniqueDomains.length === 1 ? uniqueDomains[0] : undefined;
|
return uniqueDomains.length === 1 ? uniqueDomains[0] : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const subscribePreviewGroup = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
flow_id: string,
|
||||||
|
flow_type: "config_flow" | "options_flow",
|
||||||
|
user_input: Record<string, any>,
|
||||||
|
callback: (preview: GroupPreview) => void
|
||||||
|
): Promise<UnsubscribeFunc> =>
|
||||||
|
hass.connection.subscribeMessage(callback, {
|
||||||
|
type: "group/start_preview",
|
||||||
|
flow_id,
|
||||||
|
flow_type,
|
||||||
|
user_input,
|
||||||
|
});
|
||||||
|
@@ -3,10 +3,9 @@ import {
|
|||||||
getCollection,
|
getCollection,
|
||||||
HassEventBase,
|
HassEventBase,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { HuiBadge } from "../panels/lovelace/badges/hui-badge";
|
|
||||||
import type { HuiCard } from "../panels/lovelace/cards/hui-card";
|
import type { HuiCard } from "../panels/lovelace/cards/hui-card";
|
||||||
import type { HuiSection } from "../panels/lovelace/sections/hui-section";
|
import type { HuiSection } from "../panels/lovelace/sections/hui-section";
|
||||||
import { Lovelace } from "../panels/lovelace/types";
|
import { Lovelace, LovelaceBadge } from "../panels/lovelace/types";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { LovelaceSectionConfig } from "./lovelace/config/section";
|
import { LovelaceSectionConfig } from "./lovelace/config/section";
|
||||||
import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types";
|
import { fetchConfig, LegacyLovelaceConfig } from "./lovelace/config/types";
|
||||||
@@ -22,7 +21,7 @@ export interface LovelaceViewElement extends HTMLElement {
|
|||||||
narrow?: boolean;
|
narrow?: boolean;
|
||||||
index?: number;
|
index?: number;
|
||||||
cards?: HuiCard[];
|
cards?: HuiCard[];
|
||||||
badges?: HuiBadge[];
|
badges?: LovelaceBadge[];
|
||||||
sections?: HuiSection[];
|
sections?: HuiSection[];
|
||||||
isStrategy: boolean;
|
isStrategy: boolean;
|
||||||
setConfig(config: LovelaceViewConfig): void;
|
setConfig(config: LovelaceViewConfig): void;
|
||||||
|
@@ -1,12 +1,4 @@
|
|||||||
import { Condition } from "../../../panels/lovelace/common/validate-condition";
|
|
||||||
|
|
||||||
export interface LovelaceBadgeConfig {
|
export interface LovelaceBadgeConfig {
|
||||||
type?: string;
|
type?: string;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
visibility?: Condition[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultBadgeConfig = (entity_id: string): LovelaceBadgeConfig => ({
|
|
||||||
type: "entity",
|
|
||||||
entity: entity_id,
|
|
||||||
});
|
|
||||||
|
@@ -27,7 +27,7 @@ export interface LovelaceBaseViewConfig {
|
|||||||
|
|
||||||
export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
|
export interface LovelaceViewConfig extends LovelaceBaseViewConfig {
|
||||||
type?: string;
|
type?: string;
|
||||||
badges?: (string | LovelaceBadgeConfig)[]; // Badge can be just an entity_id
|
badges?: Array<string | LovelaceBadgeConfig>;
|
||||||
cards?: LovelaceCardConfig[];
|
cards?: LovelaceCardConfig[];
|
||||||
sections?: LovelaceSectionRawConfig[];
|
sections?: LovelaceSectionRawConfig[];
|
||||||
}
|
}
|
||||||
|
@@ -8,14 +8,6 @@ export interface CustomCardEntry {
|
|||||||
documentationURL?: string;
|
documentationURL?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CustomBadgeEntry {
|
|
||||||
type: string;
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
preview?: boolean;
|
|
||||||
documentationURL?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomCardFeatureEntry {
|
export interface CustomCardFeatureEntry {
|
||||||
type: string;
|
type: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -26,7 +18,6 @@ export interface CustomCardFeatureEntry {
|
|||||||
export interface CustomCardsWindow {
|
export interface CustomCardsWindow {
|
||||||
customCards?: CustomCardEntry[];
|
customCards?: CustomCardEntry[];
|
||||||
customCardFeatures?: CustomCardFeatureEntry[];
|
customCardFeatures?: CustomCardFeatureEntry[];
|
||||||
customBadges?: CustomBadgeEntry[];
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use customCardFeatures
|
* @deprecated Use customCardFeatures
|
||||||
*/
|
*/
|
||||||
@@ -43,9 +34,6 @@ if (!("customCards" in customCardsWindow)) {
|
|||||||
if (!("customCardFeatures" in customCardsWindow)) {
|
if (!("customCardFeatures" in customCardsWindow)) {
|
||||||
customCardsWindow.customCardFeatures = [];
|
customCardsWindow.customCardFeatures = [];
|
||||||
}
|
}
|
||||||
if (!("customBadges" in customCardsWindow)) {
|
|
||||||
customCardsWindow.customBadges = [];
|
|
||||||
}
|
|
||||||
if (!("customTileFeatures" in customCardsWindow)) {
|
if (!("customTileFeatures" in customCardsWindow)) {
|
||||||
customCardsWindow.customTileFeatures = [];
|
customCardsWindow.customTileFeatures = [];
|
||||||
}
|
}
|
||||||
@@ -55,14 +43,10 @@ export const getCustomCardFeatures = () => [
|
|||||||
...customCardsWindow.customCardFeatures!,
|
...customCardsWindow.customCardFeatures!,
|
||||||
...customCardsWindow.customTileFeatures!,
|
...customCardsWindow.customTileFeatures!,
|
||||||
];
|
];
|
||||||
export const customBadges = customCardsWindow.customBadges!;
|
|
||||||
|
|
||||||
export const getCustomCardEntry = (type: string) =>
|
export const getCustomCardEntry = (type: string) =>
|
||||||
customCards.find((card) => card.type === type);
|
customCards.find((card) => card.type === type);
|
||||||
|
|
||||||
export const getCustomBadgeEntry = (type: string) =>
|
|
||||||
customBadges.find((badge) => badge.type === type);
|
|
||||||
|
|
||||||
export const isCustomType = (type: string) =>
|
export const isCustomType = (type: string) =>
|
||||||
type.startsWith(CUSTOM_TYPE_PREFIX);
|
type.startsWith(CUSTOM_TYPE_PREFIX);
|
||||||
|
|
||||||
|
@@ -5,44 +5,33 @@ export interface OTBRInfo {
|
|||||||
border_agent_id: string;
|
border_agent_id: string;
|
||||||
channel: number;
|
channel: number;
|
||||||
extended_address: string;
|
extended_address: string;
|
||||||
extended_pan_id: string;
|
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OTBRInfoDict = Record<string, OTBRInfo>;
|
export const getOTBRInfo = (hass: HomeAssistant): Promise<OTBRInfo> =>
|
||||||
|
|
||||||
export const getOTBRInfo = (hass: HomeAssistant): Promise<OTBRInfoDict> =>
|
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "otbr/info",
|
type: "otbr/info",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const OTBRCreateNetwork = (
|
export const OTBRCreateNetwork = (hass: HomeAssistant): Promise<void> =>
|
||||||
hass: HomeAssistant,
|
|
||||||
extended_address: string
|
|
||||||
): Promise<void> =>
|
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "otbr/create_network",
|
type: "otbr/create_network",
|
||||||
extended_address,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const OTBRSetNetwork = (
|
export const OTBRSetNetwork = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
extended_address: string,
|
|
||||||
dataset_id: string
|
dataset_id: string
|
||||||
): Promise<void> =>
|
): Promise<void> =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "otbr/set_network",
|
type: "otbr/set_network",
|
||||||
extended_address,
|
|
||||||
dataset_id,
|
dataset_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const OTBRSetChannel = (
|
export const OTBRSetChannel = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
extended_address: string,
|
|
||||||
channel: number
|
channel: number
|
||||||
): Promise<{ delay: number }> =>
|
): Promise<{ delay: number }> =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "otbr/set_channel",
|
type: "otbr/set_channel",
|
||||||
extended_address,
|
|
||||||
channel,
|
channel,
|
||||||
});
|
});
|
||||||
|
@@ -2,8 +2,6 @@ import type { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import { ensureArray } from "../common/array/ensure-array";
|
import { ensureArray } from "../common/array/ensure-array";
|
||||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
|
||||||
import { isHelperDomain } from "../panels/config/helpers/const";
|
|
||||||
import { UiAction } from "../panels/lovelace/components/hui-action-editor";
|
import { UiAction } from "../panels/lovelace/components/hui-action-editor";
|
||||||
import { HomeAssistant, ItemPath } from "../types";
|
import { HomeAssistant, ItemPath } from "../types";
|
||||||
import {
|
import {
|
||||||
@@ -15,6 +13,8 @@ import {
|
|||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
} from "./entity_registry";
|
} from "./entity_registry";
|
||||||
import { EntitySources } from "./entity_sources";
|
import { EntitySources } from "./entity_sources";
|
||||||
|
import { isHelperDomain } from "../panels/config/helpers/const";
|
||||||
|
import type { CropOptions } from "../dialogs/image-cropper-dialog/show-image-cropper-dialog";
|
||||||
|
|
||||||
export type Selector =
|
export type Selector =
|
||||||
| ActionSelector
|
| ActionSelector
|
||||||
@@ -64,8 +64,7 @@ export type Selector =
|
|||||||
| TTSSelector
|
| TTSSelector
|
||||||
| TTSVoiceSelector
|
| TTSVoiceSelector
|
||||||
| UiActionSelector
|
| UiActionSelector
|
||||||
| UiColorSelector
|
| UiColorSelector;
|
||||||
| UiStateContentSelector;
|
|
||||||
|
|
||||||
export interface ActionSelector {
|
export interface ActionSelector {
|
||||||
action: {
|
action: {
|
||||||
@@ -456,13 +455,6 @@ export interface UiColorSelector {
|
|||||||
ui_color: { default_color?: boolean } | null;
|
ui_color: { default_color?: boolean } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UiStateContentSelector {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
ui_state_content: {
|
|
||||||
entity_id?: string;
|
|
||||||
} | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const expandLabelTarget = (
|
export const expandLabelTarget = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
labelId: string,
|
labelId: string,
|
||||||
@@ -704,7 +696,7 @@ export const entityMeetsTargetSelector = (
|
|||||||
export const filterSelectorDevices = (
|
export const filterSelectorDevices = (
|
||||||
filterDevice: DeviceSelectorFilter,
|
filterDevice: DeviceSelectorFilter,
|
||||||
device: DeviceRegistryEntry,
|
device: DeviceRegistryEntry,
|
||||||
deviceIntegrationLookup?: Record<string, Set<string>> | undefined
|
deviceIntegrationLookup?: Record<string, string[]> | undefined
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const {
|
const {
|
||||||
manufacturer: filterManufacturer,
|
manufacturer: filterManufacturer,
|
||||||
@@ -721,7 +713,7 @@ export const filterSelectorDevices = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (filterIntegration && deviceIntegrationLookup) {
|
if (filterIntegration && deviceIntegrationLookup) {
|
||||||
if (!deviceIntegrationLookup?.[device.id]?.has(filterIntegration)) {
|
if (!deviceIntegrationLookup?.[device.id]?.includes(filterIntegration)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +1,21 @@
|
|||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
const HAS_CUSTOM_PREVIEW = ["template"];
|
export interface ThresholdPreview {
|
||||||
|
|
||||||
export interface GenericPreview {
|
|
||||||
state: string;
|
state: string;
|
||||||
attributes: Record<string, any>;
|
attributes: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const subscribePreviewGeneric = (
|
export const subscribePreviewThreshold = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
domain: string,
|
|
||||||
flow_id: string,
|
flow_id: string,
|
||||||
flow_type: "config_flow" | "options_flow",
|
flow_type: "config_flow" | "options_flow",
|
||||||
user_input: Record<string, any>,
|
user_input: Record<string, any>,
|
||||||
callback: (preview: GenericPreview) => void
|
callback: (preview: ThresholdPreview) => void
|
||||||
): Promise<UnsubscribeFunc> =>
|
): Promise<UnsubscribeFunc> =>
|
||||||
hass.connection.subscribeMessage(callback, {
|
hass.connection.subscribeMessage(callback, {
|
||||||
type: `${domain}/start_preview`,
|
type: "threshold/start_preview",
|
||||||
flow_id,
|
flow_id,
|
||||||
flow_type,
|
flow_type,
|
||||||
user_input,
|
user_input,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const previewModule = (domain: string): string =>
|
|
||||||
HAS_CUSTOM_PREVIEW.includes(domain) ? domain : "generic";
|
|
21
src/data/time_date.ts
Normal file
21
src/data/time_date.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface TimeDatePreview {
|
||||||
|
state: string;
|
||||||
|
attributes: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const subscribePreviewTimeDate = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
flow_id: string,
|
||||||
|
flow_type: "config_flow" | "options_flow",
|
||||||
|
user_input: Record<string, any>,
|
||||||
|
callback: (preview: TimeDatePreview) => void
|
||||||
|
): Promise<UnsubscribeFunc> =>
|
||||||
|
hass.connection.subscribeMessage(callback, {
|
||||||
|
type: "time_date/start_preview",
|
||||||
|
flow_id,
|
||||||
|
flow_type,
|
||||||
|
user_input,
|
||||||
|
});
|
@@ -92,7 +92,7 @@ export const computeDisplayTimer = (
|
|||||||
return hass.formatEntityState(stateObj);
|
return hass.formatEntityState(stateObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
let display = secondsToDuration(timeRemaining || 0) || "0";
|
let display = secondsToDuration(timeRemaining || 0);
|
||||||
|
|
||||||
if (stateObj.state === "paused") {
|
if (stateObj.state === "paused") {
|
||||||
display = `${display} (${hass.formatEntityState(stateObj)})`;
|
display = `${display} (${hass.formatEntityState(stateObj)})`;
|
||||||
|
90
src/dialogs/config-flow/previews/flow-preview-group.ts
Normal file
90
src/dialogs/config-flow/previews/flow-preview-group.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import { LitElement, html } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { FlowType } from "../../../data/data_entry_flow";
|
||||||
|
import { GroupPreview, subscribePreviewGroup } from "../../../data/group";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import "./entity-preview-row";
|
||||||
|
import { debounce } from "../../../common/util/debounce";
|
||||||
|
|
||||||
|
@customElement("flow-preview-group")
|
||||||
|
class FlowPreviewGroup extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public flowType!: FlowType;
|
||||||
|
|
||||||
|
public handler!: string;
|
||||||
|
|
||||||
|
@property() public stepId!: string;
|
||||||
|
|
||||||
|
@property() public flowId!: string;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stepData!: Record<string, any>;
|
||||||
|
|
||||||
|
@state() private _preview?: HassEntity;
|
||||||
|
|
||||||
|
private _unsub?: Promise<UnsubscribeFunc>;
|
||||||
|
|
||||||
|
disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (this._unsub) {
|
||||||
|
this._unsub.then((unsub) => unsub());
|
||||||
|
this._unsub = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
willUpdate(changedProps) {
|
||||||
|
if (changedProps.has("stepData")) {
|
||||||
|
this._debouncedSubscribePreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`<entity-preview-row
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${this._preview}
|
||||||
|
></entity-preview-row>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setPreview = (preview: GroupPreview) => {
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
this._preview = {
|
||||||
|
entity_id: `${this.stepId}.___flow_preview___`,
|
||||||
|
last_changed: now,
|
||||||
|
last_updated: now,
|
||||||
|
context: { id: "", parent_id: null, user_id: null },
|
||||||
|
...preview,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _debouncedSubscribePreview = debounce(() => {
|
||||||
|
this._subscribePreview();
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
private async _subscribePreview() {
|
||||||
|
if (this._unsub) {
|
||||||
|
(await this._unsub)();
|
||||||
|
this._unsub = undefined;
|
||||||
|
}
|
||||||
|
if (this.flowType === "repair_flow") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this._unsub = subscribePreviewGroup(
|
||||||
|
this.hass,
|
||||||
|
this.flowId,
|
||||||
|
this.flowType,
|
||||||
|
this.stepData,
|
||||||
|
this._setPreview
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
this._preview = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"flow-preview-group": FlowPreviewGroup;
|
||||||
|
}
|
||||||
|
}
|
@@ -2,22 +2,23 @@ import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
|||||||
import { LitElement, html } from "lit";
|
import { LitElement, html } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { FlowType } from "../../../data/data_entry_flow";
|
import { FlowType } from "../../../data/data_entry_flow";
|
||||||
import { GenericPreview, subscribePreviewGeneric } from "../../../data/preview";
|
import {
|
||||||
|
ThresholdPreview,
|
||||||
|
subscribePreviewThreshold,
|
||||||
|
} from "../../../data/threshold";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import "./entity-preview-row";
|
import "./entity-preview-row";
|
||||||
import { debounce } from "../../../common/util/debounce";
|
import { debounce } from "../../../common/util/debounce";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
|
||||||
@customElement("flow-preview-generic")
|
@customElement("flow-preview-threshold")
|
||||||
class FlowPreviewGeneric extends LitElement {
|
class FlowPreviewThreshold extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public flowType!: FlowType;
|
@property() public flowType!: FlowType;
|
||||||
|
|
||||||
public handler!: string;
|
public handler!: string;
|
||||||
|
|
||||||
@property() public domain!: string;
|
|
||||||
|
|
||||||
@property() public stepId!: string;
|
@property() public stepId!: string;
|
||||||
|
|
||||||
@property() public flowId!: string;
|
@property() public flowId!: string;
|
||||||
@@ -54,7 +55,7 @@ class FlowPreviewGeneric extends LitElement {
|
|||||||
></entity-preview-row>`;
|
></entity-preview-row>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setPreview = (preview: GenericPreview) => {
|
private _setPreview = (preview: ThresholdPreview) => {
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
this._preview = {
|
this._preview = {
|
||||||
entity_id: `${this.stepId}.___flow_preview___`,
|
entity_id: `${this.stepId}.___flow_preview___`,
|
||||||
@@ -78,14 +79,14 @@ class FlowPreviewGeneric extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
this._unsub = subscribePreviewGeneric(
|
this._unsub = subscribePreviewThreshold(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.domain,
|
|
||||||
this.flowId,
|
this.flowId,
|
||||||
this.flowType,
|
this.flowType,
|
||||||
this.stepData,
|
this.stepData,
|
||||||
this._setPreview
|
this._setPreview
|
||||||
);
|
);
|
||||||
|
await this._unsub;
|
||||||
fireEvent(this, "set-flow-errors", { errors: {} });
|
fireEvent(this, "set-flow-errors", { errors: {} });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (typeof err.message === "string") {
|
if (typeof err.message === "string") {
|
||||||
@@ -102,6 +103,6 @@ class FlowPreviewGeneric extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"flow-preview-generic": FlowPreviewGeneric;
|
"flow-preview-threshold": FlowPreviewThreshold;
|
||||||
}
|
}
|
||||||
}
|
}
|
94
src/dialogs/config-flow/previews/flow-preview-time_date.ts
Normal file
94
src/dialogs/config-flow/previews/flow-preview-time_date.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import { LitElement, html } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { FlowType } from "../../../data/data_entry_flow";
|
||||||
|
import {
|
||||||
|
TimeDatePreview,
|
||||||
|
subscribePreviewTimeDate,
|
||||||
|
} from "../../../data/time_date";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import "./entity-preview-row";
|
||||||
|
import { debounce } from "../../../common/util/debounce";
|
||||||
|
|
||||||
|
@customElement("flow-preview-time_date")
|
||||||
|
class FlowPreviewTimeDate extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() public flowType!: FlowType;
|
||||||
|
|
||||||
|
public handler!: string;
|
||||||
|
|
||||||
|
@property() public stepId!: string;
|
||||||
|
|
||||||
|
@property() public flowId!: string;
|
||||||
|
|
||||||
|
@property() public stepData!: Record<string, any>;
|
||||||
|
|
||||||
|
@state() private _preview?: HassEntity;
|
||||||
|
|
||||||
|
private _unsub?: Promise<UnsubscribeFunc>;
|
||||||
|
|
||||||
|
disconnectedCallback(): void {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (this._unsub) {
|
||||||
|
this._unsub.then((unsub) => unsub());
|
||||||
|
this._unsub = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
willUpdate(changedProps) {
|
||||||
|
if (changedProps.has("stepData")) {
|
||||||
|
this._debouncedSubscribePreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`<entity-preview-row
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${this._preview}
|
||||||
|
></entity-preview-row>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _setPreview = (preview: TimeDatePreview) => {
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
this._preview = {
|
||||||
|
entity_id: `${this.stepId}.___flow_preview___`,
|
||||||
|
last_changed: now,
|
||||||
|
last_updated: now,
|
||||||
|
context: { id: "", parent_id: null, user_id: null },
|
||||||
|
...preview,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _debouncedSubscribePreview = debounce(() => {
|
||||||
|
this._subscribePreview();
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
private async _subscribePreview() {
|
||||||
|
if (this._unsub) {
|
||||||
|
(await this._unsub)();
|
||||||
|
this._unsub = undefined;
|
||||||
|
}
|
||||||
|
if (this.flowType === "repair_flow") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this._unsub = subscribePreviewTimeDate(
|
||||||
|
this.hass,
|
||||||
|
this.flowId,
|
||||||
|
this.flowType,
|
||||||
|
this.stepData,
|
||||||
|
this._setPreview
|
||||||
|
);
|
||||||
|
await this._unsub;
|
||||||
|
} catch (err) {
|
||||||
|
this._preview = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"flow-preview-time_date": FlowPreviewTimeDate;
|
||||||
|
}
|
||||||
|
}
|
@@ -25,7 +25,6 @@ import type { HomeAssistant } from "../../types";
|
|||||||
import type { FlowConfig } from "./show-dialog-data-entry-flow";
|
import type { FlowConfig } from "./show-dialog-data-entry-flow";
|
||||||
import { configFlowContentStyles } from "./styles";
|
import { configFlowContentStyles } from "./styles";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import { previewModule } from "../../data/preview";
|
|
||||||
|
|
||||||
@customElement("step-flow-form")
|
@customElement("step-flow-form")
|
||||||
class StepFlowForm extends LitElement {
|
class StepFlowForm extends LitElement {
|
||||||
@@ -77,9 +76,8 @@ class StepFlowForm extends LitElement {
|
|||||||
"ui.panel.config.integrations.config_flow.preview"
|
"ui.panel.config.integrations.config_flow.preview"
|
||||||
)}:
|
)}:
|
||||||
</h3>
|
</h3>
|
||||||
${dynamicElement(`flow-preview-${previewModule(step.preview)}`, {
|
${dynamicElement(`flow-preview-${this.step.preview}`, {
|
||||||
hass: this.hass,
|
hass: this.hass,
|
||||||
domain: step.preview,
|
|
||||||
flowType: this.flowConfig.flowType,
|
flowType: this.flowConfig.flowType,
|
||||||
handler: step.handler,
|
handler: step.handler,
|
||||||
stepId: step.step_id,
|
stepId: step.step_id,
|
||||||
@@ -122,7 +120,7 @@ class StepFlowForm extends LitElement {
|
|||||||
protected willUpdate(changedProps: PropertyValues): void {
|
protected willUpdate(changedProps: PropertyValues): void {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
if (changedProps.has("step") && this.step?.preview) {
|
if (changedProps.has("step") && this.step?.preview) {
|
||||||
import(`./previews/flow-preview-${previewModule(this.step.preview)}`);
|
import(`./previews/flow-preview-${this.step.preview}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -325,7 +325,7 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
${!__DEMO__ && isAdmin
|
${isAdmin
|
||||||
? html`
|
? html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
slot="actionItems"
|
slot="actionItems"
|
||||||
|
@@ -75,13 +75,11 @@ export class MoreInfoHistory extends LitElement {
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
${this.hass.localize("ui.dialogs.more_info_control.history")}
|
${this.hass.localize("ui.dialogs.more_info_control.history")}
|
||||||
</div>
|
</div>
|
||||||
${__DEMO__
|
<a href=${this._showMoreHref} @click=${this._close}
|
||||||
? nothing
|
>${this.hass.localize(
|
||||||
: html`<a href=${this._showMoreHref} @click=${this._close}
|
"ui.dialogs.more_info_control.show_more"
|
||||||
>${this.hass.localize(
|
)}</a
|
||||||
"ui.dialogs.more_info_control.show_more"
|
>
|
||||||
)}</a
|
|
||||||
>`}
|
|
||||||
</div>
|
</div>
|
||||||
${this._error
|
${this._error
|
||||||
? html`<div class="errors">${this._error}</div>`
|
? html`<div class="errors">${this._error}</div>`
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../resources/compatibility";
|
import "../resources/compatibility";
|
||||||
import "../auth/ha-authorize";
|
import "../auth/ha-authorize";
|
||||||
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
|
|
||||||
import("../resources/ha-style");
|
import("../resources/ha-style");
|
||||||
import("@polymer/polymer/lib/utils/settings").then(
|
import("@polymer/polymer/lib/utils/settings").then(
|
||||||
|
@@ -25,6 +25,7 @@ import { subscribePanels } from "../data/ws-panels";
|
|||||||
import { subscribeThemes } from "../data/ws-themes";
|
import { subscribeThemes } from "../data/ws-themes";
|
||||||
import { subscribeUser } from "../data/ws-user";
|
import { subscribeUser } from "../data/ws-user";
|
||||||
import type { ExternalAuth } from "../external_app/external_auth";
|
import type { ExternalAuth } from "../external_app/external_auth";
|
||||||
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
|
|
||||||
window.name = MAIN_WINDOW_NAME;
|
window.name = MAIN_WINDOW_NAME;
|
||||||
(window as any).frontendVersion = __VERSION__;
|
(window as any).frontendVersion = __VERSION__;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../resources/compatibility";
|
import "../resources/compatibility";
|
||||||
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
|
|
||||||
import { CSSResult } from "lit";
|
import { CSSResult } from "lit";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
@@ -72,7 +73,7 @@ function initialize(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__BUILD__ === "legacy") {
|
if (__BUILD__ === "es5") {
|
||||||
start = start.then(() => window.loadES5Adapter());
|
start = start.then(() => window.loadES5Adapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../resources/compatibility";
|
import "../resources/compatibility";
|
||||||
import "../onboarding/ha-onboarding";
|
import "../onboarding/ha-onboarding";
|
||||||
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
|
|
||||||
import("../resources/ha-style");
|
import("../resources/ha-style");
|
||||||
import("@polymer/polymer/lib/utils/settings").then(
|
import("@polymer/polymer/lib/utils/settings").then(
|
||||||
|
@@ -13,16 +13,18 @@ import {
|
|||||||
StaleWhileRevalidate,
|
StaleWhileRevalidate,
|
||||||
} from "workbox-strategies";
|
} from "workbox-strategies";
|
||||||
|
|
||||||
declare const __WB_MANIFEST__: Parameters<typeof precacheAndRoute>[0];
|
|
||||||
|
|
||||||
const noFallBackRegEx =
|
const noFallBackRegEx =
|
||||||
/\/(api|static|auth|frontend_latest|frontend_es5|local)\/.*/;
|
/\/(api|static|auth|frontend_latest|frontend_es5|local)\/.*/;
|
||||||
|
|
||||||
const initRouting = () => {
|
const initRouting = () => {
|
||||||
precacheAndRoute(__WB_MANIFEST__, {
|
precacheAndRoute(
|
||||||
// Ignore all URL parameters.
|
// @ts-ignore
|
||||||
ignoreURLParametersMatching: [/.*/],
|
WB_MANIFEST,
|
||||||
});
|
{
|
||||||
|
// Ignore all URL parameters.
|
||||||
|
ignoreURLParametersMatching: [/.*/],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Cache static content (including translations) on first access.
|
// Cache static content (including translations) on first access.
|
||||||
registerRoute(
|
registerRoute(
|
||||||
@@ -54,8 +56,11 @@ const initRouting = () => {
|
|||||||
// Get api from network.
|
// Get api from network.
|
||||||
registerRoute(/\/(api|auth)\/.*/, new NetworkOnly());
|
registerRoute(/\/(api|auth)\/.*/, new NetworkOnly());
|
||||||
|
|
||||||
// Get manifest and onboarding from network.
|
// Get manifest, service worker, onboarding from network.
|
||||||
registerRoute(/\/(?:manifest\.json|onboarding\.html)/, new NetworkOnly());
|
registerRoute(
|
||||||
|
/\/(service_worker.js|manifest.json|onboarding.html)/,
|
||||||
|
new NetworkOnly()
|
||||||
|
);
|
||||||
|
|
||||||
// For the root "/" we ignore search
|
// For the root "/" we ignore search
|
||||||
registerRoute(
|
registerRoute(
|
@@ -128,9 +128,6 @@ interface EMOutgoingMessageAssistShow extends EMMessage {
|
|||||||
start_listening: boolean;
|
start_listening: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
interface EMOutgoingMessageImprovScan extends EMMessage {
|
|
||||||
type: "improv/scan";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage {
|
interface EMOutgoingMessageThreadStoreInPlatformKeychain extends EMMessage {
|
||||||
type: "thread/store_in_platform_keychain";
|
type: "thread/store_in_platform_keychain";
|
||||||
@@ -159,8 +156,7 @@ type EMOutgoingMessageWithoutAnswer =
|
|||||||
| EMOutgoingMessageSidebarShow
|
| EMOutgoingMessageSidebarShow
|
||||||
| EMOutgoingMessageTagWrite
|
| EMOutgoingMessageTagWrite
|
||||||
| EMOutgoingMessageThemeUpdate
|
| EMOutgoingMessageThemeUpdate
|
||||||
| EMOutgoingMessageThreadStoreInPlatformKeychain
|
| EMOutgoingMessageThreadStoreInPlatformKeychain;
|
||||||
| EMOutgoingMessageImprovScan;
|
|
||||||
|
|
||||||
interface EMIncomingMessageRestart {
|
interface EMIncomingMessageRestart {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -256,7 +252,6 @@ export interface ExternalConfig {
|
|||||||
canTransferThreadCredentialsToKeychain: boolean;
|
canTransferThreadCredentialsToKeychain: boolean;
|
||||||
hasAssist: boolean;
|
hasAssist: boolean;
|
||||||
hasBarCodeScanner: number;
|
hasBarCodeScanner: number;
|
||||||
canSetupImprov: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExternalMessaging {
|
export class ExternalMessaging {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user