mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-12 02:39:26 +00:00
Compare commits
420 Commits
entity-fil
...
20220317.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
03677c33f7 | ||
![]() |
bc36a206da | ||
![]() |
af06ab1e2d | ||
![]() |
3e2135a485 | ||
![]() |
2e7f8fb46f | ||
![]() |
102568c4bd | ||
![]() |
4fcdae842e | ||
![]() |
ea19740f5a | ||
![]() |
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 | ||
![]() |
db7cac5782 | ||
![]() |
099fa706a0 | ||
![]() |
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 | ||
![]() |
51938fb51f | ||
![]() |
890ad9a1c8 | ||
![]() |
8466ef371a | ||
![]() |
4e55460799 | ||
![]() |
5fde6e659d | ||
![]() |
148bb99d89 | ||
![]() |
0540bae707 | ||
![]() |
0c6f647f53 | ||
![]() |
3aca67d511 | ||
![]() |
0e41a408e7 | ||
![]() |
19e1eaf2d7 | ||
![]() |
5e80a2b465 | ||
![]() |
866a57cde4 | ||
![]() |
c85236e251 | ||
![]() |
a88da0e39a | ||
![]() |
21a8fac477 | ||
![]() |
ca5ce04a38 | ||
![]() |
7c4b9a0410 | ||
![]() |
de6f06ea6d | ||
![]() |
bbc8e323e8 | ||
![]() |
89b6863ae3 | ||
![]() |
3f1850e9eb | ||
![]() |
54d6b5b6f3 | ||
![]() |
fb55ab197f | ||
![]() |
cc2db9a761 | ||
![]() |
58ba3e5c22 | ||
![]() |
182ffccd0c | ||
![]() |
ce99d14ee0 | ||
![]() |
8ce160b9ce | ||
![]() |
fe33714c8b | ||
![]() |
afbe85625c | ||
![]() |
cb47ee7721 | ||
![]() |
5caa256f1b | ||
![]() |
c66dfb84f9 | ||
![]() |
df1d703e4e | ||
![]() |
ce0ced0b6a | ||
![]() |
730e9b144d | ||
![]() |
69ff8dd0c4 | ||
![]() |
8d2c716fbe | ||
![]() |
389a100b46 | ||
![]() |
9fee7a2829 | ||
![]() |
a91897821a | ||
![]() |
815a2a07ff | ||
![]() |
b8d3eb76ac | ||
![]() |
ba75c2e7af | ||
![]() |
f04b844223 | ||
![]() |
242bad0a29 | ||
![]() |
8b20b2b63c | ||
![]() |
e0c8efc5e6 | ||
![]() |
f59c30ac04 | ||
![]() |
e4b9c08b45 | ||
![]() |
04e63eefe2 | ||
![]() |
a064ca0856 | ||
![]() |
6044ea92ad | ||
![]() |
17e8215420 | ||
![]() |
a4ae1bee79 | ||
![]() |
7d335d7d85 | ||
![]() |
7c194d8910 | ||
![]() |
a92100bb0a | ||
![]() |
303af611d1 | ||
![]() |
559b6e9d5b | ||
![]() |
75a95ff675 | ||
![]() |
3024ee43f9 | ||
![]() |
b34b92fa87 | ||
![]() |
1832ed0a48 | ||
![]() |
f398692e75 | ||
![]() |
68bee4dd58 | ||
![]() |
f1297e1f36 | ||
![]() |
953e3e060b | ||
![]() |
c37f660718 | ||
![]() |
02754369a6 | ||
![]() |
0df9e9932f | ||
![]() |
eddb392ad0 | ||
![]() |
e8ba349447 | ||
![]() |
5be22d46ab | ||
![]() |
ffaff30b46 | ||
![]() |
069f08b55e | ||
![]() |
204ccf8b40 | ||
![]() |
0ab8f8fd7c | ||
![]() |
9b0b2c5b71 | ||
![]() |
0800c702fb | ||
![]() |
b7bd7c1065 | ||
![]() |
61bae5da64 | ||
![]() |
bdd13db8cf | ||
![]() |
cdc3d11181 | ||
![]() |
8f729e2a95 | ||
![]() |
bc9195f7d5 | ||
![]() |
7f1a321075 | ||
![]() |
72b9f8636d | ||
![]() |
c9cd316c0c | ||
![]() |
6cf3580fb4 | ||
![]() |
5d91aefb55 | ||
![]() |
e3c0530941 | ||
![]() |
2c9223ed80 |
@@ -16,6 +16,9 @@
|
||||
"runem.lit-plugin",
|
||||
"ms-python.vscode-pylance"
|
||||
],
|
||||
"containerEnv": {
|
||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||
},
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"files.eol": "\n",
|
||||
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||
- name: Build and release package
|
||||
run: |
|
||||
python3 -m pip install twine
|
||||
python3 -m pip install twine build
|
||||
export TWINE_USERNAME="__token__"
|
||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
||||
|
||||
|
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
|
||||
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
|
||||
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
||||
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
||||
@@ -1,14 +1,15 @@
|
||||
-let _ET, ET;
|
||||
+let _ET;
|
||||
+let ET;
|
||||
diff --git a/polyfillLoaders/EventTarget.js b/polyfillLoaders/EventTarget.js
|
||||
index 4e18ade7ba485849f17f28c94c42f0e0e01ac387..8f34f4f646c7f7becc208fb5a546c96034fc74dc 100644
|
||||
--- a/polyfillLoaders/EventTarget.js
|
||||
+++ b/polyfillLoaders/EventTarget.js
|
||||
@@ -6,16 +6,15 @@
|
||||
let _ET;
|
||||
let ET;
|
||||
export default async function EventTarget() {
|
||||
- return ET || init();
|
||||
+ return ET || init();
|
||||
@@ -26,4 +25,5 @@ index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc
|
||||
+ _ET = (await import("event-target-shim")).default.EventTarget;
|
||||
+ }
|
||||
+ 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
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.0.2.cjs
|
||||
yarnPath: .yarn/releases/yarn-3.2.0.cjs
|
||||
|
@@ -1,5 +1,4 @@
|
||||
include README.md
|
||||
include LICENSE.md
|
||||
graft hass_frontend
|
||||
graft hass_frontend_es5
|
||||
recursive-exclude * *.py[co]
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
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/)
|
||||
- [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
|
||||
module.exports.emptyPackages = ({ latestBuild }) =>
|
||||
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
||||
[
|
||||
// Contains all color definitions for all material color sets.
|
||||
// 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.
|
||||
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);
|
||||
|
||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
||||
@@ -196,6 +205,7 @@ module.exports.config = {
|
||||
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isHassioBuild: true,
|
||||
defineOverlay: {
|
||||
__SUPERVISOR__: true,
|
||||
},
|
||||
|
@@ -26,11 +26,11 @@ module.exports = {
|
||||
},
|
||||
version() {
|
||||
const version = fs
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
|
||||
.match(/\d{8}\.\d+/);
|
||||
.readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8")
|
||||
.match(/version\W+=\W(\d{8}\.\d)/);
|
||||
if (!version) {
|
||||
throw Error("Version not found");
|
||||
}
|
||||
return version[0];
|
||||
return version[1];
|
||||
},
|
||||
};
|
||||
|
@@ -3,7 +3,7 @@
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const marked = require("marked");
|
||||
const { marked } = require("marked");
|
||||
const glob = require("glob");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
|
@@ -7,7 +7,7 @@ const source = require("vinyl-source-stream");
|
||||
const vinylBuffer = require("vinyl-buffer");
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const foreach = require("gulp-foreach");
|
||||
const flatmap = require("gulp-flatmap");
|
||||
const merge = require("gulp-merge-json");
|
||||
const rename = require("gulp-rename");
|
||||
const transform = require("gulp-json-transform");
|
||||
@@ -183,7 +183,7 @@ gulp.task("build-merged-translations", () =>
|
||||
})
|
||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||
.pipe(
|
||||
foreach((stream, file) => {
|
||||
flatmap((stream, file) => {
|
||||
// For each language generate a merged json file. It begins with the master
|
||||
// translation as a failsafe for untranslated strings, and merges all parent
|
||||
// tags into one file for each specific subtag
|
||||
|
@@ -30,6 +30,7 @@ const createWebpackConfig = ({
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
isStatsBuild,
|
||||
isHassioBuild,
|
||||
dontHash,
|
||||
}) => {
|
||||
if (!dontHash) {
|
||||
@@ -117,7 +118,9 @@ const createWebpackConfig = ({
|
||||
},
|
||||
}),
|
||||
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")
|
||||
),
|
||||
!isProdBuild && new LogStartCompilePlugin(),
|
||||
|
@@ -1,5 +1,5 @@
|
||||
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 { LovelaceConfig } from "../../../../src/data/lovelace";
|
||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
||||
@@ -7,6 +7,9 @@ import "../../../../src/panels/lovelace/views/hui-view";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "./hc-launch-screen";
|
||||
|
||||
(window as any).loadCardHelpers = () =>
|
||||
import("../../../../src/panels/lovelace/custom-card-helpers");
|
||||
|
||||
@customElement("hc-lovelace")
|
||||
class HcLovelace extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -17,6 +20,8 @@ class HcLovelace extends LitElement {
|
||||
|
||||
@property() public urlPath: string | null = null;
|
||||
|
||||
@query("hui-view") private _huiView?: HTMLElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const index = this._viewIndex;
|
||||
if (index === undefined) {
|
||||
@@ -75,12 +80,12 @@ class HcLovelace extends LitElement {
|
||||
this.lovelaceConfig.background;
|
||||
|
||||
if (configBackground) {
|
||||
(this.shadowRoot!.querySelector(
|
||||
"hui-view"
|
||||
) as HTMLElement)!.style.setProperty(
|
||||
this._huiView!.style.setProperty(
|
||||
"--lovelace-background",
|
||||
configBackground
|
||||
);
|
||||
} else {
|
||||
this._huiView!.style.removeProperty("--lovelace-background");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,6 +118,9 @@ class HcLovelace extends LitElement {
|
||||
:host > * {
|
||||
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/roboto";
|
||||
import "./layout/hc-lovelace";
|
||||
|
@@ -2,8 +2,3 @@ import "../../src/resources/ha-style";
|
||||
import "../../src/resources/roboto";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./ha-demo";
|
||||
|
||||
/* polyfill for paper-dropdown */
|
||||
setTimeout(() => {
|
||||
import("web-animations-js/web-animations-next-lite.min");
|
||||
}, 1000);
|
||||
|
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 |
@@ -20,7 +20,6 @@ module.exports = [
|
||||
"editor-trigger",
|
||||
"editor-condition",
|
||||
"editor-action",
|
||||
"selectors",
|
||||
"trace",
|
||||
"trace-timeline",
|
||||
],
|
||||
@@ -37,6 +36,10 @@ module.exports = [
|
||||
category: "misc",
|
||||
header: "Miscelaneous",
|
||||
},
|
||||
{
|
||||
category: "brand",
|
||||
header: "Brand",
|
||||
},
|
||||
{
|
||||
category: "user-test",
|
||||
header: "User Tests",
|
||||
|
@@ -3,6 +3,7 @@ import { html, LitElement, css, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/ha-card";
|
||||
|
||||
@customElement("demo-black-white-row")
|
||||
class DemoBlackWhiteRow extends LitElement {
|
||||
|
@@ -78,6 +78,9 @@ class DemoCards extends LitElement {
|
||||
ha-formfield {
|
||||
margin-right: 16px;
|
||||
}
|
||||
#container {
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -12,7 +12,14 @@ class PageDescription extends HaMarkdown {
|
||||
if (!PAGES[this.page].description) {
|
||||
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(
|
||||
PAGES[this.page]
|
||||
.description()
|
||||
@@ -25,9 +32,22 @@ class PageDescription extends HaMarkdown {
|
||||
static styles = [
|
||||
HaMarkdown.styles,
|
||||
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 {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
margin: 16px auto;
|
||||
}
|
||||
.root > *:first-child {
|
||||
margin-top: 0;
|
||||
|
@@ -5,6 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../src/components/ha-icon-button";
|
||||
import "../../src/managers/notification-manager";
|
||||
import "../../src/components/ha-expansion-panel";
|
||||
import { haStyle } from "../../src/resources/styles";
|
||||
import { PAGES, SIDEBAR } from "../build/import-pages";
|
||||
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
|
||||
@@ -53,10 +54,9 @@ class HaGallery extends LitElement {
|
||||
sidebar.push(
|
||||
group.header
|
||||
? html`
|
||||
<details>
|
||||
<summary class="section">${group.header}</summary>
|
||||
<ha-expansion-panel .header=${group.header}>
|
||||
${links}
|
||||
</details>
|
||||
</ha-expansion-panel>
|
||||
`
|
||||
: links
|
||||
);
|
||||
@@ -92,27 +92,34 @@ class HaGallery extends LitElement {
|
||||
${dynamicElement(`demo-${this._page.replace("/", "-")}`)}
|
||||
</div>
|
||||
<div class="page-footer">
|
||||
${PAGES[this._page].description ||
|
||||
Object.keys(PAGES[this._page].metadata).length > 0
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit text
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${PAGES[this._page].demo
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit demo
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
<div class="header">Help us to improve our documentation</div>
|
||||
<div class="secondary">
|
||||
Suggest an edit to this page, or provide/view feedback for this
|
||||
page.
|
||||
</div>
|
||||
<div>
|
||||
${PAGES[this._page].description ||
|
||||
Object.keys(PAGES[this._page].metadata).length > 0
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.markdown`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit text
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${PAGES[this._page].demo
|
||||
? html`
|
||||
<a
|
||||
href=${`${GITHUB_DEMO_URL}${this._page}.ts`}
|
||||
target="_blank"
|
||||
>
|
||||
Edit demo
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mwc-drawer>
|
||||
@@ -186,26 +193,16 @@ class HaGallery extends LitElement {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.sidebar details {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.sidebar summary {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
padding: 4px 12px;
|
||||
padding: 12px;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar a[active]::before {
|
||||
border-radius: 4px;
|
||||
border-radius: 12px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 2px;
|
||||
@@ -236,14 +233,32 @@ class HaGallery extends LitElement {
|
||||
|
||||
.page-footer {
|
||||
text-align: center;
|
||||
margin: 16px 0;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
margin: 16px;
|
||||
padding: 16px;
|
||||
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 {
|
||||
display: inline-block;
|
||||
margin: 0 8px;
|
||||
text-decoration: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -3,10 +3,20 @@ import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import { describeAction } from "../../../../src/data/script_i18n";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
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" },
|
||||
{ delay: "0:05" },
|
||||
{ wait_template: "{{ true }}" },
|
||||
@@ -19,8 +29,20 @@ const actions = [
|
||||
device_id: "abcdefgh",
|
||||
domain: "plex",
|
||||
entity_id: "media_player.kitchen",
|
||||
type: "turn_on",
|
||||
},
|
||||
{ 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: [
|
||||
{
|
||||
@@ -52,7 +74,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
}
|
||||
return html`
|
||||
<ha-card header="Actions">
|
||||
${actions.map(
|
||||
${ACTIONS.map(
|
||||
(conf) => html`
|
||||
<div class="action">
|
||||
<span>${describeAction(this.hass, conf as any)}</span>
|
||||
@@ -68,6 +90,7 @@ export class DemoAutomationDescribeAction extends LitElement {
|
||||
super.firstUpdated(changedProps);
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
|
||||
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 { 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 { 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 { 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";
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
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
|
||||
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>`
|
||||
|
@@ -1,11 +1,109 @@
|
||||
/* eslint-disable lit/no-template-arrow */
|
||||
import "@material/mwc-button";
|
||||
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 type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
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: {
|
||||
title: string;
|
||||
@@ -14,6 +112,74 @@ const SCHEMAS: {
|
||||
schema: HaFormSchema[];
|
||||
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",
|
||||
translations: {
|
||||
@@ -50,13 +216,11 @@ const SCHEMAS: {
|
||||
{
|
||||
type: "boolean",
|
||||
name: "bool",
|
||||
optional: true,
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
type: "integer",
|
||||
name: "int",
|
||||
optional: true,
|
||||
default: 10,
|
||||
},
|
||||
{
|
||||
@@ -67,7 +231,6 @@ const SCHEMAS: {
|
||||
{
|
||||
type: "string",
|
||||
name: "string",
|
||||
optional: true,
|
||||
default: "Default",
|
||||
},
|
||||
{
|
||||
@@ -77,7 +240,6 @@ const SCHEMAS: {
|
||||
["other", "other"],
|
||||
],
|
||||
name: "select",
|
||||
optional: true,
|
||||
default: "default",
|
||||
},
|
||||
{
|
||||
@@ -87,7 +249,6 @@ const SCHEMAS: {
|
||||
other: "Other",
|
||||
},
|
||||
name: "multi",
|
||||
optional: true,
|
||||
default: ["default"],
|
||||
},
|
||||
{
|
||||
@@ -108,7 +269,6 @@ const SCHEMAS: {
|
||||
{
|
||||
type: "integer",
|
||||
name: "int with default",
|
||||
optional: true,
|
||||
default: 10,
|
||||
},
|
||||
{
|
||||
@@ -122,7 +282,6 @@ const SCHEMAS: {
|
||||
{
|
||||
type: "integer",
|
||||
name: "int range optional",
|
||||
optional: true,
|
||||
valueMin: 0,
|
||||
valueMax: 10,
|
||||
},
|
||||
@@ -148,7 +307,6 @@ const SCHEMAS: {
|
||||
["other", "Other"],
|
||||
],
|
||||
name: "select optional",
|
||||
optional: true,
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
@@ -161,7 +319,6 @@ const SCHEMAS: {
|
||||
["option", "1000"],
|
||||
],
|
||||
name: "select many otions",
|
||||
optional: true,
|
||||
default: "default",
|
||||
},
|
||||
],
|
||||
@@ -190,7 +347,6 @@ const SCHEMAS: {
|
||||
option: "1000",
|
||||
},
|
||||
name: "multi many otions",
|
||||
optional: true,
|
||||
default: ["default"],
|
||||
},
|
||||
],
|
||||
@@ -239,23 +395,36 @@ const SCHEMAS: {
|
||||
valueMin: 1,
|
||||
valueMax: 65535,
|
||||
name: "port",
|
||||
optional: true,
|
||||
default: 80,
|
||||
},
|
||||
{ type: "string", name: "path", optional: true, default: "/" },
|
||||
{ type: "boolean", name: "ssl", optional: true, default: false },
|
||||
{ type: "string", name: "path", default: "/" },
|
||||
{ type: "boolean", name: "ssl", default: false },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-components-ha-form")
|
||||
class DemoHaForm extends LitElement {
|
||||
@state() private hass!: HomeAssistant;
|
||||
|
||||
private data = SCHEMAS.map(
|
||||
({ schema, data }) => data || computeInitialHaFormData(schema)
|
||||
);
|
||||
|
||||
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 {
|
||||
return html`
|
||||
${SCHEMAS.map((info, idx) => {
|
||||
@@ -278,6 +447,7 @@ class DemoHaForm extends LitElement {
|
||||
(slot) => html`
|
||||
<ha-form
|
||||
slot=${slot}
|
||||
.hass=${this.hass}
|
||||
.data=${this.data[idx]}
|
||||
.schema=${info.schema}
|
||||
.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 { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||
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: {
|
||||
name: string;
|
||||
@@ -21,7 +115,12 @@ const SCHEMAS: {
|
||||
name: "One of each",
|
||||
input: {
|
||||
entity: { name: "Entity", selector: { entity: {} } },
|
||||
attribute: {
|
||||
name: "Attribute",
|
||||
selector: { attribute: { entity_id: "" } },
|
||||
},
|
||||
device: { name: "Device", selector: { device: {} } },
|
||||
duration: { name: "Duration", selector: { duration: {} } },
|
||||
addon: { name: "Addon", selector: { addon: {} } },
|
||||
area: { name: "Area", selector: { area: {} } },
|
||||
target: { name: "Target", selector: { target: {} } },
|
||||
@@ -47,24 +146,69 @@ const SCHEMAS: {
|
||||
},
|
||||
boolean: { name: "Boolean", selector: { boolean: {} } },
|
||||
time: { name: "Time", selector: { time: {} } },
|
||||
date: { name: "Date", selector: { date: {} } },
|
||||
datetime: { name: "Date Time", selector: { datetime: {} } },
|
||||
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: {
|
||||
name: "Text multiline",
|
||||
selector: { text: { multiline: true } },
|
||||
selector: {
|
||||
text: { multiline: true },
|
||||
},
|
||||
},
|
||||
object: { name: "Object", selector: { object: {} } },
|
||||
select: {
|
||||
name: "Select",
|
||||
select_radio: {
|
||||
name: "Select (Radio)",
|
||||
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")
|
||||
class DemoHaSelector extends LitElement {
|
||||
@state() private hass!: HomeAssistant;
|
||||
class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
@state() public hass!: HomeAssistant;
|
||||
|
||||
private data = SCHEMAS.map(() => ({}));
|
||||
|
||||
@@ -73,12 +217,130 @@ class DemoHaSelector extends LitElement {
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
hass.addEntities(ENTITIES);
|
||||
mockEntityRegistry(hass);
|
||||
mockDeviceRegistry(hass);
|
||||
mockAreaRegistry(hass);
|
||||
mockDeviceRegistry(hass, DEVICES);
|
||||
mockAreaRegistry(hass, AREAS);
|
||||
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 {
|
||||
return html`
|
||||
${SCHEMAS.map((info, idx) => {
|
||||
@@ -117,7 +379,6 @@ class DemoHaSelector extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
paper-input,
|
||||
ha-selector {
|
||||
width: 60;
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ We want to make it as easy for designers to contribute as it is for developers.
|
||||
|
||||
- Meet us at <a href="https://discord.gg/BPBc8rZ9" rel="noopener noreferrer" target="_blank">devs_ux Discord</a>. Feel free to share your designs, user test or strategic ideas.
|
||||
- Start designing with our <a href="https://www.figma.com/community/file/967153512097289521/Home-Assistant-DesignKit" rel="noopener noreferrer" target="_blank">Figma DesignKit</a>.
|
||||
- Find the lates UX <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!
|
||||
- Find the lates UX <a href="https://github.com/home-assistant/frontend/discussions?discussions_q=label%3Aux" rel="noopener noreferrer" target="_blank">discussions</a> and <a href="https://github.com/home-assistant/frontend/labels/ux" rel="noopener noreferrer" target="_blank">issues</a> on GitHub. Everyone can start a new issue or discussion!
|
||||
|
||||
|
||||
## Developers
|
||||
|
@@ -2,6 +2,8 @@
|
||||
title: Editing 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.
|
||||
@@ -41,15 +43,12 @@ import { html, css, LitElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
|
||||
|
||||
@customElement("demo-user-experience-usability")
|
||||
export class DemoUserExperienceUsability extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
Hello world!
|
||||
</div>
|
||||
<div class="card-content">Hello world!</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ const createConfigEntry = (
|
||||
source: "zeroconf",
|
||||
state: "loaded",
|
||||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: true,
|
||||
disabled_by: null,
|
||||
pref_disable_new_entities: false,
|
||||
@@ -187,6 +188,7 @@ const createEntityRegistryEntries = (
|
||||
device_id: "mock-device-id",
|
||||
area_id: null,
|
||||
disabled_by: null,
|
||||
hidden_by: null,
|
||||
entity_category: null,
|
||||
entity_id: "binary_sensor.updater",
|
||||
name: null,
|
||||
|
@@ -42,7 +42,9 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
const repo = this.repo;
|
||||
let _addons = this.addons;
|
||||
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);
|
||||
|
||||
|
@@ -14,7 +14,7 @@ import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
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 "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-icon-button";
|
||||
@@ -110,8 +110,6 @@ class HassioAddonStore extends LitElement {
|
||||
<div class="search">
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
no-label-float
|
||||
no-underline
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._filterChanged}
|
||||
></search-input>
|
||||
@@ -221,13 +219,14 @@ class HassioAddonStore extends LitElement {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.search {
|
||||
padding: 0 16px;
|
||||
background: var(--sidebar-background-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.search search-input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
search-input {
|
||||
display: block;
|
||||
--mdc-text-field-fill-color: var(--sidebar-background-color);
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
}
|
||||
.advanced {
|
||||
padding: 12px;
|
||||
|
@@ -1,7 +1,5 @@
|
||||
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 "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -11,10 +9,11 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
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/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-select";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
@@ -57,49 +56,44 @@ class HassioAddonAudio extends LitElement {
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<paper-dropdown-menu
|
||||
${this._inputDevices &&
|
||||
html`<ha-select
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.audio.input"
|
||||
)}
|
||||
@iron-select=${this._setInputDevice}
|
||||
@selected=${this._setInputDevice}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.value=${this._selectedInput!}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
.selected=${this._selectedInput!}
|
||||
>
|
||||
${this._inputDevices &&
|
||||
this._inputDevices.map(
|
||||
(item) => html`
|
||||
<paper-item device=${item.device || ""}>
|
||||
${item.name}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-dropdown-menu
|
||||
${this._inputDevices.map(
|
||||
(item) => html`
|
||||
<mwc-list-item .value=${item.device || ""}>
|
||||
${item.name}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>`}
|
||||
${this._outputDevices &&
|
||||
html`<ha-select
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.audio.output"
|
||||
)}
|
||||
@iron-select=${this._setOutputDevice}
|
||||
@selected=${this._setOutputDevice}
|
||||
@closed=${stopPropagation}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
.value=${this._selectedOutput!}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
.selected=${this._selectedOutput!}
|
||||
>
|
||||
${this._outputDevices &&
|
||||
this._outputDevices.map(
|
||||
(item) => html`
|
||||
<paper-item device=${item.device || ""}
|
||||
>${item.name}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
${this._outputDevices.map(
|
||||
(item) => html`
|
||||
<mwc-list-item .value=${item.device || ""}
|
||||
>${item.name}</mwc-list-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</ha-select>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button @click=${this._saveSettings}>
|
||||
@@ -116,8 +110,7 @@ class HassioAddonAudio extends LitElement {
|
||||
hassioStyle,
|
||||
css`
|
||||
:host,
|
||||
ha-card,
|
||||
paper-dropdown-menu {
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
paper-item {
|
||||
@@ -126,24 +119,30 @@ class HassioAddonAudio extends LitElement {
|
||||
.card-actions {
|
||||
text-align: right;
|
||||
}
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
ha-select:last-child {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues): void {
|
||||
super.update(changedProperties);
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (changedProperties.has("addon")) {
|
||||
this._addonChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private _setInputDevice(ev): void {
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
const device = ev.target.value;
|
||||
this._selectedInput = device;
|
||||
}
|
||||
|
||||
private _setOutputDevice(ev): void {
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
const device = ev.target.value;
|
||||
this._selectedOutput = device;
|
||||
}
|
||||
|
||||
|
@@ -114,7 +114,7 @@ class HassioAddonConfig extends LitElement {
|
||||
<div class="card-menu">
|
||||
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.menu")}
|
||||
.label=${this.supervisor.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
mdiFlask,
|
||||
mdiHomeAssistant,
|
||||
mdiKey,
|
||||
mdiLinkLock,
|
||||
mdiNetwork,
|
||||
mdiNumeric1,
|
||||
mdiNumeric2,
|
||||
@@ -16,6 +17,8 @@ import {
|
||||
mdiNumeric4,
|
||||
mdiNumeric5,
|
||||
mdiNumeric6,
|
||||
mdiNumeric7,
|
||||
mdiNumeric8,
|
||||
mdiPound,
|
||||
mdiShield,
|
||||
} from "@mdi/js";
|
||||
@@ -31,6 +34,7 @@ import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-chip";
|
||||
import "../../../../src/components/ha-chip-set";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
@@ -84,6 +88,8 @@ const RATING_ICON = {
|
||||
4: mdiNumeric4,
|
||||
5: mdiNumeric5,
|
||||
6: mdiNumeric6,
|
||||
7: mdiNumeric7,
|
||||
8: mdiNumeric8,
|
||||
};
|
||||
|
||||
@customElement("hassio-addon-info")
|
||||
@@ -209,7 +215,7 @@ class HassioAddonInfo extends LitElement {
|
||||
>`}
|
||||
</div>
|
||||
|
||||
<div class="capabilities">
|
||||
<ha-chip-set class="capabilities">
|
||||
${this.addon.stage !== "stable"
|
||||
? html` <ha-chip
|
||||
hasIcon
|
||||
@@ -234,9 +240,9 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-chip
|
||||
hasIcon
|
||||
class=${classMap({
|
||||
green: [5, 6].includes(Number(this.addon.rating)),
|
||||
yellow: [3, 4].includes(Number(this.addon.rating)),
|
||||
red: [1, 2].includes(Number(this.addon.rating)),
|
||||
green: Number(this.addon.rating) >= 6,
|
||||
yellow: [3, 4, 5].includes(Number(this.addon.rating)),
|
||||
red: Number(this.addon.rating) >= 2,
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="rating"
|
||||
@@ -364,7 +370,17 @@ class HassioAddonInfo extends LitElement {
|
||||
</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">
|
||||
${this.addon.description}.<br />
|
||||
|
@@ -191,7 +191,7 @@ export class HassioBackups extends LitElement {
|
||||
@action=${this._handleAction}
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.menu")}
|
||||
.label=${this.supervisor?.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import { mdiFolderUpload } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input-container";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
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 { formatDate } from "../../../src/common/datetime/format_date";
|
||||
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
||||
@@ -92,6 +92,8 @@ export class SupervisorBackupContent extends LitElement {
|
||||
|
||||
@property() public confirmBackupPassword = "";
|
||||
|
||||
@query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget;
|
||||
|
||||
public willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
@@ -109,6 +111,10 @@ export class SupervisorBackupContent extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
public override focus() {
|
||||
this._focusTarget?.focus();
|
||||
}
|
||||
|
||||
private _localize = (string: string) =>
|
||||
this.supervisor?.localize(`backup.${string}`) ||
|
||||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
||||
@@ -169,24 +175,23 @@ export class SupervisorBackupContent extends LitElement {
|
||||
: ""}
|
||||
${this.backupType === "partial"
|
||||
? html`<div class="partial-picker">
|
||||
${this.backup && this.backup.homeassistant
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
label="Home Assistant"
|
||||
.iconPath=${mdiHomeAssistant}
|
||||
.version=${this.backup.homeassistant}
|
||||
>
|
||||
</supervisor-formfield-label>`}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.homeAssistant}
|
||||
@click=${this.toggleHomeAssistant}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
`
|
||||
: ""}
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
label="Home Assistant"
|
||||
.iconPath=${mdiHomeAssistant}
|
||||
.version=${this.backup
|
||||
? this.backup.homeassistant
|
||||
: this.hass.config.version}
|
||||
>
|
||||
</supervisor-formfield-label>`}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.homeAssistant}
|
||||
@click=${this.toggleHomeAssistant}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
|
||||
${foldersSection?.templates.length
|
||||
? html`
|
||||
<ha-formfield
|
||||
|
@@ -148,7 +148,6 @@ export class HassioUpdate extends LitElement {
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0;
|
||||
--paper-item-body-two-line-min-height: 32px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -17,27 +17,27 @@ export class DialogHassioBackupUpload
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _params?: HassioBackupUploadDialogParams;
|
||||
@state() private _dialogParams?: HassioBackupUploadDialogParams;
|
||||
|
||||
public async showDialog(
|
||||
params: HassioBackupUploadDialogParams
|
||||
dialogParams: HassioBackupUploadDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._dialogParams = dialogParams;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
if (this._params && !this._params.onboarding) {
|
||||
if (this._params.reloadBackup) {
|
||||
this._params.reloadBackup();
|
||||
if (this._dialogParams && !this._dialogParams.onboarding) {
|
||||
if (this._dialogParams.reloadBackup) {
|
||||
this._dialogParams.reloadBackup();
|
||||
}
|
||||
}
|
||||
this._params = undefined;
|
||||
this._dialogParams = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
if (!this._dialogParams) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -47,17 +47,24 @@ export class DialogHassioBackupUpload
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
.heading=${true}
|
||||
.heading=${this.hass?.localize(
|
||||
"ui.panel.page-onboarding.restore.upload_backup"
|
||||
) || "Upload backup"}
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title"> Upload backup </span>
|
||||
<span slot="title"
|
||||
>${this.hass?.localize(
|
||||
"ui.panel.page-onboarding.restore.upload_backup"
|
||||
) || "Upload backup"}</span
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass?.localize("common.close") || "close"}
|
||||
.label=${this.hass?.localize("ui.common.close") || "Close"}
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
dialogInitialFocus
|
||||
></ha-icon-button>
|
||||
</ha-header-bar>
|
||||
</div>
|
||||
@@ -71,7 +78,7 @@ export class DialogHassioBackupUpload
|
||||
|
||||
private _backupUploaded(ev) {
|
||||
const backup = ev.detail.backup;
|
||||
this._params?.showBackup(backup.slug);
|
||||
this._dialogParams?.showBackup(backup.slug);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
|
@@ -48,9 +48,9 @@ class HassioBackupDialog
|
||||
@query("supervisor-backup-content")
|
||||
private _backupContent!: SupervisorBackupContent;
|
||||
|
||||
public async showDialog(params: HassioBackupDialogParams) {
|
||||
this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
|
||||
this._dialogParams = params;
|
||||
public async showDialog(dialogParams: HassioBackupDialogParams) {
|
||||
this._backup = await fetchHassioBackupInfo(this.hass, dialogParams.slug);
|
||||
this._dialogParams = dialogParams;
|
||||
this._restoringBackup = false;
|
||||
}
|
||||
|
||||
@@ -71,13 +71,13 @@ class HassioBackupDialog
|
||||
open
|
||||
scrimClickAction
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${true}
|
||||
.heading=${this._backup.name}
|
||||
>
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title">${this._backup.name}</span>
|
||||
<ha-icon-button
|
||||
.label=${this.hass?.localize("common.close") || "close"}
|
||||
.label=${this.hass?.localize("ui.common.close") || "Close"}
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
@@ -92,6 +92,7 @@ class HassioBackupDialog
|
||||
.backup=${this._backup}
|
||||
.onboarding=${this._dialogParams.onboarding || false}
|
||||
.localize=${this._dialogParams.localize}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</supervisor-backup-content>`}
|
||||
${this._error
|
||||
@@ -114,12 +115,20 @@ class HassioBackupDialog
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass!.localize("common.menu")}
|
||||
.label=${this.hass!.localize("ui.common.menu") || "Menu"}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
<mwc-list-item>Download Backup</mwc-list-item>
|
||||
<mwc-list-item class="error">Delete Backup</mwc-list-item>
|
||||
<mwc-list-item
|
||||
>${this._dialogParams.supervisor?.localize(
|
||||
"backup.download_backup"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
<mwc-list-item class="error"
|
||||
>${this._dialogParams.supervisor?.localize(
|
||||
"backup.delete_backup_title"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
</ha-button-menu>`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
|
@@ -30,8 +30,8 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
@query("supervisor-backup-content")
|
||||
private _backupContent!: SupervisorBackupContent;
|
||||
|
||||
public showDialog(params: HassioCreateBackupDialogParams) {
|
||||
this._dialogParams = params;
|
||||
public showDialog(dialogParams: HassioCreateBackupDialogParams) {
|
||||
this._dialogParams = dialogParams;
|
||||
this._creatingBackup = false;
|
||||
}
|
||||
|
||||
@@ -57,10 +57,11 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
)}
|
||||
>
|
||||
${this._creatingBackup
|
||||
? html` <ha-circular-progress active></ha-circular-progress>`
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</supervisor-backup-content>`}
|
||||
${this._error
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-select";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
@@ -90,18 +89,20 @@ class HassioDatadiskDialog extends LitElement {
|
||||
)}
|
||||
<br /><br />
|
||||
|
||||
<paper-dropdown-menu
|
||||
<ha-select
|
||||
.label=${this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.select_device"
|
||||
)}
|
||||
@value-changed=${this._select_device}
|
||||
@selected=${this._select_device}
|
||||
dialogInitialFocus
|
||||
>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
${this.devices.map(
|
||||
(device) => html`<paper-item>${device}</paper-item>`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
${this.devices.map(
|
||||
(device) =>
|
||||
html`<mwc-list-item .value=${device}
|
||||
>${device}</mwc-list-item
|
||||
>`
|
||||
)}
|
||||
</ha-select>
|
||||
`
|
||||
: this.devices === undefined
|
||||
? this.dialogParams.supervisor.localize(
|
||||
@@ -111,7 +112,11 @@ class HassioDatadiskDialog extends LitElement {
|
||||
"dialog.datadisk_move.no_devices"
|
||||
)}
|
||||
|
||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
dialogInitialFocus
|
||||
>
|
||||
${this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.cancel"
|
||||
)}
|
||||
@@ -130,8 +135,8 @@ class HassioDatadiskDialog extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _select_device(event) {
|
||||
this.selectedDevice = event.detail.value;
|
||||
private _select_device(ev) {
|
||||
this.selectedDevice = ev.target.value;
|
||||
}
|
||||
|
||||
private async _moveDatadisk() {
|
||||
@@ -156,7 +161,7 @@ class HassioDatadiskDialog extends LitElement {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
paper-dropdown-menu {
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
ha-circular-progress {
|
||||
|
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
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 "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-expansion-panel";
|
||||
@@ -39,8 +39,8 @@ class HassioHardwareDialog extends LitElement {
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
public showDialog(params: HassioHardwareDialogParams) {
|
||||
this._dialogParams = params;
|
||||
public showDialog(dialogParams: HassioHardwareDialogParams) {
|
||||
this._dialogParams = dialogParams;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
@@ -65,21 +65,21 @@ class HassioHardwareDialog extends LitElement {
|
||||
scrimClickAction
|
||||
hideActions
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${true}
|
||||
.heading=${this._dialogParams.supervisor.localize(
|
||||
"dialog.hardware.title"
|
||||
)}
|
||||
>
|
||||
<div class="header" slot="heading">
|
||||
<h2>
|
||||
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
|
||||
</h2>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.close")}
|
||||
.label=${this._dialogParams.supervisor.localize("common.close")}
|
||||
.path=${mdiClose}
|
||||
dialogAction="close"
|
||||
></ha-icon-button>
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
autofocus
|
||||
no-label-float
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
.label=${this._dialogParams.supervisor.localize(
|
||||
@@ -176,7 +176,7 @@ class HassioHardwareDialog extends LitElement {
|
||||
padding: 0.2em 0.4em;
|
||||
}
|
||||
search-input {
|
||||
margin: 0 16px;
|
||||
margin: 8px 16px 0;
|
||||
display: block;
|
||||
}
|
||||
.device-property {
|
||||
|
@@ -37,7 +37,10 @@ class HassioMarkdownDialog extends LitElement {
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(this.hass, this.title)}
|
||||
>
|
||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
||||
<ha-markdown
|
||||
.content=${this.content || ""}
|
||||
dialogInitialFocus
|
||||
></ha-markdown>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
@@ -94,7 +94,7 @@ export class DialogHassioNetwork
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${true}
|
||||
.heading=${this.supervisor.localize("dialog.network.title")}
|
||||
hideActions
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
@@ -104,7 +104,7 @@ export class DialogHassioNetwork
|
||||
${this.supervisor.localize("dialog.network.title")}
|
||||
</span>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.close")}
|
||||
.label=${this.supervisor.localize("common.close")}
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
@@ -119,6 +119,7 @@ export class DialogHassioNetwork
|
||||
html`<mwc-tab
|
||||
.id=${device.interface}
|
||||
.label=${device.interface}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</mwc-tab>`
|
||||
)}
|
||||
@@ -315,6 +316,7 @@ export class DialogHassioNetwork
|
||||
value="auto"
|
||||
name="${version}method"
|
||||
.checked=${this._interface![version]?.method === "auto"}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
|
@@ -19,22 +19,21 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { RegistriesDialogParams } from "./show-dialog-registries";
|
||||
|
||||
const SCHEMA = [
|
||||
const SCHEMA: HaFormSchema[] = [
|
||||
{
|
||||
type: "string",
|
||||
name: "registry",
|
||||
required: true,
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "username",
|
||||
required: true,
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "password",
|
||||
required: true,
|
||||
format: "password",
|
||||
selector: { text: { type: "password" } },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -81,6 +80,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
.schema=${SCHEMA}
|
||||
@value-changed=${this._valueChanged}
|
||||
.computeLabel=${this._computeLabel}
|
||||
dialogInitialFocus
|
||||
></ha-form>
|
||||
<div class="action">
|
||||
<mwc-button
|
||||
@@ -125,7 +125,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
</ha-alert>
|
||||
`}
|
||||
<div class="action">
|
||||
<mwc-button @click=${this._addRegistry}>
|
||||
<mwc-button @click=${this._addRegistry} dialogInitialFocus>
|
||||
${this.supervisor.localize(
|
||||
"dialog.registries.add_new_registry"
|
||||
)}
|
||||
|
@@ -106,6 +106,9 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
</paper-item-body>
|
||||
<div class="delete">
|
||||
<ha-icon-button
|
||||
.label=${this._dialogParams!.supervisor.localize(
|
||||
"dialog.repositories.remove"
|
||||
)}
|
||||
.disabled=${usedRepositories.includes(repo.slug)}
|
||||
.slug=${repo.slug}
|
||||
.path=${usedRepositories.includes(repo.slug)
|
||||
@@ -139,6 +142,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
"dialog.repositories.add"
|
||||
)}
|
||||
@keydown=${this._handleKeyAdd}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._processing
|
||||
|
@@ -1,9 +1,12 @@
|
||||
// Compat needs to be first import
|
||||
import "../../src/resources/compatibility";
|
||||
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
|
||||
import "../../src/resources/roboto";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
import "./hassio-main";
|
||||
|
||||
setCancelSyntheticClickEvents(false);
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.innerHTML = `
|
||||
body {
|
||||
|
@@ -121,7 +121,8 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
this.parentElement,
|
||||
this.hass.themes,
|
||||
themeName,
|
||||
themeSettings
|
||||
themeSettings,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -205,16 +205,6 @@ class HassioCoreInfo extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
--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 {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@@ -186,7 +186,7 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.menu")}
|
||||
.label=${this.supervisor.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
@@ -440,16 +440,6 @@ class HassioHostInfo extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
--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 {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@@ -1,12 +1,10 @@
|
||||
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 { customElement, property, state } from "lit/decorators";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-select";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
@@ -73,24 +71,19 @@ class HassioSupervisorLog extends LitElement {
|
||||
: ""}
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-dropdown-menu
|
||||
<ha-select
|
||||
.label=${this.supervisor.localize("system.log.log_provider")}
|
||||
@iron-select=${this._setLogProvider}
|
||||
@selected=${this._setLogProvider}
|
||||
.value=${this._selectedLogProvider}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="provider"
|
||||
.selected=${this._selectedLogProvider}
|
||||
>
|
||||
${logProviders.map(
|
||||
(provider) => html`
|
||||
<paper-item provider=${provider.key}>
|
||||
${provider.name}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
${logProviders.map(
|
||||
(provider) => html`
|
||||
<mwc-list-item .value=${provider.key}>
|
||||
${provider.name}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
`
|
||||
: ""}
|
||||
|
||||
@@ -110,7 +103,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
}
|
||||
|
||||
private async _setLogProvider(ev): Promise<void> {
|
||||
const provider = ev.detail.item.getAttribute("provider");
|
||||
const provider = ev.target.value;
|
||||
this._selectedLogProvider = provider;
|
||||
this._loadData();
|
||||
}
|
||||
@@ -153,9 +146,9 @@ class HassioSupervisorLog extends LitElement {
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
paper-dropdown-menu {
|
||||
padding: 0 2%;
|
||||
width: 96%;
|
||||
ha-select {
|
||||
width: 100%;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -10,7 +10,6 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/common/search/search-input";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-alert";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
@@ -33,8 +32,12 @@ import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { updateOS } from "../../../src/data/hassio/host";
|
||||
import { updateSupervisor } from "../../../src/data/hassio/supervisor";
|
||||
import { fetchHassioHassOsInfo, updateOS } from "../../../src/data/hassio/host";
|
||||
import {
|
||||
fetchHassioHomeAssistantInfo,
|
||||
fetchHassioSupervisorInfo,
|
||||
updateSupervisor,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { updateCore } from "../../../src/data/supervisor/core";
|
||||
import { StoreAddon } from "../../../src/data/supervisor/store";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
@@ -188,13 +191,7 @@ class UpdateAvailableCard extends LitElement {
|
||||
</a>`
|
||||
: ""}
|
||||
<span></span>
|
||||
<ha-progress-button
|
||||
.disabled=${!this._version ||
|
||||
(this._shouldCreateBackup &&
|
||||
this.supervisor.info?.state !== "running")}
|
||||
@click=${this._update}
|
||||
raised
|
||||
>
|
||||
<ha-progress-button @click=${this._update} raised>
|
||||
${this.supervisor.localize("common.update")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
@@ -212,11 +209,22 @@ class UpdateAvailableCard extends LitElement {
|
||||
: "addon";
|
||||
this._updateType = updateType as updateType;
|
||||
|
||||
if (updateType === "addon") {
|
||||
if (!this.addonSlug) {
|
||||
this.addonSlug = pathPart;
|
||||
}
|
||||
this._loadAddonData();
|
||||
switch (updateType) {
|
||||
case "addon":
|
||||
if (!this.addonSlug) {
|
||||
this.addonSlug = pathPart;
|
||||
}
|
||||
this._loadAddonData();
|
||||
break;
|
||||
case "core":
|
||||
this._loadCoreData();
|
||||
break;
|
||||
case "supervisor":
|
||||
this._loadSupervisorData();
|
||||
break;
|
||||
case "os":
|
||||
this._loadOsData();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,9 +316,51 @@ class UpdateAvailableCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadSupervisorData() {
|
||||
try {
|
||||
const supervisor = await fetchHassioSupervisorInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { supervisor });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this._updateType,
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadCoreData() {
|
||||
try {
|
||||
const core = await fetchHassioHomeAssistantInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { core });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this._updateType,
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadOsData() {
|
||||
try {
|
||||
const os = await fetchHassioHassOsInfo(this.hass);
|
||||
fireEvent(this, "supervisor-update", { os });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this._updateType,
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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._updating = true;
|
||||
|
||||
try {
|
||||
if (this._updateType === "addon") {
|
||||
await updateHassioAddon(
|
||||
|
77
package.json
77
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"description": "A frontend for Home Assistant using the Polymer framework",
|
||||
"description": "A frontend for Home Assistant",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/home-assistant/home-assistant-polymer"
|
||||
"url": "https://github.com/home-assistant/frontend"
|
||||
},
|
||||
"name": "home-assistant-frontend",
|
||||
"version": "1.0.0",
|
||||
@@ -22,17 +22,18 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.2",
|
||||
"@codemirror/commands": "^0.19.5",
|
||||
"@codemirror/gutter": "^0.19.4",
|
||||
"@codemirror/highlight": "^0.19.6",
|
||||
"@codemirror/history": "^0.19.0",
|
||||
"@codemirror/autocomplete": "^0.19.12",
|
||||
"@codemirror/commands": "^0.19.8",
|
||||
"@codemirror/gutter": "^0.19.9",
|
||||
"@codemirror/highlight": "^0.19.7",
|
||||
"@codemirror/history": "^0.19.2",
|
||||
"@codemirror/legacy-modes": "^0.19.0",
|
||||
"@codemirror/rectangular-selection": "^0.19.1",
|
||||
"@codemirror/search": "^0.19.2",
|
||||
"@codemirror/state": "^0.19.4",
|
||||
"@codemirror/stream-parser": "^0.19.2",
|
||||
"@codemirror/text": "^0.19.5",
|
||||
"@codemirror/view": "^0.19.15",
|
||||
"@codemirror/search": "^0.19.6",
|
||||
"@codemirror/state": "^0.19.6",
|
||||
"@codemirror/stream-parser": "^0.19.5",
|
||||
"@codemirror/text": "^0.19.6",
|
||||
"@codemirror/view": "^0.19.40",
|
||||
"@formatjs/intl-datetimeformat": "^4.2.5",
|
||||
"@formatjs/intl-getcanonicallocales": "^1.8.0",
|
||||
"@formatjs/intl-locale": "^2.4.40",
|
||||
@@ -45,7 +46,8 @@
|
||||
"@fullcalendar/daygrid": "5.9.0",
|
||||
"@fullcalendar/interaction": "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/data-table": "14.0.0-canary.261f2db59.0",
|
||||
"@material/mwc-button": "0.25.3",
|
||||
@@ -57,7 +59,7 @@
|
||||
"@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-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-radio": "0.25.3",
|
||||
"@material/mwc-ripple": "0.25.3",
|
||||
@@ -66,6 +68,7 @@
|
||||
"@material/mwc-switch": "0.25.3",
|
||||
"@material/mwc-tab": "0.25.3",
|
||||
"@material/mwc-tab-bar": "0.25.3",
|
||||
"@material/mwc-textarea": "^0.25.3",
|
||||
"@material/mwc-textfield": "0.25.3",
|
||||
"@material/mwc-top-app-bar-fixed": "^0.25.3",
|
||||
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
||||
@@ -76,7 +79,6 @@
|
||||
"@polymer/iron-icon": "^3.0.1",
|
||||
"@polymer/iron-input": "^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-item": "^3.0.1",
|
||||
"@polymer/paper-listbox": "^3.0.1",
|
||||
@@ -87,13 +89,15 @@
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.4.1",
|
||||
"@thomasloven/round-slider": "0.5.4",
|
||||
"@vaadin/vaadin-combo-box": "^21.0.2",
|
||||
"@vaadin/vaadin-date-picker": "^21.0.2",
|
||||
"@vaadin/combo-box": "^22.0.4",
|
||||
"@vaadin/vaadin-themable-mixin": "^22.0.4",
|
||||
"@vibrant/color": "^3.2.1-alpha.1",
|
||||
"@vibrant/core": "^3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
|
||||
"@vue/web-component-wrapper": "^1.2.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "^0.0.5",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"app-datepicker": "^5.0.1",
|
||||
"chart.js": "^3.3.2",
|
||||
"comlink": "^4.3.1",
|
||||
"core-js": "^3.15.2",
|
||||
@@ -103,16 +107,16 @@
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.0.11",
|
||||
"home-assistant-js-websocket": "^6.0.1",
|
||||
"hls.js": "^1.1.5",
|
||||
"home-assistant-js-websocket": "^7.0.1",
|
||||
"idb-keyval": "^5.1.3",
|
||||
"intl-messageformat": "^9.9.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"leaflet": "^1.7.1",
|
||||
"leaflet-draw": "^1.0.4",
|
||||
"lit": "^2.0.2",
|
||||
"lit-vaadin-helpers": "^0.2.1",
|
||||
"marked": "^3.0.2",
|
||||
"lit": "^2.1.2",
|
||||
"lit-vaadin-helpers": "^0.3.0",
|
||||
"marked": "^4.0.12",
|
||||
"memoize-one": "^5.2.1",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "^0.3.2",
|
||||
@@ -131,13 +135,12 @@
|
||||
"vis-network": "^8.5.4",
|
||||
"vue": "^2.6.12",
|
||||
"vue2-daterange-picker": "^0.5.1",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"workbox-cacheable-response": "^6.1.5",
|
||||
"workbox-core": "^6.1.5",
|
||||
"workbox-expiration": "^6.1.5",
|
||||
"workbox-precaching": "^6.1.5",
|
||||
"workbox-routing": "^6.1.5",
|
||||
"workbox-strategies": "^6.1.5",
|
||||
"workbox-cacheable-response": "^6.4.2",
|
||||
"workbox-core": "^6.4.2",
|
||||
"workbox-expiration": "^6.4.2",
|
||||
"workbox-precaching": "^6.4.2",
|
||||
"workbox-routing": "^6.4.2",
|
||||
"workbox-strategies": "^6.4.2",
|
||||
"xss": "^1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -166,8 +169,9 @@
|
||||
"@types/js-yaml": "^4",
|
||||
"@types/leaflet": "^1",
|
||||
"@types/leaflet-draw": "^1",
|
||||
"@types/marked": "^2",
|
||||
"@types/marked": "^4",
|
||||
"@types/mocha": "^8",
|
||||
"@types/qrcode": "^1.4.2",
|
||||
"@types/sortablejs": "^1",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^4.32.0",
|
||||
@@ -192,7 +196,7 @@
|
||||
"fs-extra": "^7.0.1",
|
||||
"glob": "^7.2.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-foreach": "^0.1.0",
|
||||
"gulp-flatmap": "^1.0.2",
|
||||
"gulp-json-transform": "^0.4.6",
|
||||
"gulp-merge-json": "^1.3.1",
|
||||
"gulp-rename": "^2.0.0",
|
||||
@@ -229,16 +233,16 @@
|
||||
"webpack-dev-server": "^4.3.0",
|
||||
"webpack-manifest-plugin": "^4.0.2",
|
||||
"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",
|
||||
"resolutions": {
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"lit": "^2.0.2",
|
||||
"lit-html": "2.0.1",
|
||||
"lit-element": "3.0.1",
|
||||
"@lit/reactive-element": "1.0.1"
|
||||
"lit": "^2.1.2",
|
||||
"lit-html": "2.1.2",
|
||||
"lit-element": "3.1.2",
|
||||
"@lit/reactive-element": "1.2.1"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
@@ -249,5 +253,6 @@
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "always"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@3.2.0"
|
||||
}
|
||||
|
@@ -2,6 +2,6 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def where():
|
||||
def where() -> Path:
|
||||
"""Return path to the frontend."""
|
||||
return Path(__file__).parent
|
||||
|
0
public/py.typed
Normal file
0
public/py.typed
Normal file
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools~=60.5", "wheel~=0.37.1"]
|
||||
build-backend = "setuptools.build_meta"
|
18
script/core
18
script/core
@@ -4,6 +4,8 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
WD="${WORKSPACE_DIRECTORY:=/workspaces/frontend}"
|
||||
|
||||
if [ -z "${DEVCONTAINER}" ]; then
|
||||
echo "This task should only run inside a devcontainer, for local install HA Core in a venv."
|
||||
exit 1
|
||||
@@ -16,9 +18,9 @@ if [ -z $(which hass) ]; then
|
||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||
fi
|
||||
|
||||
if [ ! -d "/workspaces/frontend/config" ]; then
|
||||
if [ ! -d "${WD}/config" ]; then
|
||||
echo "Creating default configuration."
|
||||
mkdir -p "/workspaces/frontend/config";
|
||||
mkdir -p "${WD}/config";
|
||||
hass --script ensure_config -c config
|
||||
echo "demo:
|
||||
|
||||
@@ -26,24 +28,24 @@ logger:
|
||||
default: info
|
||||
logs:
|
||||
homeassistant.components.frontend: debug
|
||||
" >> /workspaces/frontend/config/configuration.yaml
|
||||
" >> "${WD}/config/configuration.yaml"
|
||||
|
||||
if [ ! -z "${HASSIO}" ]; then
|
||||
echo "
|
||||
# frontend:
|
||||
# development_repo: /workspaces/frontend
|
||||
# development_repo: ${WD}
|
||||
|
||||
hassio:
|
||||
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||
else
|
||||
echo "
|
||||
frontend:
|
||||
development_repo: /workspaces/frontend
|
||||
development_repo: ${WD}
|
||||
|
||||
# hassio:
|
||||
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
hass -c /workspaces/frontend/config
|
||||
hass -c "${WD}/config"
|
||||
|
@@ -11,6 +11,6 @@ yarn install
|
||||
|
||||
script/build_frontend
|
||||
|
||||
rm -rf dist
|
||||
python3 setup.py -q sdist
|
||||
python3 -m twine upload dist/* --skip-existing
|
||||
rm -rf dist home_assistant_frontend.egg-info
|
||||
python3 -m build
|
||||
python3 -m twine upload dist/*.whl --skip-existing
|
||||
|
@@ -50,14 +50,14 @@ async function main(args) {
|
||||
return;
|
||||
}
|
||||
|
||||
const setup = fs.readFileSync("setup.py", "utf8");
|
||||
const setup = fs.readFileSync("setup.cfg", "utf8");
|
||||
const version = setup.match(/\d{8}\.\d+/)[0];
|
||||
const newVersion = method(version);
|
||||
|
||||
console.log("Current version:", version);
|
||||
console.log("New version:", newVersion);
|
||||
|
||||
fs.writeFileSync("setup.py", setup.replace(version, newVersion), "utf-8");
|
||||
fs.writeFileSync("setup.cfg", setup.replace(version, newVersion), "utf-8");
|
||||
|
||||
if (!commit) {
|
||||
return;
|
||||
|
26
setup.cfg
Normal file
26
setup.cfg
Normal file
@@ -0,0 +1,26 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220317.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
platforms = any
|
||||
description = The Home Assistant frontend
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
url = https://github.com/home-assistant/frontend
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
zip_safe = False
|
||||
include_package_data = True
|
||||
python_requires = >= 3.4.0
|
||||
|
||||
[options.packages.find]
|
||||
include =
|
||||
hass_frontend*
|
||||
|
||||
[mypy]
|
||||
python_version = 3.4
|
||||
show_error_codes = True
|
||||
strict = True
|
19
setup.py
19
setup.py
@@ -1,14 +1,7 @@
|
||||
from setuptools import setup, find_packages
|
||||
"""
|
||||
Entry point for setuptools. Required for editable installs.
|
||||
TODO: Remove file after updating to pip 21.3
|
||||
"""
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20220124.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/frontend",
|
||||
author="The Home Assistant Authors",
|
||||
author_email="hello@home-assistant.io",
|
||||
license="Apache-2.0",
|
||||
packages=find_packages(include=["hass_frontend", "hass_frontend.*"]),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
)
|
||||
setup()
|
||||
|
@@ -101,13 +101,19 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
this._fetchAuthProviders();
|
||||
|
||||
if (matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
applyThemesOnElement(document.documentElement, {
|
||||
default_theme: "default",
|
||||
default_dark_theme: null,
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
});
|
||||
applyThemesOnElement(
|
||||
document.documentElement,
|
||||
{
|
||||
default_theme: "default",
|
||||
default_dark_theme: null,
|
||||
themes: {},
|
||||
darkMode: true,
|
||||
theme: "default",
|
||||
},
|
||||
undefined,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!this.redirectUri) {
|
||||
|
@@ -184,6 +184,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"person",
|
||||
"remote",
|
||||
"script",
|
||||
"scene",
|
||||
"sun",
|
||||
"timer",
|
||||
"vacuum",
|
||||
@@ -234,7 +235,7 @@ export const DOMAINS_INPUT_ROW = [
|
||||
];
|
||||
|
||||
/** Domains that should have the history hidden in the more info dialog. */
|
||||
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator", "scene"];
|
||||
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator"];
|
||||
|
||||
/** States that we consider "off". */
|
||||
export const STATES_OFF = ["closed", "locked", "off"];
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { HaDurationData } from "../../components/ha-duration-input";
|
||||
import { ForDict } from "../../data/automation";
|
||||
import type { HaDurationData } from "../../components/ha-duration-input";
|
||||
import type { ForDict } from "../../data/automation";
|
||||
|
||||
export const createDurationData = (
|
||||
duration: string | number | ForDict | undefined
|
||||
): HaDurationData => {
|
||||
): HaDurationData | undefined => {
|
||||
if (duration === undefined) {
|
||||
return {};
|
||||
return undefined;
|
||||
}
|
||||
if (typeof duration !== "object") {
|
||||
if (typeof duration === "string" || isNaN(duration)) {
|
||||
@@ -19,6 +19,9 @@ export const createDurationData = (
|
||||
}
|
||||
return { seconds: duration };
|
||||
}
|
||||
if (!("days" in duration)) {
|
||||
return duration;
|
||||
}
|
||||
const { days, minutes, seconds, milliseconds } = duration;
|
||||
let hours = duration.hours || 0;
|
||||
hours = (hours || 0) + (days || 0) * 24;
|
||||
|
@@ -13,14 +13,19 @@ export const formatDateTime = (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
|
||||
const formatDateTimeMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// August 9, 2021, 8:23:15 AM
|
||||
@@ -31,15 +36,20 @@ export const formatDateTimeWithSeconds = (
|
||||
|
||||
const formatDateTimeWithSecondsMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// 9/8/2021, 8:23 AM
|
||||
@@ -50,12 +60,17 @@ export const formatDateTimeNumeric = (
|
||||
|
||||
const formatDateTimeNumericMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@@ -13,11 +13,16 @@ export const formatTime = (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
|
||||
const formatTimeMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// 9:15:24 PM || 21:15:24
|
||||
@@ -28,12 +33,17 @@ export const formatTimeWithSeconds = (
|
||||
|
||||
const formatTimeWithSecondsMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Tuesday 7:00 PM || Tuesday 19:00
|
||||
@@ -42,10 +52,15 @@ export const formatTimeWeekday = (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
|
||||
const formatTimeWeekdayMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
weekday: "long",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
new Intl.DateTimeFormat(
|
||||
locale.language === "en" && !useAmPm(locale)
|
||||
? "en-u-hc-h23"
|
||||
: locale.language,
|
||||
{
|
||||
weekday: "long",
|
||||
hour: useAmPm(locale) ? "numeric" : "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@@ -31,11 +31,12 @@ export const applyThemesOnElement = (
|
||||
element,
|
||||
themes: HomeAssistant["themes"],
|
||||
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`.
|
||||
const themeToApply = selectedTheme || themes.theme;
|
||||
const themeToApply = selectedTheme || (main ? themes.theme : undefined);
|
||||
|
||||
// If there is no explicitly desired dark mode provided, we automatically
|
||||
// use the active one from `themes`.
|
||||
@@ -47,7 +48,7 @@ export const applyThemesOnElement = (
|
||||
let cacheKey = themeToApply;
|
||||
let themeRules: Partial<ThemeVars> = {};
|
||||
|
||||
if (darkMode) {
|
||||
if (themeToApply && darkMode) {
|
||||
cacheKey = `${cacheKey}__dark`;
|
||||
themeRules = { ...darkStyles };
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
export const canToggleDomain = (hass: HomeAssistant, domain: string) => {
|
||||
const services = hass.services[domain];
|
||||
|
@@ -1,14 +1,30 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { canToggleDomain } from "./can_toggle_domain";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { supportsFeature } from "./supports-feature";
|
||||
|
||||
export const canToggleState = (hass: HomeAssistant, stateObj: HassEntity) => {
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
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") {
|
||||
return supportsFeature(stateObj, 4096);
|
||||
}
|
||||
|
@@ -120,9 +120,14 @@ export const computeStateDisplay = (
|
||||
if (
|
||||
domain === "button" ||
|
||||
domain === "input_button" ||
|
||||
domain === "scene" ||
|
||||
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
|
||||
) {
|
||||
return formatDateTime(new Date(compareState), locale);
|
||||
try {
|
||||
return formatDateTime(new Date(compareState), locale);
|
||||
} catch (_err) {
|
||||
return compareState;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@@ -120,6 +120,7 @@ export const computeOpenIcon = (stateObj: HassEntity): string => {
|
||||
case "awning":
|
||||
case "door":
|
||||
case "gate":
|
||||
case "curtain":
|
||||
return mdiArrowExpandHorizontal;
|
||||
default:
|
||||
return mdiArrowUp;
|
||||
@@ -131,6 +132,7 @@ export const computeCloseIcon = (stateObj: HassEntity): string => {
|
||||
case "awning":
|
||||
case "door":
|
||||
case "gate":
|
||||
case "curtain":
|
||||
return mdiArrowCollapseHorizontal;
|
||||
default:
|
||||
return mdiArrowDown;
|
||||
|
@@ -9,11 +9,10 @@ import {
|
||||
mdiCast,
|
||||
mdiCastConnected,
|
||||
mdiClock,
|
||||
mdiEmoticonDead,
|
||||
mdiFlash,
|
||||
mdiGestureTapButton,
|
||||
mdiLanConnect,
|
||||
mdiLanDisconnect,
|
||||
mdiLightSwitch,
|
||||
mdiLock,
|
||||
mdiLockAlert,
|
||||
mdiLockClock,
|
||||
@@ -22,14 +21,11 @@ import {
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRestart,
|
||||
mdiSleep,
|
||||
mdiTimerSand,
|
||||
mdiToggleSwitch,
|
||||
mdiToggleSwitchOff,
|
||||
mdiCheckCircleOutline,
|
||||
mdiCloseCircleOutline,
|
||||
mdiWeatherNight,
|
||||
mdiZWave,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
/**
|
||||
@@ -112,19 +108,7 @@ export const domainIcon = (
|
||||
case "switch":
|
||||
return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
|
||||
default:
|
||||
return mdiFlash;
|
||||
}
|
||||
|
||||
case "zwave":
|
||||
switch (compareState) {
|
||||
case "dead":
|
||||
return mdiEmoticonDead;
|
||||
case "sleeping":
|
||||
return mdiSleep;
|
||||
case "initializing":
|
||||
return mdiTimerSand;
|
||||
default:
|
||||
return mdiZWave;
|
||||
return mdiLightSwitch;
|
||||
}
|
||||
|
||||
case "sensor": {
|
||||
|
@@ -1,24 +1,32 @@
|
||||
const SUFFIXES = [" ", ": "];
|
||||
|
||||
/**
|
||||
* Strips a device name from an 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
|
||||
*/
|
||||
export const stripPrefixFromEntityName = (
|
||||
entityName: string,
|
||||
lowerCasedPrefixWithSpaceSuffix: string
|
||||
lowerCasedPrefix: string
|
||||
) => {
|
||||
if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) {
|
||||
return undefined;
|
||||
const lowerCasedEntityName = entityName.toLowerCase();
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
|
||||
|
@@ -1,2 +1,10 @@
|
||||
export const clamp = (value: number, min: number, max: number) =>
|
||||
Math.min(Math.max(value, min), max);
|
||||
|
||||
// Variant that only applies the clamping to a border if the border is defined
|
||||
export const conditionalClamp = (value: number, min?: number, max?: number) => {
|
||||
let result: number;
|
||||
result = min ? Math.max(value, min) : value;
|
||||
result = max ? Math.min(value, max) : value;
|
||||
return result;
|
||||
};
|
||||
|
@@ -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="paused"],
|
||||
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="sun"][data-state="above_horizon"],
|
||||
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="zwave"][data-state="dead"] {
|
||||
color: var(--state-icon-error-color);
|
||||
}
|
||||
|
||||
/* Color the icon if unavailable */
|
||||
ha-state-icon[data-state="unavailable"] {
|
||||
|
@@ -77,7 +77,7 @@ export const computeLocalize = async (
|
||||
|
||||
await loadPolyfillLocales(language);
|
||||
|
||||
// Everytime any of the parameters change, invalidate the strings cache.
|
||||
// Every time any of the parameters change, invalidate the strings cache.
|
||||
cache._localizationCache = {};
|
||||
|
||||
return (key, ...args) => {
|
||||
|
@@ -11,7 +11,7 @@ export const debounce = <T extends any[]>(
|
||||
immediate = false
|
||||
) => {
|
||||
let timeout: number | undefined;
|
||||
return (...args: T): void => {
|
||||
const debouncedFunc = (...args: T): void => {
|
||||
const later = () => {
|
||||
timeout = undefined;
|
||||
if (!immediate) {
|
||||
@@ -25,4 +25,8 @@ export const debounce = <T extends any[]>(
|
||||
func(...args);
|
||||
}
|
||||
};
|
||||
debouncedFunc.cancel = () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
return debouncedFunc;
|
||||
};
|
||||
|
@@ -1,8 +1,9 @@
|
||||
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 { customElement, property, query } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../ha-circular-progress";
|
||||
import "../ha-svg-icon";
|
||||
|
||||
@customElement("ha-progress-button")
|
||||
export class HaProgressButton extends LitElement {
|
||||
@@ -12,38 +13,53 @@ export class HaProgressButton extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public raised = false;
|
||||
|
||||
@query("mwc-button", true) private _button?: Button;
|
||||
@state() private _result?: "success" | "error";
|
||||
|
||||
public render(): TemplateResult {
|
||||
const overlay = this._result || this.progress;
|
||||
return html`
|
||||
<mwc-button
|
||||
?raised=${this.raised}
|
||||
.disabled=${this.disabled || this.progress}
|
||||
@click=${this._buttonTapped}
|
||||
class=${this._result || ""}
|
||||
>
|
||||
<slot></slot>
|
||||
</mwc-button>
|
||||
${this.progress
|
||||
? html`<div class="progress">
|
||||
<ha-circular-progress size="small" active></ha-circular-progress>
|
||||
</div>`
|
||||
: ""}
|
||||
${!overlay
|
||||
? ""
|
||||
: html`
|
||||
<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 {
|
||||
this._tempClass("success");
|
||||
this._setResult("success");
|
||||
}
|
||||
|
||||
public actionError(): void {
|
||||
this._tempClass("error");
|
||||
this._setResult("error");
|
||||
}
|
||||
|
||||
private _tempClass(className: string): void {
|
||||
this._button!.classList.add(className);
|
||||
private _setResult(result: "success" | "error"): void {
|
||||
this._result = result;
|
||||
setTimeout(() => {
|
||||
this._button!.classList.remove(className);
|
||||
}, 1000);
|
||||
this._result = undefined;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
private _buttonTapped(ev: Event): void {
|
||||
@@ -68,6 +84,8 @@ export class HaProgressButton extends LitElement {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--success-color);
|
||||
transition: none;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
mwc-button[raised].success {
|
||||
@@ -79,6 +97,8 @@ export class HaProgressButton extends LitElement {
|
||||
--mdc-theme-primary: white;
|
||||
background-color: var(--error-color);
|
||||
transition: none;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
mwc-button[raised].error {
|
||||
@@ -87,13 +107,21 @@ export class HaProgressButton extends LitElement {
|
||||
}
|
||||
|
||||
.progress {
|
||||
bottom: 0;
|
||||
margin-top: 4px;
|
||||
bottom: 4px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
top: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
mwc-button.success slot,
|
||||
mwc-button.error slot {
|
||||
visibility: hidden;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -183,12 +183,7 @@ class StateHistoryChartLine extends LitElement {
|
||||
prevValues = datavalues;
|
||||
};
|
||||
|
||||
const addDataSet = (
|
||||
nameY: string,
|
||||
step = false,
|
||||
fill = false,
|
||||
color?: string
|
||||
) => {
|
||||
const addDataSet = (nameY: string, fill = false, color?: string) => {
|
||||
if (!color) {
|
||||
color = getGraphColorByIndex(colorIndex, computedStyles);
|
||||
colorIndex++;
|
||||
@@ -198,7 +193,7 @@ class StateHistoryChartLine extends LitElement {
|
||||
fill: fill ? "origin" : false,
|
||||
borderColor: color,
|
||||
backgroundColor: color + "7F",
|
||||
stepped: step ? "before" : false,
|
||||
stepped: "before",
|
||||
pointRadius: 0,
|
||||
data: [],
|
||||
});
|
||||
@@ -239,14 +234,12 @@ class StateHistoryChartLine extends LitElement {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.current_temperature", {
|
||||
name: name,
|
||||
})}`,
|
||||
true
|
||||
})}`
|
||||
);
|
||||
if (hasHeat) {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.heating", { name: name })}`,
|
||||
true,
|
||||
true,
|
||||
computedStyles.getPropertyValue("--state-climate-heat-color")
|
||||
);
|
||||
// The "heating" series uses steppedArea to shade the area below the current
|
||||
@@ -256,7 +249,6 @@ class StateHistoryChartLine extends LitElement {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.cooling", { name: name })}`,
|
||||
true,
|
||||
true,
|
||||
computedStyles.getPropertyValue("--state-climate-cool-color")
|
||||
);
|
||||
// The "cooling" series uses steppedArea to shade the area below the current
|
||||
@@ -268,22 +260,19 @@ class StateHistoryChartLine extends LitElement {
|
||||
`${this.hass.localize("ui.card.climate.target_temperature_mode", {
|
||||
name: name,
|
||||
mode: this.hass.localize("ui.card.climate.high"),
|
||||
})}`,
|
||||
true
|
||||
})}`
|
||||
);
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.target_temperature_mode", {
|
||||
name: name,
|
||||
mode: this.hass.localize("ui.card.climate.low"),
|
||||
})}`,
|
||||
true
|
||||
})}`
|
||||
);
|
||||
} else {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.target_temperature_entity", {
|
||||
name: name,
|
||||
})}`,
|
||||
true
|
||||
})}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -318,14 +307,12 @@ class StateHistoryChartLine extends LitElement {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.humidifier.target_humidity_entity", {
|
||||
name: name,
|
||||
})}`,
|
||||
true
|
||||
})}`
|
||||
);
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.humidifier.on_entity", {
|
||||
name: name,
|
||||
})}`,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -337,9 +324,7 @@ class StateHistoryChartLine extends LitElement {
|
||||
pushData(new Date(entityState.last_changed), series);
|
||||
});
|
||||
} else {
|
||||
// Only interpolate for sensors
|
||||
const isStep = domain !== "sensor";
|
||||
addDataSet(name, isStep);
|
||||
addDataSet(name);
|
||||
|
||||
let lastValue: number;
|
||||
let lastDate: Date;
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { Layout1d, scroll } from "@lit-labs/virtualizer";
|
||||
import { mdiArrowDown, mdiArrowUp } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import {
|
||||
@@ -22,7 +21,7 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../common/search/search-input";
|
||||
import "../search-input";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
import { haStyleScrollbar } from "../../resources/styles";
|
||||
@@ -31,6 +30,7 @@ import type { HaCheckbox } from "../ha-checkbox";
|
||||
import "../ha-svg-icon";
|
||||
import { filterData, sortData } from "./sort-filter";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "@lit-labs/virtualizer";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -70,6 +70,7 @@ export interface DataTableSortColumnData {
|
||||
|
||||
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||
title: TemplateResult | string;
|
||||
label?: TemplateResult | string;
|
||||
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
|
||||
template?: (data: any, row: T) => TemplateResult | string;
|
||||
width?: string;
|
||||
@@ -294,6 +295,7 @@ export class HaDataTable extends LitElement {
|
||||
};
|
||||
return html`
|
||||
<div
|
||||
aria-label=${column.label}
|
||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||
style=${column.width
|
||||
? styleMap({
|
||||
@@ -337,111 +339,99 @@ export class HaDataTable extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div
|
||||
<lit-virtualizer
|
||||
scroller
|
||||
class="mdc-data-table__content scroller ha-scrollbar"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
${scroll({
|
||||
items: this._items,
|
||||
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>
|
||||
.items=${this._items}
|
||||
.renderItem=${this._renderRow}
|
||||
></lit-virtualizer>
|
||||
`}
|
||||
</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() {
|
||||
const startTime = new Date().getTime();
|
||||
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 rowId = (checkbox as any).rowId;
|
||||
|
||||
@@ -549,16 +539,16 @@ export class HaDataTable extends LitElement {
|
||||
this._checkedRows = this._checkedRows.filter((row) => row !== rowId);
|
||||
}
|
||||
this._checkedRowsChanged();
|
||||
}
|
||||
};
|
||||
|
||||
private _handleRowClick(ev: Event) {
|
||||
private _handleRowClick = (ev: Event) => {
|
||||
const target = ev.target as HTMLElement;
|
||||
if (["HA-CHECKBOX", "MWC-BUTTON"].includes(target.tagName)) {
|
||||
return;
|
||||
}
|
||||
const rowId = (ev.currentTarget as any).rowId;
|
||||
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
|
||||
}
|
||||
};
|
||||
|
||||
private _checkedRowsChanged() {
|
||||
// force scroller to update, change it's items
|
||||
@@ -571,6 +561,9 @@ export class HaDataTable extends LitElement {
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent): void {
|
||||
if (this.filter) {
|
||||
return;
|
||||
}
|
||||
this._debounceSearch(ev.detail.value);
|
||||
}
|
||||
|
||||
@@ -935,11 +928,10 @@ export class HaDataTable extends LitElement {
|
||||
}
|
||||
.table-header {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
padding: 0 16px;
|
||||
}
|
||||
search-input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
display: block;
|
||||
flex: 1;
|
||||
}
|
||||
slot[name="header"] {
|
||||
display: block;
|
||||
@@ -952,6 +944,7 @@ export class HaDataTable extends LitElement {
|
||||
}
|
||||
.scroller {
|
||||
height: calc(100% - 57px);
|
||||
overflow: overlay !important;
|
||||
}
|
||||
|
||||
.mdc-data-table__table.auto-height .scroller {
|
||||
@@ -967,6 +960,9 @@ export class HaDataTable extends LitElement {
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
lit-virtualizer {
|
||||
contain: size layout !important;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -115,6 +115,9 @@ class DateRangePickerElement extends WrappedElement {
|
||||
color: var(--primary-text-color);
|
||||
min-width: initial !important;
|
||||
}
|
||||
.daterangepicker:before {
|
||||
display: none;
|
||||
}
|
||||
.daterangepicker:after {
|
||||
border-bottom: 6px solid var(--card-background-color);
|
||||
}
|
||||
|
@@ -1,20 +1,7 @@
|
||||
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 {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -50,36 +37,12 @@ interface AreaDevices {
|
||||
devices: string[];
|
||||
}
|
||||
|
||||
// eslint-disable-next-line lit/prefer-static-styles
|
||||
const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (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>
|
||||
<paper-item-body two-line="">
|
||||
<div class="name">${item.name}</div>
|
||||
<div secondary>${item.devices.length} devices</div>
|
||||
</paper-item-body>
|
||||
</paper-item>`;
|
||||
const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (
|
||||
item
|
||||
) => html`<mwc-list-item twoline>
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary">${item.devices.length} devices</span>
|
||||
</mwc-list-item>`;
|
||||
|
||||
@customElement("ha-area-devices-picker")
|
||||
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
@@ -117,9 +80,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
@property({ type: Boolean })
|
||||
private _opened?: boolean;
|
||||
|
||||
@state() private _areaPicker = true;
|
||||
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
@@ -302,71 +262,30 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
item-value-path="id"
|
||||
item-id-path="id"
|
||||
item-label-path="name"
|
||||
.items=${areas}
|
||||
.value=${this._value}
|
||||
${comboBoxRenderer(rowRenderer)}
|
||||
@opened-changed=${this._openedChanged}
|
||||
.renderer=${rowRenderer}
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.device-picker.device")
|
||||
: `${this.label} in area`}
|
||||
@value-changed=${this._areaPicked}
|
||||
>
|
||||
<paper-input
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.device-picker.device")
|
||||
: `${this.label} in area`}
|
||||
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
|
||||
>
|
||||
</ha-combo-box>
|
||||
<mwc-button @click=${this._switchPicker}>
|
||||
Choose individual devices
|
||||
</mwc-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._setValue([]);
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || [];
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _switchPicker() {
|
||||
this._areaPicker = !this._areaPicker;
|
||||
}
|
||||
@@ -398,22 +317,6 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
||||
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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,7 +1,4 @@
|
||||
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 "@material/mwc-list/mwc-list-item";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -10,7 +7,7 @@ import {
|
||||
deviceAutomationsEqual,
|
||||
} from "../../data/device_automation";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-paper-dropdown-menu";
|
||||
import "../ha-select";
|
||||
|
||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
||||
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
||||
@@ -67,14 +64,12 @@ export abstract class HaDeviceAutomationPicker<
|
||||
this._createNoAutomation = createNoAutomation;
|
||||
}
|
||||
|
||||
private get _key() {
|
||||
if (
|
||||
!this.value ||
|
||||
deviceAutomationsEqual(
|
||||
this._createNoAutomation(this.deviceId),
|
||||
this.value
|
||||
)
|
||||
) {
|
||||
private get _value() {
|
||||
if (!this.value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!this._automations.length) {
|
||||
return NO_AUTOMATION_KEY;
|
||||
}
|
||||
|
||||
@@ -93,42 +88,32 @@ export abstract class HaDeviceAutomationPicker<
|
||||
if (this._renderEmpty) {
|
||||
return html``;
|
||||
}
|
||||
const value = this._value;
|
||||
return html`
|
||||
<ha-paper-dropdown-menu
|
||||
<ha-select
|
||||
.label=${this.label}
|
||||
.value=${this.value
|
||||
? this._localizeDeviceAutomation(this.hass, this.value)
|
||||
: ""}
|
||||
?disabled=${this._automations.length === 0}
|
||||
.value=${value}
|
||||
@selected=${this._automationChanged}
|
||||
.disabled=${this._automations.length === 0}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._key}
|
||||
attr-for-selected="key"
|
||||
@iron-select=${this._automationChanged}
|
||||
>
|
||||
<paper-item
|
||||
key=${NO_AUTOMATION_KEY}
|
||||
.automation=${this._createNoAutomation(this.deviceId)}
|
||||
hidden
|
||||
>
|
||||
${this.NO_AUTOMATION_TEXT}
|
||||
</paper-item>
|
||||
<paper-item key=${UNKNOWN_AUTOMATION_KEY} hidden>
|
||||
${this.UNKNOWN_AUTOMATION_TEXT}
|
||||
</paper-item>
|
||||
${this._automations.map(
|
||||
(automation, idx) => html`
|
||||
<paper-item
|
||||
key=${`${this.deviceId}_${idx}`}
|
||||
.automation=${automation}
|
||||
>
|
||||
${this._localizeDeviceAutomation(this.hass, automation)}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
${value === NO_AUTOMATION_KEY
|
||||
? html`<mwc-list-item .value=${NO_AUTOMATION_KEY}>
|
||||
${this.NO_AUTOMATION_TEXT}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
${value === UNKNOWN_AUTOMATION_KEY
|
||||
? html`<mwc-list-item .value=${UNKNOWN_AUTOMATION_KEY}>
|
||||
${this.UNKNOWN_AUTOMATION_TEXT}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
${this._automations.map(
|
||||
(automation, idx) => html`
|
||||
<mwc-list-item .value=${`${automation.device_id}_${idx}`}>
|
||||
${this._localizeDeviceAutomation(this.hass, automation)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -138,14 +123,6 @@ export abstract class HaDeviceAutomationPicker<
|
||||
if (changedProps.has("deviceId")) {
|
||||
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() {
|
||||
@@ -168,9 +145,16 @@ export abstract class HaDeviceAutomationPicker<
|
||||
}
|
||||
|
||||
private _automationChanged(ev) {
|
||||
if (ev.detail.item.automation) {
|
||||
this._setValue(ev.detail.item.automation);
|
||||
const value = ev.target.value;
|
||||
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) {
|
||||
@@ -183,14 +167,9 @@ export abstract class HaDeviceAutomationPicker<
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-paper-dropdown-menu {
|
||||
ha-select {
|
||||
width: 100%;
|
||||
}
|
||||
paper-listbox {
|
||||
min-width: 200px;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -1,18 +1,9 @@
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
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 { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
@@ -46,36 +37,12 @@ export type HaDevicePickerDeviceFilterFunc = (
|
||||
device: DeviceRegistryEntry
|
||||
) => boolean;
|
||||
|
||||
// eslint-disable-next-line lit/prefer-static-styles
|
||||
const rowRenderer: ComboBoxLitRenderer<Device> = (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>
|
||||
<paper-item-body two-line>
|
||||
${item.name}
|
||||
<span secondary>${item.area}</span>
|
||||
</paper-item-body>
|
||||
</paper-item>`;
|
||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<mwc-list-item
|
||||
.twoline=${!!item.area}
|
||||
>
|
||||
<span>${item.name}</span>
|
||||
<span slot="secondary">${item.area}</span>
|
||||
</mwc-list-item>`;
|
||||
|
||||
@customElement("ha-device-picker")
|
||||
export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
@@ -138,7 +105,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
if (!devices.length) {
|
||||
return [
|
||||
{
|
||||
id: "",
|
||||
id: "no_devices",
|
||||
area: "",
|
||||
name: this.hass.localize("ui.components.device-picker.no_devices"),
|
||||
},
|
||||
@@ -234,7 +201,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
if (!outputDevices.length) {
|
||||
return [
|
||||
{
|
||||
id: "",
|
||||
id: "no_devices",
|
||||
area: "",
|
||||
name: this.hass.localize("ui.components.device-picker.no_match"),
|
||||
},
|
||||
@@ -303,7 +270,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
.renderer=${rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
item-value-path="id"
|
||||
item-id-path="id"
|
||||
item-label-path="name"
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._deviceChanged}
|
||||
@@ -317,7 +283,11 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _deviceChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
let newValue = ev.detail.value;
|
||||
|
||||
if (newValue === "no_devices") {
|
||||
newValue = "";
|
||||
}
|
||||
|
||||
if (newValue !== this._value) {
|
||||
this._setValue(newValue);
|
||||
@@ -335,19 +305,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
fireEvent(this, "change");
|
||||
}, 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 {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
@@ -116,6 +116,12 @@ class HaDevicesPicker extends LitElement {
|
||||
|
||||
this._updateDevices([...currentDevices, toAdd]);
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
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 { fireEvent } from "../../common/dom/fire_event";
|
||||
import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||
@@ -51,6 +51,8 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
|
||||
@property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string;
|
||||
|
||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
@@ -94,7 +96,9 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
|
||||
private _entityFilter: HaEntityPickerEntityFilterFunc = (
|
||||
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() {
|
||||
return this.value || [];
|
||||
@@ -114,7 +118,7 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
const newValue = event.detail.value;
|
||||
if (
|
||||
newValue === curValue ||
|
||||
(newValue !== "" && !isValidEntityId(newValue))
|
||||
(newValue !== undefined && !isValidEntityId(newValue))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -145,6 +149,12 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
|
||||
this._updateEntities([...currentEntities, toAdd]);
|
||||
}
|
||||
|
||||
static override styles = css`
|
||||
div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
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 {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { formatAttributeName } from "../../data/entity_attributes";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { formatAttributeName } from "../../util/hass-attributes-util";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
|
||||
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")
|
||||
class HaEntityAttributePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -68,7 +28,7 @@ class HaEntityAttributePicker extends LitElement {
|
||||
|
||||
@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) {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
@@ -78,7 +38,10 @@ class HaEntityAttributePicker extends LitElement {
|
||||
if (changedProps.has("_opened") && this._opened) {
|
||||
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
|
||||
(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`
|
||||
<vaadin-combo-box-light
|
||||
.value=${this._value}
|
||||
<ha-combo-box
|
||||
.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}
|
||||
attr-for-value="bind-value"
|
||||
${comboBoxRenderer(rowRenderer)}
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<paper-input
|
||||
.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>
|
||||
</ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._setValue("");
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||
const newValue = 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;
|
||||
}
|
||||
`;
|
||||
this.value = ev.detail.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,61 +1,35 @@
|
||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } 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 "@material/mwc-list/mwc-list-item";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
|
||||
interface HassEntityWithCachedName extends HassEntity {
|
||||
friendly_name: string;
|
||||
}
|
||||
|
||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||
|
||||
// eslint-disable-next-line lit/prefer-static-styles
|
||||
const rowRenderer: ComboBoxLitRenderer<HassEntity> = (item) => html`<style>
|
||||
paper-icon-item {
|
||||
padding: 0;
|
||||
margin: -8px;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
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>`;
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<HassEntityWithCachedName> = (item) =>
|
||||
html`<mwc-list-item graphic="avatar" .twoline=${!!item.entity_id}>
|
||||
${item.state
|
||||
? html`<state-badge slot="graphic" .stateObj=${item}></state-badge>`
|
||||
: ""}
|
||||
<span>${item.friendly_name}</span>
|
||||
<span slot="secondary">${item.entity_id}</span>
|
||||
</mwc-list-item>`;
|
||||
@customElement("ha-entity-picker")
|
||||
export class HaEntityPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -107,25 +81,25 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
@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() {
|
||||
this.updateComplete.then(() => {
|
||||
(this.shadowRoot?.querySelector("vaadin-combo-box-light") as any)?.open();
|
||||
this.comboBox?.open();
|
||||
});
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot?.querySelector("paper-input")?.focus();
|
||||
this.comboBox?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
private _initedStates = false;
|
||||
|
||||
private _states: HassEntity[] = [];
|
||||
private _states: HassEntityWithCachedName[] = [];
|
||||
|
||||
private _getStates = memoizeOne(
|
||||
(
|
||||
@@ -136,14 +110,35 @@ export class HaEntityPicker extends LitElement {
|
||||
entityFilter: this["entityFilter"],
|
||||
includeDeviceClasses: this["includeDeviceClasses"],
|
||||
includeUnitOfMeasurement: this["includeUnitOfMeasurement"]
|
||||
) => {
|
||||
let states: HassEntity[] = [];
|
||||
): HassEntityWithCachedName[] => {
|
||||
let states: HassEntityWithCachedName[] = [];
|
||||
|
||||
if (!hass) {
|
||||
return [];
|
||||
}
|
||||
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 (includeDomains) {
|
||||
entityIds = entityIds.filter((eid) =>
|
||||
includeDomains.includes(computeDomain(eid))
|
||||
@@ -156,7 +151,10 @@ export class HaEntityPicker extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
states = entityIds.sort().map((key) => hass!.states[key]);
|
||||
states = entityIds.sort().map((key) => ({
|
||||
...hass!.states[key],
|
||||
friendly_name: computeStateName(hass!.states[key]) || key,
|
||||
}));
|
||||
|
||||
if (includeDeviceClasses) {
|
||||
states = states.filter(
|
||||
@@ -195,7 +193,10 @@ export class HaEntityPicker extends LitElement {
|
||||
state: "",
|
||||
last_changed: "",
|
||||
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: {
|
||||
friendly_name: this.hass!.localize(
|
||||
"ui.components.entity.entity-picker.no_match"
|
||||
@@ -241,64 +242,25 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
<ha-combo-box
|
||||
item-value-path="entity_id"
|
||||
item-label-path="entity_id"
|
||||
item-label-path="friendly_name"
|
||||
.hass=${this.hass}
|
||||
.value=${this._value}
|
||||
.label=${this.label === undefined
|
||||
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
||||
: this.label}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.filteredItems=${this._states}
|
||||
${comboBoxRenderer(rowRenderer)}
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
@filter-changed=${this._filterChanged}
|
||||
>
|
||||
<paper-input
|
||||
.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>
|
||||
</ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._setValue("");
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
@@ -308,6 +270,7 @@ export class HaEntityPicker extends LitElement {
|
||||
}
|
||||
|
||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const newValue = ev.detail.value;
|
||||
if (newValue !== this._value) {
|
||||
this._setValue(newValue);
|
||||
@@ -317,9 +280,9 @@ export class HaEntityPicker extends LitElement {
|
||||
private _filterChanged(ev: CustomEvent): void {
|
||||
const filterString = ev.detail.value.toLowerCase();
|
||||
(this.comboBox as any).filteredItems = this._states.filter(
|
||||
(state) =>
|
||||
state.entity_id.toLowerCase().includes(filterString) ||
|
||||
computeStateName(state).toLowerCase().includes(filterString)
|
||||
(entityState) =>
|
||||
entityState.entity_id.toLowerCase().includes(filterString) ||
|
||||
computeStateName(entityState).toLowerCase().includes(filterString)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -330,22 +293,6 @@ export class HaEntityPicker extends LitElement {
|
||||
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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -12,7 +12,7 @@ import { property, state } from "lit/decorators";
|
||||
import { STATES_OFF } from "../../common/const";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../data/entity";
|
||||
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../data/entity";
|
||||
import { forwardHaptic } from "../../data/haptics";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-formfield";
|
||||
@@ -39,21 +39,26 @@ export class HaEntityToggle extends LitElement {
|
||||
return html` <ha-switch disabled></ha-switch> `;
|
||||
}
|
||||
|
||||
if (this.stateObj.attributes.assumed_state) {
|
||||
if (
|
||||
this.stateObj.attributes.assumed_state ||
|
||||
this.stateObj.state === UNKNOWN
|
||||
) {
|
||||
return html`
|
||||
<ha-icon-button
|
||||
.label=${`Turn ${computeStateName(this.stateObj)} off`}
|
||||
.path=${mdiFlashOff}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@click=${this._turnOff}
|
||||
?state-active=${!this._isOn}
|
||||
class=${!this._isOn && this.stateObj.state !== UNKNOWN
|
||||
? "state-active"
|
||||
: ""}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
.label=${`Turn ${computeStateName(this.stateObj)} on`}
|
||||
.path=${mdiFlash}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@click=${this._turnOn}
|
||||
?state-active=${this._isOn}
|
||||
class=${this._isOn ? "state-active" : ""}
|
||||
></ha-icon-button>
|
||||
`;
|
||||
}
|
||||
@@ -63,7 +68,7 @@ export class HaEntityToggle extends LitElement {
|
||||
this._isOn ? "off" : "on"
|
||||
}`}
|
||||
.checked=${this._isOn}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
|
||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||
@change=${this._toggleChanged}
|
||||
></ha-switch>`;
|
||||
|
||||
@@ -156,10 +161,11 @@ export class HaEntityToggle extends LitElement {
|
||||
min-width: 38px;
|
||||
}
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 40px;
|
||||
color: var(--ha-icon-button-inactive-color, var(--primary-text-color));
|
||||
transition: color 0.5s;
|
||||
}
|
||||
ha-icon-button[state-active] {
|
||||
ha-icon-button.state-active {
|
||||
color: var(--ha-icon-button-active-color, var(--primary-color));
|
||||
}
|
||||
ha-switch {
|
||||
|
@@ -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 {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -76,54 +64,24 @@ export class HaStatisticPicker extends LitElement {
|
||||
id: string;
|
||||
name: string;
|
||||
state?: HassEntity;
|
||||
// eslint-disable-next-line lit/prefer-static-styles
|
||||
}> = (item) => html`<style>
|
||||
paper-icon-item {
|
||||
padding: 0;
|
||||
margin: -8px;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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>`;
|
||||
}> = (item) => html`<mwc-list-item graphic="avatar" twoline>
|
||||
${item.state
|
||||
? html`<state-badge slot="graphic" .stateObj=${item.state}></state-badge>`
|
||||
: ""}
|
||||
<span>${item.name}</span>
|
||||
<span slot="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
|
||||
>
|
||||
</mwc-list-item>`;
|
||||
|
||||
private _getStatistics = memoizeOne(
|
||||
(
|
||||
@@ -293,19 +251,6 @@ export class HaStatisticPicker extends LitElement {
|
||||
fireEvent(this, "change");
|
||||
}, 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 {
|
||||
|
@@ -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 { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { PolymerChangedEvent } from "../../polymer-types";
|
||||
@@ -103,6 +103,20 @@ class HaStatisticsPicker extends LitElement {
|
||||
|
||||
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 {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user