mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-14 11:49:26 +00:00
Compare commits
356 Commits
20220203.0
...
sensor_dev
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c7e9ee785d | ||
![]() |
079cc39a6e | ||
![]() |
d6a1d5af79 | ||
![]() |
c0dce08e19 | ||
![]() |
a7a347ed05 | ||
![]() |
2d9b50defc | ||
![]() |
840858b18c | ||
![]() |
afd2e71f6c | ||
![]() |
88af0aa788 | ||
![]() |
49124f6f09 | ||
![]() |
73f5580555 | ||
![]() |
bdde5268c6 | ||
![]() |
15e972c158 | ||
![]() |
0fc4c24f5a | ||
![]() |
9eba50df0c | ||
![]() |
0e0e07437f | ||
![]() |
6ac51ede52 | ||
![]() |
ccf1fb573a | ||
![]() |
fa537968c4 | ||
![]() |
6bf2111a3c | ||
![]() |
ddf1cc0733 | ||
![]() |
9c1d1cb6f6 | ||
![]() |
470225abde | ||
![]() |
ee230b86c1 | ||
![]() |
f927fc64a9 | ||
![]() |
03677c33f7 | ||
![]() |
bc36a206da | ||
![]() |
af06ab1e2d | ||
![]() |
3e2135a485 | ||
![]() |
2e7f8fb46f | ||
![]() |
102568c4bd | ||
![]() |
4fcdae842e | ||
![]() |
ea19740f5a | ||
![]() |
3e0942b631 | ||
![]() |
0261cea796 | ||
![]() |
5247b2813f | ||
![]() |
8a5090684e | ||
![]() |
1784ba5e68 | ||
![]() |
4fbe9a7b10 | ||
![]() |
1ca9c7838a | ||
![]() |
4fc2c3ef05 | ||
![]() |
73ff8e28a8 | ||
![]() |
dde1c5e03c | ||
![]() |
01eed22592 | ||
![]() |
94ebb63589 | ||
![]() |
29119db5ce | ||
![]() |
9908162ac2 | ||
![]() |
1e929ae78a | ||
![]() |
ab5df0fe6e | ||
![]() |
d5010dda9e | ||
![]() |
4ac097f32b | ||
![]() |
5d3d15072f | ||
![]() |
5c53bc4225 | ||
![]() |
d5a307f8f4 | ||
![]() |
a27dd1e7f1 | ||
![]() |
c86ed1fb3e | ||
![]() |
7fa7a48072 | ||
![]() |
4e0fc8ee08 | ||
![]() |
5f6490e54e | ||
![]() |
db78b046a2 | ||
![]() |
c37fe1e7ff | ||
![]() |
f1ec479d41 | ||
![]() |
e01cb3ca82 | ||
![]() |
b8d3c68a7a | ||
![]() |
641003bb2a | ||
![]() |
3358fc2b18 | ||
![]() |
dcf50e055b | ||
![]() |
1fa04baa16 | ||
![]() |
84ffa2369a | ||
![]() |
cc27ddb362 | ||
![]() |
c4dc6bfb0d | ||
![]() |
4fbcc30a37 | ||
![]() |
4916527e5f | ||
![]() |
fad8a27232 | ||
![]() |
a993d3a753 | ||
![]() |
5dfe17a43a | ||
![]() |
9b6c935ffb | ||
![]() |
f4e28da0a3 | ||
![]() |
294a69d7e4 | ||
![]() |
f89b8cffcf | ||
![]() |
99fd3a1b6f | ||
![]() |
246e426182 | ||
![]() |
9f1e9b43fe | ||
![]() |
8301ae262c | ||
![]() |
d968fe41ee | ||
![]() |
db830e9014 | ||
![]() |
fc6b594a27 | ||
![]() |
86dbf99ebe | ||
![]() |
68e7ce1883 | ||
![]() |
e9003ac35e | ||
![]() |
1dd5214b42 | ||
![]() |
96738350bb | ||
![]() |
5bdecf57cf | ||
![]() |
ec12282f8c | ||
![]() |
552dbca201 | ||
![]() |
0bbc0ebb3c | ||
![]() |
ac7acc5802 | ||
![]() |
64e1d160d1 | ||
![]() |
8e51878b6d | ||
![]() |
7c94ced303 | ||
![]() |
a040e1d5e0 | ||
![]() |
87c7407857 | ||
![]() |
d0d0c44ec7 | ||
![]() |
4cdff3faea | ||
![]() |
0dac10aa23 | ||
![]() |
4b8b14a69d | ||
![]() |
9d28df31bd | ||
![]() |
8258641443 | ||
![]() |
dfcb0f6ba0 | ||
![]() |
2e10eb04b6 | ||
![]() |
b4b52d3872 | ||
![]() |
3873203721 | ||
![]() |
ccb91e0b49 | ||
![]() |
bd20c15a55 | ||
![]() |
0936fd9ae4 | ||
![]() |
adefc7a4e2 | ||
![]() |
8f8017ecff | ||
![]() |
604b79696e | ||
![]() |
8c445f6409 | ||
![]() |
797c871137 | ||
![]() |
24829bd903 | ||
![]() |
add92a559d | ||
![]() |
7f086c0900 | ||
![]() |
17018c0f26 | ||
![]() |
cd6a478130 | ||
![]() |
4f6d7ca5c9 | ||
![]() |
c2994343b4 | ||
![]() |
e5f77c35d4 | ||
![]() |
a9e5a5dd44 | ||
![]() |
1159798b8d | ||
![]() |
437de42c55 | ||
![]() |
89e0bb3f16 | ||
![]() |
28c9631b6c | ||
![]() |
8882624618 | ||
![]() |
a769f84755 | ||
![]() |
7abf9c2473 | ||
![]() |
298296a81f | ||
![]() |
6907fa5c8e | ||
![]() |
546461b70f | ||
![]() |
4031009c26 | ||
![]() |
91e4557625 | ||
![]() |
f0c4b92dbb | ||
![]() |
ffac3d055e | ||
![]() |
04ae8c9d14 | ||
![]() |
0158610d42 | ||
![]() |
5ab6121581 | ||
![]() |
3d9c31aef9 | ||
![]() |
acfeea5c92 | ||
![]() |
75e8e17073 | ||
![]() |
976fd4b32d | ||
![]() |
49beafbe5f | ||
![]() |
151f8d5524 | ||
![]() |
48355aa98e | ||
![]() |
fc31929f41 | ||
![]() |
b7c149fcc1 | ||
![]() |
02d058561b | ||
![]() |
4e57fb1ec1 | ||
![]() |
30f79c5a46 | ||
![]() |
30f7252d84 | ||
![]() |
8af795a7ce | ||
![]() |
8576eeae41 | ||
![]() |
cd740ed135 | ||
![]() |
892f774792 | ||
![]() |
aa504fe1f8 | ||
![]() |
be491451d5 | ||
![]() |
bad184210d | ||
![]() |
a43b3b64b3 | ||
![]() |
aa831a9adf | ||
![]() |
43d4f55392 | ||
![]() |
130c66fb24 | ||
![]() |
684c232c8c | ||
![]() |
09f8f816d1 | ||
![]() |
1719d062b3 | ||
![]() |
87290c4330 | ||
![]() |
fec0dc0032 | ||
![]() |
70ca27c8c9 | ||
![]() |
9ae1f01ad6 | ||
![]() |
0113cc3cf6 | ||
![]() |
2a98ace0b3 | ||
![]() |
5f69a4c165 | ||
![]() |
8db22d4f88 | ||
![]() |
3204dbfc4d | ||
![]() |
430b47fc4a | ||
![]() |
5d8b3227f3 | ||
![]() |
b341ee9d38 | ||
![]() |
e6dbbc31a8 | ||
![]() |
0010bf5a8f | ||
![]() |
6e2e80a297 | ||
![]() |
aa9ff01030 | ||
![]() |
7f8ecf57d7 | ||
![]() |
6be6755f6f | ||
![]() |
64459a06c6 | ||
![]() |
df35496c6e | ||
![]() |
aa988c758d | ||
![]() |
1dd1095d19 | ||
![]() |
7e68393c84 | ||
![]() |
540c06c9f7 | ||
![]() |
f633cc2b0d | ||
![]() |
1baaf76471 | ||
![]() |
8263e299a8 | ||
![]() |
ebd6a26554 | ||
![]() |
5335772a7a | ||
![]() |
f5b5414461 | ||
![]() |
1e6f402d0f | ||
![]() |
ed9d886009 | ||
![]() |
940f5c0002 | ||
![]() |
15d1b8b2ac | ||
![]() |
73855e6f99 | ||
![]() |
d230541256 | ||
![]() |
b1f369a355 | ||
![]() |
e6d1e86c64 | ||
![]() |
eb1f94c370 | ||
![]() |
27750b8b5d | ||
![]() |
564a725284 | ||
![]() |
a5ee610af5 | ||
![]() |
eaf97ee7f5 | ||
![]() |
a14d75deec | ||
![]() |
72b5721c88 | ||
![]() |
94b4b818aa | ||
![]() |
98699b640a | ||
![]() |
decc0d3e0d | ||
![]() |
2281f5bafa | ||
![]() |
6cac7eeff0 | ||
![]() |
794bc161c8 | ||
![]() |
28cd9b6408 | ||
![]() |
9b4c6eea63 | ||
![]() |
afe044d152 | ||
![]() |
dc2038916b | ||
![]() |
cf8e2a6d02 | ||
![]() |
3269b2878b | ||
![]() |
29e1b7b452 | ||
![]() |
3d6d07e5bd | ||
![]() |
7bac41fe41 | ||
![]() |
6e4b027575 | ||
![]() |
728c391b5d | ||
![]() |
8999ca2ea0 | ||
![]() |
4fc0617289 | ||
![]() |
494cc3a569 | ||
![]() |
cc177ef911 | ||
![]() |
41ec65ef3d | ||
![]() |
79e1e195a0 | ||
![]() |
dfbf7fb436 | ||
![]() |
f37a5fa021 | ||
![]() |
5e2fcf928c | ||
![]() |
bc6ef7780c | ||
![]() |
b29563a254 | ||
![]() |
fe8a1152c4 | ||
![]() |
26689a0a85 | ||
![]() |
4f6a241817 | ||
![]() |
eae7e82127 | ||
![]() |
9500ac498c | ||
![]() |
5c5459bcaf | ||
![]() |
246724c59e | ||
![]() |
8f5c9295d3 | ||
![]() |
0abafff4c9 | ||
![]() |
f88ce269a7 | ||
![]() |
0dc56d7983 | ||
![]() |
cbd0ef6b65 | ||
![]() |
f923228078 | ||
![]() |
b55c7edd70 | ||
![]() |
bfb90632ac | ||
![]() |
3a664d45a9 | ||
![]() |
53607fe8c6 | ||
![]() |
9dec0f8ccd | ||
![]() |
89f4fe9d20 | ||
![]() |
f43655eea5 | ||
![]() |
6563984fdd | ||
![]() |
16d8eb0be3 | ||
![]() |
965fc9bc4e | ||
![]() |
56cb958a47 | ||
![]() |
f5feb1d8aa | ||
![]() |
e95065ed08 | ||
![]() |
68a411838d | ||
![]() |
ba63ab8b7a | ||
![]() |
26d4599ef4 | ||
![]() |
d049990f04 | ||
![]() |
9c8d683a19 | ||
![]() |
901677bbdf | ||
![]() |
8bb2374b1b | ||
![]() |
520896a3c2 | ||
![]() |
92db272759 | ||
![]() |
fc654d86c6 | ||
![]() |
523afe2f6f | ||
![]() |
460b9003fc | ||
![]() |
2ac0ad1d98 | ||
![]() |
a321432175 | ||
![]() |
63c9b3f830 | ||
![]() |
806b1296b0 | ||
![]() |
7f90ffa82f | ||
![]() |
db33c38e21 | ||
![]() |
a8c1fdd21e | ||
![]() |
d86a18b80b | ||
![]() |
bef6591548 | ||
![]() |
e1c07f109c | ||
![]() |
fb66d224ae | ||
![]() |
ee1fd3e865 | ||
![]() |
a9bfea233c | ||
![]() |
35cc291118 | ||
![]() |
35a41b3490 | ||
![]() |
db7cac5782 | ||
![]() |
099fa706a0 | ||
![]() |
f59cb661cd | ||
![]() |
ed84ce9692 | ||
![]() |
9912d427f2 | ||
![]() |
76f574f875 | ||
![]() |
ac90bb7088 | ||
![]() |
ce9f83e9a2 | ||
![]() |
fca7d2c5b0 | ||
![]() |
d7a5921e7b | ||
![]() |
cefa2ee183 | ||
![]() |
0eeed85193 | ||
![]() |
fd80408de2 | ||
![]() |
467a5169c0 | ||
![]() |
b0b3222b33 | ||
![]() |
b053881cef | ||
![]() |
92a9ed7080 | ||
![]() |
830b449006 | ||
![]() |
d38a8a317e | ||
![]() |
a0aed9112c | ||
![]() |
ce3b8544b9 | ||
![]() |
134ed7d303 | ||
![]() |
dc27871189 | ||
![]() |
9c9bfa2b77 | ||
![]() |
f02dd39619 | ||
![]() |
d37d99223d | ||
![]() |
4db943c5ff | ||
![]() |
ed001fb10b | ||
![]() |
5f43715dd8 | ||
![]() |
5435218187 | ||
![]() |
4ef5f3af89 | ||
![]() |
9eea17b793 | ||
![]() |
6a51e2aaad | ||
![]() |
2d33327d88 | ||
![]() |
e9ec2da917 | ||
![]() |
09d46dac61 | ||
![]() |
236fa14ec3 | ||
![]() |
2cb37820df | ||
![]() |
869fa91ae5 | ||
![]() |
22df03427f | ||
![]() |
e72a4e4a20 | ||
![]() |
ca8d31c6bb | ||
![]() |
354ea88984 | ||
![]() |
76af6e48cd | ||
![]() |
d05f807b9d | ||
![]() |
4092f7f75d | ||
![]() |
04668ad809 | ||
![]() |
9be5a15c77 | ||
![]() |
21d86f4797 | ||
![]() |
45e6ec1ee2 | ||
![]() |
9b97faa5e3 | ||
![]() |
8730c122fd | ||
![]() |
0046252e32 | ||
![]() |
f47440083e | ||
![]() |
bfaf44f9d1 | ||
![]() |
deba6a0db4 | ||
![]() |
8d2c716fbe |
@@ -16,6 +16,9 @@
|
|||||||
"runem.lit-plugin",
|
"runem.lit-plugin",
|
||||||
"ms-python.vscode-pylance"
|
"ms-python.vscode-pylance"
|
||||||
],
|
],
|
||||||
|
"containerEnv": {
|
||||||
|
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||||
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"terminal.integrated.shell.linux": "/bin/bash",
|
"terminal.integrated.shell.linux": "/bin/bash",
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
|
15
.github/workflows/release.yaml
vendored
15
.github/workflows/release.yaml
vendored
@@ -10,10 +10,18 @@ env:
|
|||||||
NODE_VERSION: 14
|
NODE_VERSION: 14
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
NODE_OPTIONS: --max_old_space_size=6144
|
||||||
|
|
||||||
|
# Set default workflow permissions
|
||||||
|
# All scopes not mentioned here are set to no access
|
||||||
|
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
|
||||||
|
permissions:
|
||||||
|
actions: none
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # Required to upload release assets
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -47,6 +55,13 @@ jobs:
|
|||||||
|
|
||||||
script/release
|
script/release
|
||||||
|
|
||||||
|
- name: Upload release assets
|
||||||
|
uses: softprops/action-gh-release@v0.1.14
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
dist/*.whl
|
||||||
|
dist/*.tar.gz
|
||||||
|
|
||||||
wheels-init:
|
wheels-init:
|
||||||
name: Init wheels build
|
name: Init wheels build
|
||||||
needs: release
|
needs: release
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -1,11 +1,10 @@
|
|||||||
diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
diff --git a/polyfillLoaders/EventTarget.js b/polyfillLoaders/EventTarget.js
|
||||||
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
|
index 4e18ade7ba485849f17f28c94c42f0e0e01ac387..8f34f4f646c7f7becc208fb5a546c96034fc74dc 100644
|
||||||
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
--- a/polyfillLoaders/EventTarget.js
|
||||||
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
+++ b/polyfillLoaders/EventTarget.js
|
||||||
@@ -1,14 +1,15 @@
|
@@ -6,16 +6,15 @@
|
||||||
-let _ET, ET;
|
let _ET;
|
||||||
+let _ET;
|
let ET;
|
||||||
+let ET;
|
|
||||||
export default async function EventTarget() {
|
export default async function EventTarget() {
|
||||||
- return ET || init();
|
- return ET || init();
|
||||||
+ return ET || init();
|
+ return ET || init();
|
||||||
@@ -26,4 +25,5 @@ index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc
|
|||||||
+ _ET = (await import("event-target-shim")).default.EventTarget;
|
+ _ET = (await import("event-target-shim")).default.EventTarget;
|
||||||
+ }
|
+ }
|
||||||
+ return (ET = _ET);
|
+ return (ET = _ET);
|
||||||
}
|
}
|
||||||
|
//# sourceMappingURL=EventTarget.js.map
|
631
.yarn/releases/yarn-3.0.2.cjs
vendored
631
.yarn/releases/yarn-3.0.2.cjs
vendored
File diff suppressed because one or more lines are too long
785
.yarn/releases/yarn-3.2.0.cjs
vendored
Executable file
785
.yarn/releases/yarn-3.2.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ plugins:
|
|||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
spec: "@yarnpkg/plugin-interactive-tools"
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.0.2.cjs
|
yarnPath: .yarn/releases/yarn-3.2.0.cjs
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
||||||
|
|
||||||
[](https://demo.home-assistant.io/)
|
[](https://demo.home-assistant.io/)
|
||||||
|
|
||||||
- [View demo of Home Assistant](https://demo.home-assistant.io/)
|
- [View demo of Home Assistant](https://demo.home-assistant.io/)
|
||||||
- [More information about Home Assistant](https://home-assistant.io)
|
- [More information about Home Assistant](https://home-assistant.io)
|
||||||
|
@@ -10,7 +10,7 @@ module.exports.ignorePackages = ({ latestBuild }) => [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Files from NPM packages that we should replace with empty file
|
// Files from NPM packages that we should replace with empty file
|
||||||
module.exports.emptyPackages = ({ latestBuild }) =>
|
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
||||||
[
|
[
|
||||||
// Contains all color definitions for all material color sets.
|
// Contains all color definitions for all material color sets.
|
||||||
// We don't use it
|
// We don't use it
|
||||||
@@ -28,6 +28,15 @@ module.exports.emptyPackages = ({ latestBuild }) =>
|
|||||||
),
|
),
|
||||||
// This polyfill is loaded in workers to support ES5, filter it out.
|
// This polyfill is loaded in workers to support ES5, filter it out.
|
||||||
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
|
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
|
||||||
|
// Icons in supervisor conflict with icons in HA so we don't load.
|
||||||
|
isHassioBuild &&
|
||||||
|
require.resolve(
|
||||||
|
path.resolve(paths.polymer_dir, "src/components/ha-icon.ts")
|
||||||
|
),
|
||||||
|
isHassioBuild &&
|
||||||
|
require.resolve(
|
||||||
|
path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts")
|
||||||
|
),
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||||
@@ -196,6 +205,7 @@ module.exports.config = {
|
|||||||
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
||||||
isProdBuild,
|
isProdBuild,
|
||||||
latestBuild,
|
latestBuild,
|
||||||
|
isHassioBuild: true,
|
||||||
defineOverlay: {
|
defineOverlay: {
|
||||||
__SUPERVISOR__: true,
|
__SUPERVISOR__: true,
|
||||||
},
|
},
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const marked = require("marked");
|
const { marked } = require("marked");
|
||||||
const glob = require("glob");
|
const glob = require("glob");
|
||||||
const yaml = require("js-yaml");
|
const yaml = require("js-yaml");
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ const source = require("vinyl-source-stream");
|
|||||||
const vinylBuffer = require("vinyl-buffer");
|
const vinylBuffer = require("vinyl-buffer");
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const foreach = require("gulp-foreach");
|
const flatmap = require("gulp-flatmap");
|
||||||
const merge = require("gulp-merge-json");
|
const merge = require("gulp-merge-json");
|
||||||
const rename = require("gulp-rename");
|
const rename = require("gulp-rename");
|
||||||
const transform = require("gulp-json-transform");
|
const transform = require("gulp-json-transform");
|
||||||
@@ -183,7 +183,7 @@ gulp.task("build-merged-translations", () =>
|
|||||||
})
|
})
|
||||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||||
.pipe(
|
.pipe(
|
||||||
foreach((stream, file) => {
|
flatmap((stream, file) => {
|
||||||
// For each language generate a merged json file. It begins with the master
|
// For each language generate a merged json file. It begins with the master
|
||||||
// translation as a failsafe for untranslated strings, and merges all parent
|
// translation as a failsafe for untranslated strings, and merges all parent
|
||||||
// tags into one file for each specific subtag
|
// tags into one file for each specific subtag
|
||||||
|
@@ -30,6 +30,7 @@ const createWebpackConfig = ({
|
|||||||
isProdBuild,
|
isProdBuild,
|
||||||
latestBuild,
|
latestBuild,
|
||||||
isStatsBuild,
|
isStatsBuild,
|
||||||
|
isHassioBuild,
|
||||||
dontHash,
|
dontHash,
|
||||||
}) => {
|
}) => {
|
||||||
if (!dontHash) {
|
if (!dontHash) {
|
||||||
@@ -117,7 +118,9 @@ const createWebpackConfig = ({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
new RegExp(
|
||||||
|
bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
|
||||||
|
),
|
||||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||||
),
|
),
|
||||||
!isProdBuild && new LogStartCompilePlugin(),
|
!isProdBuild && new LogStartCompilePlugin(),
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
||||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
||||||
@@ -7,6 +7,9 @@ import "../../../../src/panels/lovelace/views/hui-view";
|
|||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
import "./hc-launch-screen";
|
import "./hc-launch-screen";
|
||||||
|
|
||||||
|
(window as any).loadCardHelpers = () =>
|
||||||
|
import("../../../../src/panels/lovelace/custom-card-helpers");
|
||||||
|
|
||||||
@customElement("hc-lovelace")
|
@customElement("hc-lovelace")
|
||||||
class HcLovelace extends LitElement {
|
class HcLovelace extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -17,6 +20,8 @@ class HcLovelace extends LitElement {
|
|||||||
|
|
||||||
@property() public urlPath: string | null = null;
|
@property() public urlPath: string | null = null;
|
||||||
|
|
||||||
|
@query("hui-view") private _huiView?: HTMLElement;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const index = this._viewIndex;
|
const index = this._viewIndex;
|
||||||
if (index === undefined) {
|
if (index === undefined) {
|
||||||
@@ -75,12 +80,12 @@ class HcLovelace extends LitElement {
|
|||||||
this.lovelaceConfig.background;
|
this.lovelaceConfig.background;
|
||||||
|
|
||||||
if (configBackground) {
|
if (configBackground) {
|
||||||
(this.shadowRoot!.querySelector(
|
this._huiView!.style.setProperty(
|
||||||
"hui-view"
|
|
||||||
) as HTMLElement)!.style.setProperty(
|
|
||||||
"--lovelace-background",
|
"--lovelace-background",
|
||||||
configBackground
|
configBackground
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
this._huiView!.style.removeProperty("--lovelace-background");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,6 +118,9 @@ class HcLovelace extends LitElement {
|
|||||||
:host > * {
|
:host > * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
hui-view {
|
||||||
|
background: var(--lovelace-background, var(--primary-background-color));
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import "web-animations-js/web-animations-next-lite.min";
|
|
||||||
import "../../../src/resources/ha-style";
|
import "../../../src/resources/ha-style";
|
||||||
import "../../../src/resources/roboto";
|
import "../../../src/resources/roboto";
|
||||||
import "./layout/hc-lovelace";
|
import "./layout/hc-lovelace";
|
||||||
|
@@ -2,8 +2,3 @@ import "../../src/resources/ha-style";
|
|||||||
import "../../src/resources/roboto";
|
import "../../src/resources/roboto";
|
||||||
import "../../src/resources/safari-14-attachshadow-patch";
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./ha-demo";
|
import "./ha-demo";
|
||||||
|
|
||||||
/* polyfill for paper-dropdown */
|
|
||||||
setTimeout(() => {
|
|
||||||
import("web-animations-js/web-animations-next-lite.min");
|
|
||||||
}, 1000);
|
|
||||||
|
BIN
gallery/public/images/clearspace.png
Normal file
BIN
gallery/public/images/clearspace.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
gallery/public/images/logo-variants.png
Normal file
BIN
gallery/public/images/logo-variants.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
BIN
gallery/public/images/logo-with-text.png
Normal file
BIN
gallery/public/images/logo-with-text.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
BIN
gallery/public/images/logo.png
Normal file
BIN
gallery/public/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
gallery/public/images/using-our-logo.png
Normal file
BIN
gallery/public/images/using-our-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
@@ -23,7 +23,7 @@ if [[ "${PULL_REQUEST}" == "true" ]]; then
|
|||||||
createStatus "pending" "Building design preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
createStatus "pending" "Building design preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||||
gulp build-gallery
|
gulp build-gallery
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
createStatus "success" "Build complete" "$DEPLOY_URL"
|
createStatus "success" "Build complete" "$DEPLOY_PRIME_URL"
|
||||||
else
|
else
|
||||||
createStatus "error" "Build failed" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
createStatus "error" "Build failed" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||||
fi
|
fi
|
||||||
|
@@ -20,7 +20,6 @@ module.exports = [
|
|||||||
"editor-trigger",
|
"editor-trigger",
|
||||||
"editor-condition",
|
"editor-condition",
|
||||||
"editor-action",
|
"editor-action",
|
||||||
"selectors",
|
|
||||||
"trace",
|
"trace",
|
||||||
"trace-timeline",
|
"trace-timeline",
|
||||||
],
|
],
|
||||||
@@ -37,12 +36,17 @@ module.exports = [
|
|||||||
category: "misc",
|
category: "misc",
|
||||||
header: "Miscelaneous",
|
header: "Miscelaneous",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
category: "brand",
|
||||||
|
header: "Brand",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
category: "user-test",
|
category: "user-test",
|
||||||
header: "User Tests",
|
header: "Users",
|
||||||
|
pages: ["user-types", "configuration-menu"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
category: "design.home-assistant.io",
|
category: "design.home-assistant.io",
|
||||||
header: "Design Documentation",
|
header: "About",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -3,6 +3,7 @@ import { html, LitElement, css, TemplateResult } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
import "../../../src/components/ha-card";
|
||||||
|
|
||||||
@customElement("demo-black-white-row")
|
@customElement("demo-black-white-row")
|
||||||
class DemoBlackWhiteRow extends LitElement {
|
class DemoBlackWhiteRow extends LitElement {
|
||||||
|
@@ -78,6 +78,9 @@ class DemoCards extends LitElement {
|
|||||||
ha-formfield {
|
ha-formfield {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
#container {
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,7 +12,14 @@ class PageDescription extends HaMarkdown {
|
|||||||
if (!PAGES[this.page].description) {
|
if (!PAGES[this.page].description) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
|
<div class="heading">
|
||||||
|
<div class="title">
|
||||||
|
${PAGES[this.page].metadata.title || this.page.split("/")[1]}
|
||||||
|
</div>
|
||||||
|
<div class="subtitle">${PAGES[this.page].metadata.subtitle}</div>
|
||||||
|
</div>
|
||||||
${until(
|
${until(
|
||||||
PAGES[this.page]
|
PAGES[this.page]
|
||||||
.description()
|
.description()
|
||||||
@@ -25,9 +32,22 @@ class PageDescription extends HaMarkdown {
|
|||||||
static styles = [
|
static styles = [
|
||||||
HaMarkdown.styles,
|
HaMarkdown.styles,
|
||||||
css`
|
css`
|
||||||
|
.heading {
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid var(--secondary-background-color);
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 42px;
|
||||||
|
line-height: 56px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.subtitle {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
.root {
|
.root {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 16px auto;
|
||||||
}
|
}
|
||||||
.root > *:first-child {
|
.root > *:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@@ -5,6 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit";
|
|||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import "../../src/components/ha-icon-button";
|
import "../../src/components/ha-icon-button";
|
||||||
import "../../src/managers/notification-manager";
|
import "../../src/managers/notification-manager";
|
||||||
|
import "../../src/components/ha-expansion-panel";
|
||||||
import { haStyle } from "../../src/resources/styles";
|
import { haStyle } from "../../src/resources/styles";
|
||||||
import { PAGES, SIDEBAR } from "../build/import-pages";
|
import { PAGES, SIDEBAR } from "../build/import-pages";
|
||||||
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
|
||||||
@@ -44,6 +45,10 @@ class HaGallery extends LitElement {
|
|||||||
for (const page of group.pages!) {
|
for (const page of group.pages!) {
|
||||||
const key = `${group.category}/${page}`;
|
const key = `${group.category}/${page}`;
|
||||||
const active = this._page === key;
|
const active = this._page === key;
|
||||||
|
if (!(key in PAGES)) {
|
||||||
|
console.error("Undefined page referenced in sidebar.js:", key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const title = PAGES[key].metadata.title || page;
|
const title = PAGES[key].metadata.title || page;
|
||||||
links.push(html`
|
links.push(html`
|
||||||
<a ?active=${active} href=${`#${group.category}/${page}`}>${title}</a>
|
<a ?active=${active} href=${`#${group.category}/${page}`}>${title}</a>
|
||||||
@@ -53,10 +58,9 @@ class HaGallery extends LitElement {
|
|||||||
sidebar.push(
|
sidebar.push(
|
||||||
group.header
|
group.header
|
||||||
? html`
|
? html`
|
||||||
<details>
|
<ha-expansion-panel .header=${group.header}>
|
||||||
<summary class="section">${group.header}</summary>
|
|
||||||
${links}
|
${links}
|
||||||
</details>
|
</ha-expansion-panel>
|
||||||
`
|
`
|
||||||
: links
|
: links
|
||||||
);
|
);
|
||||||
@@ -92,27 +96,34 @@ class HaGallery extends LitElement {
|
|||||||
${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
|
${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
|
||||||
</div>
|
</div>
|
||||||
<div class="page-footer">
|
<div class="page-footer">
|
||||||
${PAGES[this._page].description ||
|
<div class="header">Help us to improve our documentation</div>
|
||||||
Object.keys(PAGES[this._page].metadata).length > 0
|
<div class="secondary">
|
||||||
? html`
|
Suggest an edit to this page, or provide/view feedback for this
|
||||||
<a
|
page.
|
||||||
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
</div>
|
||||||
target="_blank"
|
<div>
|
||||||
>
|
${PAGES[this._page].description ||
|
||||||
Edit text
|
Object.keys(PAGES[this._page].metadata).length > 0
|
||||||
</a>
|
? html`
|
||||||
`
|
<a
|
||||||
: ""}
|
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
||||||
${PAGES[this._page].demo
|
target="_blank"
|
||||||
? html`
|
>
|
||||||
<a
|
Edit text
|
||||||
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
</a>
|
||||||
target="_blank"
|
`
|
||||||
>
|
: ""}
|
||||||
Edit demo
|
${PAGES[this._page].demo
|
||||||
</a>
|
? html`
|
||||||
`
|
<a
|
||||||
: ""}
|
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Edit demo
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</mwc-drawer>
|
</mwc-drawer>
|
||||||
@@ -186,27 +197,16 @@ class HaGallery extends LitElement {
|
|||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar details {
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar summary {
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar a {
|
.sidebar a {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
display: block;
|
display: block;
|
||||||
padding: 4px 12px;
|
padding: 12px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar a[active]::before {
|
.sidebar a[active]::before {
|
||||||
border-radius: 4px;
|
border-radius: 12px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 2px;
|
right: 2px;
|
||||||
@@ -237,14 +237,32 @@ class HaGallery extends LitElement {
|
|||||||
|
|
||||||
.page-footer {
|
.page-footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 16px 0;
|
margin: 16px;
|
||||||
padding-top: 16px;
|
padding: 16px;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
border-radius: 12px;
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-footer div {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-footer .header {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 28px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-footer .secondary {
|
||||||
|
line-height: 23px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-footer a {
|
.page-footer a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -3,10 +3,20 @@ import { html, css, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import { describeAction } from "../../../../src/data/script_i18n";
|
import { describeAction } from "../../../../src/data/script_i18n";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
|
|
||||||
const actions = [
|
const ENTITIES = [
|
||||||
|
getEntity("scene", "kitchen_morning", "scening", {
|
||||||
|
friendly_name: "Kitchen Morning",
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "kitchen", "playing", {
|
||||||
|
friendly_name: "Sonos Kitchen",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const ACTIONS = [
|
||||||
{ wait_template: "{{ true }}", alias: "Something with an alias" },
|
{ wait_template: "{{ true }}", alias: "Something with an alias" },
|
||||||
{ delay: "0:05" },
|
{ delay: "0:05" },
|
||||||
{ wait_template: "{{ true }}" },
|
{ wait_template: "{{ true }}" },
|
||||||
@@ -19,8 +29,20 @@ const actions = [
|
|||||||
device_id: "abcdefgh",
|
device_id: "abcdefgh",
|
||||||
domain: "plex",
|
domain: "plex",
|
||||||
entity_id: "media_player.kitchen",
|
entity_id: "media_player.kitchen",
|
||||||
|
type: "turn_on",
|
||||||
},
|
},
|
||||||
{ scene: "scene.kitchen_morning" },
|
{ scene: "scene.kitchen_morning" },
|
||||||
|
{
|
||||||
|
service: "scene.turn_on",
|
||||||
|
target: { entity_id: "scene.kitchen_morning" },
|
||||||
|
metadata: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
service: "media_player.play_media",
|
||||||
|
target: { entity_id: "media_player.kitchen" },
|
||||||
|
data: { media_content_id: "", media_content_type: "" },
|
||||||
|
metadata: { title: "Happy Song" },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
wait_for_trigger: [
|
wait_for_trigger: [
|
||||||
{
|
{
|
||||||
@@ -52,7 +74,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-card header="Actions">
|
<ha-card header="Actions">
|
||||||
${actions.map(
|
${ACTIONS.map(
|
||||||
(conf) => html`
|
(conf) => html`
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<span>${describeAction(this.hass, conf as any)}</span>
|
<span>${describeAction(this.hass, conf as any)}</span>
|
||||||
@@ -68,6 +90,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
|
@@ -14,7 +14,7 @@ import { HaDelayAction } from "../../../../src/panels/config/automation/action/t
|
|||||||
import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
|
import { HaDeviceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
|
||||||
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
||||||
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
||||||
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-scene";
|
import { HaSceneAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-activate_scene";
|
||||||
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
||||||
import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
|
import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
|
||||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
title: Selectors
|
|
||||||
---
|
|
@@ -1,102 +0,0 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
|
||||||
import { LitElement, TemplateResult, html } from "lit";
|
|
||||||
import { customElement, state } from "lit/decorators";
|
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
|
||||||
import "../../components/demo-black-white-row";
|
|
||||||
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
|
||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
|
||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
|
||||||
import "../../../../src/panels/config/automation/trigger/ha-automation-trigger";
|
|
||||||
import { Selector } from "../../../../src/data/selector";
|
|
||||||
import "../../../../src/components/ha-selector/ha-selector";
|
|
||||||
|
|
||||||
const SCHEMAS: { name: string; selector: Selector }[] = [
|
|
||||||
{ name: "Addon", selector: { addon: {} } },
|
|
||||||
|
|
||||||
{ name: "Entity", selector: { entity: {} } },
|
|
||||||
{ name: "Device", selector: { device: {} } },
|
|
||||||
{ name: "Area", selector: { area: {} } },
|
|
||||||
{ name: "Target", selector: { target: {} } },
|
|
||||||
{
|
|
||||||
name: "Number",
|
|
||||||
selector: {
|
|
||||||
number: {
|
|
||||||
min: 0,
|
|
||||||
max: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ name: "Boolean", selector: { boolean: {} } },
|
|
||||||
{ name: "Time", selector: { time: {} } },
|
|
||||||
{ name: "Action", selector: { action: {} } },
|
|
||||||
{ name: "Text", selector: { text: { multiline: false } } },
|
|
||||||
{ name: "Text Multiline", selector: { text: { multiline: true } } },
|
|
||||||
{ name: "Object", selector: { object: {} } },
|
|
||||||
{
|
|
||||||
name: "Select",
|
|
||||||
selector: {
|
|
||||||
select: {
|
|
||||||
options: ["Everyone Home", "Some Home", "All gone"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@customElement("demo-automation-selectors")
|
|
||||||
class DemoHaSelector extends LitElement {
|
|
||||||
@state() private hass!: HomeAssistant;
|
|
||||||
|
|
||||||
private data: any = SCHEMAS.map(() => undefined);
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
const hass = provideHass(this);
|
|
||||||
hass.updateTranslations(null, "en");
|
|
||||||
hass.updateTranslations("config", "en");
|
|
||||||
mockEntityRegistry(hass);
|
|
||||||
mockDeviceRegistry(hass);
|
|
||||||
mockAreaRegistry(hass);
|
|
||||||
mockHassioSupervisor(hass);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
const valueChanged = (ev) => {
|
|
||||||
const sampleIdx = ev.target.sampleIdx;
|
|
||||||
this.data[sampleIdx] = ev.detail.value;
|
|
||||||
this.requestUpdate();
|
|
||||||
};
|
|
||||||
return html`
|
|
||||||
${SCHEMAS.map(
|
|
||||||
(info, sampleIdx) => html`
|
|
||||||
<demo-black-white-row
|
|
||||||
.title=${info.name}
|
|
||||||
.value=${{ selector: info.selector, data: this.data[sampleIdx] }}
|
|
||||||
>
|
|
||||||
${["light", "dark"].map(
|
|
||||||
(slot) =>
|
|
||||||
html`
|
|
||||||
<ha-selector
|
|
||||||
slot=${slot}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.selector=${info.selector}
|
|
||||||
.label=${info.name}
|
|
||||||
.value=${this.data[sampleIdx]}
|
|
||||||
.sampleIdx=${sampleIdx}
|
|
||||||
@value-changed=${valueChanged}
|
|
||||||
></ha-selector>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</demo-black-white-row>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"demo-automation-selectors": DemoHaSelector;
|
|
||||||
}
|
|
||||||
}
|
|
34
gallery/src/pages/brand/logo.markdown
Normal file
34
gallery/src/pages/brand/logo.markdown
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: "Logo"
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Using our logo
|
||||||
|
|
||||||
|
As a community, we are proud of our logo. Follow these guidelines to ensure it always looks its best. Our logo follows Google's material design spec and uses the blue interface color.
|
||||||
|
|
||||||
|
[Download Logo](https://github.com/home-assistant/assets/tree/master/logo)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Using the icon
|
||||||
|
|
||||||
|
Our icon is a shorter and most used version of our logo. The icon can exist without the wordmark, the wordmark should never exist without the icon.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Using the right variant
|
||||||
|
|
||||||
|
The pretty blue logo with a background shadow, pictured top left, is our primary logo. It should only be used with black, white, and non-duotone photography.
|
||||||
|
|
||||||
|
When needed you can use our logo without a shadow, as seen as the second variant.
|
||||||
|
|
||||||
|
The outlined logo should only be used on packaging.
|
||||||
|
|
||||||
|
## Exclusion zone
|
||||||
|
|
||||||
|
The logo needs some personal space. It's exclusion zone is equal to a quarter the height of the icon.
|
||||||
|
|
||||||
|

|
41
gallery/src/pages/brand/our-story.markdown
Normal file
41
gallery/src/pages/brand/our-story.markdown
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
title: "Our story"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open source home automation that puts local control and privacy first
|
||||||
|
|
||||||
|
Home Assistant is a free and open-source software for home automation that is designed to be the central control system for smart home devices with a focus on local control and privacy. It can be accessed via a web-based user interface, via apps for Android and iOS, or using voice commands via a supported virtual assistant like Google Assistant and Amazon Alexa.
|
||||||
|
|
||||||
|
IoT devices and services are supported by modular support for controlling proprietary ecosystems if they provide public access via an Open API for third-party integrations and protocols like Bluetooth, MQTT, Zigbee, and Z-Wave, After the Home Assistant software application is installed as a computer appliance it will act as a central control system for home automation. Information from all entities it sees can be used and controlled from within scripts trigger automations using scheduling and "blueprint" subroutines, e.g. for controlling lighting, climate, entertainment systems, and appliances.
|
||||||
|
|
||||||
|
# Open Home
|
||||||
|
|
||||||
|
The Open Home is our vision for the smart home. It defines the values that we put at the heart of every decision we make at Home Assistant. It’s woven into our architecture, licensing, community, and everything else.
|
||||||
|
|
||||||
|
The Open Home is about privacy, choice, and durability.
|
||||||
|
|
||||||
|
## Privacy
|
||||||
|
|
||||||
|
Your home should be your safe space. A place where you can be your true self without having to bother about what the world thinks of you. A place where you don’t need to act differently to avoid an algorithm categorizing your behavior. Privacy for the Open Home means that devices need to work locally. No one else needs to know if you turn on a light bulb or change the thermostat.
|
||||||
|
|
||||||
|
It is okay for a product to offer a cloud connection, but it should be extra and opt-in.
|
||||||
|
|
||||||
|
## Choice
|
||||||
|
|
||||||
|
Devices in your home gather data about themselves and their surroundings. Your data. Vendors shouldn’t be able to limit your access to your data or limit the interoperability of your devices with the rest of your smart home.
|
||||||
|
|
||||||
|
Choice for the Open Home means that devices need to make the gathered data available through local APIs. This avoids vendor lock-in and allows users to create their own smart home with devices from different manufacturers.
|
||||||
|
|
||||||
|
## Durability
|
||||||
|
|
||||||
|
If there is one thing that technology firms are very good at, it is launching new products. However, maintaining the products and making sure they keep working is an afterthought for most. The result is that vendors can decide to no longer support your device, crippling its features or even preventing it from working at all. As we install more and more devices in our home, durability is becoming more and more important. We shouldn’t have to buy everything new every couple of years because the manufacturer decided to move on.
|
||||||
|
|
||||||
|
Durability for the Open Home means that devices are designed and built to keep working. Not just this year, but for the next decade.
|
||||||
|
|
||||||
|
# Our history
|
||||||
|
|
||||||
|
The project was started as a Python application by Paulus Schoutsen in September 2013 and first published publicly on GitHub in November 2013. In July 2017, a managed operating system called Hass.io was initially introduced to make it easier use to use Home Assistant on single-board computers like the Raspberry Pi series. Its bundled "supervisor" management system allowed users to manage, backup, and update the local installation and introduced the option to extend the functionality of the software with add-ons.
|
||||||
|
|
||||||
|
An optional subscription service was introduced in December 2017 for $5/month to solve the complexities associated with secured remote access, as well as linking to Amazon Alexa and Google Assistant. Nabu Casa, Inc. was formed in September 2018 to take over the subscription service. The company's funding is based solely on revenue from the subscription service. It is used to finance the project's infrastructure and to pay for full-time employees contributing to the project.
|
||||||
|
|
||||||
|
In January 2020, branding was adjusted to make it easier to refer to different parts of the project. The main piece of software was renamed to Home Assistant Core, while the full suite of software with the embedded operating system and bundled "supervisor" management system was renamed to Home Assistant.
|
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Alerts
|
title: Alerts
|
||||||
|
subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Alert `<ha-alert>`
|
# Alert `<ha-alert>`
|
||||||
|
@@ -1,11 +1,109 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
/* eslint-disable lit/no-template-arrow */
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { LitElement, TemplateResult, html } from "lit";
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
import { computeInitialHaFormData } from "../../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||||
import "../../../../src/components/ha-form/ha-form";
|
import "../../../../src/components/ha-form/ha-form";
|
||||||
import "../../components/demo-black-white-row";
|
import "../../components/demo-black-white-row";
|
||||||
|
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||||
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
|
import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
||||||
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
|
|
||||||
|
const ENTITIES = [
|
||||||
|
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||||
|
friendly_name: "Alarm",
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "livingroom", "playing", {
|
||||||
|
friendly_name: "Livingroom",
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "lounge", "idle", {
|
||||||
|
friendly_name: "Lounge",
|
||||||
|
supported_features: 444983,
|
||||||
|
}),
|
||||||
|
getEntity("light", "bedroom", "on", {
|
||||||
|
friendly_name: "Bedroom",
|
||||||
|
}),
|
||||||
|
getEntity("switch", "coffee", "off", {
|
||||||
|
friendly_name: "Coffee",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEVICES = [
|
||||||
|
{
|
||||||
|
area_id: "bedroom",
|
||||||
|
configuration_url: null,
|
||||||
|
config_entries: ["config_entry_1"],
|
||||||
|
connections: [],
|
||||||
|
disabled_by: null,
|
||||||
|
entry_type: null,
|
||||||
|
id: "device_1",
|
||||||
|
identifiers: [["demo", "volume1"] as [string, string]],
|
||||||
|
manufacturer: null,
|
||||||
|
model: null,
|
||||||
|
name_by_user: null,
|
||||||
|
name: "Dishwasher",
|
||||||
|
sw_version: null,
|
||||||
|
hw_version: null,
|
||||||
|
via_device_id: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: "backyard",
|
||||||
|
configuration_url: null,
|
||||||
|
config_entries: ["config_entry_2"],
|
||||||
|
connections: [],
|
||||||
|
disabled_by: null,
|
||||||
|
entry_type: null,
|
||||||
|
id: "device_2",
|
||||||
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
|
manufacturer: null,
|
||||||
|
model: null,
|
||||||
|
name_by_user: null,
|
||||||
|
name: "Lamp",
|
||||||
|
sw_version: null,
|
||||||
|
hw_version: null,
|
||||||
|
via_device_id: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: null,
|
||||||
|
configuration_url: null,
|
||||||
|
config_entries: ["config_entry_3"],
|
||||||
|
connections: [],
|
||||||
|
disabled_by: null,
|
||||||
|
entry_type: null,
|
||||||
|
id: "device_3",
|
||||||
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
|
manufacturer: null,
|
||||||
|
model: null,
|
||||||
|
name_by_user: "User name",
|
||||||
|
name: "Technical name",
|
||||||
|
sw_version: null,
|
||||||
|
hw_version: null,
|
||||||
|
via_device_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const AREAS = [
|
||||||
|
{
|
||||||
|
area_id: "backyard",
|
||||||
|
name: "Backyard",
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: "bedroom",
|
||||||
|
name: "Bedroom",
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: "livingroom",
|
||||||
|
name: "Livingroom",
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const SCHEMAS: {
|
const SCHEMAS: {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -14,6 +112,74 @@ const SCHEMAS: {
|
|||||||
schema: HaFormSchema[];
|
schema: HaFormSchema[];
|
||||||
data?: Record<string, any>;
|
data?: Record<string, any>;
|
||||||
}[] = [
|
}[] = [
|
||||||
|
{
|
||||||
|
title: "Selectors",
|
||||||
|
translations: {
|
||||||
|
addon: "Addon",
|
||||||
|
entity: "Entity",
|
||||||
|
device: "Device",
|
||||||
|
area: "Area",
|
||||||
|
target: "Target",
|
||||||
|
number: "Number",
|
||||||
|
boolean: "Boolean",
|
||||||
|
time: "Time",
|
||||||
|
action: "Action",
|
||||||
|
text: "Text",
|
||||||
|
text_multiline: "Text Multiline",
|
||||||
|
object: "Object",
|
||||||
|
select: "Select",
|
||||||
|
icon: "Icon",
|
||||||
|
media: "Media",
|
||||||
|
location: "Location",
|
||||||
|
entities: "Entities",
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{ name: "addon", selector: { addon: {} } },
|
||||||
|
{ name: "entity", selector: { entity: {} } },
|
||||||
|
{
|
||||||
|
name: "Attribute",
|
||||||
|
selector: { attribute: { entity_id: "" } },
|
||||||
|
context: { filter_entity: "entity" },
|
||||||
|
},
|
||||||
|
{ name: "Device", selector: { device: {} } },
|
||||||
|
{ name: "Duration", selector: { duration: {} } },
|
||||||
|
{ name: "area", selector: { area: {} } },
|
||||||
|
{ name: "target", selector: { target: {} } },
|
||||||
|
{ name: "number", selector: { number: { min: 0, max: 10 } } },
|
||||||
|
{ name: "boolean", selector: { boolean: {} } },
|
||||||
|
{ name: "time", selector: { time: {} } },
|
||||||
|
{ name: "action", selector: { action: {} } },
|
||||||
|
{ name: "text", selector: { text: { multiline: false } } },
|
||||||
|
{ name: "text_multiline", selector: { text: { multiline: true } } },
|
||||||
|
{ name: "object", selector: { object: {} } },
|
||||||
|
{
|
||||||
|
name: "select",
|
||||||
|
selector: {
|
||||||
|
select: { options: ["Everyone Home", "Some Home", "All gone"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
selector: {
|
||||||
|
icon: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "media",
|
||||||
|
selector: {
|
||||||
|
media: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "location",
|
||||||
|
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "entities",
|
||||||
|
selector: { entity: { multiple: true } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Authentication",
|
title: "Authentication",
|
||||||
translations: {
|
translations: {
|
||||||
@@ -50,13 +216,11 @@ const SCHEMAS: {
|
|||||||
{
|
{
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
name: "bool",
|
name: "bool",
|
||||||
optional: true,
|
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "integer",
|
type: "integer",
|
||||||
name: "int",
|
name: "int",
|
||||||
optional: true,
|
|
||||||
default: 10,
|
default: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -67,7 +231,6 @@ const SCHEMAS: {
|
|||||||
{
|
{
|
||||||
type: "string",
|
type: "string",
|
||||||
name: "string",
|
name: "string",
|
||||||
optional: true,
|
|
||||||
default: "Default",
|
default: "Default",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -77,7 +240,6 @@ const SCHEMAS: {
|
|||||||
["other", "other"],
|
["other", "other"],
|
||||||
],
|
],
|
||||||
name: "select",
|
name: "select",
|
||||||
optional: true,
|
|
||||||
default: "default",
|
default: "default",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -87,7 +249,6 @@ const SCHEMAS: {
|
|||||||
other: "Other",
|
other: "Other",
|
||||||
},
|
},
|
||||||
name: "multi",
|
name: "multi",
|
||||||
optional: true,
|
|
||||||
default: ["default"],
|
default: ["default"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -108,7 +269,6 @@ const SCHEMAS: {
|
|||||||
{
|
{
|
||||||
type: "integer",
|
type: "integer",
|
||||||
name: "int with default",
|
name: "int with default",
|
||||||
optional: true,
|
|
||||||
default: 10,
|
default: 10,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -122,7 +282,6 @@ const SCHEMAS: {
|
|||||||
{
|
{
|
||||||
type: "integer",
|
type: "integer",
|
||||||
name: "int range optional",
|
name: "int range optional",
|
||||||
optional: true,
|
|
||||||
valueMin: 0,
|
valueMin: 0,
|
||||||
valueMax: 10,
|
valueMax: 10,
|
||||||
},
|
},
|
||||||
@@ -148,7 +307,6 @@ const SCHEMAS: {
|
|||||||
["other", "Other"],
|
["other", "Other"],
|
||||||
],
|
],
|
||||||
name: "select optional",
|
name: "select optional",
|
||||||
optional: true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "select",
|
type: "select",
|
||||||
@@ -161,7 +319,6 @@ const SCHEMAS: {
|
|||||||
["option", "1000"],
|
["option", "1000"],
|
||||||
],
|
],
|
||||||
name: "select many otions",
|
name: "select many otions",
|
||||||
optional: true,
|
|
||||||
default: "default",
|
default: "default",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -190,7 +347,6 @@ const SCHEMAS: {
|
|||||||
option: "1000",
|
option: "1000",
|
||||||
},
|
},
|
||||||
name: "multi many otions",
|
name: "multi many otions",
|
||||||
optional: true,
|
|
||||||
default: ["default"],
|
default: ["default"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -239,23 +395,36 @@ const SCHEMAS: {
|
|||||||
valueMin: 1,
|
valueMin: 1,
|
||||||
valueMax: 65535,
|
valueMax: 65535,
|
||||||
name: "port",
|
name: "port",
|
||||||
optional: true,
|
|
||||||
default: 80,
|
default: 80,
|
||||||
},
|
},
|
||||||
{ type: "string", name: "path", optional: true, default: "/" },
|
{ type: "string", name: "path", default: "/" },
|
||||||
{ type: "boolean", name: "ssl", optional: true, default: false },
|
{ type: "boolean", name: "ssl", default: false },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-components-ha-form")
|
@customElement("demo-components-ha-form")
|
||||||
class DemoHaForm extends LitElement {
|
class DemoHaForm extends LitElement {
|
||||||
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
private data = SCHEMAS.map(
|
private data = SCHEMAS.map(
|
||||||
({ schema, data }) => data || computeInitialHaFormData(schema)
|
({ schema, data }) => data || computeInitialHaFormData(schema)
|
||||||
);
|
);
|
||||||
|
|
||||||
private disabled = SCHEMAS.map(() => false);
|
private disabled = SCHEMAS.map(() => false);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const hass = provideHass(this);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
|
mockEntityRegistry(hass);
|
||||||
|
mockDeviceRegistry(hass, DEVICES);
|
||||||
|
mockAreaRegistry(hass, AREAS);
|
||||||
|
mockHassioSupervisor(hass);
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${SCHEMAS.map((info, idx) => {
|
${SCHEMAS.map((info, idx) => {
|
||||||
@@ -278,6 +447,7 @@ class DemoHaForm extends LitElement {
|
|||||||
(slot) => html`
|
(slot) => html`
|
||||||
<ha-form
|
<ha-form
|
||||||
slot=${slot}
|
slot=${slot}
|
||||||
|
.hass=${this.hass}
|
||||||
.data=${this.data[idx]}
|
.data=${this.data[idx]}
|
||||||
.schema=${info.schema}
|
.schema=${info.schema}
|
||||||
.error=${info.error}
|
.error=${info.error}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Target Selectors
|
title: Selectors
|
||||||
---
|
---
|
||||||
|
|
||||||
|
See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/).
|
||||||
|
@@ -12,6 +12,100 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
|||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
|
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||||
|
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||||
|
|
||||||
|
const ENTITIES = [
|
||||||
|
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||||
|
friendly_name: "Alarm",
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "livingroom", "playing", {
|
||||||
|
friendly_name: "Livingroom",
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "lounge", "idle", {
|
||||||
|
friendly_name: "Lounge",
|
||||||
|
supported_features: 444983,
|
||||||
|
}),
|
||||||
|
getEntity("light", "bedroom", "on", {
|
||||||
|
friendly_name: "Bedroom",
|
||||||
|
}),
|
||||||
|
getEntity("switch", "coffee", "off", {
|
||||||
|
friendly_name: "Coffee",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEVICES = [
|
||||||
|
{
|
||||||
|
area_id: "bedroom",
|
||||||
|
configuration_url: null,
|
||||||
|
config_entries: ["config_entry_1"],
|
||||||
|
connections: [],
|
||||||
|
disabled_by: null,
|
||||||
|
entry_type: null,
|
||||||
|
id: "device_1",
|
||||||
|
identifiers: [["demo", "volume1"] as [string, string]],
|
||||||
|
manufacturer: null,
|
||||||
|
model: null,
|
||||||
|
name_by_user: null,
|
||||||
|
name: "Dishwasher",
|
||||||
|
sw_version: null,
|
||||||
|
hw_version: null,
|
||||||
|
via_device_id: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: "backyard",
|
||||||
|
configuration_url: null,
|
||||||
|
config_entries: ["config_entry_2"],
|
||||||
|
connections: [],
|
||||||
|
disabled_by: null,
|
||||||
|
entry_type: null,
|
||||||
|
id: "device_2",
|
||||||
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
|
manufacturer: null,
|
||||||
|
model: null,
|
||||||
|
name_by_user: null,
|
||||||
|
name: "Lamp",
|
||||||
|
sw_version: null,
|
||||||
|
hw_version: null,
|
||||||
|
via_device_id: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: null,
|
||||||
|
configuration_url: null,
|
||||||
|
config_entries: ["config_entry_3"],
|
||||||
|
connections: [],
|
||||||
|
disabled_by: null,
|
||||||
|
entry_type: null,
|
||||||
|
id: "device_3",
|
||||||
|
identifiers: [["demo", "pwm1"] as [string, string]],
|
||||||
|
manufacturer: null,
|
||||||
|
model: null,
|
||||||
|
name_by_user: "User name",
|
||||||
|
name: "Technical name",
|
||||||
|
sw_version: null,
|
||||||
|
hw_version: null,
|
||||||
|
via_device_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const AREAS = [
|
||||||
|
{
|
||||||
|
area_id: "backyard",
|
||||||
|
name: "Backyard",
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: "bedroom",
|
||||||
|
name: "Bedroom",
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area_id: "livingroom",
|
||||||
|
name: "Livingroom",
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const SCHEMAS: {
|
const SCHEMAS: {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -21,7 +115,12 @@ const SCHEMAS: {
|
|||||||
name: "One of each",
|
name: "One of each",
|
||||||
input: {
|
input: {
|
||||||
entity: { name: "Entity", selector: { entity: {} } },
|
entity: { name: "Entity", selector: { entity: {} } },
|
||||||
|
attribute: {
|
||||||
|
name: "Attribute",
|
||||||
|
selector: { attribute: { entity_id: "" } },
|
||||||
|
},
|
||||||
device: { name: "Device", selector: { device: {} } },
|
device: { name: "Device", selector: { device: {} } },
|
||||||
|
duration: { name: "Duration", selector: { duration: {} } },
|
||||||
addon: { name: "Addon", selector: { addon: {} } },
|
addon: { name: "Addon", selector: { addon: {} } },
|
||||||
area: { name: "Area", selector: { area: {} } },
|
area: { name: "Area", selector: { area: {} } },
|
||||||
target: { name: "Target", selector: { target: {} } },
|
target: { name: "Target", selector: { target: {} } },
|
||||||
@@ -47,24 +146,69 @@ const SCHEMAS: {
|
|||||||
},
|
},
|
||||||
boolean: { name: "Boolean", selector: { boolean: {} } },
|
boolean: { name: "Boolean", selector: { boolean: {} } },
|
||||||
time: { name: "Time", selector: { time: {} } },
|
time: { name: "Time", selector: { time: {} } },
|
||||||
|
date: { name: "Date", selector: { date: {} } },
|
||||||
|
datetime: { name: "Date Time", selector: { datetime: {} } },
|
||||||
action: { name: "Action", selector: { action: {} } },
|
action: { name: "Action", selector: { action: {} } },
|
||||||
text: { name: "Text", selector: { text: { multiline: false } } },
|
text: {
|
||||||
|
name: "Text",
|
||||||
|
selector: { text: {} },
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
name: "Password",
|
||||||
|
selector: { text: { type: "password" } },
|
||||||
|
},
|
||||||
text_multiline: {
|
text_multiline: {
|
||||||
name: "Text multiline",
|
name: "Text multiline",
|
||||||
selector: { text: { multiline: true } },
|
selector: {
|
||||||
|
text: { multiline: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
object: { name: "Object", selector: { object: {} } },
|
object: { name: "Object", selector: { object: {} } },
|
||||||
select: {
|
select_radio: {
|
||||||
name: "Select",
|
name: "Select (Radio)",
|
||||||
selector: { select: { options: ["Option 1", "Option 2"] } },
|
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||||
},
|
},
|
||||||
|
select: {
|
||||||
|
name: "Select",
|
||||||
|
selector: {
|
||||||
|
select: {
|
||||||
|
options: [
|
||||||
|
"Option 1",
|
||||||
|
"Option 2",
|
||||||
|
"Option 3",
|
||||||
|
"Option 4",
|
||||||
|
"Option 5",
|
||||||
|
"Option 6",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
icon: { name: "Icon", selector: { icon: {} } },
|
||||||
|
media: { name: "Media", selector: { media: {} } },
|
||||||
|
location: { name: "Location", selector: { location: {} } },
|
||||||
|
location_radius: {
|
||||||
|
name: "Location with radius",
|
||||||
|
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||||
|
},
|
||||||
|
color_temp: {
|
||||||
|
name: "Color Temperature",
|
||||||
|
selector: { color_temp: {} },
|
||||||
|
},
|
||||||
|
color_rgb: { name: "Color", selector: { color_rgb: {} } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiples",
|
||||||
|
input: {
|
||||||
|
entity: { name: "Entity", selector: { entity: { multiple: true } } },
|
||||||
|
device: { name: "Device", selector: { device: { multiple: true } } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-components-ha-selector")
|
@customElement("demo-components-ha-selector")
|
||||||
class DemoHaSelector extends LitElement {
|
class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||||
@state() private hass!: HomeAssistant;
|
@state() public hass!: HomeAssistant;
|
||||||
|
|
||||||
private data = SCHEMAS.map(() => ({}));
|
private data = SCHEMAS.map(() => ({}));
|
||||||
|
|
||||||
@@ -73,12 +217,130 @@ class DemoHaSelector extends LitElement {
|
|||||||
const hass = provideHass(this);
|
const hass = provideHass(this);
|
||||||
hass.updateTranslations(null, "en");
|
hass.updateTranslations(null, "en");
|
||||||
hass.updateTranslations("config", "en");
|
hass.updateTranslations("config", "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
mockEntityRegistry(hass);
|
mockEntityRegistry(hass);
|
||||||
mockDeviceRegistry(hass);
|
mockDeviceRegistry(hass, DEVICES);
|
||||||
mockAreaRegistry(hass);
|
mockAreaRegistry(hass, AREAS);
|
||||||
mockHassioSupervisor(hass);
|
mockHassioSupervisor(hass);
|
||||||
|
hass.mockWS("auth/sign_path", (params) => params);
|
||||||
|
hass.mockWS("media_player/browse_media", this._browseMedia);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public provideHass(el) {
|
||||||
|
el.hass = this.hass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
this.addEventListener("show-dialog", this._dialogManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this.removeEventListener("show-dialog", this._dialogManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _browseMedia = ({ media_content_id }) => {
|
||||||
|
if (media_content_id === undefined) {
|
||||||
|
return {
|
||||||
|
title: "Media",
|
||||||
|
media_class: "directory",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/.",
|
||||||
|
can_play: false,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: "directory",
|
||||||
|
thumbnail: null,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: "Misc",
|
||||||
|
media_class: "directory",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/misc",
|
||||||
|
can_play: false,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: null,
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Movies",
|
||||||
|
media_class: "directory",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/movies",
|
||||||
|
can_play: true,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: "movie",
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Music",
|
||||||
|
media_class: "album",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/music",
|
||||||
|
can_play: false,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: "music",
|
||||||
|
thumbnail: "/images/album_cover_2.jpg",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
title: "Subfolder",
|
||||||
|
media_class: "directory",
|
||||||
|
media_content_type: "",
|
||||||
|
media_content_id: "media-source://media_source/local/sub",
|
||||||
|
can_play: false,
|
||||||
|
can_expand: true,
|
||||||
|
children_media_class: "directory",
|
||||||
|
thumbnail: null,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
title: "audio.mp3",
|
||||||
|
media_class: "music",
|
||||||
|
media_content_type: "audio/mpeg",
|
||||||
|
media_content_id: "media-source://media_source/local/audio.mp3",
|
||||||
|
can_play: true,
|
||||||
|
can_expand: false,
|
||||||
|
children_media_class: null,
|
||||||
|
thumbnail: "/images/album_cover.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "image.jpg",
|
||||||
|
media_class: "image",
|
||||||
|
media_content_type: "image/jpeg",
|
||||||
|
media_content_id: "media-source://media_source/local/image.jpg",
|
||||||
|
can_play: true,
|
||||||
|
can_expand: false,
|
||||||
|
children_media_class: null,
|
||||||
|
thumbnail: "https://brands.home-assistant.io/_/image/logo.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "movie.mp4",
|
||||||
|
media_class: "movie",
|
||||||
|
media_content_type: "image/jpeg",
|
||||||
|
media_content_id: "media-source://media_source/local/movie.mp4",
|
||||||
|
can_play: true,
|
||||||
|
can_expand: false,
|
||||||
|
children_media_class: null,
|
||||||
|
thumbnail: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _dialogManager = (e) => {
|
||||||
|
const { dialogTag, dialogImport, dialogParams, addHistory } = e.detail;
|
||||||
|
showDialog(
|
||||||
|
this,
|
||||||
|
this.shadowRoot!,
|
||||||
|
dialogTag,
|
||||||
|
dialogParams,
|
||||||
|
dialogImport,
|
||||||
|
addHistory
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${SCHEMAS.map((info, idx) => {
|
${SCHEMAS.map((info, idx) => {
|
||||||
@@ -117,7 +379,6 @@ class DemoHaSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
paper-input,
|
|
||||||
ha-selector {
|
ha-selector {
|
||||||
width: 60;
|
width: 60;
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
title: Editing design.home-assistant.io
|
title: Editing design.home-assistant.io
|
||||||
---
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
# How to edit design.home-assistant.io
|
# How to edit design.home-assistant.io
|
||||||
|
|
||||||
All pages are stored in [the pages folder][pages-folder] on GitHub. Pages are grouped in a folder per sidebar section. Each page can contain a `<page name>.markdown` description file, a `<page name>.ts` demo file or both. If both are defined the description is rendered first. The description can contain metadata to specify the title of the page.
|
All pages are stored in [the pages folder][pages-folder] on GitHub. Pages are grouped in a folder per sidebar section. Each page can contain a `<page name>.markdown` description file, a `<page name>.ts` demo file or both. If both are defined the description is rendered first. The description can contain metadata to specify the title of the page.
|
||||||
@@ -41,15 +43,12 @@ import { html, css, LitElement } from "lit";
|
|||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
|
|
||||||
|
|
||||||
@customElement("demo-user-experience-usability")
|
@customElement("demo-user-experience-usability")
|
||||||
export class DemoUserExperienceUsability extends LitElement {
|
export class DemoUserExperienceUsability extends LitElement {
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="card-content">
|
<div class="card-content">Hello world!</div>
|
||||||
Hello world!
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -29,6 +29,7 @@ const createConfigEntry = (
|
|||||||
source: "zeroconf",
|
source: "zeroconf",
|
||||||
state: "loaded",
|
state: "loaded",
|
||||||
supports_options: false,
|
supports_options: false,
|
||||||
|
supports_remove_device: false,
|
||||||
supports_unload: true,
|
supports_unload: true,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
pref_disable_new_entities: false,
|
pref_disable_new_entities: false,
|
||||||
@@ -187,6 +188,7 @@ const createEntityRegistryEntries = (
|
|||||||
device_id: "mock-device-id",
|
device_id: "mock-device-id",
|
||||||
area_id: null,
|
area_id: null,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
|
hidden_by: null,
|
||||||
entity_category: null,
|
entity_category: null,
|
||||||
entity_id: "binary_sensor.updater",
|
entity_id: "binary_sensor.updater",
|
||||||
name: null,
|
name: null,
|
||||||
|
3
gallery/src/pages/more-info/update.markdown
Normal file
3
gallery/src/pages/more-info/update.markdown
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
title: Update
|
||||||
|
---
|
140
gallery/src/pages/more-info/update.ts
Normal file
140
gallery/src/pages/more-info/update.ts
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import "../../../../src/components/ha-card";
|
||||||
|
import {
|
||||||
|
UPDATE_SUPPORT_BACKUP,
|
||||||
|
UPDATE_SUPPORT_PROGRESS,
|
||||||
|
UPDATE_SUPPORT_INSTALL,
|
||||||
|
} from "../../../../src/data/update";
|
||||||
|
import "../../../../src/dialogs/more-info/more-info-content";
|
||||||
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
|
import {
|
||||||
|
MockHomeAssistant,
|
||||||
|
provideHass,
|
||||||
|
} from "../../../../src/fake_data/provide_hass";
|
||||||
|
import "../../components/demo-more-infos";
|
||||||
|
|
||||||
|
const base_attributes = {
|
||||||
|
title: "Awesome",
|
||||||
|
current_version: "1.2.2",
|
||||||
|
latest_version: "1.2.3",
|
||||||
|
release_url: "https://home-assistant.io",
|
||||||
|
supported_features: UPDATE_SUPPORT_INSTALL,
|
||||||
|
skipped_version: null,
|
||||||
|
in_progress: false,
|
||||||
|
release_summary:
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In nec metus aliquet, porta mi ut, ultrices odio. Etiam egestas orci tellus, non semper metus blandit tincidunt. Praesent elementum turpis vel tempor pharetra. Sed quis cursus diam. Proin sem justo.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ENTITIES = [
|
||||||
|
getEntity("update", "update1", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
friendly_name: "Update",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update2", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
title: null,
|
||||||
|
friendly_name: "Update without title",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update3", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
release_url: null,
|
||||||
|
friendly_name: "Update without release_url",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update4", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
release_summary: null,
|
||||||
|
friendly_name: "Update without release_summary",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update5", "off", {
|
||||||
|
...base_attributes,
|
||||||
|
current_version: "1.2.3",
|
||||||
|
friendly_name: "No update",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update6", "off", {
|
||||||
|
...base_attributes,
|
||||||
|
skipped_version: "1.2.3",
|
||||||
|
friendly_name: "Skipped version",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update7", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
supported_features:
|
||||||
|
base_attributes.supported_features + UPDATE_SUPPORT_BACKUP,
|
||||||
|
friendly_name: "With backup support",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update8", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
in_progress: true,
|
||||||
|
friendly_name: "With true in_progress",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update9", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
in_progress: 25,
|
||||||
|
supported_features:
|
||||||
|
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||||
|
friendly_name: "With 25 in_progress",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update10", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
in_progress: 50,
|
||||||
|
supported_features:
|
||||||
|
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||||
|
friendly_name: "With 50 in_progress",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update11", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
in_progress: 75,
|
||||||
|
supported_features:
|
||||||
|
base_attributes.supported_features + UPDATE_SUPPORT_PROGRESS,
|
||||||
|
friendly_name: "With 75 in_progress",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update12", "unavailable", {
|
||||||
|
...base_attributes,
|
||||||
|
in_progress: 50,
|
||||||
|
friendly_name: "Unavailable",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update13", "on", {
|
||||||
|
...base_attributes,
|
||||||
|
supported_features: 0,
|
||||||
|
friendly_name: "No install support",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update14", "off", {
|
||||||
|
...base_attributes,
|
||||||
|
current_version: null,
|
||||||
|
friendly_name: "Update without current_version",
|
||||||
|
}),
|
||||||
|
getEntity("update", "update15", "off", {
|
||||||
|
...base_attributes,
|
||||||
|
latest_version: null,
|
||||||
|
friendly_name: "Update without latest_version",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-more-info-update")
|
||||||
|
class DemoMoreInfoUpdate extends LitElement {
|
||||||
|
@property() public hass!: MockHomeAssistant;
|
||||||
|
|
||||||
|
@query("demo-more-infos") private _demoRoot!: HTMLElement;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<demo-more-infos
|
||||||
|
.hass=${this.hass}
|
||||||
|
.entities=${ENTITIES.map((ent) => ent.entityId)}
|
||||||
|
></demo-more-infos>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProperties: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
const hass = provideHass(this._demoRoot);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.addEntities(ENTITIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-more-info-update": DemoMoreInfoUpdate;
|
||||||
|
}
|
||||||
|
}
|
17
gallery/src/pages/user-test/user-types.markdown
Normal file
17
gallery/src/pages/user-test/user-types.markdown
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
title: "User types"
|
||||||
|
---
|
||||||
|
|
||||||
|
We have defined three user types for Home Assistant. They are a lean segmentation of users that helps us make decisions throughout the product. User types differ from traditional personas in that the segmentation criteria aren’t demographic and don’t personify a group into a single character with a fictitious background story.
|
||||||
|
|
||||||
|
# Outgrowers
|
||||||
|
|
||||||
|
Users that outgrow big tech smart home solutions. It just needs to work with easy setup via an app.
|
||||||
|
|
||||||
|
# Tinkerers
|
||||||
|
|
||||||
|
Technoid users in home networking and development that know how to code.
|
||||||
|
|
||||||
|
# Questioner
|
||||||
|
|
||||||
|
Users who want more advanced home automation, but need support to make it work.
|
@@ -42,7 +42,9 @@ class HassioAddonRepositoryEl extends LitElement {
|
|||||||
const repo = this.repo;
|
const repo = this.repo;
|
||||||
let _addons = this.addons;
|
let _addons = this.addons;
|
||||||
if (!this.hass.userData?.showAdvanced) {
|
if (!this.hass.userData?.showAdvanced) {
|
||||||
_addons = _addons.filter((addon) => !addon.advanced);
|
_addons = _addons.filter(
|
||||||
|
(addon) => !addon.advanced && addon.stage === "stable"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const addons = this._getAddons(_addons, this.filter);
|
const addons = this._getAddons(_addons, this.filter);
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ import memoizeOne from "memoize-one";
|
|||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import { navigate } from "../../../src/common/navigate";
|
import { navigate } from "../../../src/common/navigate";
|
||||||
import "../../../src/common/search/search-input";
|
import "../../../src/components/search-input";
|
||||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
import "../../../src/components/ha-icon-button";
|
import "../../../src/components/ha-icon-button";
|
||||||
@@ -110,8 +110,6 @@ class HassioAddonStore extends LitElement {
|
|||||||
<div class="search">
|
<div class="search">
|
||||||
<search-input
|
<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
no-label-float
|
|
||||||
no-underline
|
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._filterChanged}
|
@value-changed=${this._filterChanged}
|
||||||
></search-input>
|
></search-input>
|
||||||
@@ -221,13 +219,14 @@ class HassioAddonStore extends LitElement {
|
|||||||
margin-top: 24px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
.search {
|
.search {
|
||||||
padding: 0 16px;
|
position: sticky;
|
||||||
background: var(--sidebar-background-color);
|
top: 0;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.search search-input {
|
search-input {
|
||||||
position: relative;
|
display: block;
|
||||||
top: 2px;
|
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||||
|
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||||
}
|
}
|
||||||
.advanced {
|
.advanced {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -11,10 +9,11 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "web-animations-js/web-animations-next-lite.min";
|
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
|
||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
|
import "../../../../src/components/ha-select";
|
||||||
import {
|
import {
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
HassioAddonSetOptionParams,
|
HassioAddonSetOptionParams,
|
||||||
@@ -57,49 +56,44 @@ class HassioAddonAudio extends LitElement {
|
|||||||
${this._error
|
${this._error
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this._inputDevices &&
|
||||||
<paper-dropdown-menu
|
html`<ha-select
|
||||||
.label=${this.supervisor.localize(
|
.label=${this.supervisor.localize(
|
||||||
"addon.configuration.audio.input"
|
"addon.configuration.audio.input"
|
||||||
)}
|
)}
|
||||||
@iron-select=${this._setInputDevice}
|
@selected=${this._setInputDevice}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
.value=${this._selectedInput!}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
${this._inputDevices.map(
|
||||||
slot="dropdown-content"
|
(item) => html`
|
||||||
attr-for-selected="device"
|
<mwc-list-item .value=${item.device || ""}>
|
||||||
.selected=${this._selectedInput!}
|
${item.name}
|
||||||
>
|
</mwc-list-item>
|
||||||
${this._inputDevices &&
|
`
|
||||||
this._inputDevices.map(
|
)}
|
||||||
(item) => html`
|
</ha-select>`}
|
||||||
<paper-item device=${item.device || ""}>
|
${this._outputDevices &&
|
||||||
${item.name}
|
html`<ha-select
|
||||||
</paper-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
<paper-dropdown-menu
|
|
||||||
.label=${this.supervisor.localize(
|
.label=${this.supervisor.localize(
|
||||||
"addon.configuration.audio.output"
|
"addon.configuration.audio.output"
|
||||||
)}
|
)}
|
||||||
@iron-select=${this._setOutputDevice}
|
@selected=${this._setOutputDevice}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
.value=${this._selectedOutput!}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
${this._outputDevices.map(
|
||||||
slot="dropdown-content"
|
(item) => html`
|
||||||
attr-for-selected="device"
|
<mwc-list-item .value=${item.device || ""}
|
||||||
.selected=${this._selectedOutput!}
|
>${item.name}</mwc-list-item
|
||||||
>
|
>
|
||||||
${this._outputDevices &&
|
`
|
||||||
this._outputDevices.map(
|
)}
|
||||||
(item) => html`
|
</ha-select>`}
|
||||||
<paper-item device=${item.device || ""}
|
|
||||||
>${item.name}</paper-item
|
|
||||||
>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-progress-button @click=${this._saveSettings}>
|
<ha-progress-button @click=${this._saveSettings}>
|
||||||
@@ -116,8 +110,7 @@ class HassioAddonAudio extends LitElement {
|
|||||||
hassioStyle,
|
hassioStyle,
|
||||||
css`
|
css`
|
||||||
:host,
|
:host,
|
||||||
ha-card,
|
ha-card {
|
||||||
paper-dropdown-menu {
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
paper-item {
|
paper-item {
|
||||||
@@ -126,24 +119,30 @@ class HassioAddonAudio extends LitElement {
|
|||||||
.card-actions {
|
.card-actions {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
ha-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
ha-select:last-child {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected update(changedProperties: PropertyValues): void {
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
super.update(changedProperties);
|
super.willUpdate(changedProperties);
|
||||||
if (changedProperties.has("addon")) {
|
if (changedProperties.has("addon")) {
|
||||||
this._addonChanged();
|
this._addonChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setInputDevice(ev): void {
|
private _setInputDevice(ev): void {
|
||||||
const device = ev.detail.item.getAttribute("device");
|
const device = ev.target.value;
|
||||||
this._selectedInput = device;
|
this._selectedInput = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setOutputDevice(ev): void {
|
private _setOutputDevice(ev): void {
|
||||||
const device = ev.detail.item.getAttribute("device");
|
const device = ev.target.value;
|
||||||
this._selectedOutput = device;
|
this._selectedOutput = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ import {
|
|||||||
mdiFlask,
|
mdiFlask,
|
||||||
mdiHomeAssistant,
|
mdiHomeAssistant,
|
||||||
mdiKey,
|
mdiKey,
|
||||||
|
mdiLinkLock,
|
||||||
mdiNetwork,
|
mdiNetwork,
|
||||||
mdiNumeric1,
|
mdiNumeric1,
|
||||||
mdiNumeric2,
|
mdiNumeric2,
|
||||||
@@ -16,6 +17,8 @@ import {
|
|||||||
mdiNumeric4,
|
mdiNumeric4,
|
||||||
mdiNumeric5,
|
mdiNumeric5,
|
||||||
mdiNumeric6,
|
mdiNumeric6,
|
||||||
|
mdiNumeric7,
|
||||||
|
mdiNumeric8,
|
||||||
mdiPound,
|
mdiPound,
|
||||||
mdiShield,
|
mdiShield,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
@@ -31,6 +34,7 @@ import "../../../../src/components/buttons/ha-progress-button";
|
|||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-chip";
|
import "../../../../src/components/ha-chip";
|
||||||
|
import "../../../../src/components/ha-chip-set";
|
||||||
import "../../../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
import "../../../../src/components/ha-settings-row";
|
import "../../../../src/components/ha-settings-row";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-svg-icon";
|
||||||
@@ -84,6 +88,8 @@ const RATING_ICON = {
|
|||||||
4: mdiNumeric4,
|
4: mdiNumeric4,
|
||||||
5: mdiNumeric5,
|
5: mdiNumeric5,
|
||||||
6: mdiNumeric6,
|
6: mdiNumeric6,
|
||||||
|
7: mdiNumeric7,
|
||||||
|
8: mdiNumeric8,
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("hassio-addon-info")
|
@customElement("hassio-addon-info")
|
||||||
@@ -209,7 +215,7 @@ class HassioAddonInfo extends LitElement {
|
|||||||
>`}
|
>`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="capabilities">
|
<ha-chip-set class="capabilities">
|
||||||
${this.addon.stage !== "stable"
|
${this.addon.stage !== "stable"
|
||||||
? html` <ha-chip
|
? html` <ha-chip
|
||||||
hasIcon
|
hasIcon
|
||||||
@@ -234,9 +240,9 @@ class HassioAddonInfo extends LitElement {
|
|||||||
<ha-chip
|
<ha-chip
|
||||||
hasIcon
|
hasIcon
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
green: [5, 6].includes(Number(this.addon.rating)),
|
green: Number(this.addon.rating) >= 6,
|
||||||
yellow: [3, 4].includes(Number(this.addon.rating)),
|
yellow: [3, 4, 5].includes(Number(this.addon.rating)),
|
||||||
red: [1, 2].includes(Number(this.addon.rating)),
|
red: Number(this.addon.rating) >= 2,
|
||||||
})}
|
})}
|
||||||
@click=${this._showMoreInfo}
|
@click=${this._showMoreInfo}
|
||||||
id="rating"
|
id="rating"
|
||||||
@@ -364,7 +370,17 @@ class HassioAddonInfo extends LitElement {
|
|||||||
</ha-chip>
|
</ha-chip>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
${this.addon.signed
|
||||||
|
? html`
|
||||||
|
<ha-chip hasIcon @click=${this._showMoreInfo} id="signed">
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiLinkLock}></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.signed"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</ha-chip-set>
|
||||||
|
|
||||||
<div class="description light-color">
|
<div class="description light-color">
|
||||||
${this.addon.description}.<br />
|
${this.addon.description}.<br />
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { mdiFolderUpload } from "@mdi/js";
|
import { mdiFolderUpload } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input-container";
|
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import { formatDate } from "../../../src/common/datetime/format_date";
|
import { formatDate } from "../../../src/common/datetime/format_date";
|
||||||
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
||||||
@@ -92,6 +92,8 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
|
|
||||||
@property() public confirmBackupPassword = "";
|
@property() public confirmBackupPassword = "";
|
||||||
|
|
||||||
|
@query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget;
|
||||||
|
|
||||||
public willUpdate(changedProps) {
|
public willUpdate(changedProps) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
@@ -109,6 +111,10 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override focus() {
|
||||||
|
this._focusTarget?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
private _localize = (string: string) =>
|
private _localize = (string: string) =>
|
||||||
this.supervisor?.localize(`backup.${string}`) ||
|
this.supervisor?.localize(`backup.${string}`) ||
|
||||||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
||||||
@@ -169,24 +175,23 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.backupType === "partial"
|
${this.backupType === "partial"
|
||||||
? html`<div class="partial-picker">
|
? html`<div class="partial-picker">
|
||||||
${this.backup && this.backup.homeassistant
|
<ha-formfield
|
||||||
? html`
|
.label=${html`<supervisor-formfield-label
|
||||||
<ha-formfield
|
label="Home Assistant"
|
||||||
.label=${html`<supervisor-formfield-label
|
.iconPath=${mdiHomeAssistant}
|
||||||
label="Home Assistant"
|
.version=${this.backup
|
||||||
.iconPath=${mdiHomeAssistant}
|
? this.backup.homeassistant
|
||||||
.version=${this.backup.homeassistant}
|
: this.hass.config.version}
|
||||||
>
|
>
|
||||||
</supervisor-formfield-label>`}
|
</supervisor-formfield-label>`}
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this.homeAssistant}
|
.checked=${this.homeAssistant}
|
||||||
@click=${this.toggleHomeAssistant}
|
@click=${this.toggleHomeAssistant}
|
||||||
>
|
>
|
||||||
</ha-checkbox>
|
</ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${foldersSection?.templates.length
|
${foldersSection?.templates.length
|
||||||
? html`
|
? html`
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
|
@@ -148,7 +148,6 @@ export class HassioUpdate extends LitElement {
|
|||||||
}
|
}
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
--paper-item-body-two-line-min-height: 32px;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -64,6 +64,7 @@ export class DialogHassioBackupUpload
|
|||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
slot="actionItems"
|
slot="actionItems"
|
||||||
dialogAction="cancel"
|
dialogAction="cancel"
|
||||||
|
dialogInitialFocus
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -92,6 +92,7 @@ class HassioBackupDialog
|
|||||||
.backup=${this._backup}
|
.backup=${this._backup}
|
||||||
.onboarding=${this._dialogParams.onboarding || false}
|
.onboarding=${this._dialogParams.onboarding || false}
|
||||||
.localize=${this._dialogParams.localize}
|
.localize=${this._dialogParams.localize}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</supervisor-backup-content>`}
|
</supervisor-backup-content>`}
|
||||||
${this._error
|
${this._error
|
||||||
|
@@ -61,6 +61,7 @@ class HassioCreateBackupDialog extends LitElement {
|
|||||||
: html`<supervisor-backup-content
|
: html`<supervisor-backup-content
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.supervisor=${this._dialogParams.supervisor}
|
.supervisor=${this._dialogParams.supervisor}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</supervisor-backup-content>`}
|
</supervisor-backup-content>`}
|
||||||
${this._error
|
${this._error
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import "../../../../src/components/ha-circular-progress";
|
import "../../../../src/components/ha-circular-progress";
|
||||||
import "../../../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
|
import "../../../../src/components/ha-select";
|
||||||
import {
|
import {
|
||||||
extractApiErrorMessage,
|
extractApiErrorMessage,
|
||||||
ignoreSupervisorError,
|
ignoreSupervisorError,
|
||||||
@@ -90,18 +89,20 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
)}
|
)}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<paper-dropdown-menu
|
<ha-select
|
||||||
.label=${this.dialogParams.supervisor.localize(
|
.label=${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.select_device"
|
"dialog.datadisk_move.select_device"
|
||||||
)}
|
)}
|
||||||
@value-changed=${this._select_device}
|
@selected=${this._select_device}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
<paper-listbox slot="dropdown-content">
|
${this.devices.map(
|
||||||
${this.devices.map(
|
(device) =>
|
||||||
(device) => html`<paper-item>${device}</paper-item>`
|
html`<mwc-list-item .value=${device}
|
||||||
)}
|
>${device}</mwc-list-item
|
||||||
</paper-listbox>
|
>`
|
||||||
</paper-dropdown-menu>
|
)}
|
||||||
|
</ha-select>
|
||||||
`
|
`
|
||||||
: this.devices === undefined
|
: this.devices === undefined
|
||||||
? this.dialogParams.supervisor.localize(
|
? this.dialogParams.supervisor.localize(
|
||||||
@@ -111,7 +112,11 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
"dialog.datadisk_move.no_devices"
|
"dialog.datadisk_move.no_devices"
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
<mwc-button
|
||||||
|
slot="secondaryAction"
|
||||||
|
@click=${this.closeDialog}
|
||||||
|
dialogInitialFocus
|
||||||
|
>
|
||||||
${this.dialogParams.supervisor.localize(
|
${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.cancel"
|
"dialog.datadisk_move.cancel"
|
||||||
)}
|
)}
|
||||||
@@ -130,8 +135,8 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _select_device(event) {
|
private _select_device(ev) {
|
||||||
this.selectedDevice = event.detail.value;
|
this.selectedDevice = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _moveDatadisk() {
|
private async _moveDatadisk() {
|
||||||
@@ -156,7 +161,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
haStyle,
|
haStyle,
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
paper-dropdown-menu {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
ha-circular-progress {
|
ha-circular-progress {
|
||||||
|
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import "../../../../src/common/search/search-input";
|
import "../../../../src/components/search-input";
|
||||||
import { stringCompare } from "../../../../src/common/string/compare";
|
import { stringCompare } from "../../../../src/common/string/compare";
|
||||||
import "../../../../src/components/ha-dialog";
|
import "../../../../src/components/ha-dialog";
|
||||||
import "../../../../src/components/ha-expansion-panel";
|
import "../../../../src/components/ha-expansion-panel";
|
||||||
@@ -80,8 +80,6 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<search-input
|
<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
autofocus
|
|
||||||
no-label-float
|
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._handleSearchChange}
|
@value-changed=${this._handleSearchChange}
|
||||||
.label=${this._dialogParams.supervisor.localize(
|
.label=${this._dialogParams.supervisor.localize(
|
||||||
@@ -178,7 +176,7 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
}
|
}
|
||||||
search-input {
|
search-input {
|
||||||
margin: 0 16px;
|
margin: 8px 16px 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.device-property {
|
.device-property {
|
||||||
|
@@ -37,7 +37,10 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
.heading=${createCloseHeading(this.hass, this.title)}
|
.heading=${createCloseHeading(this.hass, this.title)}
|
||||||
>
|
>
|
||||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
<ha-markdown
|
||||||
|
.content=${this.content || ""}
|
||||||
|
dialogInitialFocus
|
||||||
|
></ha-markdown>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -119,6 +119,7 @@ export class DialogHassioNetwork
|
|||||||
html`<mwc-tab
|
html`<mwc-tab
|
||||||
.id=${device.interface}
|
.id=${device.interface}
|
||||||
.label=${device.interface}
|
.label=${device.interface}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</mwc-tab>`
|
</mwc-tab>`
|
||||||
)}
|
)}
|
||||||
@@ -315,6 +316,7 @@ export class DialogHassioNetwork
|
|||||||
value="auto"
|
value="auto"
|
||||||
name="${version}method"
|
name="${version}method"
|
||||||
.checked=${this._interface![version]?.method === "auto"}
|
.checked=${this._interface![version]?.method === "auto"}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</ha-radio>
|
</ha-radio>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
|
@@ -19,22 +19,21 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
|||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
import { RegistriesDialogParams } from "./show-dialog-registries";
|
import { RegistriesDialogParams } from "./show-dialog-registries";
|
||||||
|
|
||||||
const SCHEMA = [
|
const SCHEMA: HaFormSchema[] = [
|
||||||
{
|
{
|
||||||
type: "string",
|
|
||||||
name: "registry",
|
name: "registry",
|
||||||
required: true,
|
required: true,
|
||||||
|
selector: { text: {} },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "string",
|
|
||||||
name: "username",
|
name: "username",
|
||||||
required: true,
|
required: true,
|
||||||
|
selector: { text: {} },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "string",
|
|
||||||
name: "password",
|
name: "password",
|
||||||
required: true,
|
required: true,
|
||||||
format: "password",
|
selector: { text: { type: "password" } },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -81,6 +80,7 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
.schema=${SCHEMA}
|
.schema=${SCHEMA}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.computeLabel=${this._computeLabel}
|
.computeLabel=${this._computeLabel}
|
||||||
|
dialogInitialFocus
|
||||||
></ha-form>
|
></ha-form>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@@ -125,7 +125,7 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
</ha-alert>
|
</ha-alert>
|
||||||
`}
|
`}
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button @click=${this._addRegistry}>
|
<mwc-button @click=${this._addRegistry} dialogInitialFocus>
|
||||||
${this.supervisor.localize(
|
${this.supervisor.localize(
|
||||||
"dialog.registries.add_new_registry"
|
"dialog.registries.add_new_registry"
|
||||||
)}
|
)}
|
||||||
|
@@ -106,6 +106,9 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
</paper-item-body>
|
</paper-item-body>
|
||||||
<div class="delete">
|
<div class="delete">
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
|
.label=${this._dialogParams!.supervisor.localize(
|
||||||
|
"dialog.repositories.remove"
|
||||||
|
)}
|
||||||
.disabled=${usedRepositories.includes(repo.slug)}
|
.disabled=${usedRepositories.includes(repo.slug)}
|
||||||
.slug=${repo.slug}
|
.slug=${repo.slug}
|
||||||
.path=${usedRepositories.includes(repo.slug)
|
.path=${usedRepositories.includes(repo.slug)
|
||||||
@@ -139,6 +142,7 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
"dialog.repositories.add"
|
"dialog.repositories.add"
|
||||||
)}
|
)}
|
||||||
@keydown=${this._handleKeyAdd}
|
@keydown=${this._handleKeyAdd}
|
||||||
|
dialogInitialFocus
|
||||||
></paper-input>
|
></paper-input>
|
||||||
<mwc-button @click=${this._addRepository}>
|
<mwc-button @click=${this._addRepository}>
|
||||||
${this._processing
|
${this._processing
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../../src/resources/compatibility";
|
import "../../src/resources/compatibility";
|
||||||
|
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
||||||
import "../../src/resources/roboto";
|
import "../../src/resources/roboto";
|
||||||
import "../../src/resources/safari-14-attachshadow-patch";
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./hassio-main";
|
import "./hassio-main";
|
||||||
|
|
||||||
|
setCancelSyntheticClickEvents(false);
|
||||||
|
|
||||||
const styleEl = document.createElement("style");
|
const styleEl = document.createElement("style");
|
||||||
styleEl.innerHTML = `
|
styleEl.innerHTML = `
|
||||||
body {
|
body {
|
||||||
|
@@ -121,7 +121,8 @@ export class HassioMain extends SupervisorBaseElement {
|
|||||||
this.parentElement,
|
this.parentElement,
|
||||||
this.hass.themes,
|
this.hass.themes,
|
||||||
themeName,
|
themeName,
|
||||||
themeSettings
|
themeSettings,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -205,16 +205,6 @@ class HassioCoreInfo extends LitElement {
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
--mdc-menu-min-width: 200px;
|
--mdc-menu-min-width: 200px;
|
||||||
}
|
}
|
||||||
@media (min-width: 563px) {
|
|
||||||
paper-listbox {
|
|
||||||
max-height: 150px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
min-height: 35px;
|
|
||||||
}
|
|
||||||
mwc-list-item ha-svg-icon {
|
mwc-list-item ha-svg-icon {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@@ -440,16 +440,6 @@ class HassioHostInfo extends LitElement {
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
--mdc-menu-min-width: 200px;
|
--mdc-menu-min-width: 200px;
|
||||||
}
|
}
|
||||||
@media (min-width: 563px) {
|
|
||||||
paper-listbox {
|
|
||||||
max-height: 150px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
min-height: 35px;
|
|
||||||
}
|
|
||||||
mwc-list-item ha-svg-icon {
|
mwc-list-item ha-svg-icon {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../src/components/ha-alert";
|
import "../../../src/components/ha-alert";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
|
import "../../../src/components/ha-select";
|
||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
@@ -73,24 +71,19 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.hass.userData?.showAdvanced
|
${this.hass.userData?.showAdvanced
|
||||||
? html`
|
? html`
|
||||||
<paper-dropdown-menu
|
<ha-select
|
||||||
.label=${this.supervisor.localize("system.log.log_provider")}
|
.label=${this.supervisor.localize("system.log.log_provider")}
|
||||||
@iron-select=${this._setLogProvider}
|
@selected=${this._setLogProvider}
|
||||||
|
.value=${this._selectedLogProvider}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
${logProviders.map(
|
||||||
slot="dropdown-content"
|
(provider) => html`
|
||||||
attr-for-selected="provider"
|
<mwc-list-item .value=${provider.key}>
|
||||||
.selected=${this._selectedLogProvider}
|
${provider.name}
|
||||||
>
|
</mwc-list-item>
|
||||||
${logProviders.map(
|
`
|
||||||
(provider) => html`
|
)}
|
||||||
<paper-item provider=${provider.key}>
|
</ha-select>
|
||||||
${provider.name}
|
|
||||||
</paper-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-dropdown-menu>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
@@ -110,7 +103,7 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _setLogProvider(ev): Promise<void> {
|
private async _setLogProvider(ev): Promise<void> {
|
||||||
const provider = ev.detail.item.getAttribute("provider");
|
const provider = ev.target.value;
|
||||||
this._selectedLogProvider = provider;
|
this._selectedLogProvider = provider;
|
||||||
this._loadData();
|
this._loadData();
|
||||||
}
|
}
|
||||||
@@ -153,9 +146,9 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
paper-dropdown-menu {
|
ha-select {
|
||||||
padding: 0 2%;
|
width: 100%;
|
||||||
width: 96%;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -10,7 +10,6 @@ import {
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import "../../../src/common/search/search-input";
|
|
||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../src/components/ha-alert";
|
import "../../../src/components/ha-alert";
|
||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
@@ -192,13 +191,7 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
</a>`
|
</a>`
|
||||||
: ""}
|
: ""}
|
||||||
<span></span>
|
<span></span>
|
||||||
<ha-progress-button
|
<ha-progress-button @click=${this._update} raised>
|
||||||
.disabled=${!this._version ||
|
|
||||||
(this._shouldCreateBackup &&
|
|
||||||
this.supervisor.info?.state !== "running")}
|
|
||||||
@click=${this._update}
|
|
||||||
raised
|
|
||||||
>
|
|
||||||
${this.supervisor.localize("common.update")}
|
${this.supervisor.localize("common.update")}
|
||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -360,8 +353,14 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _update() {
|
private async _update() {
|
||||||
|
if (this._shouldCreateBackup && this.supervisor.info.state === "freeze") {
|
||||||
|
this._error = this.supervisor.localize("backup.backup_already_running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
this._updating = true;
|
this._updating = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this._updateType === "addon") {
|
if (this._updateType === "addon") {
|
||||||
await updateHassioAddon(
|
await updateHassioAddon(
|
||||||
|
66
package.json
66
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"description": "A frontend for Home Assistant using the Polymer framework",
|
"description": "A frontend for Home Assistant",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/home-assistant/home-assistant-polymer"
|
"url": "https://github.com/home-assistant/frontend"
|
||||||
},
|
},
|
||||||
"name": "home-assistant-frontend",
|
"name": "home-assistant-frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -22,17 +22,18 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^5.0.2",
|
"@braintree/sanitize-url": "^5.0.2",
|
||||||
"@codemirror/commands": "^0.19.5",
|
"@codemirror/autocomplete": "^0.19.12",
|
||||||
"@codemirror/gutter": "^0.19.4",
|
"@codemirror/commands": "^0.19.8",
|
||||||
"@codemirror/highlight": "^0.19.6",
|
"@codemirror/gutter": "^0.19.9",
|
||||||
"@codemirror/history": "^0.19.0",
|
"@codemirror/highlight": "^0.19.7",
|
||||||
|
"@codemirror/history": "^0.19.2",
|
||||||
"@codemirror/legacy-modes": "^0.19.0",
|
"@codemirror/legacy-modes": "^0.19.0",
|
||||||
"@codemirror/rectangular-selection": "^0.19.1",
|
"@codemirror/rectangular-selection": "^0.19.1",
|
||||||
"@codemirror/search": "^0.19.2",
|
"@codemirror/search": "^0.19.6",
|
||||||
"@codemirror/state": "^0.19.4",
|
"@codemirror/state": "^0.19.6",
|
||||||
"@codemirror/stream-parser": "^0.19.2",
|
"@codemirror/stream-parser": "^0.19.5",
|
||||||
"@codemirror/text": "^0.19.5",
|
"@codemirror/text": "^0.19.6",
|
||||||
"@codemirror/view": "^0.19.15",
|
"@codemirror/view": "^0.19.40",
|
||||||
"@formatjs/intl-datetimeformat": "^4.2.5",
|
"@formatjs/intl-datetimeformat": "^4.2.5",
|
||||||
"@formatjs/intl-getcanonicallocales": "^1.8.0",
|
"@formatjs/intl-getcanonicallocales": "^1.8.0",
|
||||||
"@formatjs/intl-locale": "^2.4.40",
|
"@formatjs/intl-locale": "^2.4.40",
|
||||||
@@ -45,7 +46,8 @@
|
|||||||
"@fullcalendar/daygrid": "5.9.0",
|
"@fullcalendar/daygrid": "5.9.0",
|
||||||
"@fullcalendar/interaction": "5.9.0",
|
"@fullcalendar/interaction": "5.9.0",
|
||||||
"@fullcalendar/list": "5.9.0",
|
"@fullcalendar/list": "5.9.0",
|
||||||
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
|
"@lit-labs/motion": "^1.0.2",
|
||||||
|
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
|
||||||
"@material/chips": "14.0.0-canary.261f2db59.0",
|
"@material/chips": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/mwc-button": "0.25.3",
|
"@material/mwc-button": "0.25.3",
|
||||||
@@ -57,7 +59,7 @@
|
|||||||
"@material/mwc-formfield": "0.25.3",
|
"@material/mwc-formfield": "0.25.3",
|
||||||
"@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
|
"@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
|
||||||
"@material/mwc-linear-progress": "0.25.3",
|
"@material/mwc-linear-progress": "0.25.3",
|
||||||
"@material/mwc-list": "0.25.3",
|
"@material/mwc-list": "^0.25.3",
|
||||||
"@material/mwc-menu": "0.25.3",
|
"@material/mwc-menu": "0.25.3",
|
||||||
"@material/mwc-radio": "0.25.3",
|
"@material/mwc-radio": "0.25.3",
|
||||||
"@material/mwc-ripple": "0.25.3",
|
"@material/mwc-ripple": "0.25.3",
|
||||||
@@ -66,6 +68,7 @@
|
|||||||
"@material/mwc-switch": "0.25.3",
|
"@material/mwc-switch": "0.25.3",
|
||||||
"@material/mwc-tab": "0.25.3",
|
"@material/mwc-tab": "0.25.3",
|
||||||
"@material/mwc-tab-bar": "0.25.3",
|
"@material/mwc-tab-bar": "0.25.3",
|
||||||
|
"@material/mwc-textarea": "^0.25.3",
|
||||||
"@material/mwc-textfield": "0.25.3",
|
"@material/mwc-textfield": "0.25.3",
|
||||||
"@material/mwc-top-app-bar-fixed": "^0.25.3",
|
"@material/mwc-top-app-bar-fixed": "^0.25.3",
|
||||||
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
||||||
@@ -76,7 +79,6 @@
|
|||||||
"@polymer/iron-icon": "^3.0.1",
|
"@polymer/iron-icon": "^3.0.1",
|
||||||
"@polymer/iron-input": "^3.0.1",
|
"@polymer/iron-input": "^3.0.1",
|
||||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||||
"@polymer/paper-dropdown-menu": "^3.2.0",
|
|
||||||
"@polymer/paper-input": "^3.2.1",
|
"@polymer/paper-input": "^3.2.1",
|
||||||
"@polymer/paper-item": "^3.0.1",
|
"@polymer/paper-item": "^3.0.1",
|
||||||
"@polymer/paper-listbox": "^3.0.1",
|
"@polymer/paper-listbox": "^3.0.1",
|
||||||
@@ -87,13 +89,15 @@
|
|||||||
"@polymer/paper-tooltip": "^3.0.1",
|
"@polymer/paper-tooltip": "^3.0.1",
|
||||||
"@polymer/polymer": "3.4.1",
|
"@polymer/polymer": "3.4.1",
|
||||||
"@thomasloven/round-slider": "0.5.4",
|
"@thomasloven/round-slider": "0.5.4",
|
||||||
"@vaadin/vaadin-combo-box": "^21.0.2",
|
"@vaadin/combo-box": "^22.0.4",
|
||||||
"@vaadin/vaadin-date-picker": "^21.0.2",
|
"@vaadin/vaadin-themable-mixin": "^22.0.4",
|
||||||
"@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",
|
||||||
"@vue/web-component-wrapper": "^1.2.0",
|
"@vue/web-component-wrapper": "^1.2.0",
|
||||||
|
"@webcomponents/scoped-custom-element-registry": "^0.0.5",
|
||||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||||
|
"app-datepicker": "^5.0.1",
|
||||||
"chart.js": "^3.3.2",
|
"chart.js": "^3.3.2",
|
||||||
"comlink": "^4.3.1",
|
"comlink": "^4.3.1",
|
||||||
"core-js": "^3.15.2",
|
"core-js": "^3.15.2",
|
||||||
@@ -103,16 +107,16 @@
|
|||||||
"deep-freeze": "^0.0.1",
|
"deep-freeze": "^0.0.1",
|
||||||
"fuse.js": "^6.0.0",
|
"fuse.js": "^6.0.0",
|
||||||
"google-timezones-json": "^1.0.2",
|
"google-timezones-json": "^1.0.2",
|
||||||
"hls.js": "^1.0.11",
|
"hls.js": "^1.1.5",
|
||||||
"home-assistant-js-websocket": "^6.0.1",
|
"home-assistant-js-websocket": "^7.0.1",
|
||||||
"idb-keyval": "^5.1.3",
|
"idb-keyval": "^5.1.3",
|
||||||
"intl-messageformat": "^9.9.1",
|
"intl-messageformat": "^9.9.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"leaflet-draw": "^1.0.4",
|
"leaflet-draw": "^1.0.4",
|
||||||
"lit": "^2.1.2",
|
"lit": "^2.1.2",
|
||||||
"lit-vaadin-helpers": "^0.2.1",
|
"lit-vaadin-helpers": "^0.3.0",
|
||||||
"marked": "^3.0.2",
|
"marked": "^4.0.12",
|
||||||
"memoize-one": "^5.2.1",
|
"memoize-one": "^5.2.1",
|
||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
"proxy-polyfill": "^0.3.2",
|
"proxy-polyfill": "^0.3.2",
|
||||||
@@ -131,13 +135,12 @@
|
|||||||
"vis-network": "^8.5.4",
|
"vis-network": "^8.5.4",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
"vue2-daterange-picker": "^0.5.1",
|
"vue2-daterange-picker": "^0.5.1",
|
||||||
"web-animations-js": "^2.3.2",
|
"workbox-cacheable-response": "^6.4.2",
|
||||||
"workbox-cacheable-response": "^6.1.5",
|
"workbox-core": "^6.4.2",
|
||||||
"workbox-core": "^6.1.5",
|
"workbox-expiration": "^6.4.2",
|
||||||
"workbox-expiration": "^6.1.5",
|
"workbox-precaching": "^6.4.2",
|
||||||
"workbox-precaching": "^6.1.5",
|
"workbox-routing": "^6.4.2",
|
||||||
"workbox-routing": "^6.1.5",
|
"workbox-strategies": "^6.4.2",
|
||||||
"workbox-strategies": "^6.1.5",
|
|
||||||
"xss": "^1.0.9"
|
"xss": "^1.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -166,7 +169,7 @@
|
|||||||
"@types/js-yaml": "^4",
|
"@types/js-yaml": "^4",
|
||||||
"@types/leaflet": "^1",
|
"@types/leaflet": "^1",
|
||||||
"@types/leaflet-draw": "^1",
|
"@types/leaflet-draw": "^1",
|
||||||
"@types/marked": "^2",
|
"@types/marked": "^4",
|
||||||
"@types/mocha": "^8",
|
"@types/mocha": "^8",
|
||||||
"@types/qrcode": "^1.4.2",
|
"@types/qrcode": "^1.4.2",
|
||||||
"@types/sortablejs": "^1",
|
"@types/sortablejs": "^1",
|
||||||
@@ -193,7 +196,7 @@
|
|||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
"glob": "^7.2.0",
|
"glob": "^7.2.0",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-foreach": "^0.1.0",
|
"gulp-flatmap": "^1.0.2",
|
||||||
"gulp-json-transform": "^0.4.6",
|
"gulp-json-transform": "^0.4.6",
|
||||||
"gulp-merge-json": "^1.3.1",
|
"gulp-merge-json": "^1.3.1",
|
||||||
"gulp-rename": "^2.0.0",
|
"gulp-rename": "^2.0.0",
|
||||||
@@ -230,7 +233,7 @@
|
|||||||
"webpack-dev-server": "^4.3.0",
|
"webpack-dev-server": "^4.3.0",
|
||||||
"webpack-manifest-plugin": "^4.0.2",
|
"webpack-manifest-plugin": "^4.0.2",
|
||||||
"webpackbar": "^5.0.0-3",
|
"webpackbar": "^5.0.0-3",
|
||||||
"workbox-build": "^6.1.5"
|
"workbox-build": "^6.4.2"
|
||||||
},
|
},
|
||||||
"_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": {
|
||||||
@@ -250,5 +253,6 @@
|
|||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"arrowParens": "always"
|
"arrowParens": "always"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@3.2.0"
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,6 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def where():
|
def where() -> Path:
|
||||||
"""Return path to the frontend."""
|
"""Return path to the frontend."""
|
||||||
return Path(__file__).parent
|
return Path(__file__).parent
|
||||||
|
0
public/py.typed
Normal file
0
public/py.typed
Normal file
18
script/core
18
script/core
@@ -4,6 +4,8 @@
|
|||||||
# Stop on errors
|
# Stop on errors
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
WD="${WORKSPACE_DIRECTORY:=/workspaces/frontend}"
|
||||||
|
|
||||||
if [ -z "${DEVCONTAINER}" ]; then
|
if [ -z "${DEVCONTAINER}" ]; then
|
||||||
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -16,9 +18,9 @@ if [ -z $(which hass) ]; then
|
|||||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "/workspaces/frontend/config" ]; then
|
if [ ! -d "${WD}/config" ]; then
|
||||||
echo "Creating default configuration."
|
echo "Creating default configuration."
|
||||||
mkdir -p "/workspaces/frontend/config";
|
mkdir -p "${WD}/config";
|
||||||
hass --script ensure_config -c config
|
hass --script ensure_config -c config
|
||||||
echo "demo:
|
echo "demo:
|
||||||
|
|
||||||
@@ -26,24 +28,24 @@ logger:
|
|||||||
default: info
|
default: info
|
||||||
logs:
|
logs:
|
||||||
homeassistant.components.frontend: debug
|
homeassistant.components.frontend: debug
|
||||||
" >> /workspaces/frontend/config/configuration.yaml
|
" >> "${WD}/config/configuration.yaml"
|
||||||
|
|
||||||
if [ ! -z "${HASSIO}" ]; then
|
if [ ! -z "${HASSIO}" ]; then
|
||||||
echo "
|
echo "
|
||||||
# frontend:
|
# frontend:
|
||||||
# development_repo: /workspaces/frontend
|
# development_repo: ${WD}
|
||||||
|
|
||||||
hassio:
|
hassio:
|
||||||
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||||
else
|
else
|
||||||
echo "
|
echo "
|
||||||
frontend:
|
frontend:
|
||||||
development_repo: /workspaces/frontend
|
development_repo: ${WD}
|
||||||
|
|
||||||
# hassio:
|
# hassio:
|
||||||
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
hass -c /workspaces/frontend/config
|
hass -c "${WD}/config"
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = home-assistant-frontend
|
name = home-assistant-frontend
|
||||||
version = 20220203.0
|
version = 20220322.0
|
||||||
author = The Home Assistant Authors
|
author = The Home Assistant Authors
|
||||||
author_email = hello@home-assistant.io
|
author_email = hello@home-assistant.io
|
||||||
license = Apache-2.0
|
license = Apache-2.0
|
||||||
@@ -19,3 +19,8 @@ python_requires = >= 3.4.0
|
|||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
include =
|
include =
|
||||||
hass_frontend*
|
hass_frontend*
|
||||||
|
|
||||||
|
[mypy]
|
||||||
|
python_version = 3.4
|
||||||
|
show_error_codes = True
|
||||||
|
strict = True
|
||||||
|
7
setup.py
7
setup.py
@@ -1,7 +0,0 @@
|
|||||||
"""
|
|
||||||
Entry point for setuptools. Required for editable installs.
|
|
||||||
TODO: Remove file after updating to pip 21.3
|
|
||||||
"""
|
|
||||||
from setuptools import setup
|
|
||||||
|
|
||||||
setup()
|
|
@@ -101,13 +101,19 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
this._fetchAuthProviders();
|
this._fetchAuthProviders();
|
||||||
|
|
||||||
if (matchMedia("(prefers-color-scheme: dark)").matches) {
|
if (matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
applyThemesOnElement(document.documentElement, {
|
applyThemesOnElement(
|
||||||
default_theme: "default",
|
document.documentElement,
|
||||||
default_dark_theme: null,
|
{
|
||||||
themes: {},
|
default_theme: "default",
|
||||||
darkMode: true,
|
default_dark_theme: null,
|
||||||
theme: "default",
|
themes: {},
|
||||||
});
|
darkMode: true,
|
||||||
|
theme: "default",
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.redirectUri) {
|
if (!this.redirectUri) {
|
||||||
|
@@ -187,6 +187,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
|||||||
"scene",
|
"scene",
|
||||||
"sun",
|
"sun",
|
||||||
"timer",
|
"timer",
|
||||||
|
"update",
|
||||||
"vacuum",
|
"vacuum",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
"weather",
|
"weather",
|
||||||
@@ -200,6 +201,7 @@ export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [
|
|||||||
"input_text",
|
"input_text",
|
||||||
"number",
|
"number",
|
||||||
"scene",
|
"scene",
|
||||||
|
"update",
|
||||||
"select",
|
"select",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import { HaDurationData } from "../../components/ha-duration-input";
|
import type { HaDurationData } from "../../components/ha-duration-input";
|
||||||
import { ForDict } from "../../data/automation";
|
import type { ForDict } from "../../data/automation";
|
||||||
|
|
||||||
export const createDurationData = (
|
export const createDurationData = (
|
||||||
duration: string | number | ForDict | undefined
|
duration: string | number | ForDict | undefined
|
||||||
): HaDurationData => {
|
): HaDurationData | undefined => {
|
||||||
if (duration === undefined) {
|
if (duration === undefined) {
|
||||||
return {};
|
return undefined;
|
||||||
}
|
}
|
||||||
if (typeof duration !== "object") {
|
if (typeof duration !== "object") {
|
||||||
if (typeof duration === "string" || isNaN(duration)) {
|
if (typeof duration === "string" || isNaN(duration)) {
|
||||||
@@ -19,6 +19,9 @@ export const createDurationData = (
|
|||||||
}
|
}
|
||||||
return { seconds: duration };
|
return { seconds: duration };
|
||||||
}
|
}
|
||||||
|
if (!("days" in duration)) {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
const { days, minutes, seconds, milliseconds } = duration;
|
const { days, minutes, seconds, milliseconds } = duration;
|
||||||
let hours = duration.hours || 0;
|
let hours = duration.hours || 0;
|
||||||
hours = (hours || 0) + (days || 0) * 24;
|
hours = (hours || 0) + (days || 0) * 24;
|
||||||
|
@@ -31,11 +31,12 @@ export const applyThemesOnElement = (
|
|||||||
element,
|
element,
|
||||||
themes: HomeAssistant["themes"],
|
themes: HomeAssistant["themes"],
|
||||||
selectedTheme?: string,
|
selectedTheme?: string,
|
||||||
themeSettings?: Partial<HomeAssistant["selectedTheme"]>
|
themeSettings?: Partial<HomeAssistant["selectedTheme"]>,
|
||||||
|
main?: boolean
|
||||||
) => {
|
) => {
|
||||||
// If there is no explicitly desired theme provided, we automatically
|
// If there is no explicitly desired theme provided, and the element is the main element we automatically
|
||||||
// use the active one from `themes`.
|
// use the active one from `themes`.
|
||||||
const themeToApply = selectedTheme || themes.theme;
|
const themeToApply = selectedTheme || (main ? themes.theme : undefined);
|
||||||
|
|
||||||
// If there is no explicitly desired dark mode provided, we automatically
|
// If there is no explicitly desired dark mode provided, we automatically
|
||||||
// use the active one from `themes`.
|
// use the active one from `themes`.
|
||||||
@@ -47,7 +48,7 @@ export const applyThemesOnElement = (
|
|||||||
let cacheKey = themeToApply;
|
let cacheKey = themeToApply;
|
||||||
let themeRules: Partial<ThemeVars> = {};
|
let themeRules: Partial<ThemeVars> = {};
|
||||||
|
|
||||||
if (darkMode) {
|
if (themeToApply && darkMode) {
|
||||||
cacheKey = `${cacheKey}__dark`;
|
cacheKey = `${cacheKey}__dark`;
|
||||||
themeRules = { ...darkStyles };
|
themeRules = { ...darkStyles };
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
|
export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
|
||||||
const services = hass.services[domain];
|
const services = hass.services[domain];
|
||||||
|
@@ -1,14 +1,30 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { canToggleDomain } from "./can_toggle_domain";
|
import { canToggleDomain } from "./can_toggle_domain";
|
||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { computeStateDomain } from "./compute_state_domain";
|
||||||
import { supportsFeature } from "./supports-feature";
|
import { supportsFeature } from "./supports-feature";
|
||||||
|
|
||||||
export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
|
export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
|
||||||
const domain = computeStateDomain(stateObj);
|
const domain = computeStateDomain(stateObj);
|
||||||
|
|
||||||
if (domain === "group") {
|
if (domain === "group") {
|
||||||
return stateObj.state === "on" || stateObj.state === "off";
|
if (
|
||||||
|
stateObj.attributes?.entity_id?.some((entity) => {
|
||||||
|
const entityStateObj = hass.states[entity];
|
||||||
|
if (!entityStateObj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityDomain = computeStateDomain(entityStateObj);
|
||||||
|
return canToggleDomain(hass, entityDomain);
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return stateObj.state === "on" || stateObj.state === "off";
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain === "climate") {
|
if (domain === "climate") {
|
||||||
return supportsFeature(stateObj, 4096);
|
return supportsFeature(stateObj, 4096);
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,18 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||||
import { FrontendLocaleData } from "../../data/translation";
|
import { FrontendLocaleData } from "../../data/translation";
|
||||||
|
import {
|
||||||
|
updateIsInstalling,
|
||||||
|
UpdateEntity,
|
||||||
|
UPDATE_SUPPORT_PROGRESS,
|
||||||
|
} from "../../data/update";
|
||||||
import { formatDate } from "../datetime/format_date";
|
import { formatDate } from "../datetime/format_date";
|
||||||
import { formatDateTime } from "../datetime/format_date_time";
|
import { formatDateTime } from "../datetime/format_date_time";
|
||||||
import { formatTime } from "../datetime/format_time";
|
import { formatTime } from "../datetime/format_time";
|
||||||
import { formatNumber, isNumericState } from "../number/format_number";
|
import { formatNumber, isNumericState } from "../number/format_number";
|
||||||
import { LocalizeFunc } from "../translations/localize";
|
import { LocalizeFunc } from "../translations/localize";
|
||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { computeStateDomain } from "./compute_state_domain";
|
||||||
|
import { supportsFeature } from "./supports-feature";
|
||||||
|
|
||||||
export const computeStateDisplay = (
|
export const computeStateDisplay = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
@@ -123,7 +129,33 @@ export const computeStateDisplay = (
|
|||||||
domain === "scene" ||
|
domain === "scene" ||
|
||||||
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
|
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
|
||||||
) {
|
) {
|
||||||
return formatDateTime(new Date(compareState), locale);
|
try {
|
||||||
|
return formatDateTime(new Date(compareState), locale);
|
||||||
|
} catch (_err) {
|
||||||
|
return compareState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domain === "update") {
|
||||||
|
// When updating, and entity does not support % show "Installing"
|
||||||
|
// When updating, and entity does support % show "Installing (xx%)"
|
||||||
|
// When update available, show the version
|
||||||
|
// When the latest version is skipped, show the latest version
|
||||||
|
// When update is not available, show "Up-to-date"
|
||||||
|
// When update is not available and there is no latest_version show "Unavailable"
|
||||||
|
return compareState === "on"
|
||||||
|
? updateIsInstalling(stateObj as UpdateEntity)
|
||||||
|
? supportsFeature(stateObj, UPDATE_SUPPORT_PROGRESS)
|
||||||
|
? localize("ui.card.update.installing_with_progress", {
|
||||||
|
progress: stateObj.attributes.in_progress,
|
||||||
|
})
|
||||||
|
: localize("ui.card.update.installing")
|
||||||
|
: stateObj.attributes.latest_version
|
||||||
|
: stateObj.attributes.skipped_version ===
|
||||||
|
stateObj.attributes.latest_version
|
||||||
|
? stateObj.attributes.latest_version ??
|
||||||
|
localize("state.default.unavailable")
|
||||||
|
: localize("ui.card.update.up_to_date");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { computeDomain } from "./compute_domain";
|
import { computeDomain } from "./compute_domain";
|
||||||
|
|
||||||
export const computeStateDomain = (stateObj: HassEntity) =>
|
export const computeStateDomain = (stateObj: HassEntity) =>
|
||||||
|
@@ -120,6 +120,7 @@ export const computeOpenIcon = (stateObj: HassEntity): string => {
|
|||||||
case "awning":
|
case "awning":
|
||||||
case "door":
|
case "door":
|
||||||
case "gate":
|
case "gate":
|
||||||
|
case "curtain":
|
||||||
return mdiArrowExpandHorizontal;
|
return mdiArrowExpandHorizontal;
|
||||||
default:
|
default:
|
||||||
return mdiArrowUp;
|
return mdiArrowUp;
|
||||||
@@ -131,6 +132,7 @@ export const computeCloseIcon = (stateObj: HassEntity): string => {
|
|||||||
case "awning":
|
case "awning":
|
||||||
case "door":
|
case "door":
|
||||||
case "gate":
|
case "gate":
|
||||||
|
case "curtain":
|
||||||
return mdiArrowCollapseHorizontal;
|
return mdiArrowCollapseHorizontal;
|
||||||
default:
|
default:
|
||||||
return mdiArrowDown;
|
return mdiArrowDown;
|
||||||
|
@@ -9,11 +9,10 @@ import {
|
|||||||
mdiCast,
|
mdiCast,
|
||||||
mdiCastConnected,
|
mdiCastConnected,
|
||||||
mdiClock,
|
mdiClock,
|
||||||
mdiEmoticonDead,
|
|
||||||
mdiFlash,
|
|
||||||
mdiGestureTapButton,
|
mdiGestureTapButton,
|
||||||
mdiLanConnect,
|
mdiLanConnect,
|
||||||
mdiLanDisconnect,
|
mdiLanDisconnect,
|
||||||
|
mdiLightSwitch,
|
||||||
mdiLock,
|
mdiLock,
|
||||||
mdiLockAlert,
|
mdiLockAlert,
|
||||||
mdiLockClock,
|
mdiLockClock,
|
||||||
@@ -22,16 +21,16 @@ import {
|
|||||||
mdiPowerPlug,
|
mdiPowerPlug,
|
||||||
mdiPowerPlugOff,
|
mdiPowerPlugOff,
|
||||||
mdiRestart,
|
mdiRestart,
|
||||||
mdiSleep,
|
|
||||||
mdiTimerSand,
|
|
||||||
mdiToggleSwitch,
|
mdiToggleSwitch,
|
||||||
mdiToggleSwitchOff,
|
mdiToggleSwitchOff,
|
||||||
mdiCheckCircleOutline,
|
mdiCheckCircleOutline,
|
||||||
mdiCloseCircleOutline,
|
mdiCloseCircleOutline,
|
||||||
mdiWeatherNight,
|
mdiWeatherNight,
|
||||||
mdiZWave,
|
mdiPackage,
|
||||||
|
mdiPackageDown,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { updateIsInstalling, UpdateEntity } from "../../data/update";
|
||||||
/**
|
/**
|
||||||
* Return the icon to be used for a domain.
|
* Return the icon to be used for a domain.
|
||||||
*
|
*
|
||||||
@@ -112,19 +111,7 @@ export const domainIcon = (
|
|||||||
case "switch":
|
case "switch":
|
||||||
return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
|
return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
|
||||||
default:
|
default:
|
||||||
return mdiFlash;
|
return mdiLightSwitch;
|
||||||
}
|
|
||||||
|
|
||||||
case "zwave":
|
|
||||||
switch (compareState) {
|
|
||||||
case "dead":
|
|
||||||
return mdiEmoticonDead;
|
|
||||||
case "sleeping":
|
|
||||||
return mdiSleep;
|
|
||||||
case "initializing":
|
|
||||||
return mdiTimerSand;
|
|
||||||
default:
|
|
||||||
return mdiZWave;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "sensor": {
|
case "sensor": {
|
||||||
@@ -149,6 +136,13 @@ export const domainIcon = (
|
|||||||
return stateObj?.state === "above_horizon"
|
return stateObj?.state === "above_horizon"
|
||||||
? FIXED_DOMAIN_ICONS[domain]
|
? FIXED_DOMAIN_ICONS[domain]
|
||||||
: mdiWeatherNight;
|
: mdiWeatherNight;
|
||||||
|
|
||||||
|
case "update":
|
||||||
|
return compareState === "on"
|
||||||
|
? updateIsInstalling(stateObj as UpdateEntity)
|
||||||
|
? mdiPackageDown
|
||||||
|
: mdiPackageUp
|
||||||
|
: mdiPackage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain in FIXED_DOMAIN_ICONS) {
|
if (domain in FIXED_DOMAIN_ICONS) {
|
||||||
|
@@ -1,24 +1,32 @@
|
|||||||
|
const SUFFIXES = [" ", ": "];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strips a device name from an entity name.
|
* Strips a device name from an entity name.
|
||||||
* @param entityName the entity name
|
* @param entityName the entity name
|
||||||
* @param lowerCasedPrefixWithSpaceSuffix the prefix to strip, lower cased with a space suffix
|
* @param lowerCasedPrefix the prefix to strip, lower cased
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const stripPrefixFromEntityName = (
|
export const stripPrefixFromEntityName = (
|
||||||
entityName: string,
|
entityName: string,
|
||||||
lowerCasedPrefixWithSpaceSuffix: string
|
lowerCasedPrefix: string
|
||||||
) => {
|
) => {
|
||||||
if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) {
|
const lowerCasedEntityName = entityName.toLowerCase();
|
||||||
return undefined;
|
|
||||||
|
for (const suffix of SUFFIXES) {
|
||||||
|
const lowerCasedPrefixWithSuffix = `${lowerCasedPrefix}${suffix}`;
|
||||||
|
|
||||||
|
if (lowerCasedEntityName.startsWith(lowerCasedPrefixWithSuffix)) {
|
||||||
|
const newName = entityName.substring(lowerCasedPrefixWithSuffix.length);
|
||||||
|
|
||||||
|
// If first word already has an upper case letter (e.g. from brand name)
|
||||||
|
// leave as-is, otherwise capitalize the first word.
|
||||||
|
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
||||||
|
? newName
|
||||||
|
: newName[0].toUpperCase() + newName.slice(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newName = entityName.substring(lowerCasedPrefixWithSpaceSuffix.length);
|
return undefined;
|
||||||
|
|
||||||
// If first word already has an upper case letter (e.g. from brand name)
|
|
||||||
// leave as-is, otherwise capitalize the first word.
|
|
||||||
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
|
||||||
? newName
|
|
||||||
: newName[0].toUpperCase() + newName.slice(1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
|
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
|
||||||
|
@@ -1,112 +0,0 @@
|
|||||||
import { mdiClose, mdiMagnify } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
|
||||||
import "../../components/ha-icon-button";
|
|
||||||
import "../../components/ha-svg-icon";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import { fireEvent } from "../dom/fire_event";
|
|
||||||
|
|
||||||
@customElement("search-input")
|
|
||||||
class SearchInput extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property() public filter?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-label-float" })
|
|
||||||
public noLabelFloat? = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-underline" })
|
|
||||||
public noUnderline = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
|
||||||
public autofocus = false;
|
|
||||||
|
|
||||||
@property({ type: String })
|
|
||||||
public label?: string;
|
|
||||||
|
|
||||||
public focus() {
|
|
||||||
this.shadowRoot!.querySelector("paper-input")!.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
@query("paper-input", true) private _input!: PaperInputElement;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<paper-input
|
|
||||||
.autofocus=${this.autofocus}
|
|
||||||
.label=${this.label || "Search"}
|
|
||||||
.value=${this.filter}
|
|
||||||
@value-changed=${this._filterInputChanged}
|
|
||||||
.noLabelFloat=${this.noLabelFloat}
|
|
||||||
>
|
|
||||||
<slot name="prefix" slot="prefix">
|
|
||||||
<ha-svg-icon class="prefix" .path=${mdiMagnify}></ha-svg-icon>
|
|
||||||
</slot>
|
|
||||||
${this.filter &&
|
|
||||||
html`
|
|
||||||
<ha-icon-button
|
|
||||||
slot="suffix"
|
|
||||||
@click=${this._clearSearch}
|
|
||||||
.label=${this.hass.localize("ui.common.clear")}
|
|
||||||
.path=${mdiClose}
|
|
||||||
></ha-icon-button>
|
|
||||||
`}
|
|
||||||
</paper-input>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
|
||||||
if (
|
|
||||||
changedProps.has("noUnderline") &&
|
|
||||||
(this.noUnderline || changedProps.get("noUnderline") !== undefined)
|
|
||||||
) {
|
|
||||||
(
|
|
||||||
this._input.inputElement!.parentElement!.shadowRoot!.querySelector(
|
|
||||||
"div.unfocused-line"
|
|
||||||
) as HTMLElement
|
|
||||||
).style.display = this.noUnderline ? "none" : "block";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _filterChanged(value: string) {
|
|
||||||
fireEvent(this, "value-changed", { value: String(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _filterInputChanged(e) {
|
|
||||||
this._filterChanged(e.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _clearSearch() {
|
|
||||||
this._filterChanged("");
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
ha-svg-icon,
|
|
||||||
ha-icon-button {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
}
|
|
||||||
ha-svg-icon.prefix {
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"search-input": SearchInput;
|
|
||||||
}
|
|
||||||
}
|
|
4
src/common/string/is_ip_address.ts
Normal file
4
src/common/string/is_ip_address.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
const regexp =
|
||||||
|
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||||
|
|
||||||
|
export const isIPAddress = (input: string): boolean => regexp.test(input);
|
@@ -15,6 +15,7 @@ export const iconColorCSS = css`
|
|||||||
ha-state-icon[data-domain="media_player"][data-state="on"],
|
ha-state-icon[data-domain="media_player"][data-state="on"],
|
||||||
ha-state-icon[data-domain="media_player"][data-state="paused"],
|
ha-state-icon[data-domain="media_player"][data-state="paused"],
|
||||||
ha-state-icon[data-domain="media_player"][data-state="playing"],
|
ha-state-icon[data-domain="media_player"][data-state="playing"],
|
||||||
|
ha-state-icon[data-domain="remote"][data-state="on"],
|
||||||
ha-state-icon[data-domain="script"][data-state="on"],
|
ha-state-icon[data-domain="script"][data-state="on"],
|
||||||
ha-state-icon[data-domain="sun"][data-state="above_horizon"],
|
ha-state-icon[data-domain="sun"][data-state="above_horizon"],
|
||||||
ha-state-icon[data-domain="switch"][data-state="on"],
|
ha-state-icon[data-domain="switch"][data-state="on"],
|
||||||
@@ -69,9 +70,6 @@ export const iconColorCSS = css`
|
|||||||
}
|
}
|
||||||
|
|
||||||
ha-state-icon[data-domain="plant"][data-state="problem"],
|
ha-state-icon[data-domain="plant"][data-state="problem"],
|
||||||
ha-state-icon[data-domain="zwave"][data-state="dead"] {
|
|
||||||
color: var(--state-icon-error-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Color the icon if unavailable */
|
/* Color the icon if unavailable */
|
||||||
ha-state-icon[data-state="unavailable"] {
|
ha-state-icon[data-state="unavailable"] {
|
||||||
|
@@ -11,7 +11,7 @@ export const debounce = <T extends any[]>(
|
|||||||
immediate = false
|
immediate = false
|
||||||
) => {
|
) => {
|
||||||
let timeout: number | undefined;
|
let timeout: number | undefined;
|
||||||
return (...args: T): void => {
|
const debouncedFunc = (...args: T): void => {
|
||||||
const later = () => {
|
const later = () => {
|
||||||
timeout = undefined;
|
timeout = undefined;
|
||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
@@ -25,4 +25,8 @@ export const debounce = <T extends any[]>(
|
|||||||
func(...args);
|
func(...args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
debouncedFunc.cancel = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
return debouncedFunc;
|
||||||
};
|
};
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import type { Button } from "@material/mwc-button";
|
import { mdiAlertOctagram, mdiCheckBold } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../ha-circular-progress";
|
import "../ha-circular-progress";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
|
||||||
@customElement("ha-progress-button")
|
@customElement("ha-progress-button")
|
||||||
export class HaProgressButton extends LitElement {
|
export class HaProgressButton extends LitElement {
|
||||||
@@ -12,38 +13,53 @@ export class HaProgressButton extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public raised = false;
|
@property({ type: Boolean }) public raised = false;
|
||||||
|
|
||||||
@query("mwc-button", true) private _button?: Button;
|
@state() private _result?: "success" | "error";
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
|
const overlay = this._result || this.progress;
|
||||||
return html`
|
return html`
|
||||||
<mwc-button
|
<mwc-button
|
||||||
?raised=${this.raised}
|
?raised=${this.raised}
|
||||||
.disabled=${this.disabled || this.progress}
|
.disabled=${this.disabled || this.progress}
|
||||||
@click=${this._buttonTapped}
|
@click=${this._buttonTapped}
|
||||||
|
class=${this._result || ""}
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
${this.progress
|
${!overlay
|
||||||
? html`<div class="progress">
|
? ""
|
||||||
<ha-circular-progress size="small" active></ha-circular-progress>
|
: html`
|
||||||
</div>`
|
<div class="progress">
|
||||||
: ""}
|
${this._result === "success"
|
||||||
|
? html`<ha-svg-icon .path=${mdiCheckBold}></ha-svg-icon>`
|
||||||
|
: this._result === "error"
|
||||||
|
? html`<ha-svg-icon .path=${mdiAlertOctagram}></ha-svg-icon>`
|
||||||
|
: this.progress
|
||||||
|
? html`
|
||||||
|
<ha-circular-progress
|
||||||
|
size="small"
|
||||||
|
active
|
||||||
|
></ha-circular-progress>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public actionSuccess(): void {
|
public actionSuccess(): void {
|
||||||
this._tempClass("success");
|
this._setResult("success");
|
||||||
}
|
}
|
||||||
|
|
||||||
public actionError(): void {
|
public actionError(): void {
|
||||||
this._tempClass("error");
|
this._setResult("error");
|
||||||
}
|
}
|
||||||
|
|
||||||
private _tempClass(className: string): void {
|
private _setResult(result: "success" | "error"): void {
|
||||||
this._button!.classList.add(className);
|
this._result = result;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this._button!.classList.remove(className);
|
this._result = undefined;
|
||||||
}, 1000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _buttonTapped(ev: Event): void {
|
private _buttonTapped(ev: Event): void {
|
||||||
@@ -69,6 +85,7 @@ export class HaProgressButton extends LitElement {
|
|||||||
background-color: var(--success-color);
|
background-color: var(--success-color);
|
||||||
transition: none;
|
transition: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-button[raised].success {
|
mwc-button[raised].success {
|
||||||
@@ -81,6 +98,7 @@ export class HaProgressButton extends LitElement {
|
|||||||
background-color: var(--error-color);
|
background-color: var(--error-color);
|
||||||
transition: none;
|
transition: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-button[raised].error {
|
mwc-button[raised].error {
|
||||||
@@ -89,13 +107,21 @@ export class HaProgressButton extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
bottom: 0;
|
bottom: 4px;
|
||||||
margin-top: 4px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
top: 0;
|
top: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-svg-icon {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
mwc-button.success slot,
|
||||||
|
mwc-button.error slot {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { Layout1d, scroll } from "@lit-labs/virtualizer";
|
|
||||||
import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
|
import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
|
||||||
import deepClone from "deep-clone-simple";
|
import deepClone from "deep-clone-simple";
|
||||||
import {
|
import {
|
||||||
@@ -22,7 +21,7 @@ import { styleMap } from "lit/directives/style-map";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../common/search/search-input";
|
import "../search-input";
|
||||||
import { debounce } from "../../common/util/debounce";
|
import { debounce } from "../../common/util/debounce";
|
||||||
import { nextRender } from "../../common/util/render-status";
|
import { nextRender } from "../../common/util/render-status";
|
||||||
import { haStyleScrollbar } from "../../resources/styles";
|
import { haStyleScrollbar } from "../../resources/styles";
|
||||||
@@ -31,6 +30,7 @@ import type { HaCheckbox } from "../ha-checkbox";
|
|||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import { filterData, sortData } from "./sort-filter";
|
import { filterData, sortData } from "./sort-filter";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "@lit-labs/virtualizer";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@@ -70,6 +70,7 @@ export interface DataTableSortColumnData {
|
|||||||
|
|
||||||
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||||
title: TemplateResult | string;
|
title: TemplateResult | string;
|
||||||
|
label?: TemplateResult | string;
|
||||||
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
|
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
|
||||||
template?: (data: any, row: T) => TemplateResult | string;
|
template?: (data: any, row: T) => TemplateResult | string;
|
||||||
width?: string;
|
width?: string;
|
||||||
@@ -294,6 +295,7 @@ export class HaDataTable extends LitElement {
|
|||||||
};
|
};
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
|
aria-label=${column.label}
|
||||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||||
style=${column.width
|
style=${column.width
|
||||||
? styleMap({
|
? styleMap({
|
||||||
@@ -337,111 +339,99 @@ export class HaDataTable extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<div
|
<lit-virtualizer
|
||||||
|
scroller
|
||||||
class="mdc-data-table__content scroller ha-scrollbar"
|
class="mdc-data-table__content scroller ha-scrollbar"
|
||||||
@scroll=${this._saveScrollPos}
|
@scroll=${this._saveScrollPos}
|
||||||
>
|
.items=${this._items}
|
||||||
${scroll({
|
.renderItem=${this._renderRow}
|
||||||
items: this._items,
|
></lit-virtualizer>
|
||||||
layout: Layout1d,
|
|
||||||
renderItem: (row: DataTableRowData, index) => {
|
|
||||||
// not sure how this happens...
|
|
||||||
if (!row) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
if (row.append) {
|
|
||||||
return html`
|
|
||||||
<div class="mdc-data-table__row">${row.content}</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
if (row.empty) {
|
|
||||||
return html` <div class="mdc-data-table__row"></div> `;
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<div
|
|
||||||
aria-rowindex=${index! + 2}
|
|
||||||
role="row"
|
|
||||||
.rowId=${row[this.id]}
|
|
||||||
@click=${this._handleRowClick}
|
|
||||||
class="mdc-data-table__row ${classMap({
|
|
||||||
"mdc-data-table__row--selected":
|
|
||||||
this._checkedRows.includes(String(row[this.id])),
|
|
||||||
clickable: this.clickable,
|
|
||||||
})}"
|
|
||||||
aria-selected=${ifDefined(
|
|
||||||
this._checkedRows.includes(String(row[this.id]))
|
|
||||||
? true
|
|
||||||
: undefined
|
|
||||||
)}
|
|
||||||
.selectable=${row.selectable !== false}
|
|
||||||
>
|
|
||||||
${this.selectable
|
|
||||||
? html`
|
|
||||||
<div
|
|
||||||
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
|
||||||
role="cell"
|
|
||||||
>
|
|
||||||
<ha-checkbox
|
|
||||||
class="mdc-data-table__row-checkbox"
|
|
||||||
@change=${this._handleRowCheckboxClick}
|
|
||||||
.rowId=${row[this.id]}
|
|
||||||
.disabled=${row.selectable === false}
|
|
||||||
.checked=${this._checkedRows.includes(
|
|
||||||
String(row[this.id])
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ha-checkbox>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${Object.entries(this.columns).map(
|
|
||||||
([key, column]) => {
|
|
||||||
if (column.hidden) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<div
|
|
||||||
role="cell"
|
|
||||||
class="mdc-data-table__cell ${classMap({
|
|
||||||
"mdc-data-table__cell--numeric":
|
|
||||||
column.type === "numeric",
|
|
||||||
"mdc-data-table__cell--icon":
|
|
||||||
column.type === "icon",
|
|
||||||
"mdc-data-table__cell--icon-button":
|
|
||||||
column.type === "icon-button",
|
|
||||||
"mdc-data-table__cell--overflow-menu":
|
|
||||||
column.type === "overflow-menu",
|
|
||||||
grows: Boolean(column.grows),
|
|
||||||
forceLTR: Boolean(column.forceLTR),
|
|
||||||
})}"
|
|
||||||
style=${column.width
|
|
||||||
? styleMap({
|
|
||||||
[column.grows ? "minWidth" : "width"]:
|
|
||||||
column.width,
|
|
||||||
maxWidth: column.maxWidth
|
|
||||||
? column.maxWidth
|
|
||||||
: "",
|
|
||||||
})
|
|
||||||
: ""}
|
|
||||||
>
|
|
||||||
${column.template
|
|
||||||
? column.template(row[key], row)
|
|
||||||
: row[key]}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderRow = (
|
||||||
|
row: DataTableRowData,
|
||||||
|
index: number
|
||||||
|
): TemplateResult => {
|
||||||
|
// not sure how this happens...
|
||||||
|
if (!row) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
if (row.append) {
|
||||||
|
return html` <div class="mdc-data-table__row">${row.content}</div> `;
|
||||||
|
}
|
||||||
|
if (row.empty) {
|
||||||
|
return html` <div class="mdc-data-table__row"></div> `;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
aria-rowindex=${index + 2}
|
||||||
|
role="row"
|
||||||
|
.rowId=${row[this.id]}
|
||||||
|
@click=${this._handleRowClick}
|
||||||
|
class="mdc-data-table__row ${classMap({
|
||||||
|
"mdc-data-table__row--selected": this._checkedRows.includes(
|
||||||
|
String(row[this.id])
|
||||||
|
),
|
||||||
|
clickable: this.clickable,
|
||||||
|
})}"
|
||||||
|
aria-selected=${ifDefined(
|
||||||
|
this._checkedRows.includes(String(row[this.id])) ? true : undefined
|
||||||
|
)}
|
||||||
|
.selectable=${row.selectable !== false}
|
||||||
|
>
|
||||||
|
${this.selectable
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
||||||
|
role="cell"
|
||||||
|
>
|
||||||
|
<ha-checkbox
|
||||||
|
class="mdc-data-table__row-checkbox"
|
||||||
|
@change=${this._handleRowCheckboxClick}
|
||||||
|
.rowId=${row[this.id]}
|
||||||
|
.disabled=${row.selectable === false}
|
||||||
|
.checked=${this._checkedRows.includes(String(row[this.id]))}
|
||||||
|
>
|
||||||
|
</ha-checkbox>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${Object.entries(this.columns).map(([key, column]) => {
|
||||||
|
if (column.hidden) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
role="cell"
|
||||||
|
class="mdc-data-table__cell ${classMap({
|
||||||
|
"mdc-data-table__cell--numeric": column.type === "numeric",
|
||||||
|
"mdc-data-table__cell--icon": column.type === "icon",
|
||||||
|
"mdc-data-table__cell--icon-button":
|
||||||
|
column.type === "icon-button",
|
||||||
|
"mdc-data-table__cell--overflow-menu":
|
||||||
|
column.type === "overflow-menu",
|
||||||
|
grows: Boolean(column.grows),
|
||||||
|
forceLTR: Boolean(column.forceLTR),
|
||||||
|
})}"
|
||||||
|
style=${column.width
|
||||||
|
? styleMap({
|
||||||
|
[column.grows ? "minWidth" : "width"]: column.width,
|
||||||
|
maxWidth: column.maxWidth ? column.maxWidth : "",
|
||||||
|
})
|
||||||
|
: ""}
|
||||||
|
>
|
||||||
|
${column.template ? column.template(row[key], row) : row[key]}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
private async _sortFilterData() {
|
private async _sortFilterData() {
|
||||||
const startTime = new Date().getTime();
|
const startTime = new Date().getTime();
|
||||||
this.curRequest++;
|
this.curRequest++;
|
||||||
@@ -536,7 +526,7 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleRowCheckboxClick(ev: Event) {
|
private _handleRowCheckboxClick = (ev: Event) => {
|
||||||
const checkbox = ev.currentTarget as HaCheckbox;
|
const checkbox = ev.currentTarget as HaCheckbox;
|
||||||
const rowId = (checkbox as any).rowId;
|
const rowId = (checkbox as any).rowId;
|
||||||
|
|
||||||
@@ -549,16 +539,16 @@ export class HaDataTable extends LitElement {
|
|||||||
this._checkedRows = this._checkedRows.filter((row) => row !== rowId);
|
this._checkedRows = this._checkedRows.filter((row) => row !== rowId);
|
||||||
}
|
}
|
||||||
this._checkedRowsChanged();
|
this._checkedRowsChanged();
|
||||||
}
|
};
|
||||||
|
|
||||||
private _handleRowClick(ev: Event) {
|
private _handleRowClick = (ev: Event) => {
|
||||||
const target = ev.target as HTMLElement;
|
const target = ev.target as HTMLElement;
|
||||||
if (["HA-CHECKBOX", "MWC-BUTTON"].includes(target.tagName)) {
|
if (["HA-CHECKBOX", "MWC-BUTTON"].includes(target.tagName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rowId = (ev.currentTarget as any).rowId;
|
const rowId = (ev.currentTarget as any).rowId;
|
||||||
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
||||||
}
|
};
|
||||||
|
|
||||||
private _checkedRowsChanged() {
|
private _checkedRowsChanged() {
|
||||||
// force scroller to update, change it's items
|
// force scroller to update, change it's items
|
||||||
@@ -571,6 +561,9 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleSearchChange(ev: CustomEvent): void {
|
private _handleSearchChange(ev: CustomEvent): void {
|
||||||
|
if (this.filter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this._debounceSearch(ev.detail.value);
|
this._debounceSearch(ev.detail.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,11 +928,10 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
.table-header {
|
.table-header {
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
padding: 0 16px;
|
|
||||||
}
|
}
|
||||||
search-input {
|
search-input {
|
||||||
position: relative;
|
display: block;
|
||||||
top: 2px;
|
flex: 1;
|
||||||
}
|
}
|
||||||
slot[name="header"] {
|
slot[name="header"] {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -952,6 +944,7 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
.scroller {
|
.scroller {
|
||||||
height: calc(100% - 57px);
|
height: calc(100% - 57px);
|
||||||
|
overflow: overlay !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__table.auto-height .scroller {
|
.mdc-data-table__table.auto-height .scroller {
|
||||||
@@ -967,6 +960,9 @@ export class HaDataTable extends LitElement {
|
|||||||
.clickable {
|
.clickable {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
lit-virtualizer {
|
||||||
|
contain: size layout !important;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -115,6 +115,9 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
min-width: initial !important;
|
min-width: initial !important;
|
||||||
}
|
}
|
||||||
|
.daterangepicker:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.daterangepicker:after {
|
.daterangepicker:after {
|
||||||
border-bottom: 6px solid var(--card-background-color);
|
border-bottom: 6px solid var(--card-background-color);
|
||||||
}
|
}
|
||||||
|
@@ -1,20 +1,7 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
@@ -50,36 +37,12 @@ interface AreaDevices {
|
|||||||
devices: string[];
|
devices: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (
|
||||||
const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (item) => html`<style>
|
item
|
||||||
paper-item {
|
) => html`<mwc-list-item twoline>
|
||||||
padding: 0;
|
<span>${item.name}</span>
|
||||||
margin: -10px;
|
<span slot="secondary">${item.devices.length} devices</span>
|
||||||
margin-left: 0;
|
</mwc-list-item>`;
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item>
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
<div class="name">${item.name}</div>
|
|
||||||
<div secondary>${item.devices.length} devices</div>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-area-devices-picker")
|
@customElement("ha-area-devices-picker")
|
||||||
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||||
@@ -117,9 +80,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
@property({ type: Array, attribute: "include-device-classes" })
|
@property({ type: Array, attribute: "include-device-classes" })
|
||||||
public includeDeviceClasses?: string[];
|
public includeDeviceClasses?: string[];
|
||||||
|
|
||||||
@property({ type: Boolean })
|
|
||||||
private _opened?: boolean;
|
|
||||||
|
|
||||||
@state() private _areaPicker = true;
|
@state() private _areaPicker = true;
|
||||||
|
|
||||||
@state() private _devices?: DeviceRegistryEntry[];
|
@state() private _devices?: DeviceRegistryEntry[];
|
||||||
@@ -302,71 +262,30 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<vaadin-combo-box-light
|
<ha-combo-box
|
||||||
|
.hass=${this.hass}
|
||||||
item-value-path="id"
|
item-value-path="id"
|
||||||
item-id-path="id"
|
item-id-path="id"
|
||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
.items=${areas}
|
.items=${areas}
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
${comboBoxRenderer(rowRenderer)}
|
.renderer=${rowRenderer}
|
||||||
@opened-changed=${this._openedChanged}
|
.label=${this.label === undefined && this.hass
|
||||||
|
? this.hass.localize("ui.components.device-picker.device")
|
||||||
|
: `${this.label} in area`}
|
||||||
@value-changed=${this._areaPicked}
|
@value-changed=${this._areaPicked}
|
||||||
>
|
>
|
||||||
<paper-input
|
</ha-combo-box>
|
||||||
.label=${this.label === undefined && this.hass
|
<mwc-button @click=${this._switchPicker}>
|
||||||
? this.hass.localize("ui.components.device-picker.device")
|
Choose individual devices
|
||||||
: `${this.label} in area`}
|
</mwc-button>
|
||||||
class="input"
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
<div class="suffix" slot="suffix">
|
|
||||||
${this.value
|
|
||||||
? html`<ha-icon-button
|
|
||||||
class="clear-button"
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.device-picker.clear"
|
|
||||||
)}
|
|
||||||
.path=${mdiClose}
|
|
||||||
@click=${this._clearValue}
|
|
||||||
no-ripple
|
|
||||||
></ha-icon-button> `
|
|
||||||
: ""}
|
|
||||||
${areas.length > 0
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.device-picker.show_devices"
|
|
||||||
)}
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
class="toggle-button"
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
|
||||||
<mwc-button @click=${this._switchPicker}
|
|
||||||
>Choose individual devices</mwc-button
|
|
||||||
>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clearValue(ev: Event) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._setValue([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
return this.value || [];
|
return this.value || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
|
||||||
this._opened = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _switchPicker() {
|
private async _switchPicker() {
|
||||||
this._areaPicker = !this._areaPicker;
|
this._areaPicker = !this._areaPicker;
|
||||||
}
|
}
|
||||||
@@ -398,22 +317,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.suffix {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 0px 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,7 +1,4 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
@@ -10,7 +7,7 @@ import {
|
|||||||
deviceAutomationsEqual,
|
deviceAutomationsEqual,
|
||||||
} from "../../data/device_automation";
|
} from "../../data/device_automation";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-paper-dropdown-menu";
|
import "../ha-select";
|
||||||
|
|
||||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
||||||
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
||||||
@@ -67,14 +64,12 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
this._createNoAutomation = createNoAutomation;
|
this._createNoAutomation = createNoAutomation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _key() {
|
private get _value() {
|
||||||
if (
|
if (!this.value) {
|
||||||
!this.value ||
|
return "";
|
||||||
deviceAutomationsEqual(
|
}
|
||||||
this._createNoAutomation(this.deviceId),
|
|
||||||
this.value
|
if (!this._automations.length) {
|
||||||
)
|
|
||||||
) {
|
|
||||||
return NO_AUTOMATION_KEY;
|
return NO_AUTOMATION_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,42 +88,32 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
if (this._renderEmpty) {
|
if (this._renderEmpty) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
const value = this._value;
|
||||||
return html`
|
return html`
|
||||||
<ha-paper-dropdown-menu
|
<ha-select
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${this.value
|
.value=${value}
|
||||||
? this._localizeDeviceAutomation(this.hass, this.value)
|
@selected=${this._automationChanged}
|
||||||
: ""}
|
.disabled=${this._automations.length === 0}
|
||||||
?disabled=${this._automations.length === 0}
|
|
||||||
>
|
>
|
||||||
<paper-listbox
|
${value === NO_AUTOMATION_KEY
|
||||||
slot="dropdown-content"
|
? html`<mwc-list-item .value=${NO_AUTOMATION_KEY}>
|
||||||
.selected=${this._key}
|
${this.NO_AUTOMATION_TEXT}
|
||||||
attr-for-selected="key"
|
</mwc-list-item>`
|
||||||
@iron-select=${this._automationChanged}
|
: ""}
|
||||||
>
|
${value === UNKNOWN_AUTOMATION_KEY
|
||||||
<paper-item
|
? html`<mwc-list-item .value=${UNKNOWN_AUTOMATION_KEY}>
|
||||||
key=${NO_AUTOMATION_KEY}
|
${this.UNKNOWN_AUTOMATION_TEXT}
|
||||||
.automation=${this._createNoAutomation(this.deviceId)}
|
</mwc-list-item>`
|
||||||
hidden
|
: ""}
|
||||||
>
|
${this._automations.map(
|
||||||
${this.NO_AUTOMATION_TEXT}
|
(automation, idx) => html`
|
||||||
</paper-item>
|
<mwc-list-item .value=${`${automation.device_id}_${idx}`}>
|
||||||
<paper-item key=${UNKNOWN_AUTOMATION_KEY} hidden>
|
${this._localizeDeviceAutomation(this.hass, automation)}
|
||||||
${this.UNKNOWN_AUTOMATION_TEXT}
|
</mwc-list-item>
|
||||||
</paper-item>
|
`
|
||||||
${this._automations.map(
|
)}
|
||||||
(automation, idx) => html`
|
</ha-select>
|
||||||
<paper-item
|
|
||||||
key=${`${this.deviceId}_${idx}`}
|
|
||||||
.automation=${automation}
|
|
||||||
>
|
|
||||||
${this._localizeDeviceAutomation(this.hass, automation)}
|
|
||||||
</paper-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
</ha-paper-dropdown-menu>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,14 +123,6 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
if (changedProps.has("deviceId")) {
|
if (changedProps.has("deviceId")) {
|
||||||
this._updateDeviceInfo();
|
this._updateDeviceInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The value has changed, force the listbox to update
|
|
||||||
if (changedProps.has("value") || changedProps.has("_renderEmpty")) {
|
|
||||||
const listbox = this.shadowRoot!.querySelector("paper-listbox")!;
|
|
||||||
if (listbox) {
|
|
||||||
listbox._selectSelected(this._key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateDeviceInfo() {
|
private async _updateDeviceInfo() {
|
||||||
@@ -168,9 +145,16 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _automationChanged(ev) {
|
private _automationChanged(ev) {
|
||||||
if (ev.detail.item.automation) {
|
const value = ev.target.value;
|
||||||
this._setValue(ev.detail.item.automation);
|
if (!value || [UNKNOWN_AUTOMATION_KEY, NO_AUTOMATION_KEY].includes(value)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const [deviceId, idx] = value.split("_");
|
||||||
|
const automation = this._automations[idx];
|
||||||
|
if (automation.device_id !== deviceId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._setValue(automation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setValue(automation: T) {
|
private _setValue(automation: T) {
|
||||||
@@ -183,14 +167,9 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-paper-dropdown-menu {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
margin-top: 4px;
|
||||||
paper-listbox {
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,9 @@
|
|||||||
import "@polymer/paper-item/paper-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
import { mdiCheck } from "@mdi/js";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { stringCompare } from "../../common/string/compare";
|
import { stringCompare } from "../../common/string/compare";
|
||||||
@@ -46,36 +37,12 @@ export type HaDevicePickerDeviceFilterFunc = (
|
|||||||
device: DeviceRegistryEntry
|
device: DeviceRegistryEntry
|
||||||
) => boolean;
|
) => boolean;
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<mwc-list-item
|
||||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<style>
|
.twoline=${!!item.area}
|
||||||
paper-item {
|
>
|
||||||
padding: 0;
|
<span>${item.name}</span>
|
||||||
margin: -10px;
|
<span slot="secondary">${item.area}</span>
|
||||||
margin-left: 0;
|
</mwc-list-item>`;
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item>
|
|
||||||
<paper-item-body two-line>
|
|
||||||
${item.name}
|
|
||||||
<span secondary>${item.area}</span>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-device-picker")
|
@customElement("ha-device-picker")
|
||||||
export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||||
@@ -138,7 +105,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
if (!devices.length) {
|
if (!devices.length) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "",
|
id: "no_devices",
|
||||||
area: "",
|
area: "",
|
||||||
name: this.hass.localize("ui.components.device-picker.no_devices"),
|
name: this.hass.localize("ui.components.device-picker.no_devices"),
|
||||||
},
|
},
|
||||||
@@ -234,7 +201,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
if (!outputDevices.length) {
|
if (!outputDevices.length) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "",
|
id: "no_devices",
|
||||||
area: "",
|
area: "",
|
||||||
name: this.hass.localize("ui.components.device-picker.no_match"),
|
name: this.hass.localize("ui.components.device-picker.no_match"),
|
||||||
},
|
},
|
||||||
@@ -303,7 +270,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
item-value-path="id"
|
item-value-path="id"
|
||||||
item-id-path="id"
|
|
||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._deviceChanged}
|
@value-changed=${this._deviceChanged}
|
||||||
@@ -317,7 +283,11 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _deviceChanged(ev: PolymerChangedEvent<string>) {
|
private _deviceChanged(ev: PolymerChangedEvent<string>) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const newValue = ev.detail.value;
|
let newValue = ev.detail.value;
|
||||||
|
|
||||||
|
if (newValue === "no_devices") {
|
||||||
|
newValue = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (newValue !== this._value) {
|
if (newValue !== this._value) {
|
||||||
this._setValue(newValue);
|
this._setValue(newValue);
|
||||||
@@ -335,19 +305,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
paper-input > ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
@@ -116,6 +116,12 @@ class HaDevicesPicker extends LitElement {
|
|||||||
|
|
||||||
this._updateDevices([...currentDevices, toAdd]);
|
this._updateDevices([...currentDevices, toAdd]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static override styles = css`
|
||||||
|
div {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||||
@@ -46,11 +46,29 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
@property({ type: Array, attribute: "include-unit-of-measurement" })
|
@property({ type: Array, attribute: "include-unit-of-measurement" })
|
||||||
public includeUnitOfMeasurement?: string[];
|
public includeUnitOfMeasurement?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of allowed entities to show. Will ignore all other filters.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr include-entities
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "include-entities" })
|
||||||
|
public includeEntities?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of entities to be excluded.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr exclude-entities
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "exclude-entities" })
|
||||||
|
public excludeEntities?: string[];
|
||||||
|
|
||||||
@property({ attribute: "picked-entity-label" })
|
@property({ attribute: "picked-entity-label" })
|
||||||
public pickedEntityLabel?: string;
|
public pickedEntityLabel?: string;
|
||||||
|
|
||||||
@property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string;
|
@property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string;
|
||||||
|
|
||||||
|
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return html``;
|
return html``;
|
||||||
@@ -67,6 +85,8 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
.excludeDomains=${this.excludeDomains}
|
.excludeDomains=${this.excludeDomains}
|
||||||
|
.includeEntities=${this.includeEntities}
|
||||||
|
.excludeEntities=${this.excludeEntities}
|
||||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||||
.entityFilter=${this._entityFilter}
|
.entityFilter=${this._entityFilter}
|
||||||
@@ -82,6 +102,8 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
.excludeDomains=${this.excludeDomains}
|
.excludeDomains=${this.excludeDomains}
|
||||||
|
.includeEntities=${this.includeEntities}
|
||||||
|
.excludeEntities=${this.excludeEntities}
|
||||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||||
.entityFilter=${this._entityFilter}
|
.entityFilter=${this._entityFilter}
|
||||||
@@ -94,7 +116,9 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
|
|
||||||
private _entityFilter: HaEntityPickerEntityFilterFunc = (
|
private _entityFilter: HaEntityPickerEntityFilterFunc = (
|
||||||
stateObj: HassEntity
|
stateObj: HassEntity
|
||||||
) => !this.value || !this.value.includes(stateObj.entity_id);
|
) =>
|
||||||
|
(!this.value || !this.value.includes(stateObj.entity_id)) &&
|
||||||
|
(!this.entityFilter || this.entityFilter(stateObj));
|
||||||
|
|
||||||
private get _currentEntities() {
|
private get _currentEntities() {
|
||||||
return this.value || [];
|
return this.value || [];
|
||||||
@@ -114,7 +138,7 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
const newValue = event.detail.value;
|
const newValue = event.detail.value;
|
||||||
if (
|
if (
|
||||||
newValue === curValue ||
|
newValue === curValue ||
|
||||||
(newValue !== "" && !isValidEntityId(newValue))
|
(newValue !== undefined && !isValidEntityId(newValue))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -145,6 +169,12 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
|
|
||||||
this._updateEntities([...currentEntities, toAdd]);
|
this._updateEntities([...currentEntities, toAdd]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static override styles = css`
|
||||||
|
div {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,54 +1,14 @@
|
|||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
|
||||||
import { formatAttributeName } from "../../data/entity_attributes";
|
import { formatAttributeName } from "../../data/entity_attributes";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-icon-button";
|
import "../ha-combo-box";
|
||||||
import "../ha-svg-icon";
|
import type { HaComboBox } from "../ha-combo-box";
|
||||||
import "./state-badge";
|
|
||||||
|
|
||||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
|
|
||||||
paper-item {
|
|
||||||
padding: 0;
|
|
||||||
margin: -10px;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item>${formatAttributeName(item)}</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-entity-attribute-picker")
|
@customElement("ha-entity-attribute-picker")
|
||||||
class HaEntityAttributePicker extends LitElement {
|
class HaEntityAttributePicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -68,7 +28,7 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) private _opened = false;
|
@property({ type: Boolean }) private _opened = false;
|
||||||
|
|
||||||
@query("vaadin-combo-box-light", true) private _comboBox!: HTMLElement;
|
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues) {
|
protected shouldUpdate(changedProps: PropertyValues) {
|
||||||
return !(!changedProps.has("_opened") && this._opened);
|
return !(!changedProps.has("_opened") && this._opened);
|
||||||
@@ -78,7 +38,10 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
if (changedProps.has("_opened") && this._opened) {
|
if (changedProps.has("_opened") && this._opened) {
|
||||||
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
|
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
|
||||||
(this._comboBox as any).items = state
|
(this._comboBox as any).items = state
|
||||||
? Object.keys(state.attributes)
|
? Object.keys(state.attributes).map((key) => ({
|
||||||
|
value: key,
|
||||||
|
label: formatAttributeName(key),
|
||||||
|
}))
|
||||||
: [];
|
: [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,100 +52,31 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<vaadin-combo-box-light
|
<ha-combo-box
|
||||||
.value=${this._value}
|
.hass=${this.hass}
|
||||||
|
.value=${this.value || ""}
|
||||||
|
.autofocus=${this.autofocus}
|
||||||
|
.label=${this.label ??
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.components.entity.entity-attribute-picker.attribute"
|
||||||
|
)}
|
||||||
|
.disabled=${this.disabled || !this.entityId}
|
||||||
.allowCustomValue=${this.allowCustomValue}
|
.allowCustomValue=${this.allowCustomValue}
|
||||||
attr-for-value="bind-value"
|
item-value-path="value"
|
||||||
${comboBoxRenderer(rowRenderer)}
|
item-label-path="label"
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
>
|
>
|
||||||
<paper-input
|
</ha-combo-box>
|
||||||
.autofocus=${this.autofocus}
|
|
||||||
.label=${this.label ??
|
|
||||||
this.hass.localize(
|
|
||||||
"ui.components.entity.entity-attribute-picker.attribute"
|
|
||||||
)}
|
|
||||||
.value=${this._value ? formatAttributeName(this._value) : ""}
|
|
||||||
.disabled=${this.disabled || !this.entityId}
|
|
||||||
class="input"
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
<div class="suffix" slot="suffix">
|
|
||||||
${this.value
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.entity.entity-picker.clear"
|
|
||||||
)}
|
|
||||||
.path=${mdiClose}
|
|
||||||
class="clear-button"
|
|
||||||
tabindex="-1"
|
|
||||||
@click=${this._clearValue}
|
|
||||||
no-ripple
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.entity.entity-attribute-picker.show_attributes"
|
|
||||||
)}
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
class="toggle-button"
|
|
||||||
tabindex="-1"
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clearValue(ev: Event) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._setValue("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _value() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||||
this._opened = ev.detail.value;
|
this._opened = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||||
const newValue = ev.detail.value;
|
this.value = ev.detail.value;
|
||||||
if (newValue !== this._value) {
|
|
||||||
this._setValue(newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setValue(value: string) {
|
|
||||||
this.value = value;
|
|
||||||
setTimeout(() => {
|
|
||||||
fireEvent(this, "value-changed", { value });
|
|
||||||
fireEvent(this, "change");
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.suffix {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 0px 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,61 +1,36 @@
|
|||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
CSSResultGroup,
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
|
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-combo-box";
|
||||||
|
import type { HaComboBox } from "../ha-combo-box";
|
||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "./state-badge";
|
import "./state-badge";
|
||||||
|
|
||||||
|
interface HassEntityWithCachedName extends HassEntity {
|
||||||
|
friendly_name: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
// eslint-disable-next-line lit/prefer-static-styles
|
||||||
const rowRenderer: ComboBoxLitRenderer<HassEntity> = (item) => html`<style>
|
const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) =>
|
||||||
paper-icon-item {
|
html`<mwc-list-item graphic="avatar" .twoline=${!!item.entity_id}>
|
||||||
padding: 0;
|
${item.state
|
||||||
margin: -8px;
|
? html`<state-badge slot="graphic" .stateObj=${item}></state-badge>`
|
||||||
}
|
: ""}
|
||||||
#content {
|
<span>${item.friendly_name}</span>
|
||||||
display: flex;
|
<span slot="secondary">${item.entity_id}</span>
|
||||||
align-items: center;
|
</mwc-list-item>`;
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-icon-item {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-icon-item>
|
|
||||||
<state-badge slot="item-icon" .stateObj=${item}></state-badge>
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
${computeStateName(item)}
|
|
||||||
<span secondary>${item.entity_id}</span>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-icon-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-entity-picker")
|
@customElement("ha-entity-picker")
|
||||||
export class HaEntityPicker extends LitElement {
|
export class HaEntityPicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -103,29 +78,45 @@ export class HaEntityPicker extends LitElement {
|
|||||||
@property({ type: Array, attribute: "include-unit-of-measurement" })
|
@property({ type: Array, attribute: "include-unit-of-measurement" })
|
||||||
public includeUnitOfMeasurement?: string[];
|
public includeUnitOfMeasurement?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of allowed entities to show. Will ignore all other filters.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr include-entities
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "include-entities" })
|
||||||
|
public includeEntities?: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of entities to be excluded.
|
||||||
|
* @type {Array}
|
||||||
|
* @attr exclude-entities
|
||||||
|
*/
|
||||||
|
@property({ type: Array, attribute: "exclude-entities" })
|
||||||
|
public excludeEntities?: string[];
|
||||||
|
|
||||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||||
|
|
||||||
@property({ type: Boolean }) public hideClearIcon = false;
|
@property({ type: Boolean }) public hideClearIcon = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) private _opened = false;
|
@state() private _opened = false;
|
||||||
|
|
||||||
@query("vaadin-combo-box-light", true) private comboBox!: HTMLElement;
|
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
this.comboBox?.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
this.comboBox?.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _initedStates = false;
|
private _initedStates = false;
|
||||||
|
|
||||||
private _states: HassEntity[] = [];
|
private _states: HassEntityWithCachedName[] = [];
|
||||||
|
|
||||||
private _getStates = memoizeOne(
|
private _getStates = memoizeOne(
|
||||||
(
|
(
|
||||||
@@ -135,15 +126,62 @@ export class HaEntityPicker extends LitElement {
|
|||||||
excludeDomains: this["excludeDomains"],
|
excludeDomains: this["excludeDomains"],
|
||||||
entityFilter: this["entityFilter"],
|
entityFilter: this["entityFilter"],
|
||||||
includeDeviceClasses: this["includeDeviceClasses"],
|
includeDeviceClasses: this["includeDeviceClasses"],
|
||||||
includeUnitOfMeasurement: this["includeUnitOfMeasurement"]
|
includeUnitOfMeasurement: this["includeUnitOfMeasurement"],
|
||||||
) => {
|
includeEntities: this["includeEntities"],
|
||||||
let states: HassEntity[] = [];
|
excludeEntities: this["excludeEntities"]
|
||||||
|
): HassEntityWithCachedName[] => {
|
||||||
|
let states: HassEntityWithCachedName[] = [];
|
||||||
|
|
||||||
if (!hass) {
|
if (!hass) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
let entityIds = Object.keys(hass.states);
|
let entityIds = Object.keys(hass.states);
|
||||||
|
|
||||||
|
if (!entityIds.length) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
entity_id: "",
|
||||||
|
state: "",
|
||||||
|
last_changed: "",
|
||||||
|
last_updated: "",
|
||||||
|
context: { id: "", user_id: null, parent_id: null },
|
||||||
|
friendly_name: this.hass!.localize(
|
||||||
|
"ui.components.entity.entity-picker.no_entities"
|
||||||
|
),
|
||||||
|
attributes: {
|
||||||
|
friendly_name: this.hass!.localize(
|
||||||
|
"ui.components.entity.entity-picker.no_entities"
|
||||||
|
),
|
||||||
|
icon: "mdi:magnify",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeEntities) {
|
||||||
|
entityIds = entityIds.filter((entityId) =>
|
||||||
|
this.includeEntities!.includes(entityId)
|
||||||
|
);
|
||||||
|
|
||||||
|
return entityIds
|
||||||
|
.map((key) => ({
|
||||||
|
...hass!.states[key],
|
||||||
|
friendly_name: computeStateName(hass!.states[key]) || key,
|
||||||
|
}))
|
||||||
|
.sort((entityA, entityB) =>
|
||||||
|
caseInsensitiveStringCompare(
|
||||||
|
entityA.friendly_name,
|
||||||
|
entityB.friendly_name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excludeEntities) {
|
||||||
|
entityIds = entityIds.filter(
|
||||||
|
(entityId) => !excludeEntities!.includes(entityId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (includeDomains) {
|
if (includeDomains) {
|
||||||
entityIds = entityIds.filter((eid) =>
|
entityIds = entityIds.filter((eid) =>
|
||||||
includeDomains.includes(computeDomain(eid))
|
includeDomains.includes(computeDomain(eid))
|
||||||
@@ -156,7 +194,17 @@ export class HaEntityPicker extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
states = entityIds.sort().map((key) => hass!.states[key]);
|
states = entityIds
|
||||||
|
.map((key) => ({
|
||||||
|
...hass!.states[key],
|
||||||
|
friendly_name: computeStateName(hass!.states[key]) || key,
|
||||||
|
}))
|
||||||
|
.sort((entityA, entityB) =>
|
||||||
|
caseInsensitiveStringCompare(
|
||||||
|
entityA.friendly_name,
|
||||||
|
entityB.friendly_name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
if (includeDeviceClasses) {
|
if (includeDeviceClasses) {
|
||||||
states = states.filter(
|
states = states.filter(
|
||||||
@@ -195,7 +243,10 @@ export class HaEntityPicker extends LitElement {
|
|||||||
state: "",
|
state: "",
|
||||||
last_changed: "",
|
last_changed: "",
|
||||||
last_updated: "",
|
last_updated: "",
|
||||||
context: { id: "", user_id: null },
|
context: { id: "", user_id: null, parent_id: null },
|
||||||
|
friendly_name: this.hass!.localize(
|
||||||
|
"ui.components.entity.entity-picker.no_match"
|
||||||
|
),
|
||||||
attributes: {
|
attributes: {
|
||||||
friendly_name: this.hass!.localize(
|
friendly_name: this.hass!.localize(
|
||||||
"ui.components.entity.entity-picker.no_match"
|
"ui.components.entity.entity-picker.no_match"
|
||||||
@@ -230,7 +281,9 @@ export class HaEntityPicker extends LitElement {
|
|||||||
this.excludeDomains,
|
this.excludeDomains,
|
||||||
this.entityFilter,
|
this.entityFilter,
|
||||||
this.includeDeviceClasses,
|
this.includeDeviceClasses,
|
||||||
this.includeUnitOfMeasurement
|
this.includeUnitOfMeasurement,
|
||||||
|
this.includeEntities,
|
||||||
|
this.excludeEntities
|
||||||
);
|
);
|
||||||
if (this._initedStates) {
|
if (this._initedStates) {
|
||||||
(this.comboBox as any).filteredItems = this._states;
|
(this.comboBox as any).filteredItems = this._states;
|
||||||
@@ -241,64 +294,25 @@ export class HaEntityPicker extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<vaadin-combo-box-light
|
<ha-combo-box
|
||||||
item-value-path="entity_id"
|
item-value-path="entity_id"
|
||||||
item-label-path="entity_id"
|
item-label-path="friendly_name"
|
||||||
|
.hass=${this.hass}
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
|
.label=${this.label === undefined
|
||||||
|
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
||||||
|
: this.label}
|
||||||
.allowCustomValue=${this.allowCustomEntity}
|
.allowCustomValue=${this.allowCustomEntity}
|
||||||
.filteredItems=${this._states}
|
.filteredItems=${this._states}
|
||||||
${comboBoxRenderer(rowRenderer)}
|
.renderer=${rowRenderer}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@filter-changed=${this._filterChanged}
|
@filter-changed=${this._filterChanged}
|
||||||
>
|
>
|
||||||
<paper-input
|
</ha-combo-box>
|
||||||
.autofocus=${this.autofocus}
|
|
||||||
.label=${this.label === undefined
|
|
||||||
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
|
||||||
: this.label}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
class="input"
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
<div class="suffix" slot="suffix">
|
|
||||||
${this.value && !this.hideClearIcon
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.entity.entity-picker.clear"
|
|
||||||
)}
|
|
||||||
.path=${mdiClose}
|
|
||||||
class="clear-button"
|
|
||||||
tabindex="-1"
|
|
||||||
@click=${this._clearValue}
|
|
||||||
no-ripple
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.entity.entity-picker.show_entities"
|
|
||||||
)}
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
class="toggle-button"
|
|
||||||
tabindex="-1"
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _clearValue(ev: Event) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._setValue("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
return this.value || "";
|
return this.value || "";
|
||||||
}
|
}
|
||||||
@@ -308,6 +322,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||||
|
ev.stopPropagation();
|
||||||
const newValue = ev.detail.value;
|
const newValue = ev.detail.value;
|
||||||
if (newValue !== this._value) {
|
if (newValue !== this._value) {
|
||||||
this._setValue(newValue);
|
this._setValue(newValue);
|
||||||
@@ -317,9 +332,9 @@ export class HaEntityPicker extends LitElement {
|
|||||||
private _filterChanged(ev: CustomEvent): void {
|
private _filterChanged(ev: CustomEvent): void {
|
||||||
const filterString = ev.detail.value.toLowerCase();
|
const filterString = ev.detail.value.toLowerCase();
|
||||||
(this.comboBox as any).filteredItems = this._states.filter(
|
(this.comboBox as any).filteredItems = this._states.filter(
|
||||||
(state) =>
|
(entityState) =>
|
||||||
state.entity_id.toLowerCase().includes(filterString) ||
|
entityState.entity_id.toLowerCase().includes(filterString) ||
|
||||||
computeStateName(state).toLowerCase().includes(filterString)
|
computeStateName(entityState).toLowerCase().includes(filterString)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,22 +345,6 @@ export class HaEntityPicker extends LitElement {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.suffix {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 0px 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,17 +1,5 @@
|
|||||||
import { mdiCheck } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -76,54 +64,24 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
state?: HassEntity;
|
state?: HassEntity;
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
}> = (item) => html`<mwc-list-item graphic="avatar" twoline>
|
||||||
}> = (item) => html`<style>
|
${item.state
|
||||||
paper-icon-item {
|
? html`<state-badge slot="graphic" .stateObj=${item.state}></state-badge>`
|
||||||
padding: 0;
|
: ""}
|
||||||
margin: -8px;
|
<span>${item.name}</span>
|
||||||
}
|
<span slot="secondary"
|
||||||
#content {
|
>${item.id === "" || item.id === "__missing"
|
||||||
display: flex;
|
? html`<a
|
||||||
align-items: center;
|
target="_blank"
|
||||||
}
|
rel="noopener noreferrer"
|
||||||
ha-svg-icon {
|
href=${documentationUrl(this.hass, "/more-info/statistics/")}
|
||||||
padding-left: 2px;
|
>${this.hass.localize(
|
||||||
color: var(--secondary-text-color);
|
"ui.components.statistic-picker.learn_more"
|
||||||
}
|
)}</a
|
||||||
:host(:not([selected])) ha-svg-icon {
|
>`
|
||||||
display: none;
|
: item.id}</span
|
||||||
}
|
>
|
||||||
:host([selected]) paper-icon-item {
|
</mwc-list-item>`;
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-icon-item>
|
|
||||||
${item.state
|
|
||||||
? html`<state-badge
|
|
||||||
slot="item-icon"
|
|
||||||
.stateObj=${item.state}
|
|
||||||
></state-badge>`
|
|
||||||
: ""}
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
${item.name}
|
|
||||||
<span secondary
|
|
||||||
>${item.id === "" || item.id === "__missing"
|
|
||||||
? html`<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href=${documentationUrl(this.hass, "/more-info/statistics/")}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.components.statistic-picker.learn_more"
|
|
||||||
)}</a
|
|
||||||
>`
|
|
||||||
: item.id}</span
|
|
||||||
>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-icon-item>`;
|
|
||||||
|
|
||||||
private _getStatistics = memoizeOne(
|
private _getStatistics = memoizeOne(
|
||||||
(
|
(
|
||||||
@@ -293,19 +251,6 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
paper-input > ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import type { PolymerChangedEvent } from "../../polymer-types";
|
import type { PolymerChangedEvent } from "../../polymer-types";
|
||||||
@@ -103,6 +103,20 @@ class HaStatisticsPicker extends LitElement {
|
|||||||
|
|
||||||
this._updateStatistics([...currentEntities, toAdd]);
|
this._updateStatistics([...currentEntities, toAdd]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
width: 200px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
ha-statistic-picker {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { mdiCheck } from "@mdi/js";
|
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
@@ -12,39 +11,12 @@ import { PolymerChangedEvent } from "../polymer-types";
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { HaComboBox } from "./ha-combo-box";
|
import { HaComboBox } from "./ha-combo-box";
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (
|
||||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (item) => html`<style>
|
item
|
||||||
paper-item {
|
) => html`<mwc-list-item twoline>
|
||||||
padding: 0;
|
<span>${item.name}</span>
|
||||||
margin: -10px;
|
<span slot="secondary">${item.slug}</span>
|
||||||
margin-left: 0px;
|
</mwc-list-item>`;
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-icon-item {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item>
|
|
||||||
<paper-item-body two-line>
|
|
||||||
${item.name}
|
|
||||||
<span secondary>${item.slug}</span>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-addon-picker")
|
@customElement("ha-addon-picker")
|
||||||
class HaAddonPicker extends LitElement {
|
class HaAddonPicker extends LitElement {
|
||||||
|
@@ -1,19 +1,6 @@
|
|||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
css,
|
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -41,38 +28,18 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
|||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||||
|
import type { HaComboBox } from "./ha-combo-box";
|
||||||
|
import "./ha-combo-box";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
|
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
|
||||||
item
|
item
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
) => html`<mwc-list-item
|
||||||
) => html`<style>
|
class=${classMap({ "add-new": item.area_id === "add_new" })}
|
||||||
paper-item {
|
>
|
||||||
padding: 0;
|
${item.name}
|
||||||
margin: -10px;
|
</mwc-list-item>`;
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
#content {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding-left: 2px;
|
|
||||||
margin-right: -2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
:host(:not([selected])) ha-svg-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
:host([selected]) paper-item {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
|
||||||
<paper-item class=${classMap({ "add-new": item.area_id === "add_new" })}>
|
|
||||||
<paper-item-body two-line>${item.name}</paper-item-body>
|
|
||||||
</paper-item>`;
|
|
||||||
|
|
||||||
@customElement("ha-area-picker")
|
@customElement("ha-area-picker")
|
||||||
export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||||
@@ -125,7 +92,9 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _opened?: boolean;
|
@state() private _opened?: boolean;
|
||||||
|
|
||||||
@query("vaadin-combo-box-light", true) public comboBox!: HTMLElement;
|
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||||
|
|
||||||
|
private _filter?: string;
|
||||||
|
|
||||||
private _init = false;
|
private _init = false;
|
||||||
|
|
||||||
@@ -145,13 +114,13 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
this.comboBox?.open();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
this.comboBox?.focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +139,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
if (!areas.length) {
|
if (!areas.length) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
area_id: "",
|
area_id: "no_areas",
|
||||||
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
name: this.hass.localize("ui.components.area-picker.no_areas"),
|
||||||
picture: null,
|
picture: null,
|
||||||
},
|
},
|
||||||
@@ -294,7 +263,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
if (!outputAreas.length) {
|
if (!outputAreas.length) {
|
||||||
outputAreas = [
|
outputAreas = [
|
||||||
{
|
{
|
||||||
area_id: "",
|
area_id: "no_areas",
|
||||||
name: this.hass.localize("ui.components.area-picker.no_match"),
|
name: this.hass.localize("ui.components.area-picker.no_match"),
|
||||||
picture: null,
|
picture: null,
|
||||||
},
|
},
|
||||||
@@ -339,52 +308,25 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<vaadin-combo-box-light
|
<ha-combo-box
|
||||||
|
.hass=${this.hass}
|
||||||
item-value-path="area_id"
|
item-value-path="area_id"
|
||||||
item-id-path="area_id"
|
item-id-path="area_id"
|
||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
${comboBoxRenderer(rowRenderer)}
|
.label=${this.label === undefined && this.hass
|
||||||
|
? this.hass.localize("ui.components.area-picker.area")
|
||||||
|
: this.label}
|
||||||
|
.placeholder=${this.placeholder
|
||||||
|
? this._area(this.placeholder)?.name
|
||||||
|
: undefined}
|
||||||
|
.renderer=${rowRenderer}
|
||||||
|
@filter-changed=${this._filterChanged}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
@value-changed=${this._areaChanged}
|
@value-changed=${this._areaChanged}
|
||||||
>
|
>
|
||||||
<paper-input
|
</ha-combo-box>
|
||||||
.label=${this.label === undefined && this.hass
|
|
||||||
? this.hass.localize("ui.components.area-picker.area")
|
|
||||||
: this.label}
|
|
||||||
.placeholder=${this.placeholder
|
|
||||||
? this._area(this.placeholder)?.name
|
|
||||||
: undefined}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
class="input"
|
|
||||||
autocapitalize="none"
|
|
||||||
autocomplete="off"
|
|
||||||
autocorrect="off"
|
|
||||||
spellcheck="false"
|
|
||||||
>
|
|
||||||
${this.value
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.components.area-picker.clear"
|
|
||||||
)}
|
|
||||||
.path=${mdiClose}
|
|
||||||
slot="suffix"
|
|
||||||
class="clear-button"
|
|
||||||
@click=${this._clearValue}
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
|
|
||||||
<ha-icon-button
|
|
||||||
.label=${this.hass.localize("ui.components.area-picker.toggle")}
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
slot="suffix"
|
|
||||||
class="toggle-button"
|
|
||||||
></ha-icon-button>
|
|
||||||
</paper-input>
|
|
||||||
</vaadin-combo-box-light>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,9 +334,29 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
this._areas?.find((area) => area.area_id === areaId)
|
this._areas?.find((area) => area.area_id === areaId)
|
||||||
);
|
);
|
||||||
|
|
||||||
private _clearValue(ev: Event) {
|
private _filterChanged(ev: CustomEvent): void {
|
||||||
ev.stopPropagation();
|
this._filter = ev.detail.value;
|
||||||
this._setValue("");
|
if (!this._filter) {
|
||||||
|
this.comboBox.filteredItems = this.comboBox.items;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (!this.noAdd && this.comboBox._comboBox.filteredItems?.length === 0) {
|
||||||
|
this.comboBox.filteredItems = [
|
||||||
|
{
|
||||||
|
area_id: "add_new_suggestion",
|
||||||
|
name: this.hass.localize(
|
||||||
|
"ui.components.area-picker.add_new_sugestion",
|
||||||
|
{ name: this._filter }
|
||||||
|
),
|
||||||
|
picture: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
this.comboBox.filteredItems = this.comboBox.items?.filter((item) =>
|
||||||
|
item.name.toLowerCase().includes(this._filter!.toLowerCase())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
@@ -406,9 +368,14 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _areaChanged(ev: PolymerChangedEvent<string>) {
|
private _areaChanged(ev: PolymerChangedEvent<string>) {
|
||||||
const newValue = ev.detail.value;
|
ev.stopPropagation();
|
||||||
|
let newValue = ev.detail.value;
|
||||||
|
|
||||||
if (newValue !== "add_new") {
|
if (newValue === "no_areas") {
|
||||||
|
newValue = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!["add_new_suggestion", "add_new"].includes(newValue)) {
|
||||||
if (newValue !== this._value) {
|
if (newValue !== this._value) {
|
||||||
this._setValue(newValue);
|
this._setValue(newValue);
|
||||||
}
|
}
|
||||||
@@ -425,6 +392,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
inputLabel: this.hass.localize(
|
inputLabel: this.hass.localize(
|
||||||
"ui.components.area-picker.add_dialog.name"
|
"ui.components.area-picker.add_dialog.name"
|
||||||
),
|
),
|
||||||
|
defaultValue:
|
||||||
|
newValue === "add_new_suggestion" ? this._filter : undefined,
|
||||||
confirm: async (name) => {
|
confirm: async (name) => {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
@@ -445,6 +414,8 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
this.entityFilter,
|
this.entityFilter,
|
||||||
this.noAdd
|
this.noAdd
|
||||||
);
|
);
|
||||||
|
await this.updateComplete;
|
||||||
|
await this.comboBox.updateComplete;
|
||||||
this._setValue(area.area_id);
|
this._setValue(area.area_id);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
@@ -465,19 +436,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
paper-input > ha-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
313
src/components/ha-base-time-input.ts
Normal file
313
src/components/ha-base-time-input.ts
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
import { LitElement, html, TemplateResult, css } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import "./ha-select";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import "./ha-textfield";
|
||||||
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
|
|
||||||
|
export interface TimeChangedEvent {
|
||||||
|
hours: number;
|
||||||
|
minutes: number;
|
||||||
|
seconds: number;
|
||||||
|
milliseconds: number;
|
||||||
|
amPm?: "AM" | "PM";
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-base-time-input")
|
||||||
|
export class HaBaseTimeInput extends LitElement {
|
||||||
|
/**
|
||||||
|
* Label for the input
|
||||||
|
*/
|
||||||
|
@property() label?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* auto validate time inputs
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) autoValidate = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* determines if inputs are required
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) public required?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 12 or 24 hr format
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) format: 12 | 24 = 12;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* disables the inputs
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) disabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hour
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) hours = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* minute
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) minutes = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* second
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) seconds = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* milli second
|
||||||
|
*/
|
||||||
|
@property({ type: Number }) milliseconds = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the hour input
|
||||||
|
*/
|
||||||
|
@property() hourLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the min input
|
||||||
|
*/
|
||||||
|
@property() minLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the sec input
|
||||||
|
*/
|
||||||
|
@property() secLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label for the milli sec input
|
||||||
|
*/
|
||||||
|
@property() millisecLabel = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show the sec field
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) enableSecond = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show the milli sec field
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) enableMillisecond = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* limit hours input
|
||||||
|
*/
|
||||||
|
@property({ type: Boolean }) noHoursLimit = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AM or PM
|
||||||
|
*/
|
||||||
|
@property() amPm: "AM" | "PM" = "AM";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatted time string
|
||||||
|
*/
|
||||||
|
@property() value?: string;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this.label ? html`<label>${this.label}</label>` : ""}
|
||||||
|
<div class="time-input-wrap">
|
||||||
|
<ha-textfield
|
||||||
|
id="hour"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this.hours}
|
||||||
|
.label=${this.hourLabel}
|
||||||
|
name="hours"
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
.max=${this._hourMax}
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
suffix=":"
|
||||||
|
class="hasSuffix"
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
<ha-textfield
|
||||||
|
id="min"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this._formatValue(this.minutes)}
|
||||||
|
.label=${this.minLabel}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
name="minutes"
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
max="59"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.suffix=${this.enableSecond ? ":" : ""}
|
||||||
|
class=${this.enableSecond ? "has-suffix" : ""}
|
||||||
|
>
|
||||||
|
</ha-textfield>
|
||||||
|
${this.enableSecond
|
||||||
|
? html`<ha-textfield
|
||||||
|
id="sec"
|
||||||
|
type="number"
|
||||||
|
inputmode="numeric"
|
||||||
|
.value=${this._formatValue(this.seconds)}
|
||||||
|
.label=${this.secLabel}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
name="seconds"
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="2"
|
||||||
|
max="59"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.suffix=${this.enableMillisecond ? ":" : ""}
|
||||||
|
class=${this.enableMillisecond ? "has-suffix" : ""}
|
||||||
|
>
|
||||||
|
</ha-textfield>`
|
||||||
|
: ""}
|
||||||
|
${this.enableMillisecond
|
||||||
|
? html`<ha-textfield
|
||||||
|
id="millisec"
|
||||||
|
type="number"
|
||||||
|
.value=${this._formatValue(this.milliseconds, 3)}
|
||||||
|
.label=${this.millisecLabel}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
@focus=${this._onFocus}
|
||||||
|
name="milliseconds"
|
||||||
|
no-spinner
|
||||||
|
.required=${this.required}
|
||||||
|
.autoValidate=${this.autoValidate}
|
||||||
|
maxlength="3"
|
||||||
|
max="999"
|
||||||
|
min="0"
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
>
|
||||||
|
</ha-textfield>`
|
||||||
|
: ""}
|
||||||
|
${this.format === 24
|
||||||
|
? ""
|
||||||
|
: html`<ha-select
|
||||||
|
.required=${this.required}
|
||||||
|
.value=${this.amPm}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
name="amPm"
|
||||||
|
naturalMenuWidth
|
||||||
|
fixedMenuPosition
|
||||||
|
@selected=${this._valueChanged}
|
||||||
|
@closed=${stopPropagation}
|
||||||
|
>
|
||||||
|
<mwc-list-item value="AM">AM</mwc-list-item>
|
||||||
|
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||||
|
</ha-select>`}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _valueChanged(ev) {
|
||||||
|
this[ev.target.name] =
|
||||||
|
ev.target.name === "amPm" ? ev.target.value : Number(ev.target.value);
|
||||||
|
const value: TimeChangedEvent = {
|
||||||
|
hours: this.hours,
|
||||||
|
minutes: this.minutes,
|
||||||
|
seconds: this.seconds,
|
||||||
|
milliseconds: this.milliseconds,
|
||||||
|
};
|
||||||
|
if (this.format === 12) {
|
||||||
|
value.amPm = this.amPm;
|
||||||
|
}
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onFocus(ev) {
|
||||||
|
ev.target.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format time fragments
|
||||||
|
*/
|
||||||
|
private _formatValue(value: number, padding = 2) {
|
||||||
|
return value.toString().padStart(padding, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 24 hour format has a max hr of 23
|
||||||
|
*/
|
||||||
|
private get _hourMax() {
|
||||||
|
if (this.noHoursLimit) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.format === 12) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
return 23;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.time-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
--mdc-shape-small: 0;
|
||||||
|
--text-field-appearance: none;
|
||||||
|
--text-field-padding: 0 4px;
|
||||||
|
--text-field-suffix-padding-left: 2px;
|
||||||
|
--text-field-suffix-padding-right: 0;
|
||||||
|
--text-field-text-align: center;
|
||||||
|
}
|
||||||
|
ha-textfield.hasSuffix {
|
||||||
|
--text-field-padding: 0 0 0 4px;
|
||||||
|
}
|
||||||
|
ha-textfield:first-child {
|
||||||
|
--text-field-border-top-left-radius: var(--mdc-shape-medium);
|
||||||
|
}
|
||||||
|
ha-textfield:last-child {
|
||||||
|
--text-field-border-top-right-radius: var(--mdc-shape-medium);
|
||||||
|
}
|
||||||
|
ha-select {
|
||||||
|
--mdc-shape-small: 0;
|
||||||
|
width: 85px;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-family: var(
|
||||||
|
--mdc-typography-body2-font-family,
|
||||||
|
var(--mdc-typography-font-family, Roboto, sans-serif)
|
||||||
|
);
|
||||||
|
font-size: var(--mdc-typography-body2-font-size, 0.875rem);
|
||||||
|
line-height: var(--mdc-typography-body2-line-height, 1.25rem);
|
||||||
|
font-weight: var(--mdc-typography-body2-font-weight, 400);
|
||||||
|
letter-spacing: var(
|
||||||
|
--mdc-typography-body2-letter-spacing,
|
||||||
|
0.0178571429em
|
||||||
|
);
|
||||||
|
text-decoration: var(--mdc-typography-body2-text-decoration, inherit);
|
||||||
|
text-transform: var(--mdc-typography-body2-text-transform, inherit);
|
||||||
|
color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-base-time-input": HaBaseTimeInput;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,10 @@
|
|||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "./ha-select";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { stringCompare } from "../common/string/compare";
|
import { stringCompare } from "../common/string/compare";
|
||||||
import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
|
import { Blueprint, Blueprints, fetchBlueprints } from "../data/blueprint";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
@@ -24,7 +24,11 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
this.shadowRoot!.querySelector("paper-dropdown-menu-light")!.open();
|
const select = this.shadowRoot?.querySelector("ha-select");
|
||||||
|
if (select) {
|
||||||
|
// @ts-expect-error
|
||||||
|
select.menuOpen = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _processedBlueprints = memoizeOne((blueprints?: Blueprints) => {
|
private _processedBlueprints = memoizeOne((blueprints?: Blueprints) => {
|
||||||
@@ -45,32 +49,29 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<paper-dropdown-menu-light
|
<ha-select
|
||||||
.label=${this.label ||
|
.label=${this.label ||
|
||||||
this.hass.localize("ui.components.blueprint-picker.label")}
|
this.hass.localize("ui.components.blueprint-picker.label")}
|
||||||
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
|
.value=${this.value}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
horizontal-align="left"
|
@selected=${this._blueprintChanged}
|
||||||
|
@closed=${stopPropagation}
|
||||||
>
|
>
|
||||||
<paper-listbox
|
<mwc-list-item value="">
|
||||||
slot="dropdown-content"
|
${this.hass.localize(
|
||||||
.selected=${this.value}
|
"ui.components.blueprint-picker.select_blueprint"
|
||||||
attr-for-selected="data-blueprint-path"
|
|
||||||
@iron-select=${this._blueprintChanged}
|
|
||||||
>
|
|
||||||
<paper-item data-blueprint-path="">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.components.blueprint-picker.select_blueprint"
|
|
||||||
)}
|
|
||||||
</paper-item>
|
|
||||||
${this._processedBlueprints(this.blueprints).map(
|
|
||||||
(blueprint) => html`
|
|
||||||
<paper-item data-blueprint-path=${blueprint.path}>
|
|
||||||
${blueprint.name}
|
|
||||||
</paper-item>
|
|
||||||
`
|
|
||||||
)}
|
)}
|
||||||
</paper-listbox>
|
</mwc-list-item>
|
||||||
</paper-dropdown-menu-light>
|
${this._processedBlueprints(this.blueprints).map(
|
||||||
|
(blueprint) => html`
|
||||||
|
<mwc-list-item .value=${blueprint.path}>
|
||||||
|
${blueprint.name}
|
||||||
|
</mwc-list-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ha-select>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,10 +85,10 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _blueprintChanged(ev) {
|
private _blueprintChanged(ev) {
|
||||||
const newValue = ev.detail.item.dataset.blueprintPath;
|
const newValue = ev.target.value;
|
||||||
|
|
||||||
if (newValue !== this.value) {
|
if (newValue !== this.value) {
|
||||||
this.value = ev.detail.value;
|
this.value = newValue;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fireEvent(this, "value-changed", { value: newValue });
|
fireEvent(this, "value-changed", { value: newValue });
|
||||||
fireEvent(this, "change");
|
fireEvent(this, "change");
|
||||||
@@ -100,15 +101,11 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
paper-dropdown-menu-light {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
min-width: 200px;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user