mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-11 05:59:40 +00:00
Compare commits
446 Commits
20180920.0
...
20121207.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6c5b274792 | ||
![]() |
3094b08c5f | ||
![]() |
181539baac | ||
![]() |
2289773e36 | ||
![]() |
1ecb138ec5 | ||
![]() |
3d67d9eba3 | ||
![]() |
0fd3c03764 | ||
![]() |
6aca1d0d54 | ||
![]() |
601bbfd88e | ||
![]() |
f600b0522c | ||
![]() |
647a33ea61 | ||
![]() |
64d59fedc8 | ||
![]() |
51d592ba0d | ||
![]() |
eaaf841a87 | ||
![]() |
c6542e383c | ||
![]() |
6e3c2bfd6a | ||
![]() |
be3bfc7aa4 | ||
![]() |
bbe90c1683 | ||
![]() |
2fe1d04eb0 | ||
![]() |
f5022f4e1e | ||
![]() |
fdbb06de19 | ||
![]() |
0cd4980f44 | ||
![]() |
2d0f14d078 | ||
![]() |
9711068f8b | ||
![]() |
fb180c7b9b | ||
![]() |
5947bd6d74 | ||
![]() |
7e584402ea | ||
![]() |
3f113da056 | ||
![]() |
bfef3a96c8 | ||
![]() |
de3a467697 | ||
![]() |
0f895fd3a1 | ||
![]() |
16cc3adcff | ||
![]() |
e2e002b9a9 | ||
![]() |
8274284294 | ||
![]() |
1d7f574b9b | ||
![]() |
f680832f78 | ||
![]() |
77711ea711 | ||
![]() |
f1a6122699 | ||
![]() |
5fec881c39 | ||
![]() |
5dc05129ef | ||
![]() |
f461ad6d31 | ||
![]() |
57b5db4f43 | ||
![]() |
d015fe5160 | ||
![]() |
f7e3f4a828 | ||
![]() |
f3b8d66f4f | ||
![]() |
8ae03dd1ff | ||
![]() |
0e6f6ddbda | ||
![]() |
882c503fa9 | ||
![]() |
22eb6c6a8d | ||
![]() |
8e9ff46bab | ||
![]() |
023d8ad893 | ||
![]() |
6b730b7c40 | ||
![]() |
90cea56a1e | ||
![]() |
5e43d9b6b7 | ||
![]() |
e4cac86690 | ||
![]() |
a249289211 | ||
![]() |
8ecfd9780f | ||
![]() |
913cd2b3d4 | ||
![]() |
c02b7a33fe | ||
![]() |
7a0b2060d4 | ||
![]() |
afe9056725 | ||
![]() |
49be2ad013 | ||
![]() |
230ec51de5 | ||
![]() |
b37ea482d3 | ||
![]() |
bf69c8ce46 | ||
![]() |
d2741af24b | ||
![]() |
f04f58ac88 | ||
![]() |
8757dbb664 | ||
![]() |
ffc7f9706d | ||
![]() |
4487c3dc1a | ||
![]() |
97f5d8e7e2 | ||
![]() |
1cc6e09953 | ||
![]() |
3752530f96 | ||
![]() |
21be35bc46 | ||
![]() |
bb8ec4b2ef | ||
![]() |
278ea184cc | ||
![]() |
0b17a85c3b | ||
![]() |
0be0e9792f | ||
![]() |
14409ff5b7 | ||
![]() |
d24bc3c07c | ||
![]() |
5ab419534c | ||
![]() |
07b65f37db | ||
![]() |
8ad5280501 | ||
![]() |
69df6179bb | ||
![]() |
b939ae6ab4 | ||
![]() |
101a364a83 | ||
![]() |
412b7595d2 | ||
![]() |
a7ab652dd3 | ||
![]() |
d41a4cf78b | ||
![]() |
785ed6f9db | ||
![]() |
6885abd234 | ||
![]() |
3497cb892e | ||
![]() |
a82561355c | ||
![]() |
f054cdc9ef | ||
![]() |
463c7eae54 | ||
![]() |
cbb703e5c1 | ||
![]() |
e4dc1884f8 | ||
![]() |
f72a2b7ef8 | ||
![]() |
39819c5c58 | ||
![]() |
49542c49fa | ||
![]() |
86e501f0aa | ||
![]() |
2ca3a784e2 | ||
![]() |
c01bd57ba5 | ||
![]() |
ba5d224080 | ||
![]() |
5da16db81b | ||
![]() |
a9704b110d | ||
![]() |
b8f048d96a | ||
![]() |
c20a285003 | ||
![]() |
07cf1141c5 | ||
![]() |
ef2aa2ea6f | ||
![]() |
2058e0d3fb | ||
![]() |
773711a2d5 | ||
![]() |
0bb85bc895 | ||
![]() |
9a9986cf17 | ||
![]() |
1bb62bfc05 | ||
![]() |
f92f89e8e8 | ||
![]() |
b1a50aa0e0 | ||
![]() |
8c2a2fc043 | ||
![]() |
adb39fd820 | ||
![]() |
8a9762dd93 | ||
![]() |
4407da9364 | ||
![]() |
b533e4d093 | ||
![]() |
239ec5fb53 | ||
![]() |
d974d5dc52 | ||
![]() |
2076949289 | ||
![]() |
65bd7fd64f | ||
![]() |
1f0c7297ce | ||
![]() |
efbd97f9a4 | ||
![]() |
2ccfccc23f | ||
![]() |
acbcb6bd45 | ||
![]() |
e580dbe7f2 | ||
![]() |
9f55678cb3 | ||
![]() |
9c2b85dd6e | ||
![]() |
cb640c2e71 | ||
![]() |
56bdb6e352 | ||
![]() |
81e1e5be8f | ||
![]() |
6c44a92e2c | ||
![]() |
9596f737e8 | ||
![]() |
ad5f815273 | ||
![]() |
4a893d96a0 | ||
![]() |
59a681fcb7 | ||
![]() |
787ea885cc | ||
![]() |
a26a37233b | ||
![]() |
c1e3259b08 | ||
![]() |
9c735bb088 | ||
![]() |
10092dcadf | ||
![]() |
d31cea70bc | ||
![]() |
b04ab6faa1 | ||
![]() |
23163b3095 | ||
![]() |
849d7d2d95 | ||
![]() |
a58a324073 | ||
![]() |
7c2135f444 | ||
![]() |
f9719957b0 | ||
![]() |
9ce74e2da1 | ||
![]() |
14b959b91b | ||
![]() |
e2b9893b17 | ||
![]() |
54e43758d3 | ||
![]() |
92af45d7fd | ||
![]() |
5891a6ee7d | ||
![]() |
c10e409634 | ||
![]() |
6432207bf1 | ||
![]() |
935639e5e0 | ||
![]() |
cdb2093ea6 | ||
![]() |
856ef34964 | ||
![]() |
cf19ceb193 | ||
![]() |
e5fe2950af | ||
![]() |
1ca242405b | ||
![]() |
bcbf0ba75a | ||
![]() |
4810042373 | ||
![]() |
e1c90d74e3 | ||
![]() |
984570c55b | ||
![]() |
f489d88be4 | ||
![]() |
6a84395303 | ||
![]() |
a3847ddd2a | ||
![]() |
dc0f023754 | ||
![]() |
89677577ef | ||
![]() |
0922314134 | ||
![]() |
c68604d1fe | ||
![]() |
372cfdecf4 | ||
![]() |
ef40a0ceea | ||
![]() |
343d18241b | ||
![]() |
2ecb6e0f9e | ||
![]() |
38b8e5e7b7 | ||
![]() |
058f8d178e | ||
![]() |
fbc1a722bd | ||
![]() |
727cfe92e3 | ||
![]() |
4bcb13486e | ||
![]() |
4aa8603ebf | ||
![]() |
ba33c8a456 | ||
![]() |
6f4cd88988 | ||
![]() |
1f2deff6f0 | ||
![]() |
575882be5a | ||
![]() |
d591c45e4d | ||
![]() |
f9b06adc9f | ||
![]() |
6cc67dc790 | ||
![]() |
c0c7c0f41a | ||
![]() |
eb505d4bd7 | ||
![]() |
aebd1a1be1 | ||
![]() |
447c06d817 | ||
![]() |
ce78131258 | ||
![]() |
acab465c96 | ||
![]() |
4ea83b8bd5 | ||
![]() |
094eb632f2 | ||
![]() |
03b1e40593 | ||
![]() |
2164b629cf | ||
![]() |
a081047008 | ||
![]() |
2e395c1b0d | ||
![]() |
520e03a612 | ||
![]() |
a771a44557 | ||
![]() |
38bfe8c8de | ||
![]() |
7ca2ef4c4c | ||
![]() |
de5f02d706 | ||
![]() |
6f7ddef4a4 | ||
![]() |
d78b5fac73 | ||
![]() |
7cf65ba066 | ||
![]() |
91966f676a | ||
![]() |
5a1ca3855b | ||
![]() |
226203143b | ||
![]() |
a5304115f0 | ||
![]() |
f7458b8d41 | ||
![]() |
410b66d40f | ||
![]() |
1fcf510278 | ||
![]() |
bb4ce278b0 | ||
![]() |
6dac48e5b8 | ||
![]() |
7178d208d3 | ||
![]() |
00935c86d0 | ||
![]() |
3dde78cadf | ||
![]() |
630214ddb9 | ||
![]() |
b3f8781646 | ||
![]() |
b717402d26 | ||
![]() |
0d339e0cba | ||
![]() |
1d014bf6e3 | ||
![]() |
5ab15dc27f | ||
![]() |
c347be6f35 | ||
![]() |
d47c2a6fe0 | ||
![]() |
82eb33a7d4 | ||
![]() |
4f6bae193d | ||
![]() |
b8752c4158 | ||
![]() |
b6d0d777bf | ||
![]() |
8c155d4d0e | ||
![]() |
bdf5d0f5c6 | ||
![]() |
4959b861bd | ||
![]() |
a4fa0ae64b | ||
![]() |
7cd5f36c7a | ||
![]() |
ecfdb16957 | ||
![]() |
d31195fc87 | ||
![]() |
f3ef4cef74 | ||
![]() |
ec6db9c8ca | ||
![]() |
bb483c9d72 | ||
![]() |
414448137a | ||
![]() |
d0acef3ecb | ||
![]() |
5617416932 | ||
![]() |
bf0eb798d9 | ||
![]() |
8afc3812b7 | ||
![]() |
5a7841e6bf | ||
![]() |
2758e86fab | ||
![]() |
d9935a714e | ||
![]() |
d6a9d6829b | ||
![]() |
8b02371786 | ||
![]() |
9a5b692204 | ||
![]() |
0b504c7df2 | ||
![]() |
6cab3bbc8e | ||
![]() |
35194cf345 | ||
![]() |
13c5724d7c | ||
![]() |
156ea62ffa | ||
![]() |
22693bcbcc | ||
![]() |
ba70220659 | ||
![]() |
fc96d33d6a | ||
![]() |
685915e13c | ||
![]() |
c39b17f12c | ||
![]() |
110d9a4cc1 | ||
![]() |
8902328b30 | ||
![]() |
7ff9211dfc | ||
![]() |
17b4f873e7 | ||
![]() |
741c0c08b9 | ||
![]() |
c42d9385d1 | ||
![]() |
8cbd667286 | ||
![]() |
8bf60d502a | ||
![]() |
9f60499a3f | ||
![]() |
56a9ff2b35 | ||
![]() |
8c7b62509b | ||
![]() |
5e61065b64 | ||
![]() |
39dd0524f8 | ||
![]() |
25c6a4d3a6 | ||
![]() |
0b9a4c56a9 | ||
![]() |
faa08f9e1f | ||
![]() |
7fbe0937df | ||
![]() |
66f5e34d52 | ||
![]() |
ddf59c8d5c | ||
![]() |
0856073e85 | ||
![]() |
06aef18d0c | ||
![]() |
be63648238 | ||
![]() |
56e01e66fb | ||
![]() |
772153e58a | ||
![]() |
3882a5aa89 | ||
![]() |
edf8027bf4 | ||
![]() |
2fd459381d | ||
![]() |
76e67d27e7 | ||
![]() |
18be134ad8 | ||
![]() |
1feb9f6a27 | ||
![]() |
337a760e73 | ||
![]() |
54cd412107 | ||
![]() |
cf2171ece1 | ||
![]() |
47fb8a5513 | ||
![]() |
06bf134bd4 | ||
![]() |
cf8899fcbe | ||
![]() |
c05b77961e | ||
![]() |
19c365cd12 | ||
![]() |
29f032087e | ||
![]() |
d0cb7b9724 | ||
![]() |
ad162677a6 | ||
![]() |
fbbbe7d17d | ||
![]() |
ef0d11c042 | ||
![]() |
fc2608980f | ||
![]() |
cc97e82a78 | ||
![]() |
54e3191de6 | ||
![]() |
4f8c8762c7 | ||
![]() |
c190f1986e | ||
![]() |
b418048bc9 | ||
![]() |
0fdd1c74f2 | ||
![]() |
3b1b2b95e7 | ||
![]() |
3bb5484b7f | ||
![]() |
d93c09b27b | ||
![]() |
e7ec18d270 | ||
![]() |
3ebe21e135 | ||
![]() |
aca1ecf1ee | ||
![]() |
e8ef2fdc2c | ||
![]() |
b129d5fb08 | ||
![]() |
11f4564465 | ||
![]() |
c9d140281b | ||
![]() |
fa637a37d5 | ||
![]() |
1589c3fc51 | ||
![]() |
bdc2b31202 | ||
![]() |
0970e1e33c | ||
![]() |
028003dffc | ||
![]() |
1eb4ac7f34 | ||
![]() |
d97e356376 | ||
![]() |
d36352af16 | ||
![]() |
05ae92d5f8 | ||
![]() |
dce612f944 | ||
![]() |
3a196203c3 | ||
![]() |
33578a6289 | ||
![]() |
4c3db2119b | ||
![]() |
4a7ff3cd94 | ||
![]() |
5578580d78 | ||
![]() |
dc1d8366a5 | ||
![]() |
252f0692c8 | ||
![]() |
9d13925280 | ||
![]() |
5462a71f52 | ||
![]() |
42953a0b62 | ||
![]() |
f146a1d80f | ||
![]() |
a113c71de7 | ||
![]() |
1f642f436a | ||
![]() |
62d27a17d5 | ||
![]() |
35941a58a5 | ||
![]() |
2ace2165e0 | ||
![]() |
1cfcacfa9a | ||
![]() |
e020fd1154 | ||
![]() |
1dcc645fec | ||
![]() |
87fba75860 | ||
![]() |
294360d35a | ||
![]() |
a7684d7206 | ||
![]() |
af81ede100 | ||
![]() |
698beedaa2 | ||
![]() |
a6b4cce7f3 | ||
![]() |
ba66ff840f | ||
![]() |
c296f33ba1 | ||
![]() |
e7a49192bd | ||
![]() |
5774d913af | ||
![]() |
b068db3f7a | ||
![]() |
8e49241e7c | ||
![]() |
b8cee5cc9c | ||
![]() |
794808d3a7 | ||
![]() |
48f6d1dfec | ||
![]() |
97e1aae9c0 | ||
![]() |
e2511c5ed3 | ||
![]() |
74bdfc8c2d | ||
![]() |
fbccf23d36 | ||
![]() |
906aaa15a3 | ||
![]() |
0ae1f9c754 | ||
![]() |
f1bd89fd02 | ||
![]() |
3949b47e51 | ||
![]() |
2f6595bca7 | ||
![]() |
3bcd0ddc46 | ||
![]() |
ca93c2cfcd | ||
![]() |
a633e3c553 | ||
![]() |
bef2731207 | ||
![]() |
ee53ee4077 | ||
![]() |
34bfc12647 | ||
![]() |
3b425c3e14 | ||
![]() |
69eb007ea2 | ||
![]() |
90c3350d40 | ||
![]() |
a7ddbd72b3 | ||
![]() |
5a2ee98ae2 | ||
![]() |
ea0b5d5e26 | ||
![]() |
af2cb1be1a | ||
![]() |
c30e7ac683 | ||
![]() |
7fb5ac11fd | ||
![]() |
b2dc0ac819 | ||
![]() |
dbdf873ba4 | ||
![]() |
1b70b6e88c | ||
![]() |
c90e13d35e | ||
![]() |
442375f76e | ||
![]() |
81d493e1d6 | ||
![]() |
151f16af47 | ||
![]() |
606a220603 | ||
![]() |
362e758c40 | ||
![]() |
2eb3a55f59 | ||
![]() |
6720c03cbc | ||
![]() |
a76386b53b | ||
![]() |
0243632357 | ||
![]() |
bb24b55a67 | ||
![]() |
f47fd8eec4 | ||
![]() |
f1f9f13d82 | ||
![]() |
d2dd82c0ec | ||
![]() |
e1738b625d | ||
![]() |
7aa37183b6 | ||
![]() |
c91b28a850 | ||
![]() |
70225c1a18 | ||
![]() |
3d9d7d899d | ||
![]() |
f0619c7d13 | ||
![]() |
305fa84d38 | ||
![]() |
edf0e2bedb | ||
![]() |
8be5561d19 | ||
![]() |
f11ca53282 | ||
![]() |
2c25d6cc0a | ||
![]() |
db6ab4d8ec | ||
![]() |
3961eff372 | ||
![]() |
458a7827f9 | ||
![]() |
68d1c77a79 | ||
![]() |
aa97e30d51 | ||
![]() |
9027d7d391 | ||
![]() |
974fd5de0f | ||
![]() |
f9d28fbf83 | ||
![]() |
b944089087 | ||
![]() |
7b6cf28459 | ||
![]() |
be91688efb | ||
![]() |
a5d47231aa | ||
![]() |
01e833a399 | ||
![]() |
c363ba8056 | ||
![]() |
7cec39ba6c | ||
![]() |
3f15cbd2bd | ||
![]() |
3235d33463 | ||
![]() |
140597c7f8 | ||
![]() |
e1407a7d73 | ||
![]() |
03525c010f |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "airbnb-base",
|
||||
"extends": ["airbnb-base", "prettier"],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true,
|
||||
@@ -67,13 +67,11 @@
|
||||
"react/no-find-dom-node": 2,
|
||||
"react/no-is-mounted": 2,
|
||||
"react/jsx-no-comment-textnodes": 2,
|
||||
"react/jsx-curly-spacing": 2,
|
||||
"react/jsx-no-undef": 2,
|
||||
"react/jsx-uses-react": 2,
|
||||
"react/jsx-uses-vars": 2,
|
||||
"no-restricted-syntax": [0, "ForOfStatement"]
|
||||
"no-restricted-syntax": [0, "ForOfStatement"],
|
||||
"prettier/prettier": "error"
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
]
|
||||
"plugins": ["react", "prettier"]
|
||||
}
|
||||
|
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Ensure Docker script files uses LF to support Docker for Windows.
|
||||
# Ensure "git config --global core.autocrlf input" before you clone
|
||||
* text eol=lf
|
||||
*.ts whitespace=error
|
||||
*.js whitespace=error
|
||||
|
||||
*.ico binary
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.zip binary
|
||||
*.mp3 binary
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -22,7 +22,8 @@ bin
|
||||
dist
|
||||
|
||||
# vscode
|
||||
.vscode
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Secrets
|
||||
.lokalise_token
|
||||
|
7
.vscode/extensions.json
vendored
Executable file
7
.vscode/extensions.json
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"eg2.tslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
FROM node:8.9-alpine
|
||||
FROM node:8.11.1-alpine
|
||||
|
||||
# install yarn
|
||||
ENV PATH /root/.yarn/bin:$PATH
|
||||
|
35
config/babel.js
Normal file
35
config/babel.js
Normal file
@@ -0,0 +1,35 @@
|
||||
module.exports.babelLoaderConfig = ({ latestBuild }) => {
|
||||
if (latestBuild === undefined) {
|
||||
throw Error("latestBuild not defined for babel loader config");
|
||||
}
|
||||
return {
|
||||
test: /\.m?js$|\.ts$/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
presets: [
|
||||
!latestBuild && [
|
||||
require("@babel/preset-env").default,
|
||||
{ modules: false },
|
||||
],
|
||||
require("@babel/preset-typescript").default,
|
||||
].filter(Boolean),
|
||||
plugins: [
|
||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
||||
[
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
"@babel/syntax-dynamic-import",
|
||||
[
|
||||
"@babel/transform-react-jsx",
|
||||
{
|
||||
pragma: "h",
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
Binary file not shown.
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -1,12 +1,13 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import JsYaml from 'js-yaml';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import JsYaml from "js-yaml";
|
||||
|
||||
import HomeAssistant from '../data/hass.js';
|
||||
import demoConfig from '../data/demo_config.js';
|
||||
import demoResources from '../data/demo_resources.js';
|
||||
import demoStates from '../data/demo_states.js';
|
||||
import createCardElement from '../../../src/panels/lovelace/common/create-card-element.js';
|
||||
import HomeAssistant from "../data/hass";
|
||||
import { demoConfig } from "../data/demo_config";
|
||||
import { demoServices } from "../data/demo_services";
|
||||
import demoResources from "../data/demo_resources";
|
||||
import demoStates from "../data/demo_states";
|
||||
import createCardElement from "../../../src/panels/lovelace/common/create-card-element";
|
||||
|
||||
class DemoCard extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -37,9 +38,9 @@ class DemoCard extends PolymerElement {
|
||||
}
|
||||
</style>
|
||||
<h2>[[config.heading]]</h2>
|
||||
<div class='root'>
|
||||
<div class="root">
|
||||
<div id="card"></div>
|
||||
<template is='dom-if' if='[[showConfig]]'>
|
||||
<template is="dom-if" if="[[showConfig]]">
|
||||
<pre>[[_trim(config.config)]]</pre>
|
||||
</template>
|
||||
</div>
|
||||
@@ -50,11 +51,11 @@ class DemoCard extends PolymerElement {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
observer: '_hassChanged',
|
||||
observer: "_hassChanged",
|
||||
},
|
||||
config: {
|
||||
type: Object,
|
||||
observer: '_configChanged'
|
||||
observer: "_configChanged",
|
||||
},
|
||||
showConfig: Boolean,
|
||||
};
|
||||
@@ -73,8 +74,9 @@ class DemoCard extends PolymerElement {
|
||||
} else {
|
||||
const hass = new HomeAssistant(demoStates);
|
||||
hass.config = demoConfig;
|
||||
hass.services = demoServices;
|
||||
hass.resources = demoResources;
|
||||
hass.language = 'en';
|
||||
hass.language = "en";
|
||||
hass.states = demoStates;
|
||||
el.hass = hass;
|
||||
}
|
||||
@@ -92,4 +94,4 @@ class DemoCard extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-card', DemoCard);
|
||||
customElements.define("demo-card", DemoCard);
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
|
||||
import './demo-card.js';
|
||||
import "./demo-card";
|
||||
|
||||
class DemoCards extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -25,18 +25,18 @@ class DemoCards extends PolymerElement {
|
||||
}
|
||||
</style>
|
||||
<app-toolbar>
|
||||
<div class='filters'>
|
||||
<paper-toggle-button
|
||||
checked='{{_showConfig}}'
|
||||
>Show config</paper-toggle-button>
|
||||
<div class="filters">
|
||||
<paper-toggle-button checked="{{_showConfig}}"
|
||||
>Show config</paper-toggle-button
|
||||
>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
<div class='cards'>
|
||||
<template is='dom-repeat' items='[[configs]]'>
|
||||
<div class="cards">
|
||||
<template is="dom-repeat" items="[[configs]]">
|
||||
<demo-card
|
||||
config='[[item]]'
|
||||
show-config='[[_showConfig]]'
|
||||
hass='[[hass]]'
|
||||
config="[[item]]"
|
||||
show-config="[[_showConfig]]"
|
||||
hass="[[hass]]"
|
||||
></demo-card>
|
||||
</template>
|
||||
</div>
|
||||
@@ -50,9 +50,9 @@ class DemoCards extends PolymerElement {
|
||||
_showConfig: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-cards', DemoCards);
|
||||
customElements.define("demo-cards", DemoCards);
|
||||
|
@@ -1,10 +1,9 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/state-summary/state-card-content.js';
|
||||
import '../../../src/dialogs/more-info/controls/more-info-content.js';
|
||||
import '../../../src/components/ha-card.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../src/state-summary/state-card-content";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-content";
|
||||
import "../../../src/components/ha-card";
|
||||
|
||||
class DemoMoreInfo extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -51,11 +50,11 @@ class DemoMoreInfo extends PolymerElement {
|
||||
></state-card-content>
|
||||
|
||||
<more-info-content
|
||||
hass='[[hass]]'
|
||||
state-obj='[[_stateObj]]'
|
||||
hass="[[hass]]"
|
||||
state-obj="[[_stateObj]]"
|
||||
></more-info-content>
|
||||
</ha-card>
|
||||
<template is='dom-if' if='[[showConfig]]'>
|
||||
<template is="dom-if" if="[[showConfig]]">
|
||||
<pre>[[_jsonEntity(_stateObj)]]</pre>
|
||||
</template>
|
||||
`;
|
||||
@@ -68,8 +67,8 @@ class DemoMoreInfo extends PolymerElement {
|
||||
showConfig: Boolean,
|
||||
_stateObj: {
|
||||
type: Object,
|
||||
computed: '_getState(entityId, hass.states)'
|
||||
}
|
||||
computed: "_getState(entityId, hass.states)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,7 +81,7 @@ class DemoMoreInfo extends PolymerElement {
|
||||
// (it sucks, we will remove in the future)
|
||||
const tmp = {};
|
||||
Object.keys(stateObj).forEach((key) => {
|
||||
if (key[0] !== '_') {
|
||||
if (key[0] !== "_") {
|
||||
tmp[key] = stateObj[key];
|
||||
}
|
||||
});
|
||||
@@ -90,4 +89,4 @@ class DemoMoreInfo extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-more-info', DemoMoreInfo);
|
||||
customElements.define("demo-more-info", DemoMoreInfo);
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
|
||||
import './demo-more-info.js';
|
||||
import "./demo-more-info";
|
||||
|
||||
class DemoMoreInfos extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -25,18 +25,18 @@ class DemoMoreInfos extends PolymerElement {
|
||||
}
|
||||
</style>
|
||||
<app-toolbar>
|
||||
<div class='filters'>
|
||||
<paper-toggle-button
|
||||
checked='{{_showConfig}}'
|
||||
>Show entity</paper-toggle-button>
|
||||
<div class="filters">
|
||||
<paper-toggle-button checked="{{_showConfig}}"
|
||||
>Show entity</paper-toggle-button
|
||||
>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
<div class='cards'>
|
||||
<template is='dom-repeat' items='[[entities]]'>
|
||||
<div class="cards">
|
||||
<template is="dom-repeat" items="[[entities]]">
|
||||
<demo-more-info
|
||||
entity-id='[[item]]'
|
||||
show-config='[[_showConfig]]'
|
||||
hass='[[hass]]'
|
||||
entity-id="[[item]]"
|
||||
show-config="[[_showConfig]]"
|
||||
hass="[[hass]]"
|
||||
></demo-more-info>
|
||||
</template>
|
||||
</div>
|
||||
@@ -50,9 +50,9 @@ class DemoMoreInfos extends PolymerElement {
|
||||
_showConfig: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-more-infos', DemoMoreInfos);
|
||||
customElements.define("demo-more-infos", DemoMoreInfos);
|
||||
|
@@ -1,175 +1,11 @@
|
||||
export default {
|
||||
core: {
|
||||
export const demoConfig = {
|
||||
elevation: 300,
|
||||
latitude: 51.5287352,
|
||||
longitude: -0.381773,
|
||||
unit_system: {
|
||||
length: 'km',
|
||||
mass: 'kg',
|
||||
temperature: '°C',
|
||||
volume: 'L'
|
||||
}
|
||||
length: "km",
|
||||
mass: "kg",
|
||||
temperature: "°C",
|
||||
volume: "L",
|
||||
},
|
||||
services: {
|
||||
configurator: [
|
||||
'configure'
|
||||
],
|
||||
tts: [
|
||||
'demo_say',
|
||||
'clear_cache'
|
||||
],
|
||||
cover: [
|
||||
'open_cover',
|
||||
'close_cover',
|
||||
'open_cover_tilt',
|
||||
'close_cover_tilt',
|
||||
'set_cover_tilt_position',
|
||||
'set_cover_position',
|
||||
'stop_cover_tilt',
|
||||
'stop_cover'
|
||||
],
|
||||
group: [
|
||||
'set',
|
||||
'reload',
|
||||
'remove',
|
||||
'set_visibility'
|
||||
],
|
||||
alarm_control_panel: [
|
||||
'alarm_arm_night',
|
||||
'alarm_disarm',
|
||||
'alarm_trigger',
|
||||
'alarm_arm_home',
|
||||
'alarm_arm_away',
|
||||
'alarm_arm_custom_bypass'
|
||||
],
|
||||
conversation: [
|
||||
'process'
|
||||
],
|
||||
notify: [
|
||||
'demo_test_target_name',
|
||||
'notify'
|
||||
],
|
||||
lock: [
|
||||
'open',
|
||||
'lock',
|
||||
'unlock'
|
||||
],
|
||||
input_select: [
|
||||
'select_previous',
|
||||
'set_options',
|
||||
'select_next',
|
||||
'select_option'
|
||||
],
|
||||
recorder: [
|
||||
'purge'
|
||||
],
|
||||
persistent_notification: [
|
||||
'create',
|
||||
'dismiss'
|
||||
],
|
||||
timer: [
|
||||
'pause',
|
||||
'cancel',
|
||||
'finish',
|
||||
'start'
|
||||
],
|
||||
input_boolean: [
|
||||
'turn_off',
|
||||
'toggle',
|
||||
'turn_on'
|
||||
],
|
||||
fan: [
|
||||
'set_speed',
|
||||
'turn_on',
|
||||
'turn_off',
|
||||
'set_direction',
|
||||
'oscillate',
|
||||
'toggle'
|
||||
],
|
||||
climate: [
|
||||
'set_humidity',
|
||||
'set_operation_mode',
|
||||
'set_aux_heat',
|
||||
'turn_on',
|
||||
'set_hold_mode',
|
||||
'set_away_mode',
|
||||
'turn_off',
|
||||
'set_fan_mode',
|
||||
'set_temperature',
|
||||
'set_swing_mode'
|
||||
],
|
||||
switch: [
|
||||
'turn_off',
|
||||
'toggle',
|
||||
'turn_on'
|
||||
],
|
||||
script: [
|
||||
'turn_off',
|
||||
'demo',
|
||||
'reload',
|
||||
'toggle',
|
||||
'turn_on'
|
||||
],
|
||||
scene: [
|
||||
'turn_on'
|
||||
],
|
||||
system_log: [
|
||||
'clear',
|
||||
'write'
|
||||
],
|
||||
camera: [
|
||||
'disable_motion_detection',
|
||||
'enable_motion_detection',
|
||||
'snapshot'
|
||||
],
|
||||
image_processing: [
|
||||
'scan'
|
||||
],
|
||||
media_player: [
|
||||
'media_previous_track',
|
||||
'clear_playlist',
|
||||
'shuffle_set',
|
||||
'media_seek',
|
||||
'turn_on',
|
||||
'media_play_pause',
|
||||
'media_next_track',
|
||||
'media_pause',
|
||||
'volume_down',
|
||||
'volume_set',
|
||||
'media_stop',
|
||||
'toggle',
|
||||
'media_play',
|
||||
'play_media',
|
||||
'volume_mute',
|
||||
'turn_off',
|
||||
'select_sound_mode',
|
||||
'select_source',
|
||||
'volume_up'
|
||||
],
|
||||
input_number: [
|
||||
'set_value',
|
||||
'increment',
|
||||
'decrement'
|
||||
],
|
||||
device_tracker: [
|
||||
'see'
|
||||
],
|
||||
homeassistant: [
|
||||
'stop',
|
||||
'check_config',
|
||||
'reload_core_config',
|
||||
'turn_on',
|
||||
'turn_off',
|
||||
'restart',
|
||||
'toggle'
|
||||
],
|
||||
light: [
|
||||
'turn_off',
|
||||
'toggle',
|
||||
'turn_on'
|
||||
],
|
||||
input_text: [
|
||||
'set_value'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
@@ -1,253 +1,264 @@
|
||||
export default {
|
||||
en: {
|
||||
'state.default.off': 'Off',
|
||||
'state.default.on': 'On',
|
||||
'state.default.unknown': 'Unknown',
|
||||
'state.default.unavailable': 'Unavailable',
|
||||
'state.alarm_control_panel.armed': 'Armed',
|
||||
'state.alarm_control_panel.disarmed': 'Disarmed',
|
||||
'state.alarm_control_panel.armed_home': 'Armed home',
|
||||
'state.alarm_control_panel.armed_away': 'Armed away',
|
||||
'state.alarm_control_panel.armed_night': 'Armed night',
|
||||
'state.alarm_control_panel.armed_custom_bypass': 'Armed custom bypass',
|
||||
'state.alarm_control_panel.pending': 'Pending',
|
||||
'state.alarm_control_panel.arming': 'Arming',
|
||||
'state.alarm_control_panel.disarming': 'Disarming',
|
||||
'state.alarm_control_panel.triggered': 'Triggered',
|
||||
'state.automation.off': 'Off',
|
||||
'state.automation.on': 'On',
|
||||
'state.binary_sensor.default.off': 'Off',
|
||||
'state.binary_sensor.default.on': 'On',
|
||||
'state.binary_sensor.battery.off': 'Normal',
|
||||
'state.binary_sensor.battery.on': 'Low',
|
||||
'state.binary_sensor.cold.off': 'Normal',
|
||||
'state.binary_sensor.cold.on': 'Cold',
|
||||
'state.binary_sensor.connectivity.off': 'Disconnected',
|
||||
'state.binary_sensor.connectivity.on': 'Connected',
|
||||
'state.binary_sensor.door.off': 'Closed',
|
||||
'state.binary_sensor.door.on': 'Open',
|
||||
'state.binary_sensor.garage_door.off': 'Closed',
|
||||
'state.binary_sensor.garage_door.on': 'Open',
|
||||
'state.binary_sensor.gas.off': 'Clear',
|
||||
'state.binary_sensor.gas.on': 'Detected',
|
||||
'state.binary_sensor.heat.off': 'Normal',
|
||||
'state.binary_sensor.heat.on': 'Hot',
|
||||
'state.binary_sensor.lock.off': 'Locked',
|
||||
'state.binary_sensor.lock.on': 'Unlocked',
|
||||
'state.binary_sensor.moisture.off': 'Dry',
|
||||
'state.binary_sensor.moisture.on': 'Wet',
|
||||
'state.binary_sensor.motion.off': 'Clear',
|
||||
'state.binary_sensor.motion.on': 'Detected',
|
||||
'state.binary_sensor.occupancy.off': 'Clear',
|
||||
'state.binary_sensor.occupancy.on': 'Detected',
|
||||
'state.binary_sensor.opening.off': 'Closed',
|
||||
'state.binary_sensor.opening.on': 'Open',
|
||||
'state.binary_sensor.presence.off': 'Away',
|
||||
'state.binary_sensor.presence.on': 'Home',
|
||||
'state.binary_sensor.problem.off': 'OK',
|
||||
'state.binary_sensor.problem.on': 'Problem',
|
||||
'state.binary_sensor.safety.off': 'Safe',
|
||||
'state.binary_sensor.safety.on': 'Unsafe',
|
||||
'state.binary_sensor.smoke.off': 'Clear',
|
||||
'state.binary_sensor.smoke.on': 'Detected',
|
||||
'state.binary_sensor.sound.off': 'Clear',
|
||||
'state.binary_sensor.sound.on': 'Detected',
|
||||
'state.binary_sensor.vibration.off': 'Clear',
|
||||
'state.binary_sensor.vibration.on': 'Detected',
|
||||
'state.binary_sensor.window.off': 'Closed',
|
||||
'state.binary_sensor.window.on': 'Open',
|
||||
'state.calendar.off': 'Off',
|
||||
'state.calendar.on': 'On',
|
||||
'state.camera.recording': 'Recording',
|
||||
'state.camera.streaming': 'Streaming',
|
||||
'state.camera.idle': 'Idle',
|
||||
'state.climate.off': 'Off',
|
||||
'state.climate.on': 'On',
|
||||
'state.climate.heat': 'Heat',
|
||||
'state.climate.cool': 'Cool',
|
||||
'state.climate.idle': 'Idle',
|
||||
'state.climate.auto': 'Auto',
|
||||
'state.climate.dry': 'Dry',
|
||||
'state.climate.fan_only': 'Fan only',
|
||||
'state.climate.eco': 'Eco',
|
||||
'state.climate.electric': 'Electric',
|
||||
'state.climate.performance': 'Performance',
|
||||
'state.climate.high_demand': 'High demand',
|
||||
'state.climate.heat_pump': 'Heat pump',
|
||||
'state.climate.gas': 'Gas',
|
||||
'state.configurator.configure': 'Configure',
|
||||
'state.configurator.configured': 'Configured',
|
||||
'state.cover.open': 'Open',
|
||||
'state.cover.opening': 'Opening',
|
||||
'state.cover.closed': 'Closed',
|
||||
'state.cover.closing': 'Closing',
|
||||
'state.cover.stopped': 'Stopped',
|
||||
'state.device_tracker.home': 'Home',
|
||||
'state.device_tracker.not_home': 'Away',
|
||||
'state.fan.off': 'Off',
|
||||
'state.fan.on': 'On',
|
||||
'state.group.off': 'Off',
|
||||
'state.group.on': 'On',
|
||||
'state.group.home': 'Home',
|
||||
'state.group.not_home': 'Away',
|
||||
'state.group.open': 'Open',
|
||||
'state.group.opening': 'Opening',
|
||||
'state.group.closed': 'Closed',
|
||||
'state.group.closing': 'Closing',
|
||||
'state.group.stopped': 'Stopped',
|
||||
'state.group.locked': 'Locked',
|
||||
'state.group.unlocked': 'Unlocked',
|
||||
'state.group.ok': 'OK',
|
||||
'state.group.problem': 'Problem',
|
||||
'state.input_boolean.off': 'Off',
|
||||
'state.input_boolean.on': 'On',
|
||||
'state.light.off': 'Off',
|
||||
'state.light.on': 'On',
|
||||
'state.lock.locked': 'Locked',
|
||||
'state.lock.unlocked': 'Unlocked',
|
||||
'state.media_player.off': 'Off',
|
||||
'state.media_player.on': 'On',
|
||||
'state.media_player.playing': 'Playing',
|
||||
'state.media_player.paused': 'Paused',
|
||||
'state.media_player.idle': 'Idle',
|
||||
'state.media_player.standby': 'Standby',
|
||||
'state.plant.ok': 'OK',
|
||||
'state.plant.problem': 'Problem',
|
||||
'state.remote.off': 'Off',
|
||||
'state.remote.on': 'On',
|
||||
'state.scene.scening': 'Scening',
|
||||
'state.script.off': 'Off',
|
||||
'state.script.on': 'On',
|
||||
'state.sensor.off': 'Off',
|
||||
'state.sensor.on': 'On',
|
||||
'state.sun.above_horizon': 'Above horizon',
|
||||
'state.sun.below_horizon': 'Below horizon',
|
||||
'state.switch.off': 'Off',
|
||||
'state.switch.on': 'On',
|
||||
'state.weather.clear-night': 'Clear, night',
|
||||
'state.weather.cloudy': 'Cloudy',
|
||||
'state.weather.fog': 'Fog',
|
||||
'state.weather.hail': 'Hail',
|
||||
'state.weather.lightning': 'Lightning',
|
||||
'state.weather.lightning-rainy': 'Lightning, rainy',
|
||||
'state.weather.partlycloudy': 'Partly cloudy',
|
||||
'state.weather.pouring': 'Pouring',
|
||||
'state.weather.rainy': 'Rainy',
|
||||
'state.weather.snowy': 'Snowy',
|
||||
'state.weather.snowy-rainy': 'Snowy, rainy',
|
||||
'state.weather.sunny': 'Sunny',
|
||||
'state.weather.windy': 'Windy',
|
||||
'state.weather.windy-variant': 'Windy',
|
||||
'state.zwave.default.initializing': 'Initializing',
|
||||
'state.zwave.default.dead': 'Dead',
|
||||
'state.zwave.default.sleeping': 'Sleeping',
|
||||
'state.zwave.default.ready': 'Ready',
|
||||
'state.zwave.query_stage.initializing': 'Initializing ({query_stage})',
|
||||
'state.zwave.query_stage.dead': 'Dead ({query_stage})',
|
||||
'state_badge.default.unknown': 'Unk',
|
||||
'state_badge.default.unavailable': 'Unavai',
|
||||
'state_badge.alarm_control_panel.armed': 'Armed',
|
||||
'state_badge.alarm_control_panel.disarmed': 'Disarm',
|
||||
'state_badge.alarm_control_panel.armed_home': 'Armed',
|
||||
'state_badge.alarm_control_panel.armed_away': 'Armed',
|
||||
'state_badge.alarm_control_panel.armed_night': 'Armed',
|
||||
'state_badge.alarm_control_panel.armed_custom_bypass': 'Armed',
|
||||
'state_badge.alarm_control_panel.pending': 'Pend',
|
||||
'state_badge.alarm_control_panel.arming': 'Arming',
|
||||
'state_badge.alarm_control_panel.disarming': 'Disarm',
|
||||
'state_badge.alarm_control_panel.triggered': 'Trig',
|
||||
'state_badge.device_tracker.home': 'Home',
|
||||
'state_badge.device_tracker.not_home': 'Away',
|
||||
'ui.card.alarm_control_panel.code': 'Code',
|
||||
'ui.card.alarm_control_panel.clear_code': 'Clear',
|
||||
'ui.card.alarm_control_panel.disarm': 'Disarm',
|
||||
'ui.card.alarm_control_panel.arm_home': 'Arm home',
|
||||
'ui.card.alarm_control_panel.arm_away': 'Arm away',
|
||||
'ui.card.automation.last_triggered': 'Last triggered',
|
||||
'ui.card.automation.trigger': 'Trigger',
|
||||
'ui.card.camera.not_available': 'Image not available',
|
||||
'ui.card.climate.currently': 'Currently',
|
||||
'ui.card.climate.on_off': 'On / off',
|
||||
'ui.card.climate.target_temperature': 'Target temperature',
|
||||
'ui.card.climate.target_humidity': 'Target humidity',
|
||||
'ui.card.climate.operation': 'Operation',
|
||||
'ui.card.climate.fan_mode': 'Fan mode',
|
||||
'ui.card.climate.swing_mode': 'Swing mode',
|
||||
'ui.card.climate.away_mode': 'Away mode',
|
||||
'ui.card.climate.aux_heat': 'Aux heat',
|
||||
'ui.card.cover.position': 'Position',
|
||||
'ui.card.cover.tilt_position': 'Tilt position',
|
||||
'ui.card.fan.speed': 'Speed',
|
||||
'ui.card.fan.oscillate': 'Oscillate',
|
||||
'ui.card.fan.direction': 'Direction',
|
||||
'ui.card.light.brightness': 'Brightness',
|
||||
'ui.card.light.color_temperature': 'Color temperature',
|
||||
'ui.card.light.white_value': 'White value',
|
||||
'ui.card.light.effect': 'Effect',
|
||||
'ui.card.lock.code': 'Code',
|
||||
'ui.card.lock.lock': 'Lock',
|
||||
'ui.card.lock.unlock': 'Unlock',
|
||||
'ui.card.media_player.source': 'Source',
|
||||
'ui.card.media_player.sound_mode': 'Sound mode',
|
||||
'ui.card.media_player.text_to_speak': 'Text to speak',
|
||||
'ui.card.persistent_notification.dismiss': 'Dismiss',
|
||||
'ui.card.scene.activate': 'Activate',
|
||||
'ui.card.script.execute': 'Execute',
|
||||
'ui.card.weather.attributes.air_pressure': 'Air pressure',
|
||||
'ui.card.weather.attributes.humidity': 'Humidity',
|
||||
'ui.card.weather.attributes.temperature': 'Temperature',
|
||||
'ui.card.weather.attributes.visibility': 'Visibility',
|
||||
'ui.card.weather.attributes.wind_speed': 'Wind speed',
|
||||
'ui.card.weather.cardinal_direction.e': 'E',
|
||||
'ui.card.weather.cardinal_direction.ene': 'ENE',
|
||||
'ui.card.weather.cardinal_direction.ese': 'ESE',
|
||||
'ui.card.weather.cardinal_direction.n': 'N',
|
||||
'ui.card.weather.cardinal_direction.ne': 'NE',
|
||||
'ui.card.weather.cardinal_direction.nne': 'NNE',
|
||||
'ui.card.weather.cardinal_direction.nw': 'NW',
|
||||
'ui.card.weather.cardinal_direction.nnw': 'NNW',
|
||||
'ui.card.weather.cardinal_direction.s': 'S',
|
||||
'ui.card.weather.cardinal_direction.se': 'SE',
|
||||
'ui.card.weather.cardinal_direction.sse': 'SSE',
|
||||
'ui.card.weather.cardinal_direction.ssw': 'SSW',
|
||||
'ui.card.weather.cardinal_direction.sw': 'SW',
|
||||
'ui.card.weather.cardinal_direction.w': 'W',
|
||||
'ui.card.weather.cardinal_direction.wnw': 'WNW',
|
||||
'ui.card.weather.cardinal_direction.wsw': 'WSW',
|
||||
'ui.card.weather.forecast': 'Forecast',
|
||||
'ui.common.loading': 'Loading',
|
||||
'ui.common.cancel': 'Cancel',
|
||||
'ui.components.entity.entity-picker.entity': 'Entity',
|
||||
'ui.components.relative_time.past': '{time} ago',
|
||||
'ui.components.relative_time.future': 'In {time}',
|
||||
'ui.components.relative_time.never': 'Never',
|
||||
'ui.components.relative_time.duration.second': '{count} {count, plural,\n one {second}\n other {seconds}\n}',
|
||||
'ui.components.relative_time.duration.minute': '{count} {count, plural,\n one {minute}\n other {minutes}\n}',
|
||||
'ui.components.relative_time.duration.hour': '{count} {count, plural,\n one {hour}\n other {hours}\n}',
|
||||
'ui.components.relative_time.duration.day': '{count} {count, plural,\n one {day}\n other {days}\n}',
|
||||
'ui.components.relative_time.duration.week': '{count} {count, plural,\n one {week}\n other {weeks}\n}',
|
||||
'ui.components.history_charts.loading_history': 'Loading state history...',
|
||||
'ui.components.history_charts.no_history_found': 'No state history found.',
|
||||
'ui.components.service-picker.service': 'Service',
|
||||
'ui.dialogs.more_info_settings.save': 'Save',
|
||||
'ui.dialogs.more_info_settings.name': 'Name',
|
||||
'ui.duration.second': '{count} {count, plural,\n one {second}\n other {seconds}\n}',
|
||||
'ui.duration.minute': '{count} {count, plural,\n one {minute}\n other {minutes}\n}',
|
||||
'ui.duration.hour': '{count} {count, plural,\n one {hour}\n other {hours}\n}',
|
||||
'ui.duration.day': '{count} {count, plural,\n one {day}\n other {days}\n}',
|
||||
'ui.duration.week': '{count} {count, plural,\n one {week}\n other {weeks}\n}',
|
||||
'ui.login-form.password': 'Password',
|
||||
'ui.login-form.remember': 'Remember',
|
||||
'ui.login-form.log_in': 'Log in',
|
||||
'ui.notification_toast.entity_turned_on': 'Turned on {entity}.',
|
||||
'ui.notification_toast.entity_turned_off': 'Turned off {entity}.',
|
||||
'ui.notification_toast.service_called': 'Service {service} called.',
|
||||
'ui.notification_toast.service_call_failed': 'Failed to call service {service}.',
|
||||
'ui.notification_toast.connection_lost': 'Connection lost. Reconnecting…',
|
||||
'ui.sidebar.developer_tools': 'Developer tools',
|
||||
'ui.sidebar.log_out': 'Log out',
|
||||
'attribute.weather.humidity': 'Humidity',
|
||||
'attribute.weather.visibility': 'Visibility',
|
||||
'attribute.weather.wind_speed': 'Wind speed',
|
||||
}
|
||||
"state.default.off": "Off",
|
||||
"state.default.on": "On",
|
||||
"state.default.unknown": "Unknown",
|
||||
"state.default.unavailable": "Unavailable",
|
||||
"state.alarm_control_panel.armed": "Armed",
|
||||
"state.alarm_control_panel.disarmed": "Disarmed",
|
||||
"state.alarm_control_panel.armed_home": "Armed home",
|
||||
"state.alarm_control_panel.armed_away": "Armed away",
|
||||
"state.alarm_control_panel.armed_night": "Armed night",
|
||||
"state.alarm_control_panel.armed_custom_bypass": "Armed custom bypass",
|
||||
"state.alarm_control_panel.pending": "Pending",
|
||||
"state.alarm_control_panel.arming": "Arming",
|
||||
"state.alarm_control_panel.disarming": "Disarming",
|
||||
"state.alarm_control_panel.triggered": "Triggered",
|
||||
"state.automation.off": "Off",
|
||||
"state.automation.on": "On",
|
||||
"state.binary_sensor.default.off": "Off",
|
||||
"state.binary_sensor.default.on": "On",
|
||||
"state.binary_sensor.battery.off": "Normal",
|
||||
"state.binary_sensor.battery.on": "Low",
|
||||
"state.binary_sensor.cold.off": "Normal",
|
||||
"state.binary_sensor.cold.on": "Cold",
|
||||
"state.binary_sensor.connectivity.off": "Disconnected",
|
||||
"state.binary_sensor.connectivity.on": "Connected",
|
||||
"state.binary_sensor.door.off": "Closed",
|
||||
"state.binary_sensor.door.on": "Open",
|
||||
"state.binary_sensor.garage_door.off": "Closed",
|
||||
"state.binary_sensor.garage_door.on": "Open",
|
||||
"state.binary_sensor.gas.off": "Clear",
|
||||
"state.binary_sensor.gas.on": "Detected",
|
||||
"state.binary_sensor.heat.off": "Normal",
|
||||
"state.binary_sensor.heat.on": "Hot",
|
||||
"state.binary_sensor.lock.off": "Locked",
|
||||
"state.binary_sensor.lock.on": "Unlocked",
|
||||
"state.binary_sensor.moisture.off": "Dry",
|
||||
"state.binary_sensor.moisture.on": "Wet",
|
||||
"state.binary_sensor.motion.off": "Clear",
|
||||
"state.binary_sensor.motion.on": "Detected",
|
||||
"state.binary_sensor.occupancy.off": "Clear",
|
||||
"state.binary_sensor.occupancy.on": "Detected",
|
||||
"state.binary_sensor.opening.off": "Closed",
|
||||
"state.binary_sensor.opening.on": "Open",
|
||||
"state.binary_sensor.presence.off": "Away",
|
||||
"state.binary_sensor.presence.on": "Home",
|
||||
"state.binary_sensor.problem.off": "OK",
|
||||
"state.binary_sensor.problem.on": "Problem",
|
||||
"state.binary_sensor.safety.off": "Safe",
|
||||
"state.binary_sensor.safety.on": "Unsafe",
|
||||
"state.binary_sensor.smoke.off": "Clear",
|
||||
"state.binary_sensor.smoke.on": "Detected",
|
||||
"state.binary_sensor.sound.off": "Clear",
|
||||
"state.binary_sensor.sound.on": "Detected",
|
||||
"state.binary_sensor.vibration.off": "Clear",
|
||||
"state.binary_sensor.vibration.on": "Detected",
|
||||
"state.binary_sensor.window.off": "Closed",
|
||||
"state.binary_sensor.window.on": "Open",
|
||||
"state.calendar.off": "Off",
|
||||
"state.calendar.on": "On",
|
||||
"state.camera.recording": "Recording",
|
||||
"state.camera.streaming": "Streaming",
|
||||
"state.camera.idle": "Idle",
|
||||
"state.climate.off": "Off",
|
||||
"state.climate.on": "On",
|
||||
"state.climate.heat": "Heat",
|
||||
"state.climate.cool": "Cool",
|
||||
"state.climate.idle": "Idle",
|
||||
"state.climate.auto": "Auto",
|
||||
"state.climate.dry": "Dry",
|
||||
"state.climate.fan_only": "Fan only",
|
||||
"state.climate.eco": "Eco",
|
||||
"state.climate.electric": "Electric",
|
||||
"state.climate.performance": "Performance",
|
||||
"state.climate.high_demand": "High demand",
|
||||
"state.climate.heat_pump": "Heat pump",
|
||||
"state.climate.gas": "Gas",
|
||||
"state.configurator.configure": "Configure",
|
||||
"state.configurator.configured": "Configured",
|
||||
"state.cover.open": "Open",
|
||||
"state.cover.opening": "Opening",
|
||||
"state.cover.closed": "Closed",
|
||||
"state.cover.closing": "Closing",
|
||||
"state.cover.stopped": "Stopped",
|
||||
"state.device_tracker.home": "Home",
|
||||
"state.device_tracker.not_home": "Away",
|
||||
"state.fan.off": "Off",
|
||||
"state.fan.on": "On",
|
||||
"state.group.off": "Off",
|
||||
"state.group.on": "On",
|
||||
"state.group.home": "Home",
|
||||
"state.group.not_home": "Away",
|
||||
"state.group.open": "Open",
|
||||
"state.group.opening": "Opening",
|
||||
"state.group.closed": "Closed",
|
||||
"state.group.closing": "Closing",
|
||||
"state.group.stopped": "Stopped",
|
||||
"state.group.locked": "Locked",
|
||||
"state.group.unlocked": "Unlocked",
|
||||
"state.group.ok": "OK",
|
||||
"state.group.problem": "Problem",
|
||||
"state.input_boolean.off": "Off",
|
||||
"state.input_boolean.on": "On",
|
||||
"state.light.off": "Off",
|
||||
"state.light.on": "On",
|
||||
"state.lock.locked": "Locked",
|
||||
"state.lock.unlocked": "Unlocked",
|
||||
"state.media_player.off": "Off",
|
||||
"state.media_player.on": "On",
|
||||
"state.media_player.playing": "Playing",
|
||||
"state.media_player.paused": "Paused",
|
||||
"state.media_player.idle": "Idle",
|
||||
"state.media_player.standby": "Standby",
|
||||
"state.plant.ok": "OK",
|
||||
"state.plant.problem": "Problem",
|
||||
"state.remote.off": "Off",
|
||||
"state.remote.on": "On",
|
||||
"state.scene.scening": "Scening",
|
||||
"state.script.off": "Off",
|
||||
"state.script.on": "On",
|
||||
"state.sensor.off": "Off",
|
||||
"state.sensor.on": "On",
|
||||
"state.sun.above_horizon": "Above horizon",
|
||||
"state.sun.below_horizon": "Below horizon",
|
||||
"state.switch.off": "Off",
|
||||
"state.switch.on": "On",
|
||||
"state.weather.clear-night": "Clear, night",
|
||||
"state.weather.cloudy": "Cloudy",
|
||||
"state.weather.fog": "Fog",
|
||||
"state.weather.hail": "Hail",
|
||||
"state.weather.lightning": "Lightning",
|
||||
"state.weather.lightning-rainy": "Lightning, rainy",
|
||||
"state.weather.partlycloudy": "Partly cloudy",
|
||||
"state.weather.pouring": "Pouring",
|
||||
"state.weather.rainy": "Rainy",
|
||||
"state.weather.snowy": "Snowy",
|
||||
"state.weather.snowy-rainy": "Snowy, rainy",
|
||||
"state.weather.sunny": "Sunny",
|
||||
"state.weather.windy": "Windy",
|
||||
"state.weather.windy-variant": "Windy",
|
||||
"state.zwave.default.initializing": "Initializing",
|
||||
"state.zwave.default.dead": "Dead",
|
||||
"state.zwave.default.sleeping": "Sleeping",
|
||||
"state.zwave.default.ready": "Ready",
|
||||
"state.zwave.query_stage.initializing": "Initializing ({query_stage})",
|
||||
"state.zwave.query_stage.dead": "Dead ({query_stage})",
|
||||
"state_badge.default.unknown": "Unk",
|
||||
"state_badge.default.unavailable": "Unavai",
|
||||
"state_badge.alarm_control_panel.armed": "Armed",
|
||||
"state_badge.alarm_control_panel.disarmed": "Disarm",
|
||||
"state_badge.alarm_control_panel.armed_home": "Armed",
|
||||
"state_badge.alarm_control_panel.armed_away": "Armed",
|
||||
"state_badge.alarm_control_panel.armed_night": "Armed",
|
||||
"state_badge.alarm_control_panel.armed_custom_bypass": "Armed",
|
||||
"state_badge.alarm_control_panel.pending": "Pend",
|
||||
"state_badge.alarm_control_panel.arming": "Arming",
|
||||
"state_badge.alarm_control_panel.disarming": "Disarm",
|
||||
"state_badge.alarm_control_panel.triggered": "Trig",
|
||||
"state_badge.device_tracker.home": "Home",
|
||||
"state_badge.device_tracker.not_home": "Away",
|
||||
"ui.card.alarm_control_panel.code": "Code",
|
||||
"ui.card.alarm_control_panel.clear_code": "Clear",
|
||||
"ui.card.alarm_control_panel.disarm": "Disarm",
|
||||
"ui.card.alarm_control_panel.arm_home": "Arm home",
|
||||
"ui.card.alarm_control_panel.arm_away": "Arm away",
|
||||
"ui.card.automation.last_triggered": "Last triggered",
|
||||
"ui.card.automation.trigger": "Trigger",
|
||||
"ui.card.camera.not_available": "Image not available",
|
||||
"ui.card.climate.currently": "Currently",
|
||||
"ui.card.climate.on_off": "On / off",
|
||||
"ui.card.climate.target_temperature": "Target temperature",
|
||||
"ui.card.climate.target_humidity": "Target humidity",
|
||||
"ui.card.climate.operation": "Operation",
|
||||
"ui.card.climate.fan_mode": "Fan mode",
|
||||
"ui.card.climate.swing_mode": "Swing mode",
|
||||
"ui.card.climate.away_mode": "Away mode",
|
||||
"ui.card.climate.aux_heat": "Aux heat",
|
||||
"ui.card.cover.position": "Position",
|
||||
"ui.card.cover.tilt_position": "Tilt position",
|
||||
"ui.card.fan.speed": "Speed",
|
||||
"ui.card.fan.oscillate": "Oscillate",
|
||||
"ui.card.fan.direction": "Direction",
|
||||
"ui.card.light.brightness": "Brightness",
|
||||
"ui.card.light.color_temperature": "Color temperature",
|
||||
"ui.card.light.white_value": "White value",
|
||||
"ui.card.light.effect": "Effect",
|
||||
"ui.card.lock.code": "Code",
|
||||
"ui.card.lock.lock": "Lock",
|
||||
"ui.card.lock.unlock": "Unlock",
|
||||
"ui.card.media_player.source": "Source",
|
||||
"ui.card.media_player.sound_mode": "Sound mode",
|
||||
"ui.card.media_player.text_to_speak": "Text to speak",
|
||||
"ui.card.persistent_notification.dismiss": "Dismiss",
|
||||
"ui.card.scene.activate": "Activate",
|
||||
"ui.card.script.execute": "Execute",
|
||||
"ui.card.weather.attributes.air_pressure": "Air pressure",
|
||||
"ui.card.weather.attributes.humidity": "Humidity",
|
||||
"ui.card.weather.attributes.temperature": "Temperature",
|
||||
"ui.card.weather.attributes.visibility": "Visibility",
|
||||
"ui.card.weather.attributes.wind_speed": "Wind speed",
|
||||
"ui.card.weather.cardinal_direction.e": "E",
|
||||
"ui.card.weather.cardinal_direction.ene": "ENE",
|
||||
"ui.card.weather.cardinal_direction.ese": "ESE",
|
||||
"ui.card.weather.cardinal_direction.n": "N",
|
||||
"ui.card.weather.cardinal_direction.ne": "NE",
|
||||
"ui.card.weather.cardinal_direction.nne": "NNE",
|
||||
"ui.card.weather.cardinal_direction.nw": "NW",
|
||||
"ui.card.weather.cardinal_direction.nnw": "NNW",
|
||||
"ui.card.weather.cardinal_direction.s": "S",
|
||||
"ui.card.weather.cardinal_direction.se": "SE",
|
||||
"ui.card.weather.cardinal_direction.sse": "SSE",
|
||||
"ui.card.weather.cardinal_direction.ssw": "SSW",
|
||||
"ui.card.weather.cardinal_direction.sw": "SW",
|
||||
"ui.card.weather.cardinal_direction.w": "W",
|
||||
"ui.card.weather.cardinal_direction.wnw": "WNW",
|
||||
"ui.card.weather.cardinal_direction.wsw": "WSW",
|
||||
"ui.card.weather.forecast": "Forecast",
|
||||
"ui.common.loading": "Loading",
|
||||
"ui.common.cancel": "Cancel",
|
||||
"ui.components.entity.entity-picker.entity": "Entity",
|
||||
"ui.components.relative_time.past": "{time} ago",
|
||||
"ui.components.relative_time.future": "In {time}",
|
||||
"ui.components.relative_time.never": "Never",
|
||||
"ui.components.relative_time.duration.second":
|
||||
"{count} {count, plural,\n one {second}\n other {seconds}\n}",
|
||||
"ui.components.relative_time.duration.minute":
|
||||
"{count} {count, plural,\n one {minute}\n other {minutes}\n}",
|
||||
"ui.components.relative_time.duration.hour":
|
||||
"{count} {count, plural,\n one {hour}\n other {hours}\n}",
|
||||
"ui.components.relative_time.duration.day":
|
||||
"{count} {count, plural,\n one {day}\n other {days}\n}",
|
||||
"ui.components.relative_time.duration.week":
|
||||
"{count} {count, plural,\n one {week}\n other {weeks}\n}",
|
||||
"ui.components.history_charts.loading_history": "Loading state history...",
|
||||
"ui.components.history_charts.no_history_found": "No state history found.",
|
||||
"ui.components.service-picker.service": "Service",
|
||||
"ui.dialogs.more_info_settings.save": "Save",
|
||||
"ui.dialogs.more_info_settings.name": "Name",
|
||||
"ui.duration.second":
|
||||
"{count} {count, plural,\n one {second}\n other {seconds}\n}",
|
||||
"ui.duration.minute":
|
||||
"{count} {count, plural,\n one {minute}\n other {minutes}\n}",
|
||||
"ui.duration.hour":
|
||||
"{count} {count, plural,\n one {hour}\n other {hours}\n}",
|
||||
"ui.duration.day":
|
||||
"{count} {count, plural,\n one {day}\n other {days}\n}",
|
||||
"ui.duration.week":
|
||||
"{count} {count, plural,\n one {week}\n other {weeks}\n}",
|
||||
"ui.login-form.password": "Password",
|
||||
"ui.login-form.remember": "Remember",
|
||||
"ui.login-form.log_in": "Log in",
|
||||
"ui.notification_toast.entity_turned_on": "Turned on {entity}.",
|
||||
"ui.notification_toast.entity_turned_off": "Turned off {entity}.",
|
||||
"ui.notification_toast.service_called": "Service {service} called.",
|
||||
"ui.notification_toast.service_call_failed":
|
||||
"Failed to call service {service}.",
|
||||
"ui.notification_toast.connection_lost": "Connection lost. Reconnecting…",
|
||||
"ui.sidebar.developer_tools": "Developer tools",
|
||||
"ui.sidebar.log_out": "Log out",
|
||||
"attribute.weather.humidity": "Humidity",
|
||||
"attribute.weather.visibility": "Visibility",
|
||||
"attribute.weather.wind_speed": "Wind speed",
|
||||
},
|
||||
};
|
||||
|
96
gallery/src/data/demo_services.ts
Normal file
96
gallery/src/data/demo_services.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
export const demoServices = {
|
||||
configurator: ["configure"],
|
||||
tts: ["demo_say", "clear_cache"],
|
||||
cover: [
|
||||
"open_cover",
|
||||
"close_cover",
|
||||
"open_cover_tilt",
|
||||
"close_cover_tilt",
|
||||
"set_cover_tilt_position",
|
||||
"set_cover_position",
|
||||
"stop_cover_tilt",
|
||||
"stop_cover",
|
||||
],
|
||||
group: ["set", "reload", "remove", "set_visibility"],
|
||||
alarm_control_panel: [
|
||||
"alarm_arm_night",
|
||||
"alarm_disarm",
|
||||
"alarm_trigger",
|
||||
"alarm_arm_home",
|
||||
"alarm_arm_away",
|
||||
"alarm_arm_custom_bypass",
|
||||
],
|
||||
conversation: ["process"],
|
||||
notify: ["demo_test_target_name", "notify"],
|
||||
lock: ["open", "lock", "unlock"],
|
||||
input_select: [
|
||||
"select_previous",
|
||||
"set_options",
|
||||
"select_next",
|
||||
"select_option",
|
||||
],
|
||||
recorder: ["purge"],
|
||||
persistent_notification: ["create", "dismiss"],
|
||||
timer: ["pause", "cancel", "finish", "start"],
|
||||
input_boolean: ["turn_off", "toggle", "turn_on"],
|
||||
fan: [
|
||||
"set_speed",
|
||||
"turn_on",
|
||||
"turn_off",
|
||||
"set_direction",
|
||||
"oscillate",
|
||||
"toggle",
|
||||
],
|
||||
climate: [
|
||||
"set_humidity",
|
||||
"set_operation_mode",
|
||||
"set_aux_heat",
|
||||
"turn_on",
|
||||
"set_hold_mode",
|
||||
"set_away_mode",
|
||||
"turn_off",
|
||||
"set_fan_mode",
|
||||
"set_temperature",
|
||||
"set_swing_mode",
|
||||
],
|
||||
switch: ["turn_off", "toggle", "turn_on"],
|
||||
script: ["turn_off", "demo", "reload", "toggle", "turn_on"],
|
||||
scene: ["turn_on"],
|
||||
system_log: ["clear", "write"],
|
||||
camera: ["disable_motion_detection", "enable_motion_detection", "snapshot"],
|
||||
image_processing: ["scan"],
|
||||
media_player: [
|
||||
"media_previous_track",
|
||||
"clear_playlist",
|
||||
"shuffle_set",
|
||||
"media_seek",
|
||||
"turn_on",
|
||||
"media_play_pause",
|
||||
"media_next_track",
|
||||
"media_pause",
|
||||
"volume_down",
|
||||
"volume_set",
|
||||
"media_stop",
|
||||
"toggle",
|
||||
"media_play",
|
||||
"play_media",
|
||||
"volume_mute",
|
||||
"turn_off",
|
||||
"select_sound_mode",
|
||||
"select_source",
|
||||
"volume_up",
|
||||
],
|
||||
input_number: ["set_value", "increment", "decrement"],
|
||||
device_tracker: ["see"],
|
||||
homeassistant: [
|
||||
"stop",
|
||||
"check_config",
|
||||
"reload_core_config",
|
||||
"turn_on",
|
||||
"turn_off",
|
||||
"restart",
|
||||
"toggle",
|
||||
],
|
||||
light: ["turn_off", "toggle", "turn_on"],
|
||||
input_text: ["set_value"],
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
const now = () => new Date().toISOString();
|
||||
const randomTime = () =>
|
||||
new Date(new Date().getTime() - (Math.random() * 80 * 60 * 1000)).toISOString();
|
||||
new Date(new Date().getTime() - Math.random() * 80 * 60 * 1000).toISOString();
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
@@ -18,19 +18,23 @@ export class Entity {
|
||||
}
|
||||
|
||||
async handleService(domain, service, data) {
|
||||
console.log(`Unmocked service for ${this.entityId}: ${domain}/${service}`, data);
|
||||
console.log(
|
||||
`Unmocked service for ${this.entityId}: ${domain}/${service}`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
update(state, attributes = {}) {
|
||||
this.state = state;
|
||||
this.lastUpdated = now();
|
||||
this.lastChanged = state === this.state ? this.lastChanged : this.lastUpdated;
|
||||
this.lastChanged =
|
||||
state === this.state ? this.lastChanged : this.lastUpdated;
|
||||
this.attributes = Object.assign({}, this.baseAttributes, attributes);
|
||||
|
||||
console.log('update', this.entityId, this);
|
||||
console.log("update", this.entityId, this);
|
||||
|
||||
this.hass.updateStates({
|
||||
[this.entityId]: this.toState()
|
||||
[this.entityId]: this.toState(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,22 +51,27 @@ export class Entity {
|
||||
|
||||
export class LightEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (!['homeassistant', this.domain].includes(domain)) return;
|
||||
if (!["homeassistant", this.domain].includes(domain)) return;
|
||||
|
||||
if (service === 'turn_on') {
|
||||
if (service === "turn_on") {
|
||||
// eslint-disable-next-line
|
||||
const { brightness, hs_color } = data;
|
||||
this.update('on', Object.assign(this.attributes, {
|
||||
let { brightness, hs_color, brightness_pct } = data;
|
||||
// eslint-disable-next-line
|
||||
brightness = (255 * brightness_pct) / 100;
|
||||
this.update(
|
||||
"on",
|
||||
Object.assign(this.attributes, {
|
||||
brightness,
|
||||
hs_color,
|
||||
}));
|
||||
} else if (service === 'turn_off') {
|
||||
this.update('off');
|
||||
} else if (service === 'toggle') {
|
||||
if (this.state === 'on') {
|
||||
this.handleService(domain, 'turn_off', data);
|
||||
})
|
||||
);
|
||||
} else if (service === "turn_off") {
|
||||
this.update("off");
|
||||
} else if (service === "toggle") {
|
||||
if (this.state === "on") {
|
||||
this.handleService(domain, "turn_off", data);
|
||||
} else {
|
||||
this.handleService(domain, 'turn_on', data);
|
||||
this.handleService(domain, "turn_on", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,10 +81,10 @@ export class LockEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (domain !== this.domain) return;
|
||||
|
||||
if (service === 'lock') {
|
||||
this.update('locked');
|
||||
} else if (service === 'unlock') {
|
||||
this.update('unlocked');
|
||||
if (service === "lock") {
|
||||
this.update("locked");
|
||||
} else if (service === "unlock") {
|
||||
this.update("unlocked");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,28 +93,46 @@ export class CoverEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (domain !== this.domain) return;
|
||||
|
||||
if (service === 'open_cover') {
|
||||
this.update('open');
|
||||
} else if (service === 'close_cover') {
|
||||
this.update('closing');
|
||||
if (service === "open_cover") {
|
||||
this.update("open");
|
||||
} else if (service === "close_cover") {
|
||||
this.update("closing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ClimateEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (domain !== this.domain) return;
|
||||
|
||||
if (service === "set_operation_mode") {
|
||||
this.update(
|
||||
data.operation_mode === "heat" ? "heat" : data.operation_mode,
|
||||
Object.assign(this.attributes, {
|
||||
operation_mode: data.operation_mode,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class GroupEntity extends Entity {
|
||||
async handleService(domain, service, data) {
|
||||
if (!['homeassistant', this.domain].includes(domain)) return;
|
||||
if (!["homeassistant", this.domain].includes(domain)) return;
|
||||
|
||||
await Promise.all(this.attributes.entity_id.map((ent) => {
|
||||
await Promise.all(
|
||||
this.attributes.entity_id.map((ent) => {
|
||||
const entity = this.hass.mockEntities[ent];
|
||||
return entity.handleService(entity.domain, service, data);
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
this.update(service === 'turn_on' ? 'on' : 'off');
|
||||
this.update(service === "turn_on" ? "on" : "off");
|
||||
}
|
||||
}
|
||||
|
||||
const TYPES = {
|
||||
climate: ClimateEntity,
|
||||
light: LightEntity,
|
||||
lock: LockEntity,
|
||||
cover: CoverEntity,
|
||||
|
@@ -9,15 +9,17 @@ export default class FakeHass {
|
||||
}
|
||||
|
||||
async callService(domain, service, serviceData) {
|
||||
console.log('callService', { domain, service, serviceData });
|
||||
console.log("callService", { domain, service, serviceData });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async callWS(msg) {
|
||||
const callback = this._wsCommands[msg.type];
|
||||
return callback ? callback(msg) : Promise.reject({
|
||||
code: 'command_not_mocked',
|
||||
message: 'This command is not implemented in the gallery.',
|
||||
return callback
|
||||
? callback(msg)
|
||||
: Promise.reject({
|
||||
code: "command_not_mocked",
|
||||
message: "This command is not implemented in the gallery.",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,6 +31,6 @@ export default class FakeHass {
|
||||
} else {
|
||||
console.error(`Unknown command: ${msg.type}`);
|
||||
}
|
||||
console.log('sendWS', msg);
|
||||
console.log("sendWS", msg);
|
||||
}
|
||||
}
|
||||
|
@@ -1,48 +1,67 @@
|
||||
import fireEvent from '../../../src/common/dom/fire_event.js';
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
|
||||
import demoConfig from './demo_config.js';
|
||||
import demoResources from './demo_resources.js';
|
||||
import { demoConfig } from "./demo_config";
|
||||
import { demoServices } from "./demo_services";
|
||||
import demoResources from "./demo_resources";
|
||||
|
||||
const ensureArray = val => (Array.isArray(val) ? val : [val]);
|
||||
const ensureArray = (val) => (Array.isArray(val) ? val : [val]);
|
||||
|
||||
export default (elements, { initialStates = {} } = {}) => {
|
||||
elements = ensureArray(elements);
|
||||
|
||||
const wsCommands = {};
|
||||
const restResponses = {};
|
||||
let hass;
|
||||
const entities = {};
|
||||
|
||||
function updateHass(obj) {
|
||||
hass = Object.assign({}, hass, obj);
|
||||
elements.forEach((el) => { el.hass = hass; });
|
||||
elements.forEach((el) => {
|
||||
el.hass = hass;
|
||||
});
|
||||
}
|
||||
|
||||
updateHass({
|
||||
// Home Assistant properties
|
||||
config: demoConfig,
|
||||
language: 'en',
|
||||
services: demoServices,
|
||||
language: "en",
|
||||
resources: demoResources,
|
||||
states: initialStates,
|
||||
themes: {},
|
||||
connection: {
|
||||
subscribeEvents: async (callback, event) => {
|
||||
console.log("subscribeEvents", event);
|
||||
return () => console.log("unsubscribeEvents", event);
|
||||
},
|
||||
},
|
||||
|
||||
// Mock properties
|
||||
mockEntities: entities,
|
||||
|
||||
// Home Assistant functions
|
||||
async callService(domain, service, data) {
|
||||
fireEvent(elements[0], 'show-notification', { message: `Called service ${domain}/${service}` });
|
||||
fireEvent(elements[0], "show-notification", {
|
||||
message: `Called service ${domain}/${service}`,
|
||||
});
|
||||
if (data.entity_id) {
|
||||
await Promise.all(ensureArray(data.entity_id).map(ent =>
|
||||
entities[ent].handleService(domain, service, data)));
|
||||
await Promise.all(
|
||||
ensureArray(data.entity_id).map((ent) =>
|
||||
entities[ent].handleService(domain, service, data)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log('unmocked callService', domain, service, data);
|
||||
console.log("unmocked callService", domain, service, data);
|
||||
}
|
||||
},
|
||||
|
||||
async callWS(msg) {
|
||||
const callback = wsCommands[msg.type];
|
||||
return callback ? callback(msg) : Promise.reject({
|
||||
code: 'command_not_mocked',
|
||||
message: 'This command is not implemented in the gallery.',
|
||||
return callback
|
||||
? callback(msg)
|
||||
: Promise.reject({
|
||||
code: "command_not_mocked",
|
||||
message: "This command is not implemented in the gallery.",
|
||||
});
|
||||
},
|
||||
|
||||
@@ -54,7 +73,15 @@ export default (elements, { initialStates = {} } = {}) => {
|
||||
} else {
|
||||
console.error(`Unknown command: ${msg.type}`);
|
||||
}
|
||||
console.log('sendWS', msg);
|
||||
console.log("sendWS", msg);
|
||||
},
|
||||
|
||||
async callApi(method, path, parameters) {
|
||||
const callback = restResponses[path];
|
||||
|
||||
return callback
|
||||
? callback(method, path, parameters)
|
||||
: Promise.reject(`Mock for {path} is not implemented`);
|
||||
},
|
||||
|
||||
// Mock functions
|
||||
@@ -72,7 +99,13 @@ export default (elements, { initialStates = {} } = {}) => {
|
||||
states[ent.entityId] = ent.toState();
|
||||
});
|
||||
this.updateStates(states);
|
||||
}
|
||||
},
|
||||
mockWS(type, callback) {
|
||||
wsCommands[type] = callback;
|
||||
},
|
||||
mockAPI(path, callback) {
|
||||
restResponses[path] = callback;
|
||||
},
|
||||
});
|
||||
|
||||
return hass;
|
||||
|
79
gallery/src/demos/demo-hui-alarm-panel-card.ts
Normal file
79
gallery/src/demos/demo-hui-alarm-panel-card.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("alarm_control_panel", "alarm", "disarmed", {
|
||||
friendly_name: "Alarm",
|
||||
}),
|
||||
getEntity("alarm_control_panel", "alarm_armed", "armed_home", {
|
||||
friendly_name: "Alarm",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Basic Example",
|
||||
config: `
|
||||
- type: alarm-panel
|
||||
entity: alarm_control_panel.alarm
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With Title",
|
||||
config: `
|
||||
- type: alarm-panel
|
||||
entity: alarm_control_panel.alarm_armed
|
||||
title: My Alarm
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Using only Arm_Home State",
|
||||
config: `
|
||||
- type: alarm-panel
|
||||
entity: alarm_control_panel.alarm
|
||||
states:
|
||||
- arm_home
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Invalid Entity",
|
||||
config: `
|
||||
- type: alarm-panel
|
||||
entity: alarm_control_panel.alarm1
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoAlarmPanelEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
id="demos"
|
||||
hass="[[hass]]"
|
||||
configs="[[_configs]]"
|
||||
></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-alarm-panel-card", DemoAlarmPanelEntity);
|
@@ -1,28 +1,28 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from '../data/entity.js';
|
||||
import provideHass from '../data/provide_hass.js';
|
||||
import '../components/demo-cards.js';
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity('light', 'controller_1', 'on', {
|
||||
friendly_name: 'Controller 1'
|
||||
getEntity("light", "controller_1", "on", {
|
||||
friendly_name: "Controller 1",
|
||||
}),
|
||||
getEntity('light', 'controller_2', 'on', {
|
||||
friendly_name: 'Controller 2'
|
||||
getEntity("light", "controller_2", "on", {
|
||||
friendly_name: "Controller 2",
|
||||
}),
|
||||
getEntity('light', 'floor', 'off', {
|
||||
friendly_name: 'Floor light'
|
||||
getEntity("light", "floor", "off", {
|
||||
friendly_name: "Floor light",
|
||||
}),
|
||||
getEntity('light', 'kitchen', 'on', {
|
||||
friendly_name: 'Kitchen light'
|
||||
getEntity("light", "kitchen", "on", {
|
||||
friendly_name: "Kitchen light",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Controller',
|
||||
heading: "Controller",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
@@ -31,10 +31,10 @@ const CONFIGS = [
|
||||
- type: divider
|
||||
- light.floor
|
||||
- light.kitchen
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Demo',
|
||||
heading: "Demo",
|
||||
config: `
|
||||
- type: conditional
|
||||
conditions:
|
||||
@@ -49,7 +49,7 @@ const CONFIGS = [
|
||||
- light.controller_2
|
||||
- light.floor
|
||||
- light.kitchen
|
||||
`
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -57,8 +57,8 @@ class DemoConditional extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
id='demos'
|
||||
hass='[[hass]]'
|
||||
id="demos"
|
||||
hass="[[hass]]"
|
||||
configs="[[_configs]]"
|
||||
></demo-cards>
|
||||
`;
|
||||
@@ -68,17 +68,17 @@ class DemoConditional extends PolymerElement {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
value: CONFIGS,
|
||||
},
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-conditional-card', DemoConditional);
|
||||
customElements.define("demo-hui-conditional-card", DemoConditional);
|
@@ -1,75 +1,70 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from '../data/entity.js';
|
||||
import provideHass from '../data/provide_hass.js';
|
||||
import '../components/demo-cards.js';
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity('light', 'bed_light', 'on', {
|
||||
friendly_name: 'Bed Light'
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Bed Light",
|
||||
}),
|
||||
getEntity('group', 'kitchen', 'on', {
|
||||
entity_id: [
|
||||
'light.bed_light',
|
||||
],
|
||||
getEntity("group", "kitchen", "on", {
|
||||
entity_id: ["light.bed_light"],
|
||||
order: 8,
|
||||
friendly_name: 'Kitchen'
|
||||
friendly_name: "Kitchen",
|
||||
}),
|
||||
getEntity('lock', 'kitchen_door', 'locked', {
|
||||
friendly_name: 'Kitchen Door'
|
||||
getEntity("lock", "kitchen_door", "locked", {
|
||||
friendly_name: "Kitchen Door",
|
||||
}),
|
||||
getEntity('cover', 'kitchen_window', 'open', {
|
||||
friendly_name: 'Kitchen Window',
|
||||
supported_features: 11
|
||||
getEntity("cover", "kitchen_window", "open", {
|
||||
friendly_name: "Kitchen Window",
|
||||
supported_features: 11,
|
||||
}),
|
||||
getEntity('scene', 'romantic_lights', 'scening', {
|
||||
entity_id: [
|
||||
'light.bed_light',
|
||||
'light.ceiling_lights'
|
||||
],
|
||||
friendly_name: 'Romantic lights'
|
||||
getEntity("scene", "romantic_lights", "scening", {
|
||||
entity_id: ["light.bed_light", "light.ceiling_lights"],
|
||||
friendly_name: "Romantic lights",
|
||||
}),
|
||||
getEntity('device_tracker', 'demo_paulus', 'home', {
|
||||
source_type: 'gps',
|
||||
getEntity("device_tracker", "demo_paulus", "home", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: 'Paulus'
|
||||
friendly_name: "Paulus",
|
||||
}),
|
||||
getEntity('climate', 'ecobee', 'auto', {
|
||||
getEntity("climate", "ecobee", "auto", {
|
||||
current_temperature: 73,
|
||||
min_temp: 45,
|
||||
max_temp: 95,
|
||||
temperature: null,
|
||||
target_temp_high: 75,
|
||||
target_temp_low: 70,
|
||||
fan_mode: 'Auto Low',
|
||||
fan_list: ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off'],
|
||||
operation_mode: 'auto',
|
||||
operation_list: ['heat', 'cool', 'auto', 'off'],
|
||||
hold_mode: 'home',
|
||||
swing_mode: 'Auto',
|
||||
swing_list: ['Auto', '1', '2', '3', 'Off'],
|
||||
unit_of_measurement: '°F',
|
||||
friendly_name: 'Ecobee',
|
||||
supported_features: 1014
|
||||
fan_mode: "Auto Low",
|
||||
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
|
||||
operation_mode: "auto",
|
||||
operation_list: ["heat", "cool", "auto", "off"],
|
||||
hold_mode: "home",
|
||||
swing_mode: "Auto",
|
||||
swing_list: ["Auto", "1", "2", "3", "Off"],
|
||||
unit_of_measurement: "°F",
|
||||
friendly_name: "Ecobee",
|
||||
supported_features: 1014,
|
||||
}),
|
||||
getEntity('input_number', 'noise_allowance', 5, {
|
||||
getEntity("input_number", "noise_allowance", 5, {
|
||||
min: 0,
|
||||
max: 10,
|
||||
step: 1,
|
||||
mode: 'slider',
|
||||
unit_of_measurement: 'dB',
|
||||
friendly_name: 'Allowed Noise',
|
||||
icon: 'mdi:bell-ring'
|
||||
})
|
||||
mode: "slider",
|
||||
unit_of_measurement: "dB",
|
||||
friendly_name: "Allowed Noise",
|
||||
icon: "mdi:bell-ring",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Basic',
|
||||
heading: "Basic",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
@@ -82,10 +77,10 @@ const CONFIGS = [
|
||||
- light.non_existing
|
||||
- climate.ecobee
|
||||
- input_number.noise_allowance
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'With title, toggle-able',
|
||||
heading: "With title, toggle-able",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
@@ -98,10 +93,10 @@ const CONFIGS = [
|
||||
- climate.ecobee
|
||||
- input_number.noise_allowance
|
||||
title: Random group
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'With title, toggle = false',
|
||||
heading: "With title, toggle = false",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
@@ -115,19 +110,19 @@ const CONFIGS = [
|
||||
- input_number.noise_allowance
|
||||
title: Random group
|
||||
show_header_toggle: false
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'With title, can\'t toggle',
|
||||
heading: "With title, can't toggle",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
title: Random group
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Custom name, secondary info, custom icon',
|
||||
heading: "Custom name, secondary info, custom icon",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
@@ -147,17 +142,13 @@ const CONFIGS = [
|
||||
- input_number.noise_allowance
|
||||
title: Random group
|
||||
show_header_toggle: false
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Special rows',
|
||||
heading: "Special rows",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- type: weblink
|
||||
url: http://google.com/
|
||||
icon: mdi:google
|
||||
name: Google
|
||||
- type: call-service
|
||||
icon: mdi:power
|
||||
name: Bed light
|
||||
@@ -165,24 +156,26 @@ const CONFIGS = [
|
||||
service: light.toggle
|
||||
service_data:
|
||||
entity_id: light.bed_light
|
||||
- type: section
|
||||
label: Links
|
||||
- type: weblink
|
||||
url: http://google.com/
|
||||
icon: mdi:google
|
||||
name: Google
|
||||
- type: divider
|
||||
- type: divider
|
||||
style:
|
||||
height: 30px
|
||||
margin: 4px 0
|
||||
background: center / contain url("/images/divider.png") no-repeat
|
||||
`
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoEntities extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
id='demos'
|
||||
hass='[[hass]]'
|
||||
configs="[[_configs]]"
|
||||
></demo-cards>
|
||||
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -190,17 +183,16 @@ class DemoEntities extends PolymerElement {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
value: CONFIGS,
|
||||
},
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-entities-card', DemoEntities);
|
||||
customElements.define("demo-hui-entities-card", DemoEntities);
|
99
gallery/src/demos/demo-hui-entity-button-card.ts
Normal file
99
gallery/src/demos/demo-hui-entity-button-card.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Bed Light",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Basic example",
|
||||
config: `
|
||||
- type: entity-button
|
||||
entity: light.bed_light
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With Name",
|
||||
config: `
|
||||
- type: entity-button
|
||||
name: Bedroom
|
||||
entity: light.bed_light
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With Icon",
|
||||
config: `
|
||||
- type: entity-button
|
||||
entity: light.bed_light
|
||||
icon: mdi:hotel
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Without State",
|
||||
config: `
|
||||
- type: entity-button
|
||||
entity: light.bed_light
|
||||
show_state: false
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom Tap Action (toggle)",
|
||||
config: `
|
||||
- type: entity-button
|
||||
entity: light.bed_light
|
||||
tap_action: toggle
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Running Service",
|
||||
config: `
|
||||
- type: entity-button
|
||||
entity: light.bed_light
|
||||
service: light.toggle
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Invalid Entity",
|
||||
config: `
|
||||
- type: entity-button
|
||||
entity: sensor.invalid_entity
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoEntityButtonEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
id="demos"
|
||||
hass="[[hass]]"
|
||||
configs="[[_configs]]"
|
||||
></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-entity-button-card", DemoEntityButtonEntity);
|
@@ -1,74 +0,0 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Basic',
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- not_home
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'With card config',
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- not_home
|
||||
card:
|
||||
type: glance
|
||||
show_state: false
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Showing single entity conditionally',
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- media_player.lounge_room
|
||||
state_filter:
|
||||
- 'playing'
|
||||
card:
|
||||
type: media-control
|
||||
entity: media_player.lounge_room
|
||||
`
|
||||
}
|
||||
];
|
||||
|
||||
class DemoFilter extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-entity-filter-card', DemoFilter);
|
115
gallery/src/demos/demo-hui-entity-filter-card.ts
Normal file
115
gallery/src/demos/demo-hui-entity-filter-card.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("device_tracker", "demo_paulus", "work", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: "Paulus",
|
||||
}),
|
||||
getEntity("device_tracker", "demo_anne_therese", "school", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: "Anne Therese",
|
||||
}),
|
||||
getEntity("device_tracker", "demo_home_boy", "home", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: "Home Boy",
|
||||
}),
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Bed Light",
|
||||
}),
|
||||
getEntity("light", "kitchen_lights", "on", {
|
||||
friendly_name: "Kitchen Lights",
|
||||
}),
|
||||
getEntity("light", "ceiling_lights", "off", {
|
||||
friendly_name: "Ceiling Lights",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Controller",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Basic",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- home
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With card config",
|
||||
config: `
|
||||
- type: entity-filter
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- light.bed_light
|
||||
- light.ceiling_lights
|
||||
- light.kitchen_lights
|
||||
state_filter:
|
||||
- "on"
|
||||
- not_home
|
||||
card:
|
||||
type: glance
|
||||
show_state: false
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoFilter extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-entity-filter-card", DemoFilter);
|
83
gallery/src/demos/demo-hui-gauge-card.ts
Normal file
83
gallery/src/demos/demo-hui-gauge-card.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../components/demo-cards";
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Basic example",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.brightness
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With title",
|
||||
config: `
|
||||
- type: gauge
|
||||
title: Humidity
|
||||
entity: sensor.outside_humidity
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom Unit of Measurement",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.outside_temperature
|
||||
unit_of_measurement: C
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Setting Severity Levels",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.brightness
|
||||
severity:
|
||||
red: 32
|
||||
green: 0
|
||||
yellow: 23
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Setting Min and Max Values",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.brightness
|
||||
min: 0
|
||||
max: 38
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Invalid Entity",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: sensor.invalid_entity
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Non-Numeric Value",
|
||||
config: `
|
||||
- type: gauge
|
||||
entity: plant.bonsai
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoGaugeEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-gauge-card", DemoGaugeEntity);
|
@@ -1,164 +0,0 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Basic example',
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'With title',
|
||||
config: `
|
||||
- type: glance
|
||||
title: This is glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Custom column width',
|
||||
config: `
|
||||
- type: glance
|
||||
column_width: calc(100% / 7)
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No name',
|
||||
config: `
|
||||
- type: glance
|
||||
show_name: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No state',
|
||||
config: `
|
||||
- type: glance
|
||||
show_state: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'No name and no state',
|
||||
config: `
|
||||
- type: glance
|
||||
show_name: false
|
||||
show_state: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Custom name, custom icon',
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
name: ¯\\_(ツ)_/¯
|
||||
icon: mdi:home-assistant
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- entity: light.kitchen_lights
|
||||
icon: mdi:alarm-light
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Custom tap action',
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- entity: lock.kitchen_door
|
||||
tap_action: toggle
|
||||
- entity: light.ceiling_lights
|
||||
tap_action: call-service
|
||||
service: light.turn_on
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Selectively hidden name',
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- entity: media_player.living_room
|
||||
name:
|
||||
- sun.sun
|
||||
- entity: cover.kitchen_window
|
||||
name:
|
||||
- light.kitchen_lights
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-glance-card', DemoPicEntity);
|
244
gallery/src/demos/demo-hui-glance-card.ts
Normal file
244
gallery/src/demos/demo-hui-glance-card.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("device_tracker", "demo_paulus", "home", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: "Paulus",
|
||||
}),
|
||||
getEntity("media_player", "living_room", "playing", {
|
||||
volume_level: 1,
|
||||
is_volume_muted: false,
|
||||
media_content_id: "eyU3bRy2x44",
|
||||
media_content_type: "movie",
|
||||
media_duration: 300,
|
||||
media_position: 45.017773,
|
||||
media_position_updated_at: "2018-07-19T10:44:45.919514+00:00",
|
||||
media_title: "♥♥ The Best Fireplace Video (3 hours)",
|
||||
app_name: "YouTube",
|
||||
sound_mode: "Dummy Music",
|
||||
sound_mode_list: ["Dummy Music", "Dummy Movie"],
|
||||
shuffle: false,
|
||||
friendly_name: "Living Room",
|
||||
entity_picture:
|
||||
"/api/media_player_proxy/media_player.living_room?token=e925f8db7f7bd1f317e4524dcb8333d60f6019219a3799a22604b5787f243567&cache=bc2ffb49c4f67034",
|
||||
supported_features: 115597,
|
||||
}),
|
||||
getEntity("sun", "sun", "below_horizon", {
|
||||
next_dawn: "2018-07-19T20:48:47+00:00",
|
||||
next_dusk: "2018-07-20T11:46:06+00:00",
|
||||
next_midnight: "2018-07-19T16:17:28+00:00",
|
||||
next_noon: "2018-07-20T04:17:26+00:00",
|
||||
next_rising: "2018-07-19T21:16:31+00:00",
|
||||
next_setting: "2018-07-20T11:18:22+00:00",
|
||||
elevation: 67.69,
|
||||
azimuth: 338.55,
|
||||
friendly_name: "Sun",
|
||||
}),
|
||||
getEntity("cover", "kitchen_window", "open", {
|
||||
friendly_name: "Kitchen Window",
|
||||
supported_features: 11,
|
||||
}),
|
||||
getEntity("light", "kitchen_lights", "on", {
|
||||
friendly_name: "Kitchen Lights",
|
||||
}),
|
||||
getEntity("light", "ceiling_lights", "off", {
|
||||
friendly_name: "Ceiling Lights",
|
||||
}),
|
||||
getEntity("lock", "kitchen_door", "locked", {
|
||||
friendly_name: "Kitchen Door",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Basic example",
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With title",
|
||||
config: `
|
||||
- type: glance
|
||||
title: This is glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom number of columns",
|
||||
config: `
|
||||
- type: glance
|
||||
columns: 7
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "No name",
|
||||
config: `
|
||||
- type: glance
|
||||
show_name: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "No state",
|
||||
config: `
|
||||
- type: glance
|
||||
show_state: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "No name and no state",
|
||||
config: `
|
||||
- type: glance
|
||||
show_name: false
|
||||
show_state: false
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom name, custom icon",
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
name: ¯\\_(ツ)_/¯
|
||||
icon: mdi:home-assistant
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- entity: light.kitchen_lights
|
||||
icon: mdi:alarm-light
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom tap action",
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- entity: lock.kitchen_door
|
||||
tap_action:
|
||||
type: toggle
|
||||
- entity: light.ceiling_lights
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: light.turn_on
|
||||
service_data:
|
||||
entity_id: light.ceiling_lights
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Selectively hidden name",
|
||||
config: `
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- entity: media_player.living_room
|
||||
name:
|
||||
- sun.sun
|
||||
- entity: cover.kitchen_window
|
||||
name:
|
||||
- light.kitchen_lights
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Primary theme",
|
||||
config: `
|
||||
- type: glance
|
||||
theming: primary
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
- media_player.living_room
|
||||
- sun.sun
|
||||
- cover.kitchen_window
|
||||
- light.kitchen_lights
|
||||
- lock.kitchen_door
|
||||
- light.ceiling_lights
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-glance-card", DemoPicEntity);
|
@@ -1,39 +1,39 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
import "../components/demo-cards";
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Without title',
|
||||
heading: "Without title",
|
||||
config: `
|
||||
- type: iframe
|
||||
url: https://embed.windy.com/embed2.html
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'With title',
|
||||
heading: "With title",
|
||||
config: `
|
||||
- type: iframe
|
||||
url: https://embed.windy.com/embed2.html
|
||||
title: Weather radar
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Height-Width 3:4',
|
||||
heading: "Height-Width 3:4",
|
||||
config: `
|
||||
- type: iframe
|
||||
url: https://embed.windy.com/embed2.html
|
||||
aspect_ratio: 75%
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Height-Width 1:1',
|
||||
heading: "Height-Width 1:1",
|
||||
config: `
|
||||
- type: iframe
|
||||
url: https://embed.windy.com/embed2.html
|
||||
aspect_ratio: 100%
|
||||
`
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -48,10 +48,10 @@ class DemoIframe extends PolymerElement {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-iframe-card', DemoIframe);
|
||||
customElements.define("demo-hui-iframe-card", DemoIframe);
|
48
gallery/src/demos/demo-hui-light-card.ts
Normal file
48
gallery/src/demos/demo-hui-light-card.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Bed Light",
|
||||
brightness: 130,
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Basic example",
|
||||
config: `
|
||||
- type: light
|
||||
entity: light.bed_light
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoLightEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-light-card", DemoLightEntity);
|
@@ -1,120 +1,120 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from '../data/entity.js';
|
||||
import provideHass from '../data/provide_hass.js';
|
||||
import '../components/demo-cards.js';
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity('device_tracker', 'demo_paulus', 'not_home', {
|
||||
source_type: 'gps',
|
||||
getEntity("device_tracker", "demo_paulus", "not_home", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: 'Paulus'
|
||||
friendly_name: "Paulus",
|
||||
}),
|
||||
getEntity('device_tracker', 'demo_home_boy', 'home', {
|
||||
source_type: 'gps',
|
||||
getEntity("device_tracker", "demo_home_boy", "home", {
|
||||
source_type: "gps",
|
||||
latitude: 32.87334,
|
||||
longitude: 117.22745,
|
||||
gps_accuracy: 20,
|
||||
battery: 53,
|
||||
friendly_name: 'Home Boy'
|
||||
friendly_name: "Home Boy",
|
||||
}),
|
||||
getEntity('zone', 'home', 'zoning', {
|
||||
getEntity("zone", "home", "zoning", {
|
||||
latitude: 32.87354,
|
||||
longitude: 117.22765,
|
||||
radius: 100,
|
||||
friendly_name: 'Home',
|
||||
icon: 'mdi:home'
|
||||
})
|
||||
friendly_name: "Home",
|
||||
icon: "mdi:home",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Without title',
|
||||
heading: "Without title",
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- device_tracker.demo_home_boy
|
||||
- zone.home
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'With title',
|
||||
heading: "With title",
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- zone.home
|
||||
title: Where is Paulus?
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Height-Width 1:2',
|
||||
heading: "Height-Width 1:2",
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- zone.home
|
||||
aspect_ratio: 50%
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Default Zoom',
|
||||
heading: "Default Zoom",
|
||||
config: `
|
||||
- type: map
|
||||
default_zoom: 12
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- zone.home
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Default Zoom too High',
|
||||
heading: "Default Zoom too High",
|
||||
config: `
|
||||
- type: map
|
||||
default_zoom: 20
|
||||
entities:
|
||||
- entity: device_tracker.demo_paulus
|
||||
- zone.home
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Single Marker',
|
||||
heading: "Single Marker",
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Single Marker Default Zoom',
|
||||
heading: "Single Marker Default Zoom",
|
||||
config: `
|
||||
- type: map
|
||||
default_zoom: 8
|
||||
entities:
|
||||
- device_tracker.demo_paulus
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'No Entities',
|
||||
heading: "No Entities",
|
||||
config: `
|
||||
- type: map
|
||||
entities:
|
||||
- light.bed_light
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'No Entities, Default Zoom',
|
||||
heading: "No Entities, Default Zoom",
|
||||
config: `
|
||||
- type: map
|
||||
default_zoom: 8
|
||||
entities:
|
||||
- light.bed_light
|
||||
`
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -133,17 +133,17 @@ class DemoMap extends PolymerElement {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
value: CONFIGS,
|
||||
},
|
||||
hass: Object
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-map-card', DemoMap);
|
||||
customElements.define("demo-hui-map-card", DemoMap);
|
@@ -1,11 +1,11 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
import "../components/demo-cards";
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'markdown-it demo',
|
||||
heading: "markdown-it demo",
|
||||
config: `
|
||||
- type: markdown
|
||||
content: >
|
||||
@@ -248,7 +248,7 @@ const CONFIGS = [
|
||||
::: warning
|
||||
*here be dragons*
|
||||
:::
|
||||
`
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -263,10 +263,10 @@ class DemoMarkdown extends PolymerElement {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-markdown-card', DemoMarkdown);
|
||||
customElements.define("demo-hui-markdown-card", DemoMarkdown);
|
@@ -1,105 +0,0 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import getEntity from '../data/entity.js';
|
||||
import provideHass from '../data/provide_hass.js';
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity('media_player', 'bedroom', 'playing', {
|
||||
media_content_type: 'movie',
|
||||
media_title: 'Epic sax guy 10 hours',
|
||||
app_name: 'YouTube',
|
||||
supported_features: 32
|
||||
}),
|
||||
getEntity('media_player', 'family_room', 'paused', {
|
||||
media_content_type: 'music',
|
||||
media_title: 'I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)',
|
||||
media_artist: 'Technohead',
|
||||
supported_features: 16417
|
||||
}),
|
||||
getEntity('media_player', 'family_room_no_play', 'paused', {
|
||||
media_content_type: 'movie',
|
||||
media_title: 'Epic sax guy 10 hours',
|
||||
app_name: 'YouTube',
|
||||
supported_features: 33
|
||||
}),
|
||||
getEntity('media_player', 'living_room', 'playing', {
|
||||
media_content_type: 'tvshow',
|
||||
media_title: 'Chapter 1',
|
||||
media_series_title: 'House of Cards',
|
||||
app_name: 'Netflix',
|
||||
supported_features: 1
|
||||
}),
|
||||
getEntity('media_player', 'lounge_room', 'idle', {
|
||||
media_content_type: 'music',
|
||||
media_title: 'I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)',
|
||||
media_artist: 'Technohead',
|
||||
supported_features: 1,
|
||||
}),
|
||||
getEntity('media_player', 'theater', 'off', {
|
||||
media_content_type: 'movie',
|
||||
media_title: 'Epic sax guy 10 hours',
|
||||
app_name: 'YouTube',
|
||||
supported_features: 33
|
||||
}),
|
||||
getEntity('media_player', 'android_cast', 'playing', {
|
||||
media_title: 'Android Screen Casting',
|
||||
app_name: 'Screen Mirroring',
|
||||
supported_features: 21437
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Media Players',
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: media_player.bedroom
|
||||
name: Skip, no pause
|
||||
- entity: media_player.family_room
|
||||
name: Paused, music
|
||||
- entity: media_player.family_room_no_play
|
||||
name: Paused, no play
|
||||
- entity: media_player.living_room
|
||||
name: Pause, No skip, tvshow
|
||||
- entity: media_player.android_cast
|
||||
name: Screen casting
|
||||
- entity: media_player.lounge_room
|
||||
name: Chromcast Idle
|
||||
- entity: media_player.theater
|
||||
name: 'Player Off'
|
||||
`
|
||||
}
|
||||
];
|
||||
|
||||
class DemoHuiMediaPlayerRows extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
id='demos'
|
||||
hass='[[hass]]'
|
||||
configs="[[_configs]]"
|
||||
></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
},
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-media-player-rows', DemoHuiMediaPlayerRows);
|
105
gallery/src/demos/demo-hui-media-player-rows.ts
Normal file
105
gallery/src/demos/demo-hui-media-player-rows.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("media_player", "bedroom", "playing", {
|
||||
media_content_type: "movie",
|
||||
media_title: "Epic sax guy 10 hours",
|
||||
app_name: "YouTube",
|
||||
supported_features: 32,
|
||||
}),
|
||||
getEntity("media_player", "family_room", "paused", {
|
||||
media_content_type: "music",
|
||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||
media_artist: "Technohead",
|
||||
supported_features: 16417,
|
||||
}),
|
||||
getEntity("media_player", "family_room_no_play", "paused", {
|
||||
media_content_type: "movie",
|
||||
media_title: "Epic sax guy 10 hours",
|
||||
app_name: "YouTube",
|
||||
supported_features: 33,
|
||||
}),
|
||||
getEntity("media_player", "living_room", "playing", {
|
||||
media_content_type: "tvshow",
|
||||
media_title: "Chapter 1",
|
||||
media_series_title: "House of Cards",
|
||||
app_name: "Netflix",
|
||||
supported_features: 1,
|
||||
}),
|
||||
getEntity("media_player", "lounge_room", "idle", {
|
||||
media_content_type: "music",
|
||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||
media_artist: "Technohead",
|
||||
supported_features: 1,
|
||||
}),
|
||||
getEntity("media_player", "theater", "off", {
|
||||
media_content_type: "movie",
|
||||
media_title: "Epic sax guy 10 hours",
|
||||
app_name: "YouTube",
|
||||
supported_features: 33,
|
||||
}),
|
||||
getEntity("media_player", "android_cast", "playing", {
|
||||
media_title: "Android Screen Casting",
|
||||
app_name: "Screen Mirroring",
|
||||
supported_features: 21437,
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Media Players",
|
||||
config: `
|
||||
- type: entities
|
||||
entities:
|
||||
- entity: media_player.bedroom
|
||||
name: Skip, no pause
|
||||
- entity: media_player.family_room
|
||||
name: Paused, music
|
||||
- entity: media_player.family_room_no_play
|
||||
name: Paused, no play
|
||||
- entity: media_player.living_room
|
||||
name: Pause, No skip, tvshow
|
||||
- entity: media_player.android_cast
|
||||
name: Screen casting
|
||||
- entity: media_player.lounge_room
|
||||
name: Chromcast Idle
|
||||
- entity: media_player.theater
|
||||
name: 'Player Off'
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoHuiMediaPlayerRows extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
id="demos"
|
||||
hass="[[hass]]"
|
||||
configs="[[_configs]]"
|
||||
></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
hass: Object,
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-media-player-rows", DemoHuiMediaPlayerRows);
|
@@ -1,11 +1,35 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Bed Light",
|
||||
}),
|
||||
getEntity("group", "all_lights", "on", {
|
||||
entity_id: ["light.bed_light"],
|
||||
order: 8,
|
||||
friendly_name: "All Lights",
|
||||
}),
|
||||
getEntity("camera", "demo_camera", "idle", {
|
||||
access_token:
|
||||
"2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
|
||||
friendly_name: "Demo camera",
|
||||
entity_picture:
|
||||
"/api/camera_proxy/camera.demo_camera?token=2f5bb163fb91cd8770a9494fa5e7eab172d8d34f4aba806eb6b59411b8c720b8",
|
||||
}),
|
||||
getEntity("binary_sensor", "movement_backyard", "on", {
|
||||
friendly_name: "Movement Backyard",
|
||||
device_class: "motion",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Card with few elements',
|
||||
heading: "Card with few elements",
|
||||
config: `
|
||||
- type: picture-elements
|
||||
image: /images/floorplan.png
|
||||
@@ -32,7 +56,8 @@ const CONFIGS = [
|
||||
--iron-icon-fill-color: rgba(50, 50, 50, .75)
|
||||
- type: image
|
||||
entity: light.bed_light
|
||||
tap_action: toggle
|
||||
tap_action:
|
||||
action: toggle
|
||||
image: /images/light_bulb_off.png
|
||||
state_image:
|
||||
'on': /images/light_bulb_on.png
|
||||
@@ -49,14 +74,14 @@ const CONFIGS = [
|
||||
style:
|
||||
top: 8%
|
||||
left: 35%
|
||||
`
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoPicElements extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -64,10 +89,16 @@ class DemoPicElements extends PolymerElement {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-picture-elements-card', DemoPicElements);
|
||||
customElements.define("demo-hui-picture-elements-card", DemoPicElements);
|
@@ -1,67 +1,67 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
import "../components/demo-cards";
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'State on',
|
||||
heading: "State on",
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'State off',
|
||||
heading: "State off",
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/bed.png
|
||||
entity: light.bed_light
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Entity unavailable',
|
||||
heading: "Entity unavailable",
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/living_room.png
|
||||
entity: light.non_existing
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Camera entity',
|
||||
heading: "Camera entity",
|
||||
config: `
|
||||
- type: picture-entity
|
||||
entity: camera.demo_camera
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Hidden name',
|
||||
heading: "Hidden name",
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
show_name: false
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Hidden state',
|
||||
heading: "Hidden state",
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
show_state: false
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Both hidden',
|
||||
heading: "Both hidden",
|
||||
config: `
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
show_name: false
|
||||
show_state: false
|
||||
`
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -76,10 +76,10 @@ class DemoPicEntity extends PolymerElement {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-picture-entity-card', DemoPicEntity);
|
||||
customElements.define("demo-hui-picture-entity-card", DemoPicEntity);
|
@@ -1,11 +1,11 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
import "../components/demo-cards";
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Title, dialog, toggle',
|
||||
heading: "Title, dialog, toggle",
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
@@ -15,10 +15,10 @@ const CONFIGS = [
|
||||
- light.ceiling_lights
|
||||
- binary_sensor.movement_backyard
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Title, dialog, no toggle',
|
||||
heading: "Title, dialog, no toggle",
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
@@ -26,10 +26,10 @@ const CONFIGS = [
|
||||
entities:
|
||||
- binary_sensor.movement_backyard
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Title, no dialog, toggle',
|
||||
heading: "Title, no dialog, toggle",
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
@@ -37,10 +37,10 @@ const CONFIGS = [
|
||||
entities:
|
||||
- switch.decorative_lights
|
||||
- light.ceiling_lights
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'No title, dialog, toggle',
|
||||
heading: "No title, dialog, toggle",
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
@@ -49,30 +49,30 @@ const CONFIGS = [
|
||||
- light.ceiling_lights
|
||||
- binary_sensor.movement_backyard
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'No title, dialog, no toggle',
|
||||
heading: "No title, dialog, no toggle",
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
entities:
|
||||
- binary_sensor.movement_backyard
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'No title, no dialog, toggle',
|
||||
heading: "No title, no dialog, toggle",
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
entities:
|
||||
- switch.decorative_lights
|
||||
- light.ceiling_lights
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: 'Custom icon',
|
||||
heading: "Custom icon",
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
@@ -81,7 +81,24 @@ const CONFIGS = [
|
||||
- entity: switch.decorative_lights
|
||||
icon: mdi:power
|
||||
- binary_sensor.basement_floor_wet
|
||||
`
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Custom tap action",
|
||||
config: `
|
||||
- type: picture-glance
|
||||
image: /images/living_room.png
|
||||
title: Living room
|
||||
entity: light.ceiling_lights
|
||||
tap_action:
|
||||
action: toggle
|
||||
entities:
|
||||
- entity: switch.decorative_lights
|
||||
icon: mdi:power
|
||||
tap_action:
|
||||
action: toggle
|
||||
- binary_sensor.basement_floor_wet
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -96,10 +113,10 @@ class DemoPicGlance extends PolymerElement {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-picture-glance-card', DemoPicGlance);
|
||||
customElements.define("demo-hui-picture-glance-card", DemoPicGlance);
|
52
gallery/src/demos/demo-hui-shopping-list-card.ts
Normal file
52
gallery/src/demos/demo-hui-shopping-list-card.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "List example",
|
||||
config: `
|
||||
- type: shopping-list
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "List with title example",
|
||||
config: `
|
||||
- type: shopping-list
|
||||
title: Shopping List
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoShoppingListEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
|
||||
hass.mockAPI("shopping_list", () => [
|
||||
{ name: "list", id: 1, complete: false },
|
||||
{ name: "all", id: 2, complete: false },
|
||||
{ name: "the", id: 3, complete: false },
|
||||
{ name: "things", id: 4, complete: true },
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-shopping-list-card", DemoShoppingListEntity);
|
@@ -1,76 +0,0 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/demo-cards.js';
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: 'Vertical Stack',
|
||||
config: `
|
||||
- type: vertical-stack
|
||||
cards:
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Horizontal Stack',
|
||||
config: `
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
`
|
||||
},
|
||||
{
|
||||
heading: 'Combination of both',
|
||||
config: `
|
||||
- type: vertical-stack
|
||||
cards:
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- type: picture-entity
|
||||
image: /images/bed.png
|
||||
entity: light.bed_light
|
||||
`
|
||||
},
|
||||
];
|
||||
|
||||
class DemoStack extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-hui-stack-card', DemoStack);
|
114
gallery/src/demos/demo-hui-stack-card.ts
Normal file
114
gallery/src/demos/demo-hui-stack-card.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "kitchen_lights", "on", {
|
||||
friendly_name: "Kitchen Lights",
|
||||
}),
|
||||
getEntity("device_tracker", "demo_paulus", "work", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: "Paulus",
|
||||
}),
|
||||
getEntity("device_tracker", "demo_anne_therese", "school", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: "Anne Therese",
|
||||
}),
|
||||
getEntity("device_tracker", "demo_home_boy", "home", {
|
||||
source_type: "gps",
|
||||
latitude: 32.877105,
|
||||
longitude: 117.232185,
|
||||
gps_accuracy: 91,
|
||||
battery: 71,
|
||||
friendly_name: "Home Boy",
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Vertical Stack",
|
||||
config: `
|
||||
- type: vertical-stack
|
||||
cards:
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Horizontal Stack",
|
||||
config: `
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Combination of both",
|
||||
config: `
|
||||
- type: vertical-stack
|
||||
cards:
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: picture-entity
|
||||
image: /images/kitchen.png
|
||||
entity: light.kitchen_lights
|
||||
- type: glance
|
||||
entities:
|
||||
- device_tracker.demo_anne_therese
|
||||
- device_tracker.demo_home_boy
|
||||
- device_tracker.demo_paulus
|
||||
- type: picture-entity
|
||||
image: /images/bed.png
|
||||
entity: light.bed_light
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoStack extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-stack-card", DemoStack);
|
85
gallery/src/demos/demo-hui-thermostat-card.ts
Normal file
85
gallery/src/demos/demo-hui-thermostat-card.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
import "../components/demo-cards";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("climate", "ecobee", "auto", {
|
||||
current_temperature: 73,
|
||||
min_temp: 45,
|
||||
max_temp: 95,
|
||||
temperature: null,
|
||||
target_temp_high: 75,
|
||||
target_temp_low: 70,
|
||||
fan_mode: "Auto Low",
|
||||
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
|
||||
operation_mode: "auto",
|
||||
operation_list: ["heat", "cool", "auto", "off"],
|
||||
hold_mode: "home",
|
||||
swing_mode: "Auto",
|
||||
swing_list: ["Auto", "1", "2", "3", "Off"],
|
||||
friendly_name: "Ecobee",
|
||||
supported_features: 1014,
|
||||
}),
|
||||
getEntity("climate", "nest", "heat", {
|
||||
current_temperature: 17,
|
||||
min_temp: 15,
|
||||
max_temp: 25,
|
||||
temperature: 19,
|
||||
fan_mode: "Auto Low",
|
||||
fan_list: ["On Low", "On High", "Auto Low", "Auto High", "Off"],
|
||||
operation_mode: "heat",
|
||||
operation_list: ["heat", "cool", "auto", "off"],
|
||||
hold_mode: "home",
|
||||
swing_mode: "Auto",
|
||||
swing_list: ["Auto", "1", "2", "3", "Off"],
|
||||
friendly_name: "Nest",
|
||||
supported_features: 1014,
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
{
|
||||
heading: "Range example",
|
||||
config: `
|
||||
- type: thermostat
|
||||
entity: climate.ecobee
|
||||
- type: thermostat
|
||||
entity: climate.nest
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Single temp example",
|
||||
config: `
|
||||
- type: thermostat
|
||||
entity: climate.nest
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoThermostatEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_configs: {
|
||||
type: Object,
|
||||
value: CONFIGS,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this.$.demos);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-thermostat-card", DemoThermostatEntity);
|
@@ -1,60 +0,0 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../../src/dialogs/more-info/controls/more-info-content.js';
|
||||
import '../../../src/components/ha-card.js';
|
||||
|
||||
import getEntity from '../data/entity.js';
|
||||
import provideHass from '../data/provide_hass.js';
|
||||
|
||||
import '../components/demo-more-infos.js';
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
const SUPPORT_BRIGHTNESS = 1;
|
||||
const SUPPORT_COLOR_TEMP = 2;
|
||||
const SUPPORT_EFFECT = 4;
|
||||
const SUPPORT_FLASH = 8;
|
||||
const SUPPORT_COLOR = 16;
|
||||
const SUPPORT_TRANSITION = 32;
|
||||
const SUPPORT_WHITE_VALUE = 128;
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity('light', 'bed_light', 'on', {
|
||||
friendly_name: 'Basic Light'
|
||||
}),
|
||||
getEntity('light', 'kitchen_light', 'on', {
|
||||
friendly_name: 'Brightness Light',
|
||||
brightness: 80,
|
||||
supported_features: SUPPORT_BRIGHTNESS,
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
class DemoMoreInfoLight extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
hass='[[hass]]'
|
||||
entities='[[_entities]]'
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_entities: {
|
||||
type: Array,
|
||||
value: ENTITIES.map(ent => ent.entityId),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('demo-more-info-light', DemoMoreInfoLight);
|
50
gallery/src/demos/demo-more-info-light.ts
Normal file
50
gallery/src/demos/demo-more-info-light.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../src/dialogs/more-info/controls/more-info-content";
|
||||
import "../../../src/components/ha-card";
|
||||
|
||||
import getEntity from "../data/entity";
|
||||
import provideHass from "../data/provide_hass";
|
||||
|
||||
import "../components/demo-more-infos";
|
||||
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
friendly_name: "Basic Light",
|
||||
}),
|
||||
getEntity("light", "kitchen_light", "on", {
|
||||
friendly_name: "Brightness Light",
|
||||
brightness: 80,
|
||||
supported_features: SUPPORT_BRIGHTNESS,
|
||||
}),
|
||||
];
|
||||
|
||||
class DemoMoreInfoLight extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-more-infos
|
||||
hass="[[hass]]"
|
||||
entities="[[_entities]]"
|
||||
></demo-more-infos>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_entities: {
|
||||
type: Array,
|
||||
value: ENTITIES.map((ent) => ent.entityId),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public ready() {
|
||||
super.ready();
|
||||
const hass = provideHass(this);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-more-info-light", DemoMoreInfoLight);
|
79
gallery/src/demos/demo-util-long-press.ts
Normal file
79
gallery/src/demos/demo-util-long-press.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { html, LitElement } from "@polymer/lit-element";
|
||||
import { TemplateResult } from "lit-html";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
|
||||
import "../../../src/components/ha-card";
|
||||
import { longPress } from "../../../src/panels/lovelace/common/directives/long-press-directive";
|
||||
|
||||
export class DemoUtilLongPress extends LitElement {
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
${
|
||||
[1, 2, 3].map(
|
||||
() => html`
|
||||
<ha-card>
|
||||
<paper-button
|
||||
@ha-click="${this._handleTap}"
|
||||
@ha-hold="${this._handleHold}"
|
||||
.longPress="${longPress()}"
|
||||
>
|
||||
(long) press me!
|
||||
</paper-button>
|
||||
|
||||
<textarea></textarea>
|
||||
|
||||
<div>(try pressing and scrolling too!)</div>
|
||||
</ha-card>
|
||||
`
|
||||
)
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleTap(ev: Event) {
|
||||
this._addValue(ev, "tap");
|
||||
}
|
||||
|
||||
private _handleHold(ev: Event) {
|
||||
this._addValue(ev, "hold");
|
||||
}
|
||||
|
||||
private _addValue(ev: Event, value: string) {
|
||||
const area = (ev.currentTarget as HTMLElement)
|
||||
.nextElementSibling! as HTMLTextAreaElement;
|
||||
const now = new Date().toTimeString().split(" ")[0];
|
||||
area.value += `${now}: ${value}\n`;
|
||||
area.scrollTop = area.scrollHeight;
|
||||
}
|
||||
|
||||
private renderStyle() {
|
||||
return html`
|
||||
<style>
|
||||
ha-card {
|
||||
width: 200px;
|
||||
margin: calc(42vh - 140px) auto;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
ha-card:first-of-type {
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-card:last-of-type {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
paper-button {
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 50px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-util-long-press", DemoUtilLongPress);
|
@@ -1,12 +1,12 @@
|
||||
import '@polymer/paper-styles/typography.js';
|
||||
import '@polymer/polymer/lib/elements/dom-if.js';
|
||||
import '@polymer/polymer/lib/elements/dom-repeat.js';
|
||||
import "@polymer/paper-styles/typography";
|
||||
import "@polymer/polymer/lib/elements/dom-if";
|
||||
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||
|
||||
import '../../src/resources/hass-icons.js';
|
||||
import '../../src/resources/ha-style.js';
|
||||
import '../../src/resources/roboto.js';
|
||||
import '../../src/components/ha-iconset-svg.js';
|
||||
import "../../src/resources/hass-icons";
|
||||
import "../../src/resources/ha-style";
|
||||
import "../../src/resources/roboto";
|
||||
import "../../src/components/ha-iconset-svg";
|
||||
|
||||
import './ha-gallery.js';
|
||||
import "./ha-gallery";
|
||||
|
||||
document.body.appendChild(document.createElement('ha-gallery'));
|
||||
document.body.appendChild(document.createElement("ha-gallery"));
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
|
||||
import '@polymer/app-layout/app-header/app-header.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-item/paper-item.js';
|
||||
import '@polymer/paper-item/paper-item-body.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../src/managers/notification-manager.js';
|
||||
import "../../src/managers/notification-manager";
|
||||
|
||||
const DEMOS = require.context('./demos', true, /^(.*\.(js$))[^.]*$/im);
|
||||
const DEMOS = require.context("./demos", true, /^(.*\.(ts$))[^.]*$/im);
|
||||
|
||||
const fixPath = path => path.substr(2, path.length - 5);
|
||||
const fixPath = (path) => path.substr(2, path.length - 5);
|
||||
|
||||
class HaGallery extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -118,6 +118,22 @@ class HaGallery extends PolymerElement {
|
||||
</a>
|
||||
</template>
|
||||
</paper-card>
|
||||
|
||||
<paper-card heading="Util demos">
|
||||
<div class='card-content intro'>
|
||||
<p>
|
||||
Test pages for our utility functions.
|
||||
</p>
|
||||
</div>
|
||||
<template is='dom-repeat' items='[[_utilDemos]]'>
|
||||
<a href='#[[item]]'>
|
||||
<paper-item>
|
||||
<paper-item-body>{{ item }}</paper-item-body>
|
||||
<iron-icon icon="hass:chevron-right"></iron-icon>
|
||||
</paper-item>
|
||||
</a>
|
||||
</template>
|
||||
</paper-card>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -131,19 +147,23 @@ class HaGallery extends PolymerElement {
|
||||
_demo: {
|
||||
type: String,
|
||||
value: document.location.hash.substr(1),
|
||||
observer: '_demoChanged',
|
||||
observer: "_demoChanged",
|
||||
},
|
||||
_demos: {
|
||||
type: Array,
|
||||
value: DEMOS.keys().map(fixPath)
|
||||
value: DEMOS.keys().map(fixPath),
|
||||
},
|
||||
_lovelaceDemos: {
|
||||
type: Array,
|
||||
computed: '_computeLovelace(_demos)',
|
||||
computed: "_computeLovelace(_demos)",
|
||||
},
|
||||
_moreInfoDemos: {
|
||||
type: Array,
|
||||
computed: '_computeMoreInfos(_demos)',
|
||||
computed: "_computeMoreInfos(_demos)",
|
||||
},
|
||||
_utilDemos: {
|
||||
type: Array,
|
||||
computed: "_computeUtil(_demos)",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -151,18 +171,21 @@ class HaGallery extends PolymerElement {
|
||||
ready() {
|
||||
super.ready();
|
||||
|
||||
this.addEventListener(
|
||||
'show-notification',
|
||||
ev => this.$.notifications.showNotification(ev.detail.message)
|
||||
this.addEventListener("show-notification", (ev) =>
|
||||
this.$.notifications.showDialog({ message: ev.detail.message })
|
||||
);
|
||||
|
||||
this.addEventListener('hass-more-info', (ev) => {
|
||||
this.addEventListener("hass-more-info", (ev) => {
|
||||
if (ev.detail.entityId) {
|
||||
this.$.notifications.showNotification(`Showing more info for ${ev.detail.entityId}`);
|
||||
this.$.notifications.showDialog({
|
||||
message: `Showing more info for ${ev.detail.entityId}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', () => { this._demo = document.location.hash.substr(1); });
|
||||
window.addEventListener("hashchange", () => {
|
||||
this._demo = document.location.hash.substr(1);
|
||||
});
|
||||
}
|
||||
|
||||
_withDefault(value, def) {
|
||||
@@ -175,27 +198,31 @@ class HaGallery extends PolymerElement {
|
||||
while (root.lastChild) root.removeChild(root.lastChild);
|
||||
|
||||
if (demo) {
|
||||
DEMOS(`./${demo}.js`);
|
||||
DEMOS(`./${demo}.ts`);
|
||||
const el = document.createElement(demo);
|
||||
root.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
_computeHeaderButtonClass(demo) {
|
||||
return demo ? '' : 'invisible';
|
||||
return demo ? "" : "invisible";
|
||||
}
|
||||
|
||||
_backTapped() {
|
||||
document.location.hash = '';
|
||||
document.location.hash = "";
|
||||
}
|
||||
|
||||
_computeLovelace(demos) {
|
||||
return demos.filter(demo => demo.includes('hui'));
|
||||
return demos.filter((demo) => demo.includes("hui"));
|
||||
}
|
||||
|
||||
_computeMoreInfos(demos) {
|
||||
return demos.filter(demo => demo.includes('more-info'));
|
||||
return demos.filter((demo) => demo.includes("more-info"));
|
||||
}
|
||||
|
||||
_computeUtil(demos) {
|
||||
return demos.filter((demo) => demo.includes("util"));
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ha-gallery', HaGallery);
|
||||
customElements.define("ha-gallery", HaGallery);
|
||||
|
@@ -1,76 +1,75 @@
|
||||
const path = require('path');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const path = require("path");
|
||||
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const { babelLoaderConfig } = require("../config/babel.js");
|
||||
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
const chunkFilename = isProd ?
|
||||
'chunk.[chunkhash].js' : '[name].chunk.js';
|
||||
const buildPath = path.resolve(__dirname, 'dist');
|
||||
const publicPath = isProd ? './' : 'http://localhost:8080/';
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
||||
const buildPath = path.resolve(__dirname, "dist");
|
||||
const publicPath = isProd ? "./" : "http://localhost:8080/";
|
||||
|
||||
module.exports = {
|
||||
mode: isProd ? 'production' : 'development',
|
||||
mode: isProd ? "production" : "development",
|
||||
// Disabled in prod while we make Home Assistant able to serve the right files.
|
||||
// Was source-map
|
||||
devtool: isProd ? 'none' : 'inline-source-map',
|
||||
entry: './src/entrypoint.js',
|
||||
devtool: isProd ? "none" : "inline-source-map",
|
||||
entry: "./src/entrypoint.js",
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfig({ latestBuild: true }),
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: [
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
'syntax-dynamic-import',
|
||||
[
|
||||
'transform-react-jsx',
|
||||
{
|
||||
pragma: 'h'
|
||||
}
|
||||
],
|
||||
|
||||
],
|
||||
},
|
||||
},
|
||||
test: /\.css$/,
|
||||
use: "raw-loader",
|
||||
},
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: 'html-loader',
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
exportAsEs6Default: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
'public',
|
||||
{ from: '../public', to: 'static' },
|
||||
{ from: '../build-translations/output', to: 'static/translations' },
|
||||
{ from: '../node_modules/leaflet/dist/leaflet.css', to: 'static/images/leaflet/' },
|
||||
{ from: '../node_modules/@polymer/font-roboto-local/fonts', to: 'static/fonts' },
|
||||
{ from: '../node_modules/leaflet/dist/images', to: 'static/images/leaflet/' },
|
||||
"public",
|
||||
{ from: "../public", to: "static" },
|
||||
{ from: "../build-translations/output", to: "static/translations" },
|
||||
{
|
||||
from: "../node_modules/leaflet/dist/leaflet.css",
|
||||
to: "static/images/leaflet/",
|
||||
},
|
||||
{
|
||||
from: "../node_modules/@polymer/font-roboto-local/fonts",
|
||||
to: "static/fonts",
|
||||
},
|
||||
{
|
||||
from: "../node_modules/leaflet/dist/images",
|
||||
to: "static/images/leaflet/",
|
||||
},
|
||||
]),
|
||||
isProd && new UglifyJsPlugin({
|
||||
isProd &&
|
||||
new UglifyJsPlugin({
|
||||
extractComments: true,
|
||||
sourceMap: true,
|
||||
uglifyOptions: {
|
||||
// Disabling because it broke output
|
||||
mangle: false,
|
||||
}
|
||||
},
|
||||
}),
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
extensions: [".ts", ".js", ".json"],
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
filename: "[name].js",
|
||||
chunkFilename: chunkFilename,
|
||||
path: buildPath,
|
||||
publicPath,
|
||||
},
|
||||
devServer: {
|
||||
contentBase: './public',
|
||||
}
|
||||
contentBase: "./public",
|
||||
},
|
||||
};
|
||||
|
@@ -1,8 +1,8 @@
|
||||
var path = require('path');
|
||||
var path = require("path");
|
||||
|
||||
module.exports = {
|
||||
polymer_dir: path.resolve(__dirname, '..'),
|
||||
build_dir: path.resolve(__dirname, '../build'),
|
||||
output: path.resolve(__dirname, '../hass_frontend'),
|
||||
output_es5: path.resolve(__dirname, '../hass_frontend_es5'),
|
||||
polymer_dir: path.resolve(__dirname, ".."),
|
||||
build_dir: path.resolve(__dirname, "../build"),
|
||||
output: path.resolve(__dirname, "../hass_frontend"),
|
||||
output_es5: path.resolve(__dirname, "../hass_frontend_es5"),
|
||||
};
|
||||
|
@@ -1,31 +1,34 @@
|
||||
const gulp = require('gulp');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const config = require('../config');
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const config = require("../config");
|
||||
|
||||
const ICON_PACKAGE_PATH = path.resolve(__dirname, '../../node_modules/@mdi/svg/');
|
||||
const META_PATH = path.resolve(ICON_PACKAGE_PATH, 'meta.json');
|
||||
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, 'svg');
|
||||
const OUTPUT_DIR = path.resolve(__dirname, '../../build');
|
||||
const MDI_OUTPUT_PATH = path.resolve(OUTPUT_DIR, 'mdi.html');
|
||||
const HASS_OUTPUT_PATH = path.resolve(OUTPUT_DIR, 'hass-icons.html');
|
||||
const ICON_PACKAGE_PATH = path.resolve(
|
||||
__dirname,
|
||||
"../../node_modules/@mdi/svg/"
|
||||
);
|
||||
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
|
||||
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
|
||||
const OUTPUT_DIR = path.resolve(__dirname, "../../build");
|
||||
const MDI_OUTPUT_PATH = path.resolve(OUTPUT_DIR, "mdi.html");
|
||||
const HASS_OUTPUT_PATH = path.resolve(OUTPUT_DIR, "hass-icons.html");
|
||||
|
||||
const BUILT_IN_PANEL_ICONS = [
|
||||
'calendar', // Calendar
|
||||
'settings', // Config
|
||||
'home-assistant', // Hass.io
|
||||
'poll-box', // History panel
|
||||
'format-list-bulleted-type', // Logbook
|
||||
'mailbox', // Mailbox
|
||||
'account-location', // Map
|
||||
'cart', // Shopping List
|
||||
"calendar", // Calendar
|
||||
"settings", // Config
|
||||
"home-assistant", // Hass.io
|
||||
"poll-box", // History panel
|
||||
"format-list-bulleted-type", // Logbook
|
||||
"mailbox", // Mailbox
|
||||
"account-location", // Map
|
||||
"cart", // Shopping List
|
||||
];
|
||||
|
||||
// Given an icon name, load the SVG file
|
||||
function loadIcon(name) {
|
||||
const iconPath = path.resolve(ICON_PATH, `${name}.svg`);
|
||||
try {
|
||||
return fs.readFileSync(iconPath, 'utf-8');
|
||||
return fs.readFileSync(iconPath, "utf-8");
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
@@ -33,7 +36,7 @@ function loadIcon(name) {
|
||||
|
||||
// Given an SVG file, convert it to an iron-iconset-svg definition
|
||||
function transformXMLtoPolymer(name, xml) {
|
||||
const start = xml.indexOf('><path') + 1;
|
||||
const start = xml.indexOf("><path") + 1;
|
||||
const end = xml.length - start - 6;
|
||||
const path = xml.substr(start, end);
|
||||
return `<g id="${name}">${path}</g>`;
|
||||
@@ -41,22 +44,26 @@ function transformXMLtoPolymer(name, xml) {
|
||||
|
||||
// Given an iconset name and icon names, generate a polymer iconset
|
||||
function generateIconset(name, iconNames) {
|
||||
const iconDefs = iconNames.map(name => {
|
||||
const iconDefs = iconNames
|
||||
.map((name) => {
|
||||
const iconDef = loadIcon(name);
|
||||
if (!iconDef) {
|
||||
throw new Error(`Unknown icon referenced: ${name}`);
|
||||
}
|
||||
return transformXMLtoPolymer(name, iconDef)
|
||||
}).join('');
|
||||
return transformXMLtoPolymer(name, iconDef);
|
||||
})
|
||||
.join("");
|
||||
return `<ha-iconset-svg name="${name}" size="24"><svg><defs>${iconDefs}</defs></svg></ha-iconset-svg>`;
|
||||
}
|
||||
|
||||
// Generate the full MDI iconset
|
||||
function genMDIIcons() {
|
||||
const meta = JSON.parse(fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), 'UTF-8'));
|
||||
const iconNames = meta.map(iconInfo => iconInfo.name);
|
||||
const meta = JSON.parse(
|
||||
fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), "UTF-8")
|
||||
);
|
||||
const iconNames = meta.map((iconInfo) => iconInfo.name);
|
||||
fs.existsSync(OUTPUT_DIR) || fs.mkdirSync(OUTPUT_DIR);
|
||||
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset('mdi', iconNames));
|
||||
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset("mdi", iconNames));
|
||||
}
|
||||
|
||||
// Helper function to map recursively over files in a folder and it's subfolders
|
||||
@@ -75,32 +82,34 @@ function mapFiles(startPath, filter, mapFunc) {
|
||||
|
||||
// Find all icons used by the project.
|
||||
function findIcons(path, iconsetName) {
|
||||
const iconRegex = new RegExp(`${iconsetName}:[\\w-]+`, 'g');
|
||||
const iconRegex = new RegExp(`${iconsetName}:[\\w-]+`, "g");
|
||||
const icons = new Set();
|
||||
function processFile(filename) {
|
||||
const content = fs.readFileSync(filename);
|
||||
let match;
|
||||
// eslint-disable-next-line
|
||||
while (match = iconRegex.exec(content)) {
|
||||
while ((match = iconRegex.exec(content))) {
|
||||
// strip off "hass:" and add to set
|
||||
icons.add(match[0].substr(iconsetName.length + 1));
|
||||
}
|
||||
}
|
||||
mapFiles(path, '.js', processFile);
|
||||
mapFiles(path, ".js", processFile);
|
||||
mapFiles(path, ".ts", processFile);
|
||||
return Array.from(icons);
|
||||
}
|
||||
|
||||
function genHassIcons() {
|
||||
const iconNames = findIcons('./src', 'hass').concat(BUILT_IN_PANEL_ICONS);
|
||||
const iconNames = findIcons("./src", "hass").concat(BUILT_IN_PANEL_ICONS);
|
||||
fs.existsSync(OUTPUT_DIR) || fs.mkdirSync(OUTPUT_DIR);
|
||||
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset('hass', iconNames));
|
||||
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset("hass", iconNames));
|
||||
}
|
||||
|
||||
gulp.task('gen-icons-mdi', () => genMDIIcons());
|
||||
gulp.task('gen-icons-hass', () => genHassIcons());
|
||||
gulp.task('gen-icons', ['gen-icons-hass', 'gen-icons-mdi'], () => {});
|
||||
gulp.task("gen-icons-mdi", () => genMDIIcons());
|
||||
gulp.task("gen-icons-hass", () => genHassIcons());
|
||||
gulp.task("gen-icons", ["gen-icons-hass", "gen-icons-mdi"], () => {});
|
||||
|
||||
module.exports = {
|
||||
findIcons,
|
||||
generateIconset,
|
||||
genMDIIcons,
|
||||
};
|
||||
|
@@ -1,40 +1,44 @@
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const foreach = require('gulp-foreach');
|
||||
const hash = require('gulp-hash');
|
||||
const insert = require('gulp-insert');
|
||||
const merge = require('gulp-merge-json');
|
||||
const minify = require('gulp-jsonminify');
|
||||
const rename = require('gulp-rename');
|
||||
const transform = require('gulp-json-transform');
|
||||
const path = require("path");
|
||||
const gulp = require("gulp");
|
||||
const foreach = require("gulp-foreach");
|
||||
const hash = require("gulp-hash");
|
||||
const insert = require("gulp-insert");
|
||||
const merge = require("gulp-merge-json");
|
||||
const minify = require("gulp-jsonminify");
|
||||
const rename = require("gulp-rename");
|
||||
const transform = require("gulp-json-transform");
|
||||
|
||||
const inDir = 'translations';
|
||||
const workDir = 'build-translations';
|
||||
const fullDir = workDir + '/full';
|
||||
const coreDir = workDir + '/core';
|
||||
const outDir = workDir + '/output';
|
||||
const inDir = "translations";
|
||||
const workDir = "build-translations";
|
||||
const fullDir = workDir + "/full";
|
||||
const coreDir = workDir + "/core";
|
||||
const outDir = workDir + "/output";
|
||||
|
||||
// Panel translations which should be split from the core translations. These
|
||||
// should mirror the fragment definitions in polymer.json, so that we load
|
||||
// additional resources at equivalent points.
|
||||
const TRANSLATION_FRAGMENTS = [
|
||||
'config',
|
||||
'history',
|
||||
'logbook',
|
||||
'mailbox',
|
||||
'profile',
|
||||
'shopping-list',
|
||||
'page-authorize',
|
||||
'page-onboarding',
|
||||
"config",
|
||||
"history",
|
||||
"logbook",
|
||||
"mailbox",
|
||||
"profile",
|
||||
"shopping-list",
|
||||
"page-authorize",
|
||||
"page-onboarding",
|
||||
];
|
||||
|
||||
const tasks = [];
|
||||
|
||||
function recursiveFlatten(prefix, data) {
|
||||
let output = {};
|
||||
Object.keys(data).forEach(function (key) {
|
||||
if (typeof (data[key]) === 'object') {
|
||||
output = Object.assign({}, output, recursiveFlatten(prefix + key + '.', data[key]));
|
||||
Object.keys(data).forEach(function(key) {
|
||||
if (typeof data[key] === "object") {
|
||||
output = Object.assign(
|
||||
{},
|
||||
output,
|
||||
recursiveFlatten(prefix + key + ".", data[key])
|
||||
);
|
||||
} else {
|
||||
output[prefix + key] = data[key];
|
||||
}
|
||||
@@ -43,14 +47,14 @@ function recursiveFlatten(prefix, data) {
|
||||
}
|
||||
|
||||
function flatten(data) {
|
||||
return recursiveFlatten('', data);
|
||||
return recursiveFlatten("", data);
|
||||
}
|
||||
|
||||
function emptyFilter(data) {
|
||||
const newData = {};
|
||||
Object.keys(data).forEach((key) => {
|
||||
if (data[key]) {
|
||||
if (typeof (data[key]) === 'object') {
|
||||
if (typeof data[key] === "object") {
|
||||
newData[key] = emptyFilter(data[key]);
|
||||
} else {
|
||||
newData[key] = data[key];
|
||||
@@ -70,16 +74,18 @@ function emptyFilter(data) {
|
||||
* @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
|
||||
*/
|
||||
const re_key_reference = /\[%key:([^%]+)%\]/;
|
||||
function lokalise_transform (data, original) {
|
||||
function lokalise_transform(data, original) {
|
||||
const output = {};
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
if (value instanceof Object) {
|
||||
output[key] = lokalise_transform(value, original);
|
||||
} else {
|
||||
output[key] = value.replace(re_key_reference, (match, key) => {
|
||||
const replace = key.split('::').reduce((tr, k) => tr[k], original);
|
||||
if (typeof replace !== 'string') {
|
||||
throw Error(`Invalid key placeholder ${key} in src/translations/en.json`);
|
||||
const replace = key.split("::").reduce((tr, k) => tr[k], original);
|
||||
if (typeof replace !== "string") {
|
||||
throw Error(
|
||||
`Invalid key placeholder ${key} in src/translations/en.json`
|
||||
);
|
||||
}
|
||||
return replace;
|
||||
});
|
||||
@@ -97,21 +103,24 @@ function lokalise_transform (data, original) {
|
||||
* project is buildable immediately after merging new translation keys, since
|
||||
* the Lokalise update to translations/en.json will not happen immediately.
|
||||
*/
|
||||
let taskName = 'build-master-translation';
|
||||
gulp.task(taskName, function () {
|
||||
return gulp.src('src/translations/en.json')
|
||||
.pipe(transform(function(data, file) {
|
||||
let taskName = "build-master-translation";
|
||||
gulp.task(taskName, function() {
|
||||
return gulp
|
||||
.src("src/translations/en.json")
|
||||
.pipe(
|
||||
transform(function(data, file) {
|
||||
return lokalise_transform(data, data);
|
||||
}))
|
||||
.pipe(rename('translationMaster.json'))
|
||||
})
|
||||
)
|
||||
.pipe(rename("translationMaster.json"))
|
||||
.pipe(gulp.dest(workDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = 'build-merged-translations';
|
||||
gulp.task(taskName, ['build-master-translation'], function () {
|
||||
return gulp.src(inDir + '/*.json')
|
||||
.pipe(foreach(function(stream, file) {
|
||||
taskName = "build-merged-translations";
|
||||
gulp.task(taskName, ["build-master-translation"], function() {
|
||||
return gulp.src(inDir + "/*.json").pipe(
|
||||
foreach(function(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
|
||||
@@ -119,97 +128,119 @@ gulp.task(taskName, ['build-master-translation'], function () {
|
||||
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
||||
// Will be OK for now as long as we don't have anything more complicated
|
||||
// than a base translation + region.
|
||||
const tr = path.basename(file.history[0], '.json');
|
||||
const subtags = tr.split('-');
|
||||
const src = [workDir + '/translationMaster.json'];
|
||||
const tr = path.basename(file.history[0], ".json");
|
||||
const subtags = tr.split("-");
|
||||
const src = [workDir + "/translationMaster.json"];
|
||||
for (let i = 1; i <= subtags.length; i++) {
|
||||
const lang = subtags.slice(0, i).join('-');
|
||||
src.push(inDir + '/' + lang + '.json');
|
||||
const lang = subtags.slice(0, i).join("-");
|
||||
src.push(inDir + "/" + lang + ".json");
|
||||
}
|
||||
return gulp.src(src)
|
||||
.pipe(transform(data => emptyFilter(data)))
|
||||
.pipe(merge({
|
||||
fileName: tr + '.json',
|
||||
}))
|
||||
return gulp
|
||||
.src(src)
|
||||
.pipe(transform((data) => emptyFilter(data)))
|
||||
.pipe(
|
||||
merge({
|
||||
fileName: tr + ".json",
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(fullDir));
|
||||
}));
|
||||
})
|
||||
);
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
const splitTasks = [];
|
||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
taskName = 'build-translation-fragment-' + fragment;
|
||||
gulp.task(taskName, ['build-merged-translations'], function () {
|
||||
taskName = "build-translation-fragment-" + fragment;
|
||||
gulp.task(taskName, ["build-merged-translations"], function() {
|
||||
// Return only the translations for this fragment.
|
||||
return gulp.src(fullDir + '/*.json')
|
||||
.pipe(transform(data => ({
|
||||
return gulp
|
||||
.src(fullDir + "/*.json")
|
||||
.pipe(
|
||||
transform((data) => ({
|
||||
ui: {
|
||||
panel: {
|
||||
[fragment]: data.ui.panel[fragment],
|
||||
},
|
||||
},
|
||||
})))
|
||||
.pipe(gulp.dest(workDir + '/' + fragment));
|
||||
}))
|
||||
)
|
||||
.pipe(gulp.dest(workDir + "/" + fragment));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
splitTasks.push(taskName);
|
||||
});
|
||||
|
||||
taskName = 'build-translation-core';
|
||||
gulp.task(taskName, ['build-merged-translations'], function () {
|
||||
taskName = "build-translation-core";
|
||||
gulp.task(taskName, ["build-merged-translations"], function() {
|
||||
// Remove the fragment translations from the core translation.
|
||||
return gulp.src(fullDir + '/*.json')
|
||||
.pipe(transform((data) => {
|
||||
return gulp
|
||||
.src(fullDir + "/*.json")
|
||||
.pipe(
|
||||
transform((data) => {
|
||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
||||
delete data.ui.panel[fragment];
|
||||
});
|
||||
return data;
|
||||
}))
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(coreDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
splitTasks.push(taskName);
|
||||
|
||||
taskName = 'build-flattened-translations';
|
||||
gulp.task(taskName, splitTasks, function () {
|
||||
taskName = "build-flattened-translations";
|
||||
gulp.task(taskName, splitTasks, function() {
|
||||
// Flatten the split versions of our translations, and move them into outDir
|
||||
return gulp.src(
|
||||
TRANSLATION_FRAGMENTS.map(fragment => workDir + '/' + fragment + '/*.json')
|
||||
.concat(coreDir + '/*.json'),
|
||||
{ base: workDir },
|
||||
return gulp
|
||||
.src(
|
||||
TRANSLATION_FRAGMENTS.map(
|
||||
(fragment) => workDir + "/" + fragment + "/*.json"
|
||||
).concat(coreDir + "/*.json"),
|
||||
{ base: workDir }
|
||||
)
|
||||
.pipe(transform(function (data) {
|
||||
.pipe(
|
||||
transform(function(data) {
|
||||
// Polymer.AppLocalizeBehavior requires flattened json
|
||||
return flatten(data);
|
||||
}))
|
||||
})
|
||||
)
|
||||
.pipe(minify())
|
||||
.pipe(rename((filePath) => {
|
||||
if (filePath.dirname === 'core') {
|
||||
filePath.dirname = '';
|
||||
.pipe(
|
||||
rename((filePath) => {
|
||||
if (filePath.dirname === "core") {
|
||||
filePath.dirname = "";
|
||||
}
|
||||
}))
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(outDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = 'build-translation-fingerprints';
|
||||
gulp.task(taskName, ['build-flattened-translations'], function () {
|
||||
return gulp.src(outDir + '/**/*.json')
|
||||
.pipe(rename({
|
||||
extname: '',
|
||||
}))
|
||||
.pipe(hash({
|
||||
algorithm: 'md5',
|
||||
taskName = "build-translation-fingerprints";
|
||||
gulp.task(taskName, ["build-flattened-translations"], function() {
|
||||
return gulp
|
||||
.src(outDir + "/**/*.json")
|
||||
.pipe(
|
||||
rename({
|
||||
extname: "",
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
hash({
|
||||
algorithm: "md5",
|
||||
hashLength: 32,
|
||||
template: '<%= name %>-<%= hash %>.json',
|
||||
}))
|
||||
.pipe(hash.manifest('translationFingerprints.json'))
|
||||
.pipe(transform(function (data) {
|
||||
template: "<%= name %>-<%= hash %>.json",
|
||||
})
|
||||
)
|
||||
.pipe(hash.manifest("translationFingerprints.json"))
|
||||
.pipe(
|
||||
transform(function(data) {
|
||||
// After generating fingerprints of our translation files, consolidate
|
||||
// all translation fragment fingerprints under the translation name key
|
||||
const newData = {};
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
const parts = key.split('/');
|
||||
const parts = key.split("/");
|
||||
let translation = key;
|
||||
if (parts.length === 2) {
|
||||
translation = parts[1];
|
||||
@@ -222,36 +253,44 @@ gulp.task(taskName, ['build-flattened-translations'], function () {
|
||||
newData[translation].fingerprints[key] = value;
|
||||
});
|
||||
return newData;
|
||||
}))
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(workDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
||||
taskName = 'build-translations';
|
||||
gulp.task(taskName, ['build-translation-fingerprints'], function () {
|
||||
return gulp.src([
|
||||
'src/translations/translationMetadata.json',
|
||||
workDir + '/translationFingerprints.json',
|
||||
taskName = "build-translations";
|
||||
gulp.task(taskName, ["build-translation-fingerprints"], function() {
|
||||
return gulp
|
||||
.src([
|
||||
"src/translations/translationMetadata.json",
|
||||
workDir + "/translationFingerprints.json",
|
||||
])
|
||||
.pipe(merge({}))
|
||||
.pipe(transform(function (data) {
|
||||
.pipe(
|
||||
transform(function(data) {
|
||||
const newData = {};
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
// Filter out translations without native name.
|
||||
if (data[key].nativeName) {
|
||||
newData[key] = data[key];
|
||||
} else {
|
||||
console.warn(`Skipping language ${key}. Native name was not translated.`);
|
||||
console.warn(
|
||||
`Skipping language ${key}. Native name was not translated.`
|
||||
);
|
||||
}
|
||||
if (data[key]) newData[key] = value;
|
||||
});
|
||||
return newData;
|
||||
}))
|
||||
.pipe(transform(data => ({
|
||||
})
|
||||
)
|
||||
.pipe(
|
||||
transform((data) => ({
|
||||
fragments: TRANSLATION_FRAGMENTS,
|
||||
translations: data,
|
||||
})))
|
||||
.pipe(rename('translationMetadata.json'))
|
||||
}))
|
||||
)
|
||||
.pipe(rename("translationMetadata.json"))
|
||||
.pipe(gulp.dest(workDir));
|
||||
});
|
||||
tasks.push(taskName);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
const path = require('path');
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
// Target directory for the build.
|
||||
buildDir: path.resolve(__dirname, 'build'),
|
||||
buildDir: path.resolve(__dirname, "build"),
|
||||
// Path where the Hass.io frontend will be publicly available.
|
||||
publicPath: '/api/hassio/app',
|
||||
}
|
||||
publicPath: "/api/hassio/app",
|
||||
};
|
||||
|
@@ -11,4 +11,4 @@ OUTPUT_DIR=build
|
||||
rm -rf $OUTPUT_DIR
|
||||
|
||||
node script/gen-icons.js
|
||||
NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js
|
||||
NODE_ENV=production CI=false ../node_modules/.bin/webpack -p --config webpack.config.js
|
||||
|
@@ -4,6 +4,8 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
OUTPUT_DIR=build
|
||||
|
||||
rm -rf $OUTPUT_DIR
|
||||
|
@@ -1,15 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const fs = require("fs");
|
||||
const {
|
||||
findIcons,
|
||||
generateIconset,
|
||||
} = require('../../gulp/tasks/gen-icons.js');
|
||||
genMDIIcons,
|
||||
} = require("../../gulp/tasks/gen-icons.js");
|
||||
|
||||
const MENU_BUTTON_ICON = 'menu';
|
||||
const MENU_BUTTON_ICON = "menu";
|
||||
|
||||
function genHassioIcons() {
|
||||
const iconNames = findIcons('./src', 'hassio').concat(MENU_BUTTON_ICON);
|
||||
fs.writeFileSync('./hassio-icons.html', generateIconset('hassio', iconNames));
|
||||
const iconNames = findIcons("./src", "hassio").concat(MENU_BUTTON_ICON);
|
||||
fs.writeFileSync("./hassio-icons.html", generateIconset("hassio", iconNames));
|
||||
}
|
||||
|
||||
genMDIIcons();
|
||||
genHassioIcons();
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
import NavigateMixin from '../../../src/mixins/navigate-mixin.js';
|
||||
import "../components/hassio-card-content";
|
||||
import "../resources/hassio-style";
|
||||
import NavigateMixin from "../../../src/mixins/navigate-mixin";
|
||||
|
||||
class HassioAddonRepository extends NavigateMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -13,6 +13,9 @@ class HassioAddonRepository extends NavigateMixin(PolymerElement) {
|
||||
paper-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
.not_available {
|
||||
opacity: 0.6;
|
||||
}
|
||||
a.repo {
|
||||
display: block;
|
||||
color: var(--primary-text-color);
|
||||
@@ -24,19 +27,34 @@ class HassioAddonRepository extends NavigateMixin(PolymerElement) {
|
||||
[[repo.name]]
|
||||
<div class="description">
|
||||
Maintained by [[repo.maintainer]]
|
||||
<a class="repo" href="[[repo.url]]" target="_blank">[[repo.url]]</a>
|
||||
<a class="repo" href="[[repo.url]]" target="_blank"
|
||||
>[[repo.url]]</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<template is="dom-repeat" items="[[addons]]" as="addon" sort="sortAddons">
|
||||
<paper-card on-click="addonTapped">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[addons]]"
|
||||
as="addon"
|
||||
sort="sortAddons"
|
||||
>
|
||||
<paper-card class$="[[computeClass(addon)]]" on-click="addonTapped">
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[addon.name]]" description="[[addon.description]]" icon="[[computeIcon(addon)]]" icon-title="[[computeIconTitle(addon)]]" icon-class="[[computeIconClass(addon)]]"></hassio-card-content>
|
||||
<hassio-card-content
|
||||
hass="[[hass]]"
|
||||
title="[[addon.name]]"
|
||||
description="[[addon.description]]"
|
||||
available="[[addon.available]]"
|
||||
icon="[[computeIcon(addon)]]"
|
||||
icon-title="[[computeIconTitle(addon)]]"
|
||||
icon-class="[[computeIconClass(addon)]]"
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -48,21 +66,33 @@ class HassioAddonRepository extends NavigateMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
sortAddons(a, b) {
|
||||
return a.name < b.name ? -1 : 1;
|
||||
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
|
||||
}
|
||||
|
||||
computeIcon(addon) {
|
||||
return addon.installed && addon.installed !== addon.version ? 'hassio:arrow-up-bold-circle' : 'hassio:puzzle';
|
||||
return addon.installed && addon.installed !== addon.version
|
||||
? "hassio:arrow-up-bold-circle"
|
||||
: "hassio:puzzle";
|
||||
}
|
||||
|
||||
computeIconTitle(addon) {
|
||||
if (addon.installed) return addon.installed !== addon.version ? 'New version available' : 'Add-on is installed';
|
||||
return 'Add-on is not installed';
|
||||
if (addon.installed)
|
||||
return addon.installed !== addon.version
|
||||
? "New version available"
|
||||
: "Add-on is installed";
|
||||
return addon.available
|
||||
? "Add-on is not installed"
|
||||
: "Add-on is not available on your system";
|
||||
}
|
||||
|
||||
computeIconClass(addon) {
|
||||
if (addon.installed) return addon.installed !== addon.version ? 'update' : 'installed';
|
||||
return '';
|
||||
if (addon.installed)
|
||||
return addon.installed !== addon.version ? "update" : "installed";
|
||||
return !addon.available ? "not_available" : "";
|
||||
}
|
||||
|
||||
computeClass(addon) {
|
||||
return !addon.available ? "not_available" : "";
|
||||
}
|
||||
|
||||
addonTapped(ev) {
|
||||
@@ -70,4 +100,4 @@ class HassioAddonRepository extends NavigateMixin(PolymerElement) {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-repository', HassioAddonRepository);
|
||||
customElements.define("hassio-addon-repository", HassioAddonRepository);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import './hassio-addon-repository.js';
|
||||
import './hassio-repositories-editor.js';
|
||||
import "./hassio-addon-repository";
|
||||
import "./hassio-repositories-editor";
|
||||
|
||||
class HassioAddonStore extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -12,12 +12,19 @@ class HassioAddonStore extends PolymerElement {
|
||||
margin-top: 24px;
|
||||
}
|
||||
</style>
|
||||
<hassio-repositories-editor hass="[[hass]]" repos="[[repos]]"></hassio-repositories-editor>
|
||||
<hassio-repositories-editor
|
||||
hass="[[hass]]"
|
||||
repos="[[repos]]"
|
||||
></hassio-repositories-editor>
|
||||
|
||||
<template is="dom-repeat" items="[[repos]]" as="repo" sort="sortRepos">
|
||||
<hassio-addon-repository hass="[[hass]]" repo="[[repo]]" addons="[[computeAddons(repo.slug)]]"></hassio-addon-repository>
|
||||
<hassio-addon-repository
|
||||
hass="[[hass]]"
|
||||
repo="[[repo]]"
|
||||
addons="[[computeAddons(repo.slug)]]"
|
||||
></hassio-addon-repository>
|
||||
</template>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -30,7 +37,7 @@ class HassioAddonStore extends PolymerElement {
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
@@ -41,42 +48,45 @@ class HassioAddonStore extends PolymerElement {
|
||||
}
|
||||
|
||||
sortRepos(a, b) {
|
||||
if (a.slug === 'local') {
|
||||
if (a.slug === "local") {
|
||||
return -1;
|
||||
} if (b.slug === 'local') {
|
||||
return 1;
|
||||
} if (a.slug === 'core') {
|
||||
return -1;
|
||||
} if (b.slug === 'core') {
|
||||
}
|
||||
if (b.slug === "local") {
|
||||
return 1;
|
||||
}
|
||||
return a.name < b.name ? -1 : 1;
|
||||
if (a.slug === "core") {
|
||||
return -1;
|
||||
}
|
||||
if (b.slug === "core") {
|
||||
return 1;
|
||||
}
|
||||
return a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1;
|
||||
}
|
||||
|
||||
computeAddons(repo) {
|
||||
return this.addons.filter(function (addon) {
|
||||
return this.addons.filter(function(addon) {
|
||||
return addon.repository === repo;
|
||||
});
|
||||
}
|
||||
|
||||
loadData() {
|
||||
this.hass.callApi('get', 'hassio/addons')
|
||||
.then((info) => {
|
||||
this.hass.callApi("get", "hassio/addons").then(
|
||||
(info) => {
|
||||
this.addons = info.data.addons;
|
||||
this.repos = info.data.repositories;
|
||||
}, () => {
|
||||
},
|
||||
() => {
|
||||
this.addons = [];
|
||||
this.repos = [];
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
refreshData() {
|
||||
this.hass.callApi('post', 'hassio/addons/reload')
|
||||
.then(() => {
|
||||
this.hass.callApi("post", "hassio/addons/reload").then(() => {
|
||||
this.loadData();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-store', HassioAddonStore);
|
||||
customElements.define("hassio-addon-store", HassioAddonStore);
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-input/paper-input.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../components/hassio-card-content";
|
||||
import "../resources/hassio-style";
|
||||
|
||||
class HassioRepositoriesEditor extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -32,27 +32,52 @@ class HassioRepositoriesEditor extends PolymerElement {
|
||||
Configure which add-on repositories to fetch data from:
|
||||
</div>
|
||||
</div>
|
||||
<template id="list" is="dom-repeat" items="[[repoList]]" as="repo" sort="sortRepos">
|
||||
<template
|
||||
id="list"
|
||||
is="dom-repeat"
|
||||
items="[[repoList]]"
|
||||
as="repo"
|
||||
sort="sortRepos"
|
||||
>
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[repo.name]]" description="[[repo.url]]" icon="hassio:github-circle"></hassio-card-content>
|
||||
<hassio-card-content
|
||||
hass="[[hass]]"
|
||||
title="[[repo.name]]"
|
||||
description="[[repo.url]]"
|
||||
icon="hassio:github-circle"
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/options" data="[[computeRemoveRepoData(repoList, repo.url)]]" class="warning">Remove</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
hass="[[hass]]"
|
||||
path="hassio/supervisor/options"
|
||||
data="[[computeRemoveRepoData(repoList, repo.url)]]"
|
||||
class="warning"
|
||||
>Remove</ha-call-api-button
|
||||
>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
<paper-card>
|
||||
<div class="card-content add">
|
||||
<iron-icon icon="hassio:github-circle"></iron-icon>
|
||||
<paper-input label="Add new repository by URL" value="{{repoUrl}}"></paper-input>
|
||||
<paper-input
|
||||
label="Add new repository by URL"
|
||||
value="{{repoUrl}}"
|
||||
></paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/options" data="[[computeAddRepoData(repoList, repoUrl)]]">Add</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
hass="[[hass]]"
|
||||
path="hassio/supervisor/options"
|
||||
data="[[computeAddRepoData(repoList, repoUrl)]]"
|
||||
>Add</ha-call-api-button
|
||||
>
|
||||
</div>
|
||||
</paper-card>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -60,7 +85,7 @@ class HassioRepositoriesEditor extends PolymerElement {
|
||||
hass: Object,
|
||||
repos: {
|
||||
type: Array,
|
||||
observer: 'reposChanged',
|
||||
observer: "reposChanged",
|
||||
},
|
||||
repoList: Array,
|
||||
repoUrl: String,
|
||||
@@ -68,8 +93,10 @@ class HassioRepositoriesEditor extends PolymerElement {
|
||||
}
|
||||
|
||||
reposChanged(repos) {
|
||||
this.repoList = repos.filter(repo => repo.slug !== 'core' && repo.slug !== 'local');
|
||||
this.repoUrl = '';
|
||||
this.repoList = repos.filter(
|
||||
(repo) => repo.slug !== "core" && repo.slug !== "local"
|
||||
);
|
||||
this.repoUrl = "";
|
||||
}
|
||||
|
||||
sortRepos(a, b) {
|
||||
@@ -77,15 +104,17 @@ class HassioRepositoriesEditor extends PolymerElement {
|
||||
}
|
||||
|
||||
computeRemoveRepoData(repoList, url) {
|
||||
const list = repoList.filter(repo => repo.url !== url).map(repo => repo.url);
|
||||
const list = repoList
|
||||
.filter((repo) => repo.url !== url)
|
||||
.map((repo) => repo.url);
|
||||
return { addons_repositories: list };
|
||||
}
|
||||
|
||||
computeAddRepoData(repoList, url) {
|
||||
const list = repoList ? repoList.map(repo => repo.url) : [];
|
||||
const list = repoList ? repoList.map((repo) => repo.url) : [];
|
||||
list.push(url);
|
||||
return { addons_repositories: list };
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-repositories-editor', HassioRepositoriesEditor);
|
||||
customElements.define("hassio-repositories-editor", HassioRepositoriesEditor);
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import 'web-animations-js/web-animations-next-lite.min.js';
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-dropdown-menu/paper-dropdown-menu.js';
|
||||
import '@polymer/paper-item/paper-item.js';
|
||||
import '@polymer/paper-listbox/paper-listbox.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
import "../../../src/resources/ha-style";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
|
||||
class HassioAddonAudio extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -38,16 +38,28 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
|
||||
</template>
|
||||
|
||||
<paper-dropdown-menu label="Input">
|
||||
<paper-listbox slot="dropdown-content" attr-for-selected="device" selected="{{selectedInput}}">
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
selected="{{selectedInput}}"
|
||||
>
|
||||
<template is="dom-repeat" items="[[inputDevices]]">
|
||||
<paper-item device\$="[[item.device]]">[[item.name]]</paper-item>
|
||||
<paper-item device\$="[[item.device]]"
|
||||
>[[item.name]]</paper-item
|
||||
>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-dropdown-menu label="Output">
|
||||
<paper-listbox slot="dropdown-content" attr-for-selected="device" selected="{{selectedOutput}}">
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="device"
|
||||
selected="{{selectedOutput}}"
|
||||
>
|
||||
<template is="dom-repeat" items="[[outputDevices]]">
|
||||
<paper-item device\$="[[item.device]]">[[item.name]]</paper-item>
|
||||
<paper-item device\$="[[item.device]]"
|
||||
>[[item.name]]</paper-item
|
||||
>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
@@ -56,7 +68,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
|
||||
<paper-button on-click="_saveSettings">Save</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -64,7 +76,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
|
||||
hass: Object,
|
||||
addon: {
|
||||
type: Object,
|
||||
observer: 'addonChanged'
|
||||
observer: "addonChanged",
|
||||
},
|
||||
inputDevices: Array,
|
||||
outputDevices: Array,
|
||||
@@ -76,40 +88,55 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
|
||||
|
||||
addonChanged(addon) {
|
||||
this.setProperties({
|
||||
selectedInput: addon.audio_input || 'null',
|
||||
selectedOutput: addon.audio_output || 'null'
|
||||
selectedInput: addon.audio_input || "null",
|
||||
selectedOutput: addon.audio_output || "null",
|
||||
});
|
||||
if (this.outputDevices) return;
|
||||
|
||||
const noDevice = [{ device: 'null', name: '-' }];
|
||||
this.hass.callApi('get', 'hassio/hardware/audio').then((resp) => {
|
||||
const noDevice = [{ device: "null", name: "-" }];
|
||||
this.hass.callApi("get", "hassio/hardware/audio").then(
|
||||
(resp) => {
|
||||
const dev = resp.data.audio;
|
||||
const input = Object.keys(dev.input).map(key => ({ device: key, name: dev.input[key] }));
|
||||
const output = Object.keys(dev.output).map(key => ({ device: key, name: dev.output[key] }));
|
||||
const input = Object.keys(dev.input).map((key) => ({
|
||||
device: key,
|
||||
name: dev.input[key],
|
||||
}));
|
||||
const output = Object.keys(dev.output).map((key) => ({
|
||||
device: key,
|
||||
name: dev.output[key],
|
||||
}));
|
||||
this.setProperties({
|
||||
inputDevices: noDevice.concat(input),
|
||||
outputDevices: noDevice.concat(output)
|
||||
outputDevices: noDevice.concat(output),
|
||||
});
|
||||
}, () => {
|
||||
},
|
||||
() => {
|
||||
this.setProperties({
|
||||
inputDevices: noDevice,
|
||||
outputDevices: noDevice
|
||||
});
|
||||
outputDevices: noDevice,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_saveSettings() {
|
||||
this.error = null;
|
||||
const path = `hassio/addons/${this.addon.slug}/options`;
|
||||
this.hass.callApi('post', path, {
|
||||
audio_input: this.selectedInput === 'null' ? null : this.selectedInput,
|
||||
audio_output: this.selectedOutput === 'null' ? null : this.selectedOutput
|
||||
}).then(() => {
|
||||
this.fire('hass-api-called', { success: true, path: path });
|
||||
}, (resp) => {
|
||||
this.hass
|
||||
.callApi("post", path, {
|
||||
audio_input: this.selectedInput === "null" ? null : this.selectedInput,
|
||||
audio_output:
|
||||
this.selectedOutput === "null" ? null : this.selectedOutput,
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
this.fire("hass-api-called", { success: true, path: path });
|
||||
},
|
||||
(resp) => {
|
||||
this.error = resp.body.message;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-audio', HassioAddonAudio);
|
||||
customElements.define("hassio-addon-audio", HassioAddonAudio);
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import '@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
|
||||
class HassioAddonConfig extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -37,14 +37,25 @@ class HassioAddonConfig extends PolymerElement {
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<div class="errors">[[error]]</div>
|
||||
</template>
|
||||
<iron-autogrow-textarea id="config" value="{{config}}"></iron-autogrow-textarea>
|
||||
<iron-autogrow-textarea
|
||||
id="config"
|
||||
value="{{config}}"
|
||||
></iron-autogrow-textarea>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/options" data="[[resetData]]">Reset to defaults</ha-call-api-button>
|
||||
<paper-button on-click="saveTapped" disabled="[[!configParsed]]">Save</paper-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
path="hassio/addons/[[addonSlug]]/options"
|
||||
data="[[resetData]]"
|
||||
>Reset to defaults</ha-call-api-button
|
||||
>
|
||||
<paper-button on-click="saveTapped" disabled="[[!configParsed]]"
|
||||
>Save</paper-button
|
||||
>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -52,12 +63,12 @@ class HassioAddonConfig extends PolymerElement {
|
||||
hass: Object,
|
||||
addon: {
|
||||
type: Object,
|
||||
observer: 'addonChanged',
|
||||
observer: "addonChanged",
|
||||
},
|
||||
addonSlug: String,
|
||||
config: {
|
||||
type: String,
|
||||
observer: 'configChanged',
|
||||
observer: "configChanged",
|
||||
},
|
||||
configParsed: Object,
|
||||
error: String,
|
||||
@@ -71,15 +82,15 @@ class HassioAddonConfig extends PolymerElement {
|
||||
}
|
||||
|
||||
addonChanged(addon) {
|
||||
this.config = addon ? JSON.stringify(addon.options, null, 2) : '';
|
||||
this.config = addon ? JSON.stringify(addon.options, null, 2) : "";
|
||||
}
|
||||
|
||||
configChanged(config) {
|
||||
try {
|
||||
this.$.config.classList.remove('syntaxerror');
|
||||
this.$.config.classList.remove("syntaxerror");
|
||||
this.configParsed = JSON.parse(config);
|
||||
} catch (err) {
|
||||
this.$.config.classList.add('syntaxerror');
|
||||
this.$.config.classList.add("syntaxerror");
|
||||
this.configParsed = null;
|
||||
}
|
||||
}
|
||||
@@ -87,12 +98,14 @@ class HassioAddonConfig extends PolymerElement {
|
||||
saveTapped() {
|
||||
this.error = null;
|
||||
|
||||
this.hass.callApi('post', `hassio/addons/${this.addonSlug}/options`, {
|
||||
options: this.configParsed
|
||||
}).catch((resp) => {
|
||||
this.hass
|
||||
.callApi("post", `hassio/addons/${this.addonSlug}/options`, {
|
||||
options: this.configParsed,
|
||||
})
|
||||
.catch((resp) => {
|
||||
this.error = resp.body.message;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-config', HassioAddonConfig);
|
||||
customElements.define("hassio-addon-config", HassioAddonConfig);
|
||||
|
@@ -1,16 +1,65 @@
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-toggle-button/paper-toggle-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import '../../../src/components/ha-markdown.js';
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
import "../../../src/components/ha-label-badge";
|
||||
import "../../../src/components/ha-markdown";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/resources/ha-style";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
|
||||
import '../components/hassio-card-content.js';
|
||||
import "../components/hassio-card-content";
|
||||
|
||||
const PERMIS_DESC = {
|
||||
rating: {
|
||||
title: "Addon Security Rating",
|
||||
description:
|
||||
"Hass.io provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an addon requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 6. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 6 is the highest score (considered the most secure and lowest risk).",
|
||||
},
|
||||
host_network: {
|
||||
title: "Host Network",
|
||||
description:
|
||||
"Add-ons usually run in their own isolated network layer, which prevents them from accessing the network of the host operating system. In some cases, this network isolation can limit add-ons in providing their services and therefore, the isolation can be lifted by the add-on author, giving the addon full access to the network capabilities of the host machine. This gives the addon more networking capabilities but lowers the security, hence, the security rating of the add-on will be lowered when this option is used by the addon.",
|
||||
},
|
||||
homeassistant_api: {
|
||||
title: "Home Assistant API Access",
|
||||
description:
|
||||
"This add-on is allowed to access your running Home Assistant instance directly via the Home Assistant API. This mode handles authentication for the addon as well, which enables an addon to interact with Home Assistant without the need for additional authentication tokens.",
|
||||
},
|
||||
full_access: {
|
||||
title: "Full Hardware Access",
|
||||
description:
|
||||
"This addon is given full access to the hardware of your system, by request of the addon author. Access is comparable to the privileged mode in Docker. Since this opens up possible security risks, this feature impacts the addon security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the addon manually. Only disable the protection mode if you know, need AND trust the source of this addon.",
|
||||
},
|
||||
hassio_api: {
|
||||
title: "Hass.io API Access",
|
||||
description:
|
||||
"The addon was given access to the Hass.io API, by request of the addon author. By default, the addon can access general version information of your system. When the addon requests 'manager' or 'admin' level access to the API, it will gain access to control multiple parts of your Hass.io system. This permission is indicated by this badge and will impact the security score of the addon negatively.",
|
||||
},
|
||||
docker_api: {
|
||||
title: "Full Docker Access",
|
||||
description:
|
||||
"The addon author has requested the addon to have management access to the Docker instance running on your system. This mode gives the addon full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the addon security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the addon manually. Only disable the protection mode if you know, need AND trust the source of this addon.",
|
||||
},
|
||||
host_pid: {
|
||||
title: "Host Processes Namespace",
|
||||
description:
|
||||
"Usually, the processes the addon runs, are isolated from all other system processes. The addon author has requested the addon to have access to the system processes running on the host system instance, and allow the addon to spawn processes on the host system as well. This mode gives the addon full access and control to your entire Hass.io system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the addon security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the addon manually. Only disable the protection mode if you know, need AND trust the source of this addon.",
|
||||
},
|
||||
apparmor: {
|
||||
title: "AppArmor",
|
||||
description:
|
||||
"AppArmor ('Application Armor') is a Linux kernel security module that restricts addons capabilities like network access, raw socket access, and permission to read, write, or execute specific files.\n\nAddon authors can provide their security profiles, optimized for the addon, or request it to be disabled. If AppArmor is disabled, it will raise security risks and therefore, has a negative impact on the security score of the addon.",
|
||||
},
|
||||
auth_api: {
|
||||
title: "Home Assistant Authentication",
|
||||
description:
|
||||
"An addon can authenticate users against Home Assistant, allowing add-ons to give users the possibility to log into applications running inside add-ons, using their Home Assistant username/password. This badge indicates if the add-on author requests this capability.",
|
||||
},
|
||||
};
|
||||
|
||||
class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -23,6 +72,17 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
paper-card.warning {
|
||||
background-color: var(--google-red-500);
|
||||
color: white;
|
||||
--paper-card-header-color: white;
|
||||
}
|
||||
paper-card.warning paper-button {
|
||||
color: white !important;
|
||||
}
|
||||
.warning {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.addon-header {
|
||||
@apply --paper-font-headline;
|
||||
}
|
||||
@@ -42,7 +102,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
margin: 16px 0;
|
||||
display: block;
|
||||
}
|
||||
.state div{
|
||||
.state div {
|
||||
width: 150px;
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -65,14 +125,49 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
ha-markdown img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.red {
|
||||
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
||||
}
|
||||
.blue {
|
||||
--ha-label-badge-color: var(--label-badge-blue, #039be5);
|
||||
}
|
||||
.green {
|
||||
--ha-label-badge-color: var(--label-badge-green, #0da035);
|
||||
}
|
||||
.yellow {
|
||||
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
|
||||
}
|
||||
.security {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.security h3 {
|
||||
margin-bottom: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.security ha-label-badge {
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
--iron-icon-height: 45px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template is="dom-if" if="[[computeUpdateAvailable(addon)]]">
|
||||
<paper-card heading="Update available! 🎉">
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[addon.name]] [[addon.last_version]] is available" description="You are currently running version [[addon.version]]" icon="hassio:arrow-up-bold-circle" icon-class="update"></hassio-card-content>
|
||||
<hassio-card-content
|
||||
hass="[[hass]]"
|
||||
title="[[addon.name]] [[addon.last_version]] is available"
|
||||
description="You are currently running version [[addon.version]]"
|
||||
icon="hassio:arrow-up-bold-circle"
|
||||
icon-class="update"
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/addons/[[addonSlug]]/update">Update</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
hass="[[hass]]"
|
||||
path="hassio/addons/[[addonSlug]]/update"
|
||||
>Update</ha-call-api-button
|
||||
>
|
||||
<template is="dom-if" if="[[addon.changelog]]">
|
||||
<paper-button on-click="openChangelog">Changelog</paper-button>
|
||||
</template>
|
||||
@@ -82,15 +177,24 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<div class="addon-header">[[addon.name]]
|
||||
<div class="addon-header">
|
||||
[[addon.name]]
|
||||
<div class="addon-version light-color">
|
||||
<template is="dom-if" if="[[addon.version]]">
|
||||
[[addon.version]]
|
||||
<template is="dom-if" if="[[isRunning]]">
|
||||
<iron-icon title="Add-on is running" class="running" icon="hassio:circle"></iron-icon>
|
||||
<iron-icon
|
||||
title="Add-on is running"
|
||||
class="running"
|
||||
icon="hassio:circle"
|
||||
></iron-icon>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!isRunning]]">
|
||||
<iron-icon title="Add-on is stopped" class="stopped" icon="hassio:circle"></iron-icon>
|
||||
<iron-icon
|
||||
title="Add-on is stopped"
|
||||
class="stopped"
|
||||
icon="hassio:circle"
|
||||
></iron-icon>
|
||||
</template>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!addon.version]]">
|
||||
@@ -99,44 +203,195 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="description light-color">
|
||||
[[addon.description]].<br>
|
||||
Visit <a href="[[addon.url]]" target="_blank">[[addon.name]] page</a> for details.
|
||||
[[addon.description]].<br />
|
||||
Visit
|
||||
<a href="[[addon.url]]" target="_blank">[[addon.name]] page</a> for
|
||||
details.
|
||||
</div>
|
||||
<template is="dom-if" if="[[addon.logo]]">
|
||||
<a href="[[addon.url]]" target="_blank" class="logo">
|
||||
<img src="/api/hassio/addons/[[addonSlug]]/logo">
|
||||
<img src="/api/hassio/addons/[[addonSlug]]/logo" />
|
||||
</a>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!addon.protected]]">
|
||||
<paper-card heading="Warning: Protection mode is disabled!" class="warning">
|
||||
<div class="card-content">
|
||||
Protection mode on this addon is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this addon.
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<paper-button on-click="protectionToggled">Enable Protection mode</paper-button>
|
||||
</div>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
<div class="security">
|
||||
<h3>Addon Security Rating</h3>
|
||||
<div class="description light-color">
|
||||
Hass.io provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an addon requires on your system, the lower the score, thus raising the possible security risks.
|
||||
</div>
|
||||
<ha-label-badge
|
||||
class$="[[computeSecurityClassName(addon.rating)]]"
|
||||
on-click="showMoreInfo"
|
||||
id="rating"
|
||||
value="[[addon.rating]]"
|
||||
label="rating"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
<template is="dom-if" if="[[addon.host_network]]">
|
||||
<ha-label-badge
|
||||
on-click="showMoreInfo"
|
||||
id="host_network"
|
||||
icon="hassio:network"
|
||||
label="host"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
</template>
|
||||
<template is="dom-if" if="[[addon.full_access]]">
|
||||
<ha-label-badge
|
||||
on-click="showMoreInfo"
|
||||
id="full_access"
|
||||
icon="hassio:chip"
|
||||
label="hardware"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
</template>
|
||||
<template is="dom-if" if="[[addon.homeassistant_api]]">
|
||||
<ha-label-badge
|
||||
on-click="showMoreInfo"
|
||||
id="homeassistant_api"
|
||||
icon="hassio:home-assistant"
|
||||
label="hass"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
</template>
|
||||
<template is="dom-if" if="[[computeHassioApi(addon)]]">
|
||||
<ha-label-badge
|
||||
on-click="showMoreInfo"
|
||||
id="hassio_api"
|
||||
icon="hassio:home-assistant"
|
||||
label="hassio"
|
||||
description="[[addon.hassio_role]]"
|
||||
></ha-label-badge>
|
||||
</template>
|
||||
<template is="dom-if" if="[[addon.docker_api]]">
|
||||
<ha-label-badge
|
||||
on-click="showMoreInfo"
|
||||
id="docker_api"
|
||||
icon="hassio:docker"
|
||||
label="docker"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
</template>
|
||||
<template is="dom-if" if="[[addon.host_pid]]">
|
||||
<ha-label-badge
|
||||
on-click="showMoreInfo"
|
||||
id="host_pid"
|
||||
icon="hassio:pound"
|
||||
label="host pid"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
</template>
|
||||
<template is="dom-if" if="[[addon.apparmor]]">
|
||||
<ha-label-badge
|
||||
on-click="showMoreInfo"
|
||||
class$="[[computeApparmorClassName(addon.apparmor)]]"
|
||||
id="apparmor"
|
||||
icon="hassio:shield"
|
||||
label="apparmor"
|
||||
description="[[addon.apparmor]]"
|
||||
></ha-label-badge>
|
||||
</template>
|
||||
<template is="dom-if" if="[[addon.auth_api]]">
|
||||
<ha-label-badge
|
||||
on-click="showMoreInfo"
|
||||
id="auth_api"
|
||||
icon="hassio:key"
|
||||
label="auth"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
</template>
|
||||
</div>
|
||||
<template is="dom-if" if="[[addon.version]]">
|
||||
<div class="state">
|
||||
<div>Start on boot</div>
|
||||
<paper-toggle-button on-change="startOnBootToggled" checked="[[computeStartOnBoot(addon.boot)]]"></paper-toggle-button>
|
||||
<paper-toggle-button
|
||||
on-change="startOnBootToggled"
|
||||
checked="[[computeStartOnBoot(addon.boot)]]"
|
||||
></paper-toggle-button>
|
||||
</div>
|
||||
<div class="state">
|
||||
<div>Auto update</div>
|
||||
<paper-toggle-button on-change="autoUpdateToggled" checked="[[addon.auto_update]]"></paper-toggle-button>
|
||||
<paper-toggle-button
|
||||
on-change="autoUpdateToggled"
|
||||
checked="[[addon.auto_update]]"
|
||||
></paper-toggle-button>
|
||||
</div>
|
||||
<div class="state">
|
||||
<div>Protection mode</div>
|
||||
<paper-toggle-button
|
||||
on-change="protectionToggled"
|
||||
checked="[[addon.protected]]"
|
||||
></paper-toggle-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<template is="dom-if" if="[[addon.version]]">
|
||||
<paper-button class="warning" on-click="_unistallClicked">Uninstall</paper-button>
|
||||
<paper-button class="warning" on-click="_unistallClicked"
|
||||
>Uninstall</paper-button
|
||||
>
|
||||
<template is="dom-if" if="[[addon.build]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/rebuild">Rebuild</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
path="hassio/addons/[[addonSlug]]/rebuild"
|
||||
>Rebuild</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
<template is="dom-if" if="[[isRunning]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/restart">Restart</ha-call-api-button>
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/stop">Stop</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
path="hassio/addons/[[addonSlug]]/restart"
|
||||
>Restart</ha-call-api-button
|
||||
>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
path="hassio/addons/[[addonSlug]]/stop"
|
||||
>Stop</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!isRunning]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/addons/[[addonSlug]]/start">Start</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
hass="[[hass]]"
|
||||
path="hassio/addons/[[addonSlug]]/start"
|
||||
>Start</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
<template is="dom-if" if="[[computeShowWebUI(addon.webui, isRunning)]]">
|
||||
<a href="[[pathWebui(addon.webui)]]" tabindex="-1" target="_blank" class="right"><paper-button>Open web UI</paper-button></a>
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[computeShowWebUI(addon.webui, isRunning)]]"
|
||||
>
|
||||
<a
|
||||
href="[[pathWebui(addon.webui)]]"
|
||||
tabindex="-1"
|
||||
target="_blank"
|
||||
class="right"
|
||||
><paper-button>Open web UI</paper-button></a
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!addon.version]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/addons/[[addonSlug]]/install">Install</ha-call-api-button>
|
||||
<template is="dom-if" if="[[!addon.available]]">
|
||||
<p class="warning">This addon is not available on your system.</p>
|
||||
</template>
|
||||
<ha-call-api-button
|
||||
disabled="[[!addon.available]]"
|
||||
hass="[[hass]]"
|
||||
path="hassio/addons/[[addonSlug]]/install"
|
||||
>Install</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
@@ -147,7 +402,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -155,23 +410,42 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
hass: Object,
|
||||
addon: Object,
|
||||
addonSlug: String,
|
||||
isRunning: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsRunning(addon)',
|
||||
},
|
||||
isRunning: { type: Boolean, computed: "computeIsRunning(addon)" },
|
||||
};
|
||||
}
|
||||
|
||||
computeIsRunning(addon) {
|
||||
return addon && addon.state === 'started';
|
||||
return addon && addon.state === "started";
|
||||
}
|
||||
|
||||
computeUpdateAvailable(addon) {
|
||||
return addon && !addon.detached && addon.version && addon.version !== addon.last_version;
|
||||
return (
|
||||
addon &&
|
||||
!addon.detached &&
|
||||
addon.version &&
|
||||
addon.version !== addon.last_version
|
||||
);
|
||||
}
|
||||
|
||||
computeHassioApi(addon) {
|
||||
return (
|
||||
addon.hassio_api &&
|
||||
(addon.hassio_role === "manager" || addon.hassio_role === "admin")
|
||||
);
|
||||
}
|
||||
|
||||
computeApparmorClassName(apparmor) {
|
||||
if (apparmor === "profile") {
|
||||
return "green";
|
||||
}
|
||||
if (apparmor === "disable") {
|
||||
return "red";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
pathWebui(webui) {
|
||||
return webui && webui.replace('[HOST]', document.location.hostname);
|
||||
return webui && webui.replace("[HOST]", document.location.hostname);
|
||||
}
|
||||
|
||||
computeShowWebUI(webui, isRunning) {
|
||||
@@ -179,49 +453,78 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
computeStartOnBoot(state) {
|
||||
return state === 'auto';
|
||||
return state === "auto";
|
||||
}
|
||||
|
||||
computeSecurityClassName(rating) {
|
||||
if (rating > 4) {
|
||||
return "green";
|
||||
}
|
||||
if (rating > 2) {
|
||||
return "yellow";
|
||||
}
|
||||
return "red";
|
||||
}
|
||||
|
||||
startOnBootToggled() {
|
||||
const data = { boot: this.addon.boot === 'auto' ? 'manual' : 'auto' };
|
||||
this.hass.callApi('POST', `hassio/addons/${this.addonSlug}/options`, data);
|
||||
const data = { boot: this.addon.boot === "auto" ? "manual" : "auto" };
|
||||
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/options`, data);
|
||||
}
|
||||
|
||||
autoUpdateToggled() {
|
||||
const data = { auto_update: !this.addon.auto_update };
|
||||
this.hass.callApi('POST', `hassio/addons/${this.addonSlug}/options`, data);
|
||||
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/options`, data);
|
||||
}
|
||||
|
||||
protectionToggled() {
|
||||
const data = { protected: !this.addon.protected };
|
||||
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/security`, data);
|
||||
this.set("addon.protected", !this.addon.protected);
|
||||
}
|
||||
|
||||
showMoreInfo(e) {
|
||||
const id = e.target.getAttribute("id");
|
||||
this.fire("hassio-markdown-dialog", {
|
||||
title: PERMIS_DESC[id].title,
|
||||
content: PERMIS_DESC[id].description,
|
||||
});
|
||||
}
|
||||
|
||||
openChangelog() {
|
||||
this.hass.callApi('get', `hassio/addons/${this.addonSlug}/changelog`)
|
||||
.then(
|
||||
resp => resp,
|
||||
() => 'Error getting changelog'
|
||||
).then((content) => {
|
||||
this.fire('hassio-markdown-dialog', {
|
||||
title: 'Changelog',
|
||||
this.hass
|
||||
.callApi("get", `hassio/addons/${this.addonSlug}/changelog`)
|
||||
.then((resp) => resp, () => "Error getting changelog")
|
||||
.then((content) => {
|
||||
this.fire("hassio-markdown-dialog", {
|
||||
title: "Changelog",
|
||||
content: content,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_unistallClicked() {
|
||||
if (!confirm('Are you sure you want to uninstall this add-on?')) {
|
||||
if (!confirm("Are you sure you want to uninstall this add-on?")) {
|
||||
return;
|
||||
}
|
||||
const path = `hassio/addons/${this.addonSlug}/uninstall`;
|
||||
const eventData = {
|
||||
path: path,
|
||||
};
|
||||
this.hass.callApi('post', path).then((resp) => {
|
||||
this.hass
|
||||
.callApi("post", path)
|
||||
.then(
|
||||
(resp) => {
|
||||
eventData.success = true;
|
||||
eventData.response = resp;
|
||||
}, (resp) => {
|
||||
},
|
||||
(resp) => {
|
||||
eventData.success = false;
|
||||
eventData.response = resp;
|
||||
}).then(() => {
|
||||
this.fire('hass-api-called', eventData);
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.fire("hass-api-called", eventData);
|
||||
});
|
||||
}
|
||||
}
|
||||
customElements.define('hassio-addon-info', HassioAddonInfo);
|
||||
customElements.define("hassio-addon-info", HassioAddonInfo);
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html";
|
||||
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import "../../../src/resources/ha-style";
|
||||
|
||||
class HassioAddonLogs extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -15,17 +16,18 @@ class HassioAddonLogs extends PolymerElement {
|
||||
}
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
${ANSI_HTML_STYLE}
|
||||
<paper-card heading="Log">
|
||||
<div class="card-content">
|
||||
<pre>[[log]]</pre>
|
||||
</div>
|
||||
<div class="card-content" id="content"></div>
|
||||
<div class="card-actions">
|
||||
<paper-button on-click="refresh">Refresh</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -33,15 +35,16 @@ class HassioAddonLogs extends PolymerElement {
|
||||
hass: Object,
|
||||
addonSlug: {
|
||||
type: String,
|
||||
observer: 'addonSlugChanged',
|
||||
observer: "addonSlugChanged",
|
||||
},
|
||||
log: String,
|
||||
};
|
||||
}
|
||||
|
||||
addonSlugChanged(slug) {
|
||||
if (!this.hass) {
|
||||
setTimeout(() => { this.addonChanged(slug); }, 0);
|
||||
setTimeout(() => {
|
||||
this.addonChanged(slug);
|
||||
}, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,11 +52,15 @@ class HassioAddonLogs extends PolymerElement {
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.hass.callApi('get', `hassio/addons/${this.addonSlug}/logs`)
|
||||
.then((info) => {
|
||||
this.log = info;
|
||||
this.hass
|
||||
.callApi("get", `hassio/addons/${this.addonSlug}/logs`)
|
||||
.then((text) => {
|
||||
while (this.$.content.lastChild) {
|
||||
this.$.content.removeChild(this.$.content.lastChild);
|
||||
}
|
||||
this.$.content.appendChild(parseTextToColoredPre(text));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-logs', HassioAddonLogs);
|
||||
customElements.define("hassio-addon-logs", HassioAddonLogs);
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-input/paper-input.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/resources/ha-style";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
|
||||
class HassioAddonNetwork extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -33,28 +33,37 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
|
||||
</template>
|
||||
|
||||
<table>
|
||||
<tbody><tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>Container</th>
|
||||
<th>Host</th>
|
||||
</tr>
|
||||
<template is="dom-repeat" items="[[config]]">
|
||||
<tr>
|
||||
<td>[[item.container]]</td>
|
||||
<td>
|
||||
[[item.container]]
|
||||
</td>
|
||||
<td>
|
||||
<paper-input value="{{item.host}}" no-label-float=""></paper-input>
|
||||
<paper-input
|
||||
value="{{item.host}}"
|
||||
no-label-float=""
|
||||
></paper-input>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody></table>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/addons/[[addonSlug]]/options" data="[[resetData]]">Reset to defaults</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
path="hassio/addons/[[addonSlug]]/options"
|
||||
data="[[resetData]]"
|
||||
>Reset to defaults</ha-call-api-button
|
||||
>
|
||||
<paper-button on-click="saveTapped">Save</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -64,7 +73,7 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
|
||||
config: Object,
|
||||
addon: {
|
||||
type: Object,
|
||||
observer: 'addonChanged',
|
||||
observer: "addonChanged",
|
||||
},
|
||||
error: String,
|
||||
resetData: {
|
||||
@@ -80,29 +89,36 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
|
||||
if (!addon) return;
|
||||
|
||||
const network = addon.network || {};
|
||||
const items = Object.keys(network).map(key => ({
|
||||
const items = Object.keys(network).map((key) => ({
|
||||
container: key,
|
||||
host: network[key]
|
||||
host: network[key],
|
||||
}));
|
||||
this.config = items.sort(function (el1, el2) { return el1.host - el2.host; });
|
||||
this.config = items.sort(function(el1, el2) {
|
||||
return el1.host - el2.host;
|
||||
});
|
||||
}
|
||||
|
||||
saveTapped() {
|
||||
this.error = null;
|
||||
const data = {};
|
||||
this.config.forEach(function (item) {
|
||||
this.config.forEach(function(item) {
|
||||
data[item.container] = parseInt(item.host);
|
||||
});
|
||||
const path = `hassio/addons/${this.addonSlug}/options`;
|
||||
|
||||
this.hass.callApi('post', path, {
|
||||
network: data
|
||||
}).then(() => {
|
||||
this.fire('hass-api-called', { success: true, path: path });
|
||||
}, (resp) => {
|
||||
this.hass
|
||||
.callApi("post", path, {
|
||||
network: data,
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
this.fire("hass-api-called", { success: true, path: path });
|
||||
},
|
||||
(resp) => {
|
||||
this.error = resp.body.message;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-network', HassioAddonNetwork);
|
||||
customElements.define("hassio-addon-network", HassioAddonNetwork);
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
|
||||
import '@polymer/app-layout/app-header/app-header.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/app-route/app-route.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/app-route/app-route";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/components/ha-menu-button.js';
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import '../hassio-markdown-dialog.js';
|
||||
import './hassio-addon-audio.js';
|
||||
import './hassio-addon-config.js';
|
||||
import './hassio-addon-info.js';
|
||||
import './hassio-addon-logs.js';
|
||||
import './hassio-addon-network.js';
|
||||
import "../../../src/components/ha-menu-button";
|
||||
import "../../../src/resources/ha-style";
|
||||
import "../hassio-markdown-dialog";
|
||||
import "./hassio-addon-audio";
|
||||
import "./hassio-addon-config";
|
||||
import "./hassio-addon-info";
|
||||
import "./hassio-addon-logs";
|
||||
import "./hassio-addon-network";
|
||||
|
||||
class HassioAddonView extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -51,36 +51,69 @@ class HassioAddonView extends PolymerElement {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<app-route route="[[route]]" pattern="/addon/:slug" data="{{routeData}}" active="{{routeMatches}}"></app-route>
|
||||
<app-route
|
||||
route="[[route]]"
|
||||
pattern="/addon/:slug"
|
||||
data="{{routeData}}"
|
||||
active="{{routeMatches}}"
|
||||
></app-route>
|
||||
<app-header-layout has-scrolling-region="">
|
||||
<app-header fixed="" slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button hassio narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button>
|
||||
<paper-icon-button icon="hassio:arrow-left" on-click="backTapped"></paper-icon-button>
|
||||
<ha-menu-button
|
||||
hassio
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<paper-icon-button
|
||||
icon="hassio:arrow-left"
|
||||
on-click="backTapped"
|
||||
></paper-icon-button>
|
||||
<div main-title="">Hass.io: add-on details</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="content">
|
||||
<hassio-addon-info hass="[[hass]]" addon="[[addon]]" addon-slug="[[routeData.slug]]"></hassio-addon-info>
|
||||
<hassio-addon-info
|
||||
hass="[[hass]]"
|
||||
addon="[[addon]]"
|
||||
addon-slug="[[routeData.slug]]"
|
||||
></hassio-addon-info>
|
||||
|
||||
<template is="dom-if" if="[[addon.version]]">
|
||||
<hassio-addon-config hass="[[hass]]" addon="[[addon]]" addon-slug="[[routeData.slug]]"></hassio-addon-config>
|
||||
<hassio-addon-config
|
||||
hass="[[hass]]"
|
||||
addon="[[addon]]"
|
||||
addon-slug="[[routeData.slug]]"
|
||||
></hassio-addon-config>
|
||||
|
||||
<template is="dom-if" if="[[addon.audio]]">
|
||||
<hassio-addon-audio hass="[[hass]]" addon="[[addon]]"></hassio-addon-audio>
|
||||
<hassio-addon-audio
|
||||
hass="[[hass]]"
|
||||
addon="[[addon]]"
|
||||
></hassio-addon-audio>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[addon.network]]">
|
||||
<hassio-addon-network hass="[[hass]]" addon="[[addon]]" addon-slug="[[routeData.slug]]"></hassio-addon-network>
|
||||
<hassio-addon-network
|
||||
hass="[[hass]]"
|
||||
addon="[[addon]]"
|
||||
addon-slug="[[routeData.slug]]"
|
||||
></hassio-addon-network>
|
||||
</template>
|
||||
|
||||
<hassio-addon-logs hass="[[hass]]" addon-slug="[[routeData.slug]]"></hassio-addon-logs>
|
||||
<hassio-addon-logs
|
||||
hass="[[hass]]"
|
||||
addon-slug="[[routeData.slug]]"
|
||||
></hassio-addon-logs>
|
||||
</template>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
|
||||
<hassio-markdown-dialog title="[[markdownTitle]]" content="[[markdownContent]]"></hassio-markdown-dialog>
|
||||
`;
|
||||
<hassio-markdown-dialog
|
||||
title="[[markdownTitle]]"
|
||||
content="[[markdownContent]]"
|
||||
></hassio-markdown-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -91,7 +124,7 @@ class HassioAddonView extends PolymerElement {
|
||||
route: Object,
|
||||
routeData: {
|
||||
type: Object,
|
||||
observer: 'routeDataChanged',
|
||||
observer: "routeDataChanged",
|
||||
},
|
||||
routeMatches: Boolean,
|
||||
addon: Object,
|
||||
@@ -99,15 +132,17 @@ class HassioAddonView extends PolymerElement {
|
||||
markdownTitle: String,
|
||||
markdownContent: {
|
||||
type: String,
|
||||
value: '',
|
||||
value: "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
this.addEventListener('hassio-markdown-dialog', ev => this.openMarkdown(ev));
|
||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
||||
this.addEventListener("hassio-markdown-dialog", (ev) =>
|
||||
this.openMarkdown(ev)
|
||||
);
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
@@ -115,7 +150,7 @@ class HassioAddonView extends PolymerElement {
|
||||
|
||||
if (!path) return;
|
||||
|
||||
if (path.substr(path.lastIndexOf('/') + 1) === 'uninstall') {
|
||||
if (path.substr(path.lastIndexOf("/") + 1) === "uninstall") {
|
||||
this.backTapped();
|
||||
} else {
|
||||
this.routeDataChanged(this.routeData);
|
||||
@@ -124,12 +159,14 @@ class HassioAddonView extends PolymerElement {
|
||||
|
||||
routeDataChanged(routeData) {
|
||||
if (!this.routeMatches || !routeData || !routeData.slug) return;
|
||||
this.hass.callApi('get', `hassio/addons/${routeData.slug}/info`)
|
||||
.then((info) => {
|
||||
this.hass.callApi("get", `hassio/addons/${routeData.slug}/info`).then(
|
||||
(info) => {
|
||||
this.addon = info.data;
|
||||
}, () => {
|
||||
},
|
||||
() => {
|
||||
this.addon = null;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
backTapped() {
|
||||
@@ -141,8 +178,8 @@ class HassioAddonView extends PolymerElement {
|
||||
markdownTitle: ev.detail.title,
|
||||
markdownContent: ev.detail.content,
|
||||
});
|
||||
this.shadowRoot.querySelector('hassio-markdown-dialog').openDialog();
|
||||
this.shadowRoot.querySelector("hassio-markdown-dialog").openDialog();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addon-view', HassioAddonView);
|
||||
customElements.define("hassio-addon-view", HassioAddonView);
|
||||
|
203
hassio/src/ansi-to-html.js
Normal file
203
hassio/src/ansi-to-html.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
|
||||
export const ANSI_HTML_STYLE = html`
|
||||
<style>
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.underline.strikethrough {
|
||||
text-decoration: underline line-through;
|
||||
}
|
||||
.fg-red {
|
||||
color: rgb(222, 56, 43);
|
||||
}
|
||||
.fg-green {
|
||||
color: rgb(57, 181, 74);
|
||||
}
|
||||
.fg-yellow {
|
||||
color: rgb(255, 199, 6);
|
||||
}
|
||||
.fg-blue {
|
||||
color: rgb(0, 111, 184);
|
||||
}
|
||||
.fg-magenta {
|
||||
color: rgb(118, 38, 113);
|
||||
}
|
||||
.fg-cyan {
|
||||
color: rgb(44, 181, 233);
|
||||
}
|
||||
.fg-white {
|
||||
color: rgb(204, 204, 204);
|
||||
}
|
||||
.bg-black {
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
.bg-red {
|
||||
background-color: rgb(222, 56, 43);
|
||||
}
|
||||
.bg-green {
|
||||
background-color: rgb(57, 181, 74);
|
||||
}
|
||||
.bg-yellow {
|
||||
background-color: rgb(255, 199, 6);
|
||||
}
|
||||
.bg-blue {
|
||||
background-color: rgb(0, 111, 184);
|
||||
}
|
||||
.bg-magenta {
|
||||
background-color: rgb(118, 38, 113);
|
||||
}
|
||||
.bg-cyan {
|
||||
background-color: rgb(44, 181, 233);
|
||||
}
|
||||
.bg-white {
|
||||
background-color: rgb(204, 204, 204);
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
export function parseTextToColoredPre(text) {
|
||||
const pre = document.createElement("pre");
|
||||
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
|
||||
let i = 0;
|
||||
|
||||
const state = {
|
||||
bold: false,
|
||||
italic: false,
|
||||
underline: false,
|
||||
strikethrough: false,
|
||||
foregroundColor: null,
|
||||
backgroundColor: null,
|
||||
};
|
||||
|
||||
const addSpan = (content) => {
|
||||
const span = document.createElement("span");
|
||||
if (state.bold) span.classList.add("bold");
|
||||
if (state.italic) span.classList.add("italic");
|
||||
if (state.underline) span.classList.add("underline");
|
||||
if (state.strikethrough) span.classList.add("strikethrough");
|
||||
if (state.foregroundColor !== null)
|
||||
span.classList.add(`fg-${state.foregroundColor}`);
|
||||
if (state.backgroundColor !== null)
|
||||
span.classList.add(`bg-${state.backgroundColor}`);
|
||||
span.appendChild(document.createTextNode(content));
|
||||
pre.appendChild(span);
|
||||
};
|
||||
|
||||
/* eslint-disable no-cond-assign */
|
||||
let match;
|
||||
while ((match = re.exec(text)) !== null) {
|
||||
const j = match.index;
|
||||
addSpan(text.substring(i, j));
|
||||
i = j + match[0].length;
|
||||
|
||||
if (match[1] === undefined) continue;
|
||||
|
||||
for (const colorCode of match[1].split(";")) {
|
||||
switch (parseInt(colorCode)) {
|
||||
case 0:
|
||||
// reset
|
||||
state.bold = false;
|
||||
state.italic = false;
|
||||
state.underline = false;
|
||||
state.strikethrough = false;
|
||||
state.foregroundColor = null;
|
||||
state.backgroundColor = null;
|
||||
break;
|
||||
case 1:
|
||||
state.bold = true;
|
||||
break;
|
||||
case 3:
|
||||
state.italic = true;
|
||||
break;
|
||||
case 4:
|
||||
state.underline = true;
|
||||
break;
|
||||
case 9:
|
||||
state.strikethrough = true;
|
||||
break;
|
||||
case 22:
|
||||
state.bold = false;
|
||||
break;
|
||||
case 23:
|
||||
state.italic = false;
|
||||
break;
|
||||
case 24:
|
||||
state.underline = false;
|
||||
break;
|
||||
case 29:
|
||||
state.strikethrough = false;
|
||||
break;
|
||||
case 30:
|
||||
// foreground black
|
||||
state.foregroundColor = null;
|
||||
break;
|
||||
case 31:
|
||||
state.foregroundColor = "red";
|
||||
break;
|
||||
case 32:
|
||||
state.foregroundColor = "green";
|
||||
break;
|
||||
case 33:
|
||||
state.foregroundColor = "yellow";
|
||||
break;
|
||||
case 34:
|
||||
state.foregroundColor = "blue";
|
||||
break;
|
||||
case 35:
|
||||
state.foregroundColor = "magenta";
|
||||
break;
|
||||
case 36:
|
||||
state.foregroundColor = "cyan";
|
||||
break;
|
||||
case 37:
|
||||
state.foregroundColor = "white";
|
||||
break;
|
||||
case 39:
|
||||
// foreground reset
|
||||
state.foregroundColor = null;
|
||||
break;
|
||||
case 40:
|
||||
state.backgroundColor = "black";
|
||||
break;
|
||||
case 41:
|
||||
state.backgroundColor = "red";
|
||||
break;
|
||||
case 42:
|
||||
state.backgroundColor = "green";
|
||||
break;
|
||||
case 43:
|
||||
state.backgroundColor = "yellow";
|
||||
break;
|
||||
case 44:
|
||||
state.backgroundColor = "blue";
|
||||
break;
|
||||
case 45:
|
||||
state.backgroundColor = "magenta";
|
||||
break;
|
||||
case 46:
|
||||
state.backgroundColor = "cyan";
|
||||
break;
|
||||
case 47:
|
||||
state.backgroundColor = "white";
|
||||
break;
|
||||
case 49:
|
||||
// background reset
|
||||
state.backgroundColor = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
addSpan(text.substring(i));
|
||||
|
||||
return pre;
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
import '@polymer/iron-icon/iron-icon.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/components/ha-relative-time.js';
|
||||
import "../../../src/components/ha-relative-time";
|
||||
|
||||
class HassioCardContent extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -25,6 +25,9 @@ class HassioCardContent extends PolymerElement {
|
||||
iron-icon.snapshot {
|
||||
color: var(--paper-item-icon-color);
|
||||
}
|
||||
iron-icon.not_available {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.title {
|
||||
color: var(--primary-text-color);
|
||||
white-space: nowrap;
|
||||
@@ -42,19 +45,30 @@ class HassioCardContent extends PolymerElement {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<iron-icon icon="[[icon]]" class\$="[[iconClass]]" title="[[iconTitle]]"></iron-icon>
|
||||
<iron-icon
|
||||
icon="[[icon]]"
|
||||
class\$="[[iconClass]]"
|
||||
title="[[iconTitle]]"
|
||||
></iron-icon>
|
||||
<div>
|
||||
<div class="title">[[title]]</div>
|
||||
<div class="addition">
|
||||
<template is="dom-if" if="[[description]]">
|
||||
[[description]]
|
||||
</template>
|
||||
<template is="dom-if" if="[[!available]]">
|
||||
(Not available)
|
||||
</template>
|
||||
<template is="dom-if" if="[[datetime]]">
|
||||
<ha-relative-time hass="[[hass]]" class="addition" datetime="[[datetime]]"></ha-relative-time>
|
||||
<ha-relative-time
|
||||
hass="[[hass]]"
|
||||
class="addition"
|
||||
datetime="[[datetime]]"
|
||||
></ha-relative-time>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -62,14 +76,15 @@ class HassioCardContent extends PolymerElement {
|
||||
hass: Object,
|
||||
title: String,
|
||||
description: String,
|
||||
available: Boolean,
|
||||
datetime: String,
|
||||
icon: {
|
||||
type: String,
|
||||
value: 'hass:help-circle'
|
||||
value: "hass:help-circle",
|
||||
},
|
||||
iconTitle: String,
|
||||
iconClass: String,
|
||||
};
|
||||
}
|
||||
}
|
||||
customElements.define('hassio-card-content', HassioCardContent);
|
||||
customElements.define("hassio-card-content", HassioCardContent);
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
import NavigateMixin from '../../../src/mixins/navigate-mixin.js';
|
||||
import "../components/hassio-card-content";
|
||||
import "../resources/hassio-style";
|
||||
import NavigateMixin from "../../../src/mixins/navigate-mixin";
|
||||
|
||||
class HassioAddons extends NavigateMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -19,19 +19,34 @@ class HassioAddons extends NavigateMixin(PolymerElement) {
|
||||
<template is="dom-if" if="[[!addons.length]]">
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
You don't have any add-ons installed yet. Head over to <a href="#" on-click="openStore">the add-on store</a> to get started!
|
||||
You don't have any add-ons installed yet. Head over to
|
||||
<a href="#" on-click="openStore">the add-on store</a> to get
|
||||
started!
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
<template is="dom-repeat" items="[[addons]]" as="addon" sort="sortAddons">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[addons]]"
|
||||
as="addon"
|
||||
sort="sortAddons"
|
||||
>
|
||||
<paper-card on-click="addonTapped">
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[addon.name]]" description="[[addon.description]]" icon="[[computeIcon(addon)]]" icon-title="[[computeIconTitle(addon)]]" icon-class="[[computeIconClass(addon)]]"></hassio-card-content>
|
||||
<hassio-card-content
|
||||
hass="[[hass]]"
|
||||
title="[[addon.name]]"
|
||||
description="[[addon.description]]"
|
||||
available="[[addon.available]]"
|
||||
icon="[[computeIcon(addon)]]"
|
||||
icon-title="[[computeIconTitle(addon)]]"
|
||||
icon-class="[[computeIconClass(addon)]]"
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -46,28 +61,32 @@ class HassioAddons extends NavigateMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
computeIcon(addon) {
|
||||
return addon.installed !== addon.version ? 'hassio:arrow-up-bold-circle' : 'hassio:puzzle';
|
||||
return addon.installed !== addon.version
|
||||
? "hassio:arrow-up-bold-circle"
|
||||
: "hassio:puzzle";
|
||||
}
|
||||
|
||||
computeIconTitle(addon) {
|
||||
if (addon.installed !== addon.version) return 'New version available';
|
||||
return addon.state === 'started' ? 'Add-on is running' : 'Add-on is stopped';
|
||||
if (addon.installed !== addon.version) return "New version available";
|
||||
return addon.state === "started"
|
||||
? "Add-on is running"
|
||||
: "Add-on is stopped";
|
||||
}
|
||||
|
||||
computeIconClass(addon) {
|
||||
if (addon.installed !== addon.version) return 'update';
|
||||
return addon.state === 'started' ? 'running' : '';
|
||||
if (addon.installed !== addon.version) return "update";
|
||||
return addon.state === "started" ? "running" : "";
|
||||
}
|
||||
|
||||
addonTapped(ev) {
|
||||
this.navigate('/hassio/addon/' + ev.model.addon.slug);
|
||||
this.navigate("/hassio/addon/" + ev.model.addon.slug);
|
||||
ev.target.blur();
|
||||
}
|
||||
|
||||
openStore(ev) {
|
||||
this.navigate('/hassio/store');
|
||||
this.navigate("/hassio/store");
|
||||
ev.target.blur();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-addons', HassioAddons);
|
||||
customElements.define("hassio-addons", HassioAddons);
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import './hassio-addons.js';
|
||||
import './hassio-hass-update.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
import "./hassio-addons";
|
||||
import "./hassio-hass-update";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
|
||||
class HassioDashboard extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -14,10 +14,16 @@ class HassioDashboard extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
</style>
|
||||
<div class="content">
|
||||
<hassio-hass-update hass="[[hass]]" hass-info="[[hassInfo]]"></hassio-hass-update>
|
||||
<hassio-addons hass="[[hass]]" addons="[[supervisorInfo.addons]]"></hassio-addons>
|
||||
<hassio-hass-update
|
||||
hass="[[hass]]"
|
||||
hass-info="[[hassInfo]]"
|
||||
></hassio-hass-update>
|
||||
<hassio-addons
|
||||
hass="[[hass]]"
|
||||
addons="[[supervisorInfo.addons]]"
|
||||
></hassio-addons>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -29,4 +35,4 @@ class HassioDashboard extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-dashboard', HassioDashboard);
|
||||
customElements.define("hassio-dashboard", HassioDashboard);
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../components/hassio-card-content";
|
||||
import "../resources/hassio-style";
|
||||
|
||||
class HassioHassUpdate extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -19,6 +19,9 @@ class HassioHassUpdate extends PolymerElement {
|
||||
color: var(--google-red-500);
|
||||
margin-top: 16px;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="[[computeUpdateAvailable(hassInfo)]]">
|
||||
<div class="content">
|
||||
@@ -26,20 +29,41 @@ class HassioHassUpdate extends PolymerElement {
|
||||
<div class="title">Update available! 🎉</div>
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="Home Assistant [[hassInfo.last_version]] is available" description="You are currently running version [[hassInfo.version]]" icon="hassio:home-assistant" icon-class="hassupdate"></hassio-card-content>
|
||||
<hassio-card-content
|
||||
hass="[[hass]]"
|
||||
title="Home Assistant [[hassInfo.last_version]] is available"
|
||||
description="You are currently running version [[hassInfo.version]]"
|
||||
icon="hassio:home-assistant"
|
||||
icon-class="hassupdate"
|
||||
></hassio-card-content>
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<div class="error">Error: [[error]]</div>
|
||||
</template>
|
||||
<p>
|
||||
<a
|
||||
href="https://www.home-assistant.io/latest-release-notes/"
|
||||
target="_blank"
|
||||
>Read the release notes</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/homeassistant/update">Update</ha-call-api-button>
|
||||
<a href="https://github.com/home-assistant/home-assistant/releases" target="_blank"><paper-button>Release notes</paper-button></a>
|
||||
<ha-call-api-button
|
||||
hass="[[hass]]"
|
||||
path="hassio/homeassistant/update"
|
||||
>Update</ha-call-api-button
|
||||
>
|
||||
<a
|
||||
href="https://github.com/home-assistant/home-assistant/releases"
|
||||
target="_blank"
|
||||
><paper-button>Release notes</paper-button></a
|
||||
>
|
||||
</div>
|
||||
</paper-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -52,7 +76,7 @@ class HassioHassUpdate extends PolymerElement {
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
@@ -63,8 +87,8 @@ class HassioHassUpdate extends PolymerElement {
|
||||
|
||||
const response = ev.detail.response;
|
||||
|
||||
if (typeof response.body === 'object') {
|
||||
this.errors = response.body.message || 'Unknown error';
|
||||
if (typeof response.body === "object") {
|
||||
this.errors = response.body.message || "Unknown error";
|
||||
} else {
|
||||
this.errors = response.body;
|
||||
}
|
||||
@@ -75,4 +99,4 @@ class HassioHassUpdate extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-hass-update', HassioHassUpdate);
|
||||
customElements.define("hassio-hass-update", HassioHassUpdate);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
window.loadES5Adapter().then(() => {
|
||||
import(/* webpackChunkName: "hassio-icons" */ './resources/hassio-icons.js');
|
||||
import(/* webpackChunkName: "hassio-main" */ './hassio-main.js');
|
||||
import(/* webpackChunkName: "hassio-icons" */ "./resources/hassio-icons.js");
|
||||
import(/* webpackChunkName: "hassio-main" */ "./hassio-main.js");
|
||||
});
|
||||
|
@@ -1,16 +1,21 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import './hassio-main.js';
|
||||
import './resources/hassio-icons.js';
|
||||
import "./hassio-main";
|
||||
import "./resources/hassio-icons";
|
||||
|
||||
class HassioApp extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<template is="dom-if" if="[[hass]]">
|
||||
<hassio-main hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" route="[[route]]"></hassio-main>
|
||||
<hassio-main
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
route="[[route]]"
|
||||
></hassio-main>
|
||||
</template>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -29,13 +34,13 @@ class HassioApp extends PolymerElement {
|
||||
ready() {
|
||||
super.ready();
|
||||
window.setProperties = this.setProperties.bind(this);
|
||||
this.addEventListener('location-changed', () => this._locationChanged());
|
||||
this.addEventListener('hass-open-menu', () => this._menuEvent(true));
|
||||
this.addEventListener('hass-close-menu', () => this._menuEvent(false));
|
||||
this.addEventListener("location-changed", () => this._locationChanged());
|
||||
this.addEventListener("hass-open-menu", () => this._menuEvent(true));
|
||||
this.addEventListener("hass-close-menu", () => this._menuEvent(false));
|
||||
}
|
||||
|
||||
_menuEvent(shouldOpen) {
|
||||
this.hassioPanel.fire(shouldOpen ? 'hass-open-menu' : 'hass-close-menu');
|
||||
this.hassioPanel.fire(shouldOpen ? "hass-open-menu" : "hass-close-menu");
|
||||
}
|
||||
|
||||
_locationChanged() {
|
||||
@@ -43,4 +48,4 @@ class HassioApp extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-app', HassioApp);
|
||||
customElements.define("hassio-app", HassioApp);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
class HassioData extends PolymerElement {
|
||||
static get properties() {
|
||||
@@ -36,25 +36,24 @@ class HassioData extends PolymerElement {
|
||||
}
|
||||
|
||||
fetchSupervisorInfo() {
|
||||
return this.hass.callApi('get', 'hassio/supervisor/info')
|
||||
.then((info) => {
|
||||
return this.hass.callApi("get", "hassio/supervisor/info").then((info) => {
|
||||
this.supervisor = info.data;
|
||||
});
|
||||
}
|
||||
|
||||
fetchHostInfo() {
|
||||
return this.hass.callApi('get', 'hassio/host/info')
|
||||
.then((info) => {
|
||||
return this.hass.callApi("get", "hassio/host/info").then((info) => {
|
||||
this.host = info.data;
|
||||
});
|
||||
}
|
||||
|
||||
fetchHassInfo() {
|
||||
return this.hass.callApi('get', 'hassio/homeassistant/info')
|
||||
return this.hass
|
||||
.callApi("get", "hassio/homeassistant/info")
|
||||
.then((info) => {
|
||||
this.homeassistant = info.data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-data', HassioData);
|
||||
customElements.define("hassio-data", HassioData);
|
||||
|
@@ -1,34 +1,61 @@
|
||||
import '@polymer/app-route/app-route.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/app-route/app-route";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../src/layouts/hass-loading-screen.js';
|
||||
import './addon-view/hassio-addon-view.js';
|
||||
import './hassio-data.js';
|
||||
import './hassio-pages-with-tabs.js';
|
||||
import "../../src/layouts/hass-loading-screen";
|
||||
import "./addon-view/hassio-addon-view";
|
||||
import "./hassio-data";
|
||||
import "./hassio-pages-with-tabs";
|
||||
|
||||
import applyThemesOnElement from '../../src/common/dom/apply_themes_on_element.js';
|
||||
import NavigateMixin from '../../src/mixins/navigate-mixin.js';
|
||||
import applyThemesOnElement from "../../src/common/dom/apply_themes_on_element";
|
||||
import EventsMixin from "../../src/mixins/events-mixin";
|
||||
import NavigateMixin from "../../src/mixins/navigate-mixin";
|
||||
|
||||
class HassioMain extends NavigateMixin(PolymerElement) {
|
||||
class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<app-route route="[[route]]" pattern="/:page" data="{{routeData}}"></app-route>
|
||||
<hassio-data id="data" hass="[[hass]]" supervisor="{{supervisorInfo}}" homeassistant="{{hassInfo}}" host="{{hostInfo}}"></hassio-data>
|
||||
<app-route
|
||||
route="[[route]]"
|
||||
pattern="/:page"
|
||||
data="{{routeData}}"
|
||||
></app-route>
|
||||
<hassio-data
|
||||
id="data"
|
||||
hass="[[hass]]"
|
||||
supervisor="{{supervisorInfo}}"
|
||||
homeassistant="{{hassInfo}}"
|
||||
host="{{hostInfo}}"
|
||||
></hassio-data>
|
||||
|
||||
<template is="dom-if" if="[[!loaded]]">
|
||||
<hass-loading-screen narrow="[[narrow]]" show-menu="[[showMenu]]"></hass-loading-screen>
|
||||
<hass-loading-screen
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></hass-loading-screen>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[loaded]]">
|
||||
<template is="dom-if" if="[[!equalsAddon(routeData.page)]]">
|
||||
<hassio-pages-with-tabs hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" page="[[routeData.page]]" supervisor-info="[[supervisorInfo]]" hass-info="[[hassInfo]]" host-info="[[hostInfo]]"></hassio-pages-with-tabs>
|
||||
<hassio-pages-with-tabs
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
page="[[routeData.page]]"
|
||||
supervisor-info="[[supervisorInfo]]"
|
||||
hass-info="[[hassInfo]]"
|
||||
host-info="[[hostInfo]]"
|
||||
></hassio-pages-with-tabs>
|
||||
</template>
|
||||
<template is="dom-if" if="[[equalsAddon(routeData.page)]]">
|
||||
<hassio-addon-view hass="[[hass]]" narrow="[[narrow]]" show-menu="[[showMenu]]" route="[[route]]"></hassio-addon-view>
|
||||
<hassio-addon-view
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
route="[[route]]"
|
||||
></hassio-addon-view>
|
||||
</template>
|
||||
</template>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -40,11 +67,11 @@ class HassioMain extends NavigateMixin(PolymerElement) {
|
||||
type: Object,
|
||||
// Fake route object
|
||||
value: {
|
||||
prefix: '/hassio',
|
||||
path: '/dashboard',
|
||||
__queryParams: {}
|
||||
prefix: "/hassio",
|
||||
path: "/dashboard",
|
||||
__queryParams: {},
|
||||
},
|
||||
observer: 'routeChanged',
|
||||
observer: "routeChanged",
|
||||
},
|
||||
routeData: Object,
|
||||
supervisorInfo: Object,
|
||||
@@ -52,7 +79,7 @@ class HassioMain extends NavigateMixin(PolymerElement) {
|
||||
hassInfo: Object,
|
||||
loaded: {
|
||||
type: Boolean,
|
||||
computed: 'computeIsLoaded(supervisorInfo, hostInfo, hassInfo)',
|
||||
computed: "computeIsLoaded(supervisorInfo, hostInfo, hassInfo)",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -60,7 +87,7 @@ class HassioMain extends NavigateMixin(PolymerElement) {
|
||||
ready() {
|
||||
super.ready();
|
||||
applyThemesOnElement(this, this.hass.themes, this.hass.selectedTheme, true);
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@@ -73,7 +100,7 @@ class HassioMain extends NavigateMixin(PolymerElement) {
|
||||
let tries = 1;
|
||||
|
||||
const tryUpdate = () => {
|
||||
this.$.data.refresh().catch(function () {
|
||||
this.$.data.refresh().catch(function() {
|
||||
tries += 1;
|
||||
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
|
||||
});
|
||||
@@ -84,20 +111,19 @@ class HassioMain extends NavigateMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
computeIsLoaded(supervisorInfo, hostInfo, hassInfo) {
|
||||
return (supervisorInfo !== null
|
||||
&& hostInfo !== null
|
||||
&& hassInfo !== null);
|
||||
return supervisorInfo !== null && hostInfo !== null && hassInfo !== null;
|
||||
}
|
||||
|
||||
routeChanged(route) {
|
||||
if (route.path === '' && route.prefix === '/hassio') {
|
||||
this.navigate('/hassio/dashboard', true);
|
||||
if (route.path === "" && route.prefix === "/hassio") {
|
||||
this.navigate("/hassio/dashboard", true);
|
||||
}
|
||||
this.fire("iron-resize");
|
||||
}
|
||||
|
||||
equalsAddon(page) {
|
||||
return page && page === 'addon';
|
||||
return page && page === "addon";
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-main', HassioMain);
|
||||
customElements.define("hassio-main", HassioMain);
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
|
||||
import '@polymer/paper-dialog/paper-dialog.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../src/components/ha-markdown.js';
|
||||
import '../../src/resources/ha-style.js';
|
||||
import "../../src/components/ha-markdown";
|
||||
import "../../src/resources/ha-style";
|
||||
|
||||
class HassioMarkdownDialog extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -52,14 +52,17 @@ class HassioMarkdownDialog extends PolymerElement {
|
||||
</style>
|
||||
<paper-dialog id="dialog" with-backdrop="">
|
||||
<app-toolbar>
|
||||
<paper-icon-button icon="hassio:close" dialog-dismiss=""></paper-icon-button>
|
||||
<paper-icon-button
|
||||
icon="hassio:close"
|
||||
dialog-dismiss=""
|
||||
></paper-icon-button>
|
||||
<div main-title="">[[title]]</div>
|
||||
</app-toolbar>
|
||||
<paper-dialog-scrollable>
|
||||
<ha-markdown content="[[content]]"></ha-markdown>
|
||||
</paper-dialog-scrollable>
|
||||
</paper-dialog>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -73,4 +76,4 @@ class HassioMarkdownDialog extends PolymerElement {
|
||||
this.$.dialog.open();
|
||||
}
|
||||
}
|
||||
customElements.define('hassio-markdown-dialog', HassioMarkdownDialog);
|
||||
customElements.define("hassio-markdown-dialog", HassioMarkdownDialog);
|
||||
|
@@ -1,24 +1,24 @@
|
||||
import '@polymer/app-layout/app-header-layout/app-header-layout.js';
|
||||
import '@polymer/app-layout/app-header/app-header.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import '@polymer/paper-tabs/paper-tab.js';
|
||||
import '@polymer/paper-tabs/paper-tabs.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-tabs/paper-tab";
|
||||
import "@polymer/paper-tabs/paper-tabs";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../src/components/ha-menu-button.js';
|
||||
import '../../src/resources/ha-style.js';
|
||||
import './addon-store/hassio-addon-store.js';
|
||||
import './dashboard/hassio-dashboard.js';
|
||||
import './hassio-markdown-dialog.js';
|
||||
import './snapshots/hassio-snapshot.js';
|
||||
import './snapshots/hassio-snapshots.js';
|
||||
import './system/hassio-system.js';
|
||||
import "../../src/components/ha-menu-button";
|
||||
import "../../src/resources/ha-style";
|
||||
import "./addon-store/hassio-addon-store";
|
||||
import "./dashboard/hassio-dashboard";
|
||||
import "./hassio-markdown-dialog";
|
||||
import "./snapshots/hassio-snapshot";
|
||||
import "./snapshots/hassio-snapshots";
|
||||
import "./system/hassio-system";
|
||||
|
||||
import scrollToTarget from '../../src/common/dom/scroll-to-target.js';
|
||||
import scrollToTarget from "../../src/common/dom/scroll-to-target";
|
||||
|
||||
import NavigateMixin from '../../src/mixins/navigate-mixin.js';
|
||||
import NavigateMixin from "../../src/mixins/navigate-mixin";
|
||||
|
||||
class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -30,20 +30,32 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
||||
}
|
||||
paper-tabs {
|
||||
margin-left: 12px;
|
||||
--paper-tabs-selection-bar-color: #FFF;
|
||||
--paper-tabs-selection-bar-color: #fff;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
<app-header-layout id="layout" has-scrolling-region>
|
||||
<app-header fixed slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button hassio narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button>
|
||||
<ha-menu-button
|
||||
hassio
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<div main-title>Hass.io</div>
|
||||
<template is="dom-if" if="[[showRefreshButton(page)]]">
|
||||
<paper-icon-button icon="hassio:refresh" on-click="refreshClicked"></paper-icon-button>
|
||||
<paper-icon-button
|
||||
icon="hassio:refresh"
|
||||
on-click="refreshClicked"
|
||||
></paper-icon-button>
|
||||
</template>
|
||||
</app-toolbar>
|
||||
<paper-tabs scrollable="" selected="[[page]]" attr-for-selected="page-name" on-iron-activate="handlePageSelected">
|
||||
<paper-tabs
|
||||
scrollable=""
|
||||
selected="[[page]]"
|
||||
attr-for-selected="page-name"
|
||||
on-iron-activate="handlePageSelected"
|
||||
>
|
||||
<paper-tab page-name="dashboard">Dashboard</paper-tab>
|
||||
<paper-tab page-name="snapshots">Snapshots</paper-tab>
|
||||
<paper-tab page-name="store">Add-on store</paper-tab>
|
||||
@@ -51,25 +63,45 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
||||
</paper-tabs>
|
||||
</app-header>
|
||||
<template is="dom-if" if="[[equals(page, "dashboard")]]">
|
||||
<hassio-dashboard hass="[[hass]]" supervisor-info="[[supervisorInfo]]" hass-info="[[hassInfo]]"></hassio-dashboard>
|
||||
<hassio-dashboard
|
||||
hass="[[hass]]"
|
||||
supervisor-info="[[supervisorInfo]]"
|
||||
hass-info="[[hassInfo]]"
|
||||
></hassio-dashboard>
|
||||
</template>
|
||||
<template is="dom-if" if="[[equals(page, "snapshots")]]">
|
||||
<hassio-snapshots hass="[[hass]]" installed-addons="[[supervisorInfo.addons]]" snapshot-slug="{{snapshotSlug}}" snapshot-deleted="{{snapshotDeleted}}"></hassio-snapshots>
|
||||
<hassio-snapshots
|
||||
hass="[[hass]]"
|
||||
installed-addons="[[supervisorInfo.addons]]"
|
||||
snapshot-slug="{{snapshotSlug}}"
|
||||
snapshot-deleted="{{snapshotDeleted}}"
|
||||
></hassio-snapshots>
|
||||
</template>
|
||||
<template is="dom-if" if="[[equals(page, "store")]]">
|
||||
<hassio-addon-store hass="[[hass]]"></hassio-addon-store>
|
||||
</template>
|
||||
<template is="dom-if" if="[[equals(page, "system")]]">
|
||||
<hassio-system hass="[[hass]]" supervisor-info="[[supervisorInfo]]" host-info="[[hostInfo]]"></hassio-system>
|
||||
<hassio-system
|
||||
hass="[[hass]]"
|
||||
supervisor-info="[[supervisorInfo]]"
|
||||
host-info="[[hostInfo]]"
|
||||
></hassio-system>
|
||||
</template>
|
||||
</app-header-layout>
|
||||
|
||||
<hassio-markdown-dialog title="[[markdownTitle]]" content="[[markdownContent]]"></hassio-markdown-dialog>
|
||||
<hassio-markdown-dialog
|
||||
title="[[markdownTitle]]"
|
||||
content="[[markdownContent]]"
|
||||
></hassio-markdown-dialog>
|
||||
|
||||
<template is="dom-if" if="[[equals(page, "snapshots")]]">
|
||||
<hassio-snapshot hass="[[hass]]" snapshot-slug="{{snapshotSlug}}" snapshot-deleted="{{snapshotDeleted}}"></hassio-snapshot>
|
||||
<hassio-snapshot
|
||||
hass="[[hass]]"
|
||||
snapshot-slug="{{snapshotSlug}}"
|
||||
snapshot-deleted="{{snapshotDeleted}}"
|
||||
></hassio-snapshot>
|
||||
</template>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -87,18 +119,20 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
||||
markdownTitle: String,
|
||||
markdownContent: {
|
||||
type: String,
|
||||
value: '',
|
||||
value: "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hassio-markdown-dialog', ev => this.openMarkdown(ev));
|
||||
this.addEventListener("hassio-markdown-dialog", (ev) =>
|
||||
this.openMarkdown(ev)
|
||||
);
|
||||
}
|
||||
|
||||
handlePageSelected(ev) {
|
||||
const newPage = ev.detail.item.getAttribute('page-name');
|
||||
const newPage = ev.detail.item.getAttribute("page-name");
|
||||
if (newPage !== this.page) {
|
||||
this.navigate(`/hassio/${newPage}`);
|
||||
}
|
||||
@@ -110,14 +144,14 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
showRefreshButton(page) {
|
||||
return page === 'store' || page === 'snapshots';
|
||||
return page === "store" || page === "snapshots";
|
||||
}
|
||||
|
||||
refreshClicked() {
|
||||
if (this.page === 'snapshots') {
|
||||
this.shadowRoot.querySelector('hassio-snapshots').refreshData();
|
||||
if (this.page === "snapshots") {
|
||||
this.shadowRoot.querySelector("hassio-snapshots").refreshData();
|
||||
} else {
|
||||
this.shadowRoot.querySelector('hassio-addon-store').refreshData();
|
||||
this.shadowRoot.querySelector("hassio-addon-store").refreshData();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,8 +160,8 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
||||
markdownTitle: ev.detail.title,
|
||||
markdownContent: ev.detail.content,
|
||||
});
|
||||
this.shadowRoot.querySelector('hassio-markdown-dialog').openDialog();
|
||||
this.shadowRoot.querySelector("hassio-markdown-dialog").openDialog();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-pages-with-tabs', HassioPagesWithTabs);
|
||||
customElements.define("hassio-pages-with-tabs", HassioPagesWithTabs);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import '../../../src/components/ha-iconset-svg.js';
|
||||
import iconSetContent from '../../hassio-icons.html';
|
||||
import "../../../src/components/ha-iconset-svg";
|
||||
import iconSetContent from "../../hassio-icons.html";
|
||||
|
||||
const documentContainer = document.createElement('template');
|
||||
documentContainer.setAttribute('style', 'display: none;');
|
||||
const documentContainer = document.createElement("template");
|
||||
documentContainer.setAttribute("style", "display: none;");
|
||||
documentContainer.innerHTML = iconSetContent;
|
||||
document.head.appendChild(documentContainer.content);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
const documentContainer = document.createElement('template');
|
||||
documentContainer.setAttribute('style', 'display: none;');
|
||||
const documentContainer = document.createElement("template");
|
||||
documentContainer.setAttribute("style", "display: none;");
|
||||
|
||||
documentContainer.innerHTML = `<dom-module id="hassio-style">
|
||||
<template>
|
||||
|
@@ -1,14 +1,15 @@
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-checkbox/paper-checkbox.js';
|
||||
import '@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js';
|
||||
import '@polymer/paper-dialog/paper-dialog.js';
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
import '@polymer/paper-input/paper-input.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-dialog/paper-dialog";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { getSignedPath } from "../../../src/auth/data";
|
||||
|
||||
import '../../../src/resources/ha-style.js';
|
||||
import "../../../src/resources/ha-style";
|
||||
|
||||
class HassioSnapshot extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -56,13 +57,20 @@ class HassioSnapshot extends PolymerElement {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
</style>
|
||||
<paper-dialog id="dialog" with-backdrop="" on-iron-overlay-closed="_dialogClosed">
|
||||
<paper-dialog
|
||||
id="dialog"
|
||||
with-backdrop=""
|
||||
on-iron-overlay-closed="_dialogClosed"
|
||||
>
|
||||
<app-toolbar>
|
||||
<paper-icon-button icon="hassio:close" dialog-dismiss=""></paper-icon-button>
|
||||
<paper-icon-button
|
||||
icon="hassio:close"
|
||||
dialog-dismiss=""
|
||||
></paper-icon-button>
|
||||
<div main-title="">[[_computeName(snapshot)]]</div>
|
||||
</app-toolbar>
|
||||
<div class="details">
|
||||
[[_computeType(snapshot.type)]] ([[_computeSize(snapshot.size)]])<br>
|
||||
[[_computeType(snapshot.type)]] ([[_computeSize(snapshot.size)]])<br />
|
||||
[[_formatDatetime(snapshot.date)]]
|
||||
</div>
|
||||
<div>Home Assistant:</div>
|
||||
@@ -80,32 +88,52 @@ class HassioSnapshot extends PolymerElement {
|
||||
<template is="dom-if" if="[[snapshot.addons.length]]">
|
||||
<div>Add-ons:</div>
|
||||
<paper-dialog-scrollable>
|
||||
<template is="dom-repeat" items="[[snapshot.addons]]" sort="_sortAddons">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[snapshot.addons]]"
|
||||
sort="_sortAddons"
|
||||
>
|
||||
<paper-checkbox checked="{{item.checked}}">
|
||||
[[item.name]]
|
||||
<span class="details">([[item.version]])</span>
|
||||
[[item.name]] <span class="details">([[item.version]])</span>
|
||||
</paper-checkbox>
|
||||
</template>
|
||||
</paper-dialog-scrollable>
|
||||
</template>
|
||||
<template is="dom-if" if="[[snapshot.protected]]">
|
||||
<paper-input autofocus="" label="Password" type="password" value="{{snapshotPassword}}"></paper-input>
|
||||
<paper-input
|
||||
autofocus=""
|
||||
label="Password"
|
||||
type="password"
|
||||
value="{{snapshotPassword}}"
|
||||
></paper-input>
|
||||
</template>
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<p class="error">Error: [[error]]</p>
|
||||
</template>
|
||||
<div class="buttons">
|
||||
<paper-icon-button icon="hassio:delete" on-click="_deleteClicked" class="warning" title="Delete snapshot"></paper-icon-button>
|
||||
<a href="[[_computeDownloadUrl(snapshotSlug)]]" download="[[_computeDownloadName(snapshot)]]">
|
||||
<paper-icon-button icon="hassio:download" class="download" title="Download snapshot"></paper-icon-button>
|
||||
</a>
|
||||
<paper-button on-click="_partialRestoreClicked">Restore selected</paper-button>
|
||||
<paper-icon-button
|
||||
icon="hassio:delete"
|
||||
on-click="_deleteClicked"
|
||||
class="warning"
|
||||
title="Delete snapshot"
|
||||
></paper-icon-button>
|
||||
<paper-icon-button
|
||||
on-click="_downloadClicked"
|
||||
icon="hassio:download"
|
||||
class="download"
|
||||
title="Download snapshot"
|
||||
></paper-icon-button>
|
||||
<paper-button on-click="_partialRestoreClicked"
|
||||
>Restore selected</paper-button
|
||||
>
|
||||
<template is="dom-if" if="[[_isFullSnapshot(snapshot.type)]]">
|
||||
<paper-button on-click="_fullRestoreClicked">Wipe & restore</paper-button>
|
||||
<paper-button on-click="_fullRestoreClicked"
|
||||
>Wipe & restore</paper-button
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -114,7 +142,7 @@ class HassioSnapshot extends PolymerElement {
|
||||
snapshotSlug: {
|
||||
type: String,
|
||||
notify: true,
|
||||
observer: '_snapshotSlugChanged',
|
||||
observer: "_snapshotSlugChanged",
|
||||
},
|
||||
snapshotDeleted: {
|
||||
type: Boolean,
|
||||
@@ -131,95 +159,144 @@ class HassioSnapshot extends PolymerElement {
|
||||
}
|
||||
|
||||
_snapshotSlugChanged(snapshotSlug) {
|
||||
if (!snapshotSlug || snapshotSlug === 'update') return;
|
||||
this.hass.callApi('get', `hassio/snapshots/${snapshotSlug}/info`)
|
||||
.then((info) => {
|
||||
if (!snapshotSlug || snapshotSlug === "update") return;
|
||||
this.hass.callApi("get", `hassio/snapshots/${snapshotSlug}/info`).then(
|
||||
(info) => {
|
||||
info.data.folders = this._computeFolders(info.data.folders);
|
||||
info.data.addons = this._computeAddons(info.data.addons);
|
||||
this.snapshot = info.data;
|
||||
this.$.dialog.open();
|
||||
}, () => {
|
||||
},
|
||||
() => {
|
||||
this.snapshot = null;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_computeFolders(folders) {
|
||||
const list = [];
|
||||
if (folders.includes('homeassistant')) list.push({ slug: 'homeassistant', name: 'Home Assistant configuration', checked: true });
|
||||
if (folders.includes('ssl')) list.push({ slug: 'ssl', name: 'SSL', checked: true });
|
||||
if (folders.includes('share')) list.push({ slug: 'share', name: 'Share', checked: true });
|
||||
if (folders.includes('addons/local')) list.push({ slug: 'addons/local', name: 'Local add-ons', checked: true });
|
||||
if (folders.includes("homeassistant"))
|
||||
list.push({
|
||||
slug: "homeassistant",
|
||||
name: "Home Assistant configuration",
|
||||
checked: true,
|
||||
});
|
||||
if (folders.includes("ssl"))
|
||||
list.push({ slug: "ssl", name: "SSL", checked: true });
|
||||
if (folders.includes("share"))
|
||||
list.push({ slug: "share", name: "Share", checked: true });
|
||||
if (folders.includes("addons/local"))
|
||||
list.push({ slug: "addons/local", name: "Local add-ons", checked: true });
|
||||
return list;
|
||||
}
|
||||
|
||||
_computeAddons(addons) {
|
||||
return addons.map(addon => (
|
||||
{ slug: addon.slug, name: addon.name, version: addon.version, checked: true }));
|
||||
return addons.map((addon) => ({
|
||||
slug: addon.slug,
|
||||
name: addon.name,
|
||||
version: addon.version,
|
||||
checked: true,
|
||||
}));
|
||||
}
|
||||
|
||||
_isFullSnapshot(type) {
|
||||
return type === 'full';
|
||||
return type === "full";
|
||||
}
|
||||
|
||||
_partialRestoreClicked() {
|
||||
if (!confirm('Are you sure you want to restore this snapshot?')) {
|
||||
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
||||
return;
|
||||
}
|
||||
const addons = this.snapshot.addons.filter(addon => addon.checked).map(addon => addon.slug);
|
||||
const folders = this.snapshot.folders.filter(
|
||||
folder => folder.checked
|
||||
).map(folder => folder.slug);
|
||||
const addons = this.snapshot.addons
|
||||
.filter((addon) => addon.checked)
|
||||
.map((addon) => addon.slug);
|
||||
const folders = this.snapshot.folders
|
||||
.filter((folder) => folder.checked)
|
||||
.map((folder) => folder.slug);
|
||||
|
||||
const data = {
|
||||
homeassistant: this.restoreHass,
|
||||
addons: addons,
|
||||
folders: folders
|
||||
folders: folders,
|
||||
};
|
||||
if (this.snapshot.protected) data.password = this.snapshotPassword;
|
||||
|
||||
this.hass.callApi('post', `hassio/snapshots/${this.snapshotSlug}/restore/partial`, data).then(() => {
|
||||
alert('Snapshot restored!');
|
||||
this.hass
|
||||
.callApi(
|
||||
"post",
|
||||
`hassio/snapshots/${this.snapshotSlug}/restore/partial`,
|
||||
data
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
alert("Snapshot restored!");
|
||||
this.$.dialog.close();
|
||||
}, (error) => {
|
||||
},
|
||||
(error) => {
|
||||
this.error = error.body.message;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_fullRestoreClicked() {
|
||||
if (!confirm('Are you sure you want to restore this snapshot?')) {
|
||||
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
||||
return;
|
||||
}
|
||||
const data = this.snapshot.protected ? { password: this.snapshotPassword } : null;
|
||||
this.hass.callApi('post', `hassio/snapshots/${this.snapshotSlug}/restore/full`, data)
|
||||
.then(() => {
|
||||
alert('Snapshot restored!');
|
||||
const data = this.snapshot.protected
|
||||
? { password: this.snapshotPassword }
|
||||
: null;
|
||||
this.hass
|
||||
.callApi(
|
||||
"post",
|
||||
`hassio/snapshots/${this.snapshotSlug}/restore/full`,
|
||||
data
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
alert("Snapshot restored!");
|
||||
this.$.dialog.close();
|
||||
}, (error) => {
|
||||
},
|
||||
(error) => {
|
||||
this.error = error.body.message;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_deleteClicked() {
|
||||
if (!confirm('Are you sure you want to delete this snapshot?')) {
|
||||
if (!confirm("Are you sure you want to delete this snapshot?")) {
|
||||
return;
|
||||
}
|
||||
this.hass.callApi('post', `hassio/snapshots/${this.snapshotSlug}/remove`)
|
||||
.then(() => {
|
||||
this.hass
|
||||
.callApi("post", `hassio/snapshots/${this.snapshotSlug}/remove`)
|
||||
.then(
|
||||
() => {
|
||||
this.$.dialog.close();
|
||||
this.snapshotDeleted = true;
|
||||
}, (error) => {
|
||||
},
|
||||
(error) => {
|
||||
this.error = error.body.message;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_computeDownloadUrl(snapshotSlug) {
|
||||
const password = encodeURIComponent(this.hass.connection.options.authToken);
|
||||
return `/api/hassio/snapshots/${snapshotSlug}/download?api_password=${password}`;
|
||||
async _downloadClicked() {
|
||||
let signedPath;
|
||||
try {
|
||||
signedPath = await getSignedPath(
|
||||
this.hass,
|
||||
`/api/hassio/snapshots/${this.snapshotSlug}/download`
|
||||
);
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
_computeDownloadName(snapshot) {
|
||||
const name = this._computeName(snapshot).replace(/[^a-z0-9]+/gi, '_');
|
||||
return `Hass_io_${name}.tar`;
|
||||
const name = this._computeName(this.snapshot).replace(/[^a-z0-9]+/gi, "_");
|
||||
const a = document.createElement("A");
|
||||
a.href = signedPath.path;
|
||||
a.download = `Hass_io_${name}.tar`;
|
||||
this.$.dialog.appendChild(a);
|
||||
a.click();
|
||||
this.$.dialog.removeChild(a);
|
||||
}
|
||||
|
||||
_computeName(snapshot) {
|
||||
@@ -227,11 +304,11 @@ class HassioSnapshot extends PolymerElement {
|
||||
}
|
||||
|
||||
_computeType(type) {
|
||||
return type === 'full' ? 'Full snapshot' : 'Partial snapshot';
|
||||
return type === "full" ? "Full snapshot" : "Partial snapshot";
|
||||
}
|
||||
|
||||
_computeSize(size) {
|
||||
return (Math.ceil(size * 10) / 10) + ' MB';
|
||||
return Math.ceil(size * 10) / 10 + " MB";
|
||||
}
|
||||
|
||||
_sortAddons(a, b) {
|
||||
@@ -240,12 +317,12 @@ class HassioSnapshot extends PolymerElement {
|
||||
|
||||
_formatDatetime(datetime) {
|
||||
return new Date(datetime).toLocaleDateString(navigator.language, {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit'
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -253,4 +330,4 @@ class HassioSnapshot extends PolymerElement {
|
||||
this.snapshotSlug = null;
|
||||
}
|
||||
}
|
||||
customElements.define('hassio-snapshot', HassioSnapshot);
|
||||
customElements.define("hassio-snapshot", HassioSnapshot);
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-checkbox/paper-checkbox.js';
|
||||
import '@polymer/paper-input/paper-input.js';
|
||||
import '@polymer/paper-radio-button/paper-radio-button.js';
|
||||
import '@polymer/paper-radio-group/paper-radio-group.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-radio-button/paper-radio-button";
|
||||
import "@polymer/paper-radio-group/paper-radio-group";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/hassio-card-content.js';
|
||||
import '../resources/hassio-style.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
import "../components/hassio-card-content";
|
||||
import "../resources/hassio-style";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
|
||||
class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -36,13 +36,17 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
<div class="title">
|
||||
Create snapshot
|
||||
<div class="description">
|
||||
Snapshots allow you to easily backup and
|
||||
restore all data of your Hass.io instance.
|
||||
Snapshots allow you to easily backup and restore all data of your
|
||||
Hass.io instance.
|
||||
</div>
|
||||
</div>
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<paper-input autofocus="" label="Name" value="{{snapshotName}}"></paper-input>
|
||||
<paper-input
|
||||
autofocus=""
|
||||
label="Name"
|
||||
value="{{snapshotName}}"
|
||||
></paper-input>
|
||||
Type:
|
||||
<paper-radio-group selected="{{snapshotType}}">
|
||||
<paper-radio-button name="full">
|
||||
@@ -60,23 +64,37 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
</paper-checkbox>
|
||||
</template>
|
||||
Add-ons:
|
||||
<template is="dom-repeat" items="[[addonList]]" sort="_sortAddons">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[addonList]]"
|
||||
sort="_sortAddons"
|
||||
>
|
||||
<paper-checkbox checked="{{item.checked}}">
|
||||
[[item.name]]
|
||||
</paper-checkbox>
|
||||
</template>
|
||||
</template>
|
||||
Security:
|
||||
<paper-checkbox checked="{{snapshotHasPassword}}">Password protection</paper-checkbox>
|
||||
<paper-checkbox checked="{{snapshotHasPassword}}"
|
||||
>Password protection</paper-checkbox
|
||||
>
|
||||
<template is="dom-if" if="[[snapshotHasPassword]]">
|
||||
<paper-input label="Password" type="password" value="{{snapshotPassword}}"></paper-input>
|
||||
<paper-input
|
||||
label="Password"
|
||||
type="password"
|
||||
value="{{snapshotPassword}}"
|
||||
></paper-input>
|
||||
</template>
|
||||
<template is="dom-if" if="[[error]]">
|
||||
<p class="error">[[error]]</p>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<paper-button disabled="[[creatingSnapshot]]" on-click="_createSnapshot">Create</paper-button>
|
||||
<paper-button
|
||||
disabled="[[creatingSnapshot]]"
|
||||
on-click="_createSnapshot"
|
||||
>Create</paper-button
|
||||
>
|
||||
</div>
|
||||
</paper-card>
|
||||
</div>
|
||||
@@ -88,16 +106,28 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
<div class="card-content">You don't have any snapshots yet.</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
<template is="dom-repeat" items="[[snapshots]]" as="snapshot" sort="_sortSnapshots">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[snapshots]]"
|
||||
as="snapshot"
|
||||
sort="_sortSnapshots"
|
||||
>
|
||||
<paper-card class="pointer" on-click="_snapshotClicked">
|
||||
<div class="card-content">
|
||||
<hassio-card-content hass="[[hass]]" title="[[_computeName(snapshot)]]" description="[[_computeDetails(snapshot)]]" datetime="[[snapshot.date]]" icon="[[_computeIcon(snapshot.type)]]" icon-class="snapshot"></hassio-card-content>
|
||||
<hassio-card-content
|
||||
hass="[[hass]]"
|
||||
title="[[_computeName(snapshot)]]"
|
||||
description="[[_computeDetails(snapshot)]]"
|
||||
datetime="[[snapshot.date]]"
|
||||
icon="[[_computeIcon(snapshot.type)]]"
|
||||
icon-class="snapshot"
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
</paper-card>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -105,16 +135,16 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
hass: Object,
|
||||
snapshotName: {
|
||||
type: String,
|
||||
value: '',
|
||||
value: "",
|
||||
},
|
||||
snapshotPassword: {
|
||||
type: String,
|
||||
value: '',
|
||||
value: "",
|
||||
},
|
||||
snapshotHasPassword: Boolean,
|
||||
snapshotType: {
|
||||
type: String,
|
||||
value: 'full',
|
||||
value: "full",
|
||||
},
|
||||
snapshots: {
|
||||
type: Array,
|
||||
@@ -122,16 +152,20 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
},
|
||||
installedAddons: {
|
||||
type: Array,
|
||||
observer: '_installedAddonsChanged',
|
||||
observer: "_installedAddonsChanged",
|
||||
},
|
||||
addonList: Array,
|
||||
folderList: {
|
||||
type: Array,
|
||||
value: [
|
||||
{ slug: 'homeassistant', name: 'Home Assistant configuration', checked: true },
|
||||
{ slug: 'ssl', name: 'SSL', checked: true },
|
||||
{ slug: 'share', name: 'Share', checked: true },
|
||||
{ slug: 'addons/local', name: 'Local add-ons', checked: true },
|
||||
{
|
||||
slug: "homeassistant",
|
||||
name: "Home Assistant configuration",
|
||||
checked: true,
|
||||
},
|
||||
{ slug: "ssl", name: "SSL", checked: true },
|
||||
{ slug: "share", name: "Share", checked: true },
|
||||
{ slug: "addons/local", name: "Local add-ons", checked: true },
|
||||
],
|
||||
},
|
||||
snapshotSlug: {
|
||||
@@ -141,7 +175,7 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
snapshotDeleted: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
observer: '_snapshotDeletedChanged',
|
||||
observer: "_snapshotDeletedChanged",
|
||||
},
|
||||
creatingSnapshot: Boolean,
|
||||
dialogOpened: Boolean,
|
||||
@@ -151,7 +185,7 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this._apiCalled(ev));
|
||||
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||
this._updateSnapshots();
|
||||
}
|
||||
|
||||
@@ -162,57 +196,70 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
_updateSnapshots() {
|
||||
this.hass.callApi('get', 'hassio/snapshots')
|
||||
.then((result) => {
|
||||
this.hass.callApi("get", "hassio/snapshots").then(
|
||||
(result) => {
|
||||
this.snapshots = result.data.snapshots;
|
||||
}, (error) => {
|
||||
},
|
||||
(error) => {
|
||||
this.error = error.message;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_createSnapshot() {
|
||||
this.error = '';
|
||||
this.error = "";
|
||||
if (this.snapshotHasPassword && !this.snapshotPassword.length) {
|
||||
this.error = 'Please enter a password.';
|
||||
this.error = "Please enter a password.";
|
||||
return;
|
||||
}
|
||||
this.creatingSnapshot = true;
|
||||
let name = this.snapshotName;
|
||||
if (!name.length) {
|
||||
name = new Date().toLocaleDateString(navigator.language, {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric' });
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}
|
||||
let data;
|
||||
let path;
|
||||
if (this.snapshotType === 'full') {
|
||||
if (this.snapshotType === "full") {
|
||||
data = { name: name };
|
||||
path = 'hassio/snapshots/new/full';
|
||||
path = "hassio/snapshots/new/full";
|
||||
} else {
|
||||
const addons = this.addonList.filter(addon => addon.checked).map(addon => addon.slug);
|
||||
const folders = this.folderList.filter(folder => folder.checked).map(folder => folder.slug);
|
||||
const addons = this.addonList
|
||||
.filter((addon) => addon.checked)
|
||||
.map((addon) => addon.slug);
|
||||
const folders = this.folderList
|
||||
.filter((folder) => folder.checked)
|
||||
.map((folder) => folder.slug);
|
||||
|
||||
data = { name: name, folders: folders, addons: addons };
|
||||
path = 'hassio/snapshots/new/partial';
|
||||
path = "hassio/snapshots/new/partial";
|
||||
}
|
||||
if (this.snapshotHasPassword) {
|
||||
data.password = this.snapshotPassword;
|
||||
}
|
||||
|
||||
this.hass.callApi('post', path, data)
|
||||
.then(() => {
|
||||
this.hass.callApi("post", path, data).then(
|
||||
() => {
|
||||
this.creatingSnapshot = false;
|
||||
this.fire('hass-api-called', { success: true });
|
||||
}, (error) => {
|
||||
this.fire("hass-api-called", { success: true });
|
||||
},
|
||||
(error) => {
|
||||
this.creatingSnapshot = false;
|
||||
this.error = error.message;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_installedAddonsChanged(addons) {
|
||||
this.addonList = addons.map(addon => ({ slug: addon.slug, name: addon.name, checked: true }));
|
||||
this.addonList = addons.map((addon) => ({
|
||||
slug: addon.slug,
|
||||
name: addon.name,
|
||||
checked: true,
|
||||
}));
|
||||
}
|
||||
|
||||
_sortAddons(a, b) {
|
||||
@@ -228,12 +275,15 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
_computeDetails(snapshot) {
|
||||
const type = snapshot.type === 'full' ? 'Full snapshot' : 'Partial snapshot';
|
||||
const type =
|
||||
snapshot.type === "full" ? "Full snapshot" : "Partial snapshot";
|
||||
return snapshot.protected ? `${type}, password protected` : type;
|
||||
}
|
||||
|
||||
_computeIcon(type) {
|
||||
return type === 'full' ? 'hassio:package-variant-closed' : 'hassio:package-variant';
|
||||
return type === "full"
|
||||
? "hassio:package-variant-closed"
|
||||
: "hassio:package-variant";
|
||||
}
|
||||
|
||||
_snapshotClicked(ev) {
|
||||
@@ -241,7 +291,7 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
_fullSelected(type) {
|
||||
return type === 'full';
|
||||
return type === "full";
|
||||
}
|
||||
|
||||
_snapshotDeletedChanged(snapshotDeleted) {
|
||||
@@ -252,11 +302,10 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
refreshData() {
|
||||
this.hass.callApi('post', 'hassio/snapshots/reload')
|
||||
.then(() => {
|
||||
this.hass.callApi("post", "hassio/snapshots/reload").then(() => {
|
||||
this._updateSnapshots();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-snapshots', HassioSnapshots);
|
||||
customElements.define("hassio-snapshots", HassioSnapshots);
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
|
||||
class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -17,6 +17,7 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
.card-content {
|
||||
height: 200px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
@media screen and (max-width: 830px) {
|
||||
paper-card {
|
||||
@@ -25,7 +26,7 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
width: 100%;
|
||||
}
|
||||
.card-content {
|
||||
height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.info {
|
||||
@@ -41,12 +42,16 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
paper-button.info {
|
||||
max-width: calc(50% - 12px);
|
||||
}
|
||||
table.info {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<h2>Host system</h2>
|
||||
<table class="info">
|
||||
<tbody><tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Hostname</td>
|
||||
<td>[[data.hostname]]</td>
|
||||
</tr>
|
||||
@@ -60,7 +65,8 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
<td>[[data.deployment]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody></table>
|
||||
</tbody>
|
||||
</table>
|
||||
<paper-button raised on-click="_showHardware" class="info">
|
||||
Hardware
|
||||
</paper-button>
|
||||
@@ -75,20 +81,38 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<template is="dom-if" if="[[_featureAvailable(data, 'reboot')]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/host/reboot">Reboot</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
path="hassio/host/reboot"
|
||||
>Reboot</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_featureAvailable(data, 'shutdown')]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/host/shutdown">Shutdown</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
path="hassio/host/shutdown"
|
||||
>Shutdown</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_featureAvailable(data, 'hassos')]]">
|
||||
<ha-call-api-button class="warning" hass="[[hass]]" path="hassio/hassos/config/sync" title="Load HassOS configs or updates from USB">Import from USB</ha-call-api-button>
|
||||
<ha-call-api-button
|
||||
class="warning"
|
||||
hass="[[hass]]"
|
||||
path="hassio/hassos/config/sync"
|
||||
title="Load HassOS configs or updates from USB"
|
||||
>Import from USB</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_computeUpdateAvailable(_hassOs)]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/hassos/update">Update</ha-call-api-button>
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/hassos/update"
|
||||
>Update</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -96,16 +120,16 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
hass: Object,
|
||||
data: {
|
||||
type: Object,
|
||||
observer: '_dataChanged'
|
||||
observer: "_dataChanged",
|
||||
},
|
||||
errors: String,
|
||||
_hassOs: Object
|
||||
_hassOs: Object,
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
@@ -116,17 +140,16 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
|
||||
var response = ev.detail.response;
|
||||
|
||||
if (typeof response.body === 'object') {
|
||||
this.errors = response.body.message || 'Unknown error';
|
||||
if (typeof response.body === "object") {
|
||||
this.errors = response.body.message || "Unknown error";
|
||||
} else {
|
||||
this.errors = response.body;
|
||||
}
|
||||
}
|
||||
|
||||
_dataChanged(data) {
|
||||
if (data.features && data.features.includes('hassos')) {
|
||||
this.hass.callApi('get', 'hassio/hassos/info')
|
||||
.then((resp) => {
|
||||
if (data.features && data.features.includes("hassos")) {
|
||||
this.hass.callApi("get", "hassio/hassos/info").then((resp) => {
|
||||
this._hassOs = resp.data;
|
||||
});
|
||||
} else {
|
||||
@@ -143,28 +166,31 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
_showHardware() {
|
||||
this.hass.callApi('get', 'hassio/hardware/info')
|
||||
this.hass
|
||||
.callApi("get", "hassio/hardware/info")
|
||||
.then(
|
||||
resp => this._objectToMarkdown(resp.data),
|
||||
() => 'Error getting hardware info'
|
||||
).then((content) => {
|
||||
this.fire('hassio-markdown-dialog', {
|
||||
title: 'Hardware',
|
||||
(resp) => this._objectToMarkdown(resp.data),
|
||||
() => "Error getting hardware info"
|
||||
)
|
||||
.then((content) => {
|
||||
this.fire("hassio-markdown-dialog", {
|
||||
title: "Hardware",
|
||||
content: content,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_objectToMarkdown(obj, indent = '') {
|
||||
let data = '';
|
||||
_objectToMarkdown(obj, indent = "") {
|
||||
let data = "";
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] !== 'object') {
|
||||
if (typeof obj[key] !== "object") {
|
||||
data += `${indent}- ${key}: ${obj[key]}\n`;
|
||||
} else {
|
||||
data += `${indent}- ${key}:\n`;
|
||||
if (Array.isArray(obj[key])) {
|
||||
if (obj[key].length) {
|
||||
data += `${indent} - ` + obj[key].join(`\n${indent} - `) + '\n';
|
||||
data +=
|
||||
`${indent} - ` + obj[key].join(`\n${indent} - `) + "\n";
|
||||
}
|
||||
} else {
|
||||
data += this._objectToMarkdown(obj[key], ` ${indent}`);
|
||||
@@ -176,11 +202,11 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
|
||||
|
||||
_changeHostnameClicked() {
|
||||
const curHostname = this.data.hostname;
|
||||
const hostname = prompt('Please enter a new hostname:', curHostname);
|
||||
const hostname = prompt("Please enter a new hostname:", curHostname);
|
||||
if (hostname && hostname !== curHostname) {
|
||||
this.hass.callApi('post', 'hassio/host/options', { hostname });
|
||||
this.hass.callApi("post", "hassio/host/options", { hostname });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-host-info', HassioHostInfo);
|
||||
customElements.define("hassio-host-info", HassioHostInfo);
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../../../src/components/buttons/ha-call-api-button.js';
|
||||
import EventsMixin from '../../../src/mixins/events-mixin.js';
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
|
||||
class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -16,13 +16,14 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
.card-content {
|
||||
height: 200px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
@media screen and (max-width: 830px) {
|
||||
paper-card {
|
||||
width: 100%;
|
||||
}
|
||||
.card-content {
|
||||
height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
.info {
|
||||
@@ -40,41 +41,64 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
||||
<div class="card-content">
|
||||
<h2>Hass.io supervisor</h2>
|
||||
<table class="info">
|
||||
<tbody><tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>
|
||||
[[data.version]]
|
||||
</td>
|
||||
<td>[[data.version]]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Latest version</td>
|
||||
<td>[[data.last_version]]</td>
|
||||
</tr>
|
||||
<template is="dom-if" if="[[!_equals(data.channel, "stable")]]">
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[!_equals(data.channel, "stable")]]"
|
||||
>
|
||||
<tr>
|
||||
<td>Channel</td>
|
||||
<td>[[data.channel]]</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody></table>
|
||||
</tbody>
|
||||
</table>
|
||||
<template is="dom-if" if="[[errors]]">
|
||||
<div class="errors">Error: [[errors]]</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/reload">Reload</ha-call-api-button>
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/reload"
|
||||
>Reload</ha-call-api-button
|
||||
>
|
||||
<template is="dom-if" if="[[computeUpdateAvailable(data)]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/update">Update</ha-call-api-button>
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/update"
|
||||
>Update</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(data.channel, "beta")]]">
|
||||
<ha-call-api-button hass="[[hass]]" path="hassio/supervisor/options" data="[[leaveBeta]]">Leave beta channel</ha-call-api-button>
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[_equals(data.channel, "beta")]]"
|
||||
>
|
||||
<ha-call-api-button
|
||||
hass="[[hass]]"
|
||||
path="hassio/supervisor/options"
|
||||
data="[[leaveBeta]]"
|
||||
>Leave beta channel</ha-call-api-button
|
||||
>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(data.channel, "stable")]]">
|
||||
<paper-button on-click="_joinBeta" class="warning" title="Get beta updates for Home Assistant (RCs), supervisor and host">Join beta channel</paper-button>
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[_equals(data.channel, "stable")]]"
|
||||
>
|
||||
<paper-button
|
||||
on-click="_joinBeta"
|
||||
class="warning"
|
||||
title="Get beta updates for Home Assistant (RCs), supervisor and host"
|
||||
>Join beta channel</paper-button
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -84,14 +108,14 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
||||
errors: String,
|
||||
leaveBeta: {
|
||||
type: Object,
|
||||
value: { channel: 'stable' },
|
||||
value: { channel: "stable" },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('hass-api-called', ev => this.apiCalled(ev));
|
||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
||||
}
|
||||
|
||||
apiCalled(ev) {
|
||||
@@ -102,8 +126,8 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
||||
|
||||
var response = ev.detail.response;
|
||||
|
||||
if (typeof response.body === 'object') {
|
||||
this.errors = response.body.message || 'Unknown error';
|
||||
if (typeof response.body === "object") {
|
||||
this.errors = response.body.message || "Unknown error";
|
||||
} else {
|
||||
this.errors = response.body;
|
||||
}
|
||||
@@ -118,18 +142,20 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
_joinBeta() {
|
||||
if (!confirm(`WARNING:
|
||||
if (
|
||||
!confirm(`WARNING:
|
||||
Beta releases are for testers and early adopters and can contain unstable code changes. Make sure you have backups of your data before you activate this feature.
|
||||
|
||||
This inludes beta releases for:
|
||||
- Home Assistant (Release Candidates)
|
||||
- Hass.io supervisor
|
||||
- Host system`)) {
|
||||
- Host system`)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const method = 'post';
|
||||
const path = 'hassio/supervisor/options';
|
||||
const data = { channel: 'beta' };
|
||||
const method = "post";
|
||||
const path = "hassio/supervisor/options";
|
||||
const data = { channel: "beta" };
|
||||
|
||||
const eventData = {
|
||||
method: method,
|
||||
@@ -137,17 +163,22 @@ This inludes beta releases for:
|
||||
data: data,
|
||||
};
|
||||
|
||||
this.hass.callApi(method, path, data)
|
||||
.then((resp) => {
|
||||
this.hass
|
||||
.callApi(method, path, data)
|
||||
.then(
|
||||
(resp) => {
|
||||
eventData.success = true;
|
||||
eventData.response = resp;
|
||||
}, (resp) => {
|
||||
},
|
||||
(resp) => {
|
||||
eventData.success = false;
|
||||
eventData.response = resp;
|
||||
}).then(() => {
|
||||
this.fire('hass-api-called', eventData);
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.fire("hass-api-called", eventData);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-supervisor-info', HassioSupervisorInfo);
|
||||
customElements.define("hassio-supervisor-info", HassioSupervisorInfo);
|
||||
|
@@ -1,7 +1,8 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { ANSI_HTML_STYLE, parseTextToColoredPre } from "../ansi-to-html";
|
||||
|
||||
class HassioSupervisorLog extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -12,23 +13,26 @@ class HassioSupervisorLog extends PolymerElement {
|
||||
}
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
.fg-green {
|
||||
color: var(--primary-text-color) !important;
|
||||
}
|
||||
</style>
|
||||
${ANSI_HTML_STYLE}
|
||||
<paper-card>
|
||||
<div class="card-content">
|
||||
<pre>[[log]]</pre>
|
||||
</div>
|
||||
<div class="card-content" id="content"></div>
|
||||
<div class="card-actions">
|
||||
<paper-button on-click="refreshTapped">Refresh</paper-button>
|
||||
<paper-button on-click="refresh">Refresh</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
log: String,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,17 +42,23 @@ class HassioSupervisorLog extends PolymerElement {
|
||||
}
|
||||
|
||||
loadData() {
|
||||
this.hass.callApi('get', 'hassio/supervisor/logs')
|
||||
.then((info) => {
|
||||
this.log = info;
|
||||
}, () => {
|
||||
this.log = 'Error fetching logs';
|
||||
});
|
||||
this.hass.callApi("get", "hassio/supervisor/logs").then(
|
||||
(text) => {
|
||||
while (this.$.content.lastChild) {
|
||||
this.$.content.removeChild(this.$.content.lastChild);
|
||||
}
|
||||
this.$.content.appendChild(parseTextToColoredPre(text));
|
||||
},
|
||||
() => {
|
||||
this.$.content.innerHTML =
|
||||
'<span class="fg-red bold">Error fetching logs</span>';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
refreshTapped() {
|
||||
refresh() {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-supervisor-log', HassioSupervisorLog);
|
||||
customElements.define("hassio-supervisor-log", HassioSupervisorLog);
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import './hassio-host-info.js';
|
||||
import './hassio-supervisor-info.js';
|
||||
import './hassio-supervisor-log.js';
|
||||
import "./hassio-host-info";
|
||||
import "./hassio-supervisor-info";
|
||||
import "./hassio-supervisor-log";
|
||||
|
||||
class HassioSystem extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -23,12 +23,18 @@ class HassioSystem extends PolymerElement {
|
||||
</style>
|
||||
<div class="content">
|
||||
<div class="title">Information</div>
|
||||
<hassio-supervisor-info hass="[[hass]]" data="[[supervisorInfo]]"></hassio-supervisor-info>
|
||||
<hassio-host-info hass="[[hass]]" data="[[hostInfo]]"></hassio-host-info>
|
||||
<hassio-supervisor-info
|
||||
hass="[[hass]]"
|
||||
data="[[supervisorInfo]]"
|
||||
></hassio-supervisor-info>
|
||||
<hassio-host-info
|
||||
hass="[[hass]]"
|
||||
data="[[hostInfo]]"
|
||||
></hassio-host-info>
|
||||
<div class="title">System log</div>
|
||||
<hassio-supervisor-log hass="[[hass]]"></hassio-supervisor-log>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -40,4 +46,4 @@ class HassioSystem extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('hassio-system', HassioSystem);
|
||||
customElements.define("hassio-system", HassioSystem);
|
||||
|
@@ -1,69 +1,56 @@
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
const config = require('./config.js');
|
||||
const config = require("./config.js");
|
||||
const { babelLoaderConfig } = require("../config/babel.js");
|
||||
|
||||
const isProdBuild = process.env.NODE_ENV === 'production'
|
||||
const chunkFilename = isProdBuild ?
|
||||
'chunk.[chunkhash].js' : '[name].chunk.js';
|
||||
const isProdBuild = process.env.NODE_ENV === "production";
|
||||
const isCI = process.env.CI === "true";
|
||||
const chunkFilename = isProdBuild ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
||||
|
||||
module.exports = {
|
||||
mode: isProdBuild ? 'production' : 'development',
|
||||
devtool: isProdBuild ? 'source-map' : 'inline-source-map',
|
||||
mode: isProdBuild ? "production" : "development",
|
||||
devtool: isProdBuild ? "source-map" : "inline-source-map",
|
||||
entry: {
|
||||
entrypoint: './src/entrypoint.js',
|
||||
entrypoint: "./src/entrypoint.js",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
[require('babel-preset-env').default, { modules: false }]
|
||||
],
|
||||
plugins: [
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
"syntax-dynamic-import",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
babelLoaderConfig({ latestBuild: false }),
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: 'html-loader',
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
exportAsEs6Default: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
isProdBuild && new UglifyJsPlugin({
|
||||
isProdBuild &&
|
||||
new UglifyJsPlugin({
|
||||
extractComments: true,
|
||||
sourceMap: true,
|
||||
uglifyOptions: {
|
||||
// Disabling because it broke output
|
||||
mangle: false,
|
||||
}
|
||||
},
|
||||
}),
|
||||
isProdBuild && new CompressionPlugin({
|
||||
isProdBuild &&
|
||||
!isCI &&
|
||||
new CompressionPlugin({
|
||||
cache: true,
|
||||
exclude: [
|
||||
/\.js\.map$/,
|
||||
/\.LICENSE$/,
|
||||
/\.py$/,
|
||||
/\.txt$/,
|
||||
]
|
||||
exclude: [/\.js\.map$/, /\.LICENSE$/, /\.py$/, /\.txt$/],
|
||||
}),
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
extensions: [".ts", ".js", ".json"],
|
||||
},
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
filename: "[name].js",
|
||||
chunkFilename,
|
||||
path: config.buildDir,
|
||||
publicPath: `${config.publicPath}/`,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
99
package.json
99
package.json
@@ -8,8 +8,8 @@
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"build": "script/build_frontend",
|
||||
"lint": "eslint src hassio/src gallery/src test-mocha && polymer lint",
|
||||
"mocha": "node_modules/.bin/mocha --opts test-mocha/mocha.opts",
|
||||
"lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'test-mocha/**/*.ts' && polymer lint && tsc",
|
||||
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
|
||||
"test": "npm run lint && npm run mocha",
|
||||
"docker_build": "sh ./script/docker_run.sh build $npm_package_version",
|
||||
"bash": "sh ./script/docker_run.sh bash $npm_package_version"
|
||||
@@ -17,7 +17,8 @@
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@mdi/svg": "^2.7.94",
|
||||
"@material/mwc-ripple": "^0.3.1",
|
||||
"@mdi/svg": "^3.0.39",
|
||||
"@polymer/app-layout": "^3.0.1",
|
||||
"@polymer/app-localize-behavior": "^3.0.1",
|
||||
"@polymer/app-route": "^3.0.2",
|
||||
@@ -34,6 +35,7 @@
|
||||
"@polymer/iron-media-query": "^3.0.1",
|
||||
"@polymer/iron-pages": "^3.0.1",
|
||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||
"@polymer/lit-element": "0.6.2",
|
||||
"@polymer/neon-animation": "^3.0.1",
|
||||
"@polymer/paper-button": "^3.0.1",
|
||||
"@polymer/paper-card": "^3.0.1",
|
||||
@@ -62,19 +64,21 @@
|
||||
"@polymer/paper-toggle-button": "^3.0.1",
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "^3.0.5",
|
||||
"@vaadin/vaadin-combo-box": "4.2.0-alpha3",
|
||||
"@vaadin/vaadin-date-picker": "3.3.0-alpha1",
|
||||
"@webcomponents/shadycss": "^1.5.2",
|
||||
"@webcomponents/webcomponentsjs": "^2.1.3",
|
||||
"@vaadin/vaadin-combo-box": "^4.2.0",
|
||||
"@vaadin/vaadin-date-picker": "^3.3.1",
|
||||
"@webcomponents/shadycss": "^1.6.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.0",
|
||||
"chart.js": "~2.7.2",
|
||||
"chartjs-chart-timeline": "^0.2.1",
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"eslint-import-resolver-webpack": "^0.10.1",
|
||||
"fecha": "^2.3.3",
|
||||
"home-assistant-js-websocket": "^3.1.4",
|
||||
"home-assistant-js-websocket": "^3.2.4",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"jquery": "^3.3.1",
|
||||
"js-yaml": "^3.12.0",
|
||||
"leaflet": "^1.3.4",
|
||||
"lit-html": "0.12.0",
|
||||
"marked": "^0.5.0",
|
||||
"mdn-polyfills": "^5.12.0",
|
||||
"moment": "^2.22.2",
|
||||
@@ -82,28 +86,35 @@
|
||||
"preact-compat": "^3.18.4",
|
||||
"react-big-calendar": "^0.19.2",
|
||||
"regenerator-runtime": "^0.12.1",
|
||||
"round-slider": "^1.3.2",
|
||||
"superstruct": "^0.6.0",
|
||||
"unfetch": "^4.0.1",
|
||||
"web-animations-js": "^2.3.1",
|
||||
"xss": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.2",
|
||||
"@babel/plugin-external-helpers": "^7.0.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-transform-react-jsx": "^7.0.0",
|
||||
"@babel/preset-env": "^7.1.0",
|
||||
"@babel/preset-typescript": "^7.1.0",
|
||||
"@gfx/zopfli": "^1.0.9",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^9.0.0",
|
||||
"babel-loader": "^7.1.4",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"babel-eslint": "^10",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"chai": "^4.1.2",
|
||||
"chai": "^4.2.0",
|
||||
"compression-webpack-plugin": "^2.0.0",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"del": "^3.0.0",
|
||||
"eslint": "^5.6.0",
|
||||
"eslint-config-airbnb-base": "^13.1.0",
|
||||
"eslint-config-prettier": "^3.1.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-prettier": "^3.0.0",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-foreach": "^0.1.0",
|
||||
@@ -114,18 +125,26 @@
|
||||
"gulp-merge-json": "^1.3.1",
|
||||
"gulp-rename": "^1.4.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-minifier": "^3.5.20",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^1.1.0",
|
||||
"lint-staged": "^8.0.2",
|
||||
"merge-stream": "^1.0.1",
|
||||
"mocha": "^5.2.0",
|
||||
"parse5": "^5.1.0",
|
||||
"polymer-analyzer": "^3.1.2",
|
||||
"polymer-bundler": "^4.0.2",
|
||||
"polymer-cli": "^1.8.0",
|
||||
"prettier": "^1.14.3",
|
||||
"raw-loader": "^0.5.1",
|
||||
"reify": "^0.17.3",
|
||||
"reify": "^0.18.1",
|
||||
"require-dir": "^1.0.0",
|
||||
"sinon": "^6.3.4",
|
||||
"sinon": "^7.1.1",
|
||||
"ts-mocha": "^2.0.0",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-config-prettier": "^1.15.0",
|
||||
"tslint-eslint-rules": "^5.4.0",
|
||||
"tslint-plugin-prettier": "^2.0.1",
|
||||
"typescript": "^3.1.4",
|
||||
"wct-browser-legacy": "^1.0.1",
|
||||
"web-component-tester": "^6.8.0",
|
||||
"webpack": "^4.19.1",
|
||||
@@ -134,16 +153,34 @@
|
||||
"workbox-webpack-plugin": "^3.5.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"inherits": "2.0.3",
|
||||
"samsam": "1.1.3",
|
||||
"supports-color": "3.1.2",
|
||||
"type-detect": "1.0.0",
|
||||
"@polymer/polymer": "3.0.5",
|
||||
"@webcomponents/webcomponentsjs": "2.1.3",
|
||||
"@webcomponents/shadycss": "^1.5.2",
|
||||
"@vaadin/vaadin-overlay": "3.2.0-alpha3",
|
||||
"@vaadin/vaadin-lumo-styles": "1.2.0",
|
||||
"fecha": "https://github.com/balloob/fecha/archive/51d14fd0eb4781e2ecf265d1c3080706259133b5.tar.gz"
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"@webcomponents/webcomponentsjs": "2.2.1",
|
||||
"@webcomponents/shadycss": "^1.6.0",
|
||||
"@vaadin/vaadin-overlay": "3.2.2",
|
||||
"@vaadin/vaadin-lumo-styles": "1.3.0",
|
||||
"fecha": "https://github.com/taylorhakes/fecha/archive/5e8fe08d982647fdb19fb403459838b02647813c.tar.gz",
|
||||
"lit-html": "0.12.0",
|
||||
"@polymer/lit-element": "0.6.2"
|
||||
},
|
||||
"main": "src/home-assistant.js"
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"linters": {
|
||||
"*.{js,ts,json,css,md}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"ignore": [
|
||||
"translations/**"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "always"
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,8 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
BUILD_DIR=build
|
||||
OUTPUT_DIR=hass_frontend
|
||||
OUTPUT_DIR_ES5=hass_frontend_es5
|
||||
|
@@ -9,6 +9,9 @@ cd "$(dirname "$0")/.."
|
||||
# Install node modules
|
||||
yarn install
|
||||
|
||||
# Verify everything is ok
|
||||
tsc
|
||||
|
||||
script/build_frontend
|
||||
|
||||
rm -rf dist
|
||||
|
11
script/size_stats
Executable file
11
script/size_stats
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
# Analyze stats
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
STATS=1 NODE_ENV=production webpack --profile --json > compilation-stats.json
|
||||
npx webpack-bundle-analyzer compilation-stats.json hass_frontend
|
||||
rm compilation-stats.json
|
@@ -28,7 +28,7 @@ mkdir -p ${LOCAL_DIR}
|
||||
|
||||
docker run \
|
||||
-v ${LOCAL_DIR}:/opt/dest/locale \
|
||||
lokalise/lokalise-cli@sha256:ddf5677f58551261008342df5849731c88bcdc152ab645b133b21819aede8218 lokalise \
|
||||
lokalise/lokalise-cli@sha256:b8329d20280263cad04f65b843e54b9e8e6909a348a678eac959550b5ef5c75f lokalise \
|
||||
--token ${LOKALISE_TOKEN} \
|
||||
export ${PROJECT_ID} \
|
||||
--export_empty skip \
|
||||
|
54
script/version_bump.js
Executable file
54
script/version_bump.js
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require("fs");
|
||||
const util = require("util");
|
||||
const exec = util.promisify(require("child_process").exec);
|
||||
|
||||
function patch(version) {
|
||||
const parts = version.split(".");
|
||||
return `${parts[0]}.${Number(parts[1]) + 1}`;
|
||||
}
|
||||
|
||||
function today() {
|
||||
const now = new Date();
|
||||
return `${now.getFullYear()}${now.getMonth() + 1}${String(
|
||||
now.getDate()
|
||||
).padStart(2, "0")}.0`;
|
||||
}
|
||||
|
||||
const methods = {
|
||||
patch,
|
||||
today,
|
||||
};
|
||||
|
||||
async function main(args) {
|
||||
const method = args.length > 0 && methods[args[0]];
|
||||
const commit = args.length > 1 && args[1] == "--commit";
|
||||
|
||||
if (!method) {
|
||||
console.error(
|
||||
"Missing required method. Choose from",
|
||||
Object.keys(methods).join(", ")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const setup = fs.readFileSync("setup.py", "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");
|
||||
|
||||
if (!commit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { stdout } = await exec(
|
||||
`git commit -am "Bumped version to ${newVersion}"`
|
||||
);
|
||||
console.log(stdout);
|
||||
}
|
||||
|
||||
main(process.argv.slice(2));
|
36
setup.py
36
setup.py
@@ -1,20 +1,22 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name='home-assistant-frontend',
|
||||
version='20180920.0',
|
||||
description='The Home Assistant frontend',
|
||||
url='https://github.com/home-assistant/home-assistant-polymer',
|
||||
author='The Home Assistant Authors',
|
||||
author_email='hello@home-assistant.io',
|
||||
license='Apache License 2.0',
|
||||
packages=find_packages(include=[
|
||||
'hass_frontend',
|
||||
'hass_frontend_es5',
|
||||
'hass_frontend.*',
|
||||
'hass_frontend_es5.*'
|
||||
]),
|
||||
install_requires=[
|
||||
'user-agents==1.1.0',
|
||||
],
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20181207.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
author_email="hello@home-assistant.io",
|
||||
license="Apache License 2.0",
|
||||
packages=find_packages(
|
||||
include=[
|
||||
"hass_frontend",
|
||||
"hass_frontend_es5",
|
||||
"hass_frontend.*",
|
||||
"hass_frontend_es5.*",
|
||||
]
|
||||
),
|
||||
install_requires=["user-agents==1.1.0"],
|
||||
include_package_data=True,
|
||||
zip_safe=False)
|
||||
zip_safe=False,
|
||||
)
|
||||
|
7
src/auth/data.ts
Normal file
7
src/auth/data.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
import { SignedPath } from "./types";
|
||||
|
||||
export const getSignedPath = (
|
||||
hass: HomeAssistant,
|
||||
path: string
|
||||
): Promise<SignedPath> => hass.callWS({ type: "auth/sign_path", path });
|
@@ -1,10 +1,10 @@
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import '../components/ha-form.js';
|
||||
import LocalizeLiteMixin from '../mixins/localize-lite-mixin.js';
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "@polymer/paper-button/paper-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import "../components/ha-form";
|
||||
import { localizeLiteMixin } from "../mixins/localize-lite-mixin";
|
||||
|
||||
class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
class HaAuthFlow extends localizeLiteMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
@@ -25,17 +25,25 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
[[localize('ui.panel.page-authorize.form.working')]]:
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(_state, "error")]]">
|
||||
<div class='error'>Error: [[_errorMsg]]</div>
|
||||
<div class="error">Error: [[_errorMsg]]</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(_state, "step")]]">
|
||||
<template is="dom-if" if="[[_equals(_step.type, "abort")]]">
|
||||
[[localize('ui.panel.page-authorize.abort_intro')]]:
|
||||
<ha-markdown content="[[_computeStepAbortedReason(localize, _step)]]"></ha-markdown>
|
||||
<ha-markdown
|
||||
content="[[_computeStepAbortedReason(localize, _step)]]"
|
||||
></ha-markdown>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_equals(_step.type, "form")]]">
|
||||
<template is="dom-if" if="[[_computeStepDescription(localize, _step)]]">
|
||||
<ha-markdown content="[[_computeStepDescription(localize, _step)]]" allow-svg></ha-markdown>
|
||||
<template
|
||||
is="dom-if"
|
||||
if="[[_computeStepDescription(localize, _step)]]"
|
||||
>
|
||||
<ha-markdown
|
||||
content="[[_computeStepDescription(localize, _step)]]"
|
||||
allow-svg
|
||||
></ha-markdown>
|
||||
</template>
|
||||
|
||||
<ha-form
|
||||
@@ -46,29 +54,28 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
compute-error="[[_computeErrorCallback(localize, _step)]]"
|
||||
></ha-form>
|
||||
</template>
|
||||
<div class='action'>
|
||||
<paper-button
|
||||
raised
|
||||
on-click='_handleSubmit'
|
||||
>[[_computeSubmitCaption(_step.type)]]</paper-button>
|
||||
<div class="action">
|
||||
<paper-button raised on-click="_handleSubmit"
|
||||
>[[_computeSubmitCaption(_step.type)]]</paper-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</form>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
authProvider: {
|
||||
type: Object,
|
||||
observer: '_providerChanged',
|
||||
observer: "_providerChanged",
|
||||
},
|
||||
clientId: String,
|
||||
redirectUri: String,
|
||||
oauth2State: String,
|
||||
_state: {
|
||||
type: String,
|
||||
value: 'loading'
|
||||
value: "loading",
|
||||
},
|
||||
_stepData: {
|
||||
type: Object,
|
||||
@@ -85,7 +92,7 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
ready() {
|
||||
super.ready();
|
||||
|
||||
this.addEventListener('keypress', (ev) => {
|
||||
this.addEventListener("keypress", (ev) => {
|
||||
if (ev.keyCode === 13) {
|
||||
this._handleSubmit();
|
||||
}
|
||||
@@ -93,22 +100,22 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
async _providerChanged(newProvider, oldProvider) {
|
||||
if (oldProvider && this._step && this._step.type === 'form') {
|
||||
if (oldProvider && this._step && this._step.type === "form") {
|
||||
fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin',
|
||||
method: "DELETE",
|
||||
credentials: "same-origin",
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/auth/login_flow', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
const response = await fetch("/auth/login_flow", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({
|
||||
client_id: this.clientId,
|
||||
handler: [newProvider.type, newProvider.id],
|
||||
redirect_uri: this.redirectUri,
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
@@ -117,16 +124,16 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
this._updateStep(data);
|
||||
} else {
|
||||
this.setProperties({
|
||||
_state: 'error',
|
||||
_state: "error",
|
||||
_errorMsg: data.message,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error starting auth flow', err);
|
||||
console.error("Error starting auth flow", err);
|
||||
this.setProperties({
|
||||
_state: 'error',
|
||||
_errorMsg: this.localize('ui.panel.page-authorize.form.unknown_error'),
|
||||
_state: "error",
|
||||
_errorMsg: this.localize("ui.panel.page-authorize.form.unknown_error"),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -134,11 +141,14 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
_updateStep(step) {
|
||||
const props = {
|
||||
_step: step,
|
||||
_state: 'step',
|
||||
_state: "step",
|
||||
};
|
||||
|
||||
if (this._step
|
||||
&& (step.flow_id !== this._step.flow_id || step.step_id !== this._step.step_id)) {
|
||||
if (
|
||||
this._step &&
|
||||
(step.flow_id !== this._step.flow_id ||
|
||||
step.step_id !== this._step.step_id)
|
||||
) {
|
||||
props._stepData = {};
|
||||
}
|
||||
|
||||
@@ -150,15 +160,23 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
_computeSubmitCaption(stepType) {
|
||||
return stepType === 'form' ? 'Next' : 'Start over';
|
||||
return stepType === "form" ? "Next" : "Start over";
|
||||
}
|
||||
|
||||
_computeStepAbortedReason(localize, step) {
|
||||
return localize(`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${step.reason}`);
|
||||
return localize(
|
||||
`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${
|
||||
step.reason
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
_computeStepDescription(localize, step) {
|
||||
const args = [`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`];
|
||||
const args = [
|
||||
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${
|
||||
step.step_id
|
||||
}.description`,
|
||||
];
|
||||
const placeholders = step.description_placeholders || {};
|
||||
Object.keys(placeholders).forEach((key) => {
|
||||
args.push(key);
|
||||
@@ -169,22 +187,32 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
|
||||
_computeLabelCallback(localize, step) {
|
||||
// Returns a callback for ha-form to calculate labels per schema object
|
||||
return schema => localize(`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.data.${schema.name}`);
|
||||
return (schema) =>
|
||||
localize(
|
||||
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${
|
||||
step.step_id
|
||||
}.data.${schema.name}`
|
||||
);
|
||||
}
|
||||
|
||||
_computeErrorCallback(localize, step) {
|
||||
// Returns a callback for ha-form to calculate error messages
|
||||
return error => localize(`ui.panel.page-authorize.form.providers.${step.handler[0]}.error.${error}`);
|
||||
return (error) =>
|
||||
localize(
|
||||
`ui.panel.page-authorize.form.providers.${
|
||||
step.handler[0]
|
||||
}.error.${error}`
|
||||
);
|
||||
}
|
||||
|
||||
async _handleSubmit() {
|
||||
if (this._step.type !== 'form') {
|
||||
if (this._step.type !== "form") {
|
||||
this._providerChanged(this.authProvider, null);
|
||||
return;
|
||||
}
|
||||
this._state = 'loading';
|
||||
this._state = "loading";
|
||||
// To avoid a jumping UI.
|
||||
this.style.setProperty('min-height', `${this.offsetHeight}px`);
|
||||
this.style.setProperty("min-height", `${this.offsetHeight}px`);
|
||||
|
||||
const postData = Object.assign({}, this._stepData, {
|
||||
client_id: this.clientId,
|
||||
@@ -192,20 +220,20 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
|
||||
try {
|
||||
const response = await fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify(postData)
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
|
||||
const newStep = await response.json();
|
||||
|
||||
if (newStep.type === 'create_entry') {
|
||||
if (newStep.type === "create_entry") {
|
||||
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
|
||||
let url = this.redirectUri;
|
||||
if (!url.includes('?')) {
|
||||
url += '?';
|
||||
} else if (!url.endsWith('&')) {
|
||||
url += '&';
|
||||
if (!url.includes("?")) {
|
||||
url += "?";
|
||||
} else if (!url.endsWith("&")) {
|
||||
url += "&";
|
||||
}
|
||||
|
||||
url += `code=${encodeURIComponent(newStep.result)}`;
|
||||
@@ -220,11 +248,11 @@ class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||
this._updateStep(newStep);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error submitting step', err);
|
||||
this._state = 'error-loading';
|
||||
console.error("Error submitting step", err);
|
||||
this._state = "error-loading";
|
||||
} finally {
|
||||
this.style.setProperty('min-height', '');
|
||||
this.style.setProperty("min-height", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('ha-auth-flow', HaAuthFlow);
|
||||
customElements.define("ha-auth-flow", HaAuthFlow);
|
||||
|
@@ -1,139 +0,0 @@
|
||||
import '@polymer/polymer/lib/elements/dom-if.js';
|
||||
import '@polymer/polymer/lib/elements/dom-repeat.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../components/ha-markdown.js';
|
||||
|
||||
import LocalizeLiteMixin from '../mixins/localize-lite-mixin.js';
|
||||
|
||||
import './ha-auth-flow.js';
|
||||
|
||||
class HaAuthorize extends LocalizeLiteMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
ha-markdown {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-markdown a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-markdown p:last-child{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
ha-pick-auth-provider {
|
||||
display: block;
|
||||
margin-top: 48px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template is="dom-if" if="[[!_authProviders]]">
|
||||
<p>[[localize('ui.panel.page-authorize.initializing')]]</p>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[_authProviders]]">
|
||||
<ha-markdown content='[[_computeIntro(localize, clientId, _authProvider)]]'></ha-markdown>
|
||||
|
||||
<ha-auth-flow
|
||||
resources="[[resources]]"
|
||||
client-id="[[clientId]]"
|
||||
redirect-uri="[[redirectUri]]"
|
||||
oauth2-state="[[oauth2State]]"
|
||||
auth-provider="[[_authProvider]]"
|
||||
step="{{step}}"
|
||||
></ha-auth-flow>
|
||||
|
||||
<template is="dom-if" if="[[_computeMultiple(_authProviders)]]">
|
||||
<ha-pick-auth-provider
|
||||
resources="[[resources]]"
|
||||
client-id="[[clientId]]"
|
||||
auth-providers="[[_computeInactiveProvders(_authProvider, _authProviders)]]"
|
||||
on-pick="_handleAuthProviderPick"
|
||||
></ha-pick-auth-provider>
|
||||
</template>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_authProvider: String,
|
||||
_authProviders: Array,
|
||||
clientId: String,
|
||||
redirectUri: String,
|
||||
oauth2State: String,
|
||||
translationFragment: {
|
||||
type: String,
|
||||
value: 'page-authorize',
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async ready() {
|
||||
super.ready();
|
||||
const query = {};
|
||||
const values = location.search.substr(1).split('&');
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
const value = values[i].split('=');
|
||||
if (value.length > 1) {
|
||||
query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]);
|
||||
}
|
||||
}
|
||||
const props = {};
|
||||
if (query.client_id) props.clientId = query.client_id;
|
||||
if (query.redirect_uri) props.redirectUri = query.redirect_uri;
|
||||
if (query.state) props.oauth2State = query.state;
|
||||
this.setProperties(props);
|
||||
|
||||
import(/* webpackChunkName: "pick-auth-provider" */ '../auth/ha-pick-auth-provider.js');
|
||||
|
||||
// Fetch auth providers
|
||||
try {
|
||||
const response = await window.providersPromise;
|
||||
const authProviders = await response.json();
|
||||
|
||||
// Forward to main screen which will redirect to right onboarding page.
|
||||
if (response.status === 400 && authProviders.code === 'onboarding_required') {
|
||||
location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
if (authProviders.length === 0) {
|
||||
alert('No auth providers returned. Unable to finish login.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.setProperties({
|
||||
_authProviders: authProviders,
|
||||
_authProvider: authProviders[0],
|
||||
});
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error loading auth providers', err);
|
||||
this._state = 'error-loading';
|
||||
}
|
||||
}
|
||||
|
||||
_computeMultiple(array) {
|
||||
return array && array.length > 1;
|
||||
}
|
||||
|
||||
async _handleAuthProviderPick(ev) {
|
||||
this._authProvider = ev.detail;
|
||||
}
|
||||
|
||||
_computeInactiveProvders(curProvider, providers) {
|
||||
return providers.filter(prv => prv.type !== curProvider.type || prv.id !== curProvider.id);
|
||||
}
|
||||
|
||||
_computeIntro(localize, clientId, authProvider) {
|
||||
return (
|
||||
localize('ui.panel.page-authorize.authorizing_client', 'clientId', clientId)
|
||||
+ '\n\n'
|
||||
+ localize('ui.panel.page-authorize.logging_in_with', 'authProviderName', authProvider.name)
|
||||
);
|
||||
}
|
||||
}
|
||||
customElements.define('ha-authorize', HaAuthorize);
|
158
src/auth/ha-authorize.ts
Normal file
158
src/auth/ha-authorize.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { LitElement, html, PropertyDeclarations } from "@polymer/lit-element";
|
||||
import "./ha-auth-flow";
|
||||
import { AuthProvider } from "../data/auth";
|
||||
|
||||
import(/* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider");
|
||||
|
||||
interface QueryParams {
|
||||
client_id?: string;
|
||||
redirect_uri?: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
public clientId?: string;
|
||||
public redirectUri?: string;
|
||||
public oauth2State?: string;
|
||||
private _authProvider?: AuthProvider;
|
||||
private _authProviders?: AuthProvider[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.translationFragment = "page-authorize";
|
||||
const query: QueryParams = {};
|
||||
const values = location.search.substr(1).split("&");
|
||||
for (const item of values) {
|
||||
const value = item.split("=");
|
||||
if (value.length > 1) {
|
||||
query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]);
|
||||
}
|
||||
}
|
||||
if (query.client_id) {
|
||||
this.clientId = query.client_id;
|
||||
}
|
||||
if (query.redirect_uri) {
|
||||
this.redirectUri = query.redirect_uri;
|
||||
}
|
||||
if (query.state) {
|
||||
this.oauth2State = query.state;
|
||||
}
|
||||
}
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
_authProvider: {},
|
||||
_authProviders: {},
|
||||
clientId: {},
|
||||
redirectUri: {},
|
||||
oauth2State: {},
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (!this._authProviders) {
|
||||
return html`
|
||||
<p>[[localize('ui.panel.page-authorize.initializing')]]</p>
|
||||
`;
|
||||
}
|
||||
|
||||
// We don't have a good approach yet to map text markup in localization.
|
||||
// So we sanitize the translation with innerText and then inject
|
||||
// the name with a bold tag.
|
||||
const loggingInWith = document.createElement("div");
|
||||
loggingInWith.innerText = this.localize(
|
||||
"ui.panel.page-authorize.logging_in_with",
|
||||
"authProviderName",
|
||||
"NAME"
|
||||
);
|
||||
loggingInWith.innerHTML = loggingInWith.innerHTML.replace(
|
||||
"**NAME**",
|
||||
`<b>${this._authProvider!.name}</b>`
|
||||
);
|
||||
|
||||
const inactiveProviders = this._authProviders.filter(
|
||||
(prv) => prv !== this._authProvider
|
||||
);
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<p>
|
||||
${
|
||||
this.localize(
|
||||
"ui.panel.page-authorize.authorizing_client",
|
||||
"clientId",
|
||||
this.clientId
|
||||
)
|
||||
}
|
||||
</p>
|
||||
${loggingInWith}
|
||||
|
||||
<ha-auth-flow
|
||||
.resources="${this.resources}"
|
||||
.clientId="${this.clientId}"
|
||||
.redirectUri="${this.redirectUri}"
|
||||
.oauth2State="${this.oauth2State}"
|
||||
.authProvider="${this._authProvider}"
|
||||
.step="{{step}}"
|
||||
></ha-auth-flow>
|
||||
|
||||
${
|
||||
inactiveProviders.length > 0
|
||||
? html`
|
||||
<ha-pick-auth-provider
|
||||
.resources="${this.resources}"
|
||||
.clientId="${this.clientId}"
|
||||
.authProviders="${inactiveProviders}"
|
||||
@pick="${this._handleAuthProviderPick}"
|
||||
></ha-pick-auth-provider>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
// Fetch auth providers
|
||||
try {
|
||||
const response = await (window as any).providersPromise;
|
||||
const authProviders = await response.json();
|
||||
|
||||
// Forward to main screen which will redirect to right onboarding page.
|
||||
if (
|
||||
response.status === 400 &&
|
||||
authProviders.code === "onboarding_required"
|
||||
) {
|
||||
location.href = "/?";
|
||||
return;
|
||||
}
|
||||
|
||||
if (authProviders.length === 0) {
|
||||
alert("No auth providers returned. Unable to finish login.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._authProviders = authProviders;
|
||||
this._authProvider = authProviders[0];
|
||||
} catch (err) {
|
||||
// tslint:disable-next-line
|
||||
console.error("Error loading auth providers", err);
|
||||
}
|
||||
}
|
||||
|
||||
protected renderStyle() {
|
||||
return html`
|
||||
<style>
|
||||
ha-pick-auth-provider {
|
||||
display: block;
|
||||
margin-top: 48px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _handleAuthProviderPick(ev) {
|
||||
this._authProvider = ev.detail;
|
||||
}
|
||||
}
|
||||
customElements.define("ha-authorize", HaAuthorize);
|
@@ -1,15 +1,17 @@
|
||||
import '@polymer/paper-item/paper-item.js';
|
||||
import '@polymer/paper-item/paper-item-body.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import EventsMixin from '../mixins/events-mixin.js';
|
||||
import LocalizeLiteMixin from '../mixins/localize-lite-mixin.js';
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { localizeLiteMixin } from "../mixins/localize-lite-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
class HaPickAuthProvider extends EventsMixin(LocalizeLiteMixin(PolymerElement)) {
|
||||
class HaPickAuthProvider extends EventsMixin(
|
||||
localizeLiteMixin(PolymerElement)
|
||||
) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
@@ -27,25 +29,25 @@ class HaPickAuthProvider extends EventsMixin(LocalizeLiteMixin(PolymerElement))
|
||||
<iron-icon icon="hass:chevron-right"></iron-icon>
|
||||
</paper-item>
|
||||
</template>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
_state: {
|
||||
type: String,
|
||||
value: 'loading'
|
||||
value: "loading",
|
||||
},
|
||||
authProviders: Array,
|
||||
};
|
||||
}
|
||||
|
||||
_handlePick(ev) {
|
||||
this.fire('pick', ev.model.item);
|
||||
this.fire("pick", ev.model.item);
|
||||
}
|
||||
|
||||
_equal(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
}
|
||||
customElements.define('ha-pick-auth-provider', HaPickAuthProvider);
|
||||
customElements.define("ha-pick-auth-provider", HaPickAuthProvider);
|
||||
|
3
src/auth/types.ts
Normal file
3
src/auth/types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface SignedPath {
|
||||
path: string;
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/entity/ha-state-label-badge.js';
|
||||
import "../components/entity/ha-state-label-badge";
|
||||
|
||||
class HaBadgesCard extends PolymerElement {
|
||||
static get template() {
|
||||
@@ -13,9 +13,12 @@ class HaBadgesCard extends PolymerElement {
|
||||
}
|
||||
</style>
|
||||
<template is="dom-repeat" items="[[states]]">
|
||||
<ha-state-label-badge hass="[[hass]]" state="[[item]]"></ha-state-label-badge>
|
||||
<ha-state-label-badge
|
||||
hass="[[hass]]"
|
||||
state="[[item]]"
|
||||
></ha-state-label-badge>
|
||||
</template>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -25,4 +28,4 @@ class HaBadgesCard extends PolymerElement {
|
||||
};
|
||||
}
|
||||
}
|
||||
customElements.define('ha-badges-card', HaBadgesCard);
|
||||
customElements.define("ha-badges-card", HaBadgesCard);
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import '@polymer/paper-styles/element-styles/paper-material-styles.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-styles/element-styles/paper-material-styles";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
|
||||
import computeStateName from '../common/entity/compute_state_name.js';
|
||||
import EventsMixin from '../mixins/events-mixin.js';
|
||||
import LocalizeMixin from '../mixins/localize-mixin.js';
|
||||
import computeStateName from "../common/entity/compute_state_name";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
|
||||
const UPDATE_INTERVAL = 10000; // ms
|
||||
/*
|
||||
@@ -51,7 +50,11 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
</style>
|
||||
|
||||
<template is="dom-if" if="[[cameraFeedSrc]]">
|
||||
<img src="[[cameraFeedSrc]]" class="camera-feed" alt="[[_computeStateName(stateObj)]]">
|
||||
<img
|
||||
src="[[cameraFeedSrc]]"
|
||||
class="camera-feed"
|
||||
alt="[[_computeStateName(stateObj)]]"
|
||||
/>
|
||||
</template>
|
||||
<div class="caption">
|
||||
[[_computeStateName(stateObj)]]
|
||||
@@ -59,7 +62,7 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
([[localize('ui.card.camera.not_available')]])
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -67,11 +70,11 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
hass: Object,
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'updateCameraFeedSrc',
|
||||
observer: "updateCameraFeedSrc",
|
||||
},
|
||||
cameraFeedSrc: {
|
||||
type: String,
|
||||
value: '',
|
||||
value: "",
|
||||
},
|
||||
imageLoaded: {
|
||||
type: Boolean,
|
||||
@@ -82,7 +85,7 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('click', () => this.cardTapped());
|
||||
this.addEventListener("click", () => this.cardTapped());
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@@ -96,13 +99,13 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
}
|
||||
|
||||
cardTapped() {
|
||||
this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
|
||||
this.fire("hass-more-info", { entityId: this.stateObj.entity_id });
|
||||
}
|
||||
|
||||
async updateCameraFeedSrc() {
|
||||
try {
|
||||
const { content_type: contentType, content } = await this.hass.callWS({
|
||||
type: 'camera_thumbnail',
|
||||
type: "camera_thumbnail",
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
this.setProperties({
|
||||
@@ -118,4 +121,4 @@ class HaCameraCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return computeStateName(stateObj);
|
||||
}
|
||||
}
|
||||
customElements.define('ha-camera-card', HaCameraCard);
|
||||
customElements.define("ha-camera-card", HaCameraCard);
|
||||
|
@@ -1,28 +1,29 @@
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import './ha-camera-card.js';
|
||||
import './ha-entities-card.js';
|
||||
import './ha-history_graph-card.js';
|
||||
import './ha-media_player-card.js';
|
||||
import './ha-persistent_notification-card.js';
|
||||
import './ha-plant-card.js';
|
||||
import './ha-weather-card.js';
|
||||
import "./ha-camera-card";
|
||||
import "./ha-entities-card";
|
||||
import "./ha-history_graph-card";
|
||||
import "./ha-media_player-card";
|
||||
import "./ha-persistent_notification-card";
|
||||
import "./ha-plant-card";
|
||||
import "./ha-weather-card";
|
||||
|
||||
import dynamicContentUpdater from '../common/dom/dynamic_content_updater.js';
|
||||
import dynamicContentUpdater from "../common/dom/dynamic_content_updater";
|
||||
|
||||
class HaCardChooser extends PolymerElement {
|
||||
static get properties() {
|
||||
return {
|
||||
cardData: {
|
||||
type: Object,
|
||||
observer: 'cardDataChanged',
|
||||
observer: "cardDataChanged",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_updateCard(newData) {
|
||||
dynamicContentUpdater(
|
||||
this, 'HA-' + newData.cardType.toUpperCase() + '-CARD',
|
||||
this,
|
||||
"HA-" + newData.cardType.toUpperCase() + "-CARD",
|
||||
newData
|
||||
);
|
||||
}
|
||||
@@ -32,7 +33,7 @@ class HaCardChooser extends PolymerElement {
|
||||
this.observer = new IntersectionObserver((entries) => {
|
||||
if (!entries.length) return;
|
||||
if (entries[0].isIntersecting) {
|
||||
this.style.height = '';
|
||||
this.style.height = "";
|
||||
if (this._detachedChild) {
|
||||
this.appendChild(this._detachedChild);
|
||||
this._detachedChild = null;
|
||||
@@ -58,13 +59,14 @@ class HaCardChooser extends PolymerElement {
|
||||
if (!newData) return;
|
||||
// ha-entities-card is exempt from observer as it doesn't load heavy resources.
|
||||
// and usually doesn't load external resources (except for entity_picture).
|
||||
const eligibleToObserver = (window.IntersectionObserver && newData.cardType !== 'entities');
|
||||
const eligibleToObserver =
|
||||
window.IntersectionObserver && newData.cardType !== "entities";
|
||||
if (!eligibleToObserver) {
|
||||
if (this.observer) {
|
||||
this.observer.unobserve(this);
|
||||
this.observer = null;
|
||||
}
|
||||
this.style.height = '';
|
||||
this.style.height = "";
|
||||
this._updateCard(newData);
|
||||
return;
|
||||
}
|
||||
@@ -76,4 +78,4 @@ class HaCardChooser extends PolymerElement {
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('ha-card-chooser', HaCardChooser);
|
||||
customElements.define("ha-card-chooser", HaCardChooser);
|
||||
|
@@ -1,18 +1,17 @@
|
||||
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/entity/ha-entity-toggle.js';
|
||||
import '../components/ha-card.js';
|
||||
import '../state-summary/state-card-content.js';
|
||||
import "../components/entity/ha-entity-toggle";
|
||||
import "../components/ha-card";
|
||||
import "../state-summary/state-card-content";
|
||||
|
||||
|
||||
import computeStateDomain from '../common/entity/compute_state_domain.js';
|
||||
import computeStateName from '../common/entity/compute_state_name.js';
|
||||
import stateMoreInfoType from '../common/entity/state_more_info_type.js';
|
||||
import canToggleState from '../common/entity/can_toggle_state.js';
|
||||
import EventsMixin from '../mixins/events-mixin.js';
|
||||
import LocalizeMixin from '../mixins/localize-mixin.js';
|
||||
import computeStateDomain from "../common/entity/compute_state_domain";
|
||||
import computeStateName from "../common/entity/compute_state_name";
|
||||
import stateMoreInfoType from "../common/entity/state_more_info_type";
|
||||
import canToggleState from "../common/entity/can_toggle_state";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
|
||||
class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
@@ -49,22 +48,36 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
||||
<ha-card>
|
||||
<template is="dom-if" if="[[title]]">
|
||||
<div class$="[[computeTitleClass(groupEntity)]]" on-click="entityTapped">
|
||||
<div
|
||||
class$="[[computeTitleClass(groupEntity)]]"
|
||||
on-click="entityTapped"
|
||||
>
|
||||
<div class="flex name">[[title]]</div>
|
||||
<template is="dom-if" if="[[showGroupToggle(groupEntity, states)]]">
|
||||
<ha-entity-toggle hass="[[hass]]" state-obj="[[groupEntity]]"></ha-entity-toggle>
|
||||
<ha-entity-toggle
|
||||
hass="[[hass]]"
|
||||
state-obj="[[groupEntity]]"
|
||||
></ha-entity-toggle>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<div class="states">
|
||||
<template is="dom-repeat" items="[[states]]" on-dom-change="addTapEvents">
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[states]]"
|
||||
on-dom-change="addTapEvents"
|
||||
>
|
||||
<div class$="[[computeStateClass(item)]]">
|
||||
<state-card-content hass="[[hass]]" class="state-card" state-obj="[[item]]"></state-card-content>
|
||||
<state-card-content
|
||||
hass="[[hass]]"
|
||||
class="state-card"
|
||||
state-obj="[[item]]"
|
||||
></state-card-content>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -74,7 +87,7 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
groupEntity: Object,
|
||||
title: {
|
||||
type: String,
|
||||
computed: 'computeTitle(states, groupEntity, localize)',
|
||||
computed: "computeTitle(states, groupEntity, localize)",
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -91,34 +104,40 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return computeStateName(groupEntity).trim();
|
||||
}
|
||||
const domain = computeStateDomain(states[0]);
|
||||
return (localize && localize(`domain.${domain}`)) || domain.replace(/_/g, ' ');
|
||||
return (
|
||||
(localize && localize(`domain.${domain}`)) || domain.replace(/_/g, " ")
|
||||
);
|
||||
}
|
||||
|
||||
computeTitleClass(groupEntity) {
|
||||
let classes = 'header horizontal layout center ';
|
||||
let classes = "header horizontal layout center ";
|
||||
if (groupEntity) {
|
||||
classes += 'more-info';
|
||||
classes += "more-info";
|
||||
}
|
||||
return classes;
|
||||
}
|
||||
|
||||
computeStateClass(stateObj) {
|
||||
return stateMoreInfoType(stateObj) !== 'hidden' ? 'state more-info' : 'state';
|
||||
return stateMoreInfoType(stateObj) !== "hidden"
|
||||
? "state more-info"
|
||||
: "state";
|
||||
}
|
||||
|
||||
addTapEvents() {
|
||||
const cards = this.root.querySelectorAll('.state');
|
||||
const cards = this.root.querySelectorAll(".state");
|
||||
cards.forEach((card) => {
|
||||
if (card.classList.contains('more-info')) {
|
||||
card.addEventListener('click', this.entityTapped);
|
||||
if (card.classList.contains("more-info")) {
|
||||
card.addEventListener("click", this.entityTapped);
|
||||
} else {
|
||||
card.removeEventListener('click', this.entityTapped);
|
||||
card.removeEventListener("click", this.entityTapped);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
entityTapped(ev) {
|
||||
const item = this.root.querySelector('dom-repeat').itemForElement(ev.target);
|
||||
const item = this.root
|
||||
.querySelector("dom-repeat")
|
||||
.itemForElement(ev.target);
|
||||
let entityId;
|
||||
if (!item && !this.groupEntity) {
|
||||
return;
|
||||
@@ -130,12 +149,16 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
} else {
|
||||
entityId = this.groupEntity.entity_id;
|
||||
}
|
||||
this.fire('hass-more-info', { entityId: entityId });
|
||||
this.fire("hass-more-info", { entityId: entityId });
|
||||
}
|
||||
|
||||
showGroupToggle(groupEntity, states) {
|
||||
if (!groupEntity || !states || groupEntity.attributes.control === 'hidden'
|
||||
|| (groupEntity.state !== 'on' && groupEntity.state !== 'off')) {
|
||||
if (
|
||||
!groupEntity ||
|
||||
!states ||
|
||||
groupEntity.attributes.control === "hidden" ||
|
||||
(groupEntity.state !== "on" && groupEntity.state !== "off")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -156,4 +179,4 @@ class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return canToggleCount > 1;
|
||||
}
|
||||
}
|
||||
customElements.define('ha-entities-card', HaEntitiesCard);
|
||||
customElements.define("ha-entities-card", HaEntitiesCard);
|
||||
|
@@ -1,13 +1,12 @@
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import '../components/state-history-charts.js';
|
||||
import '../data/ha-state-history-data.js';
|
||||
import "../components/state-history-charts";
|
||||
import "../data/ha-state-history-data";
|
||||
|
||||
|
||||
import computeStateName from '../common/entity/compute_state_name.js';
|
||||
import EventsMixin from '../mixins/events-mixin.js';
|
||||
import computeStateName from "../common/entity/compute_state_name";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
@@ -39,15 +38,32 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<ha-state-history-data hass="[[hass]]" filter-type="recent-entity" entity-id="[[computeHistoryEntities(stateObj)]]" data="{{stateHistory}}" is-loading="{{stateHistoryLoading}}" cache-config="[[cacheConfig]]"></ha-state-history-data>
|
||||
<paper-card dialog$="[[inDialog]]" on-click="cardTapped" elevation="[[computeElevation(inDialog)]]">
|
||||
<ha-state-history-data
|
||||
hass="[[hass]]"
|
||||
filter-type="recent-entity"
|
||||
entity-id="[[computeHistoryEntities(stateObj)]]"
|
||||
data="{{stateHistory}}"
|
||||
is-loading="{{stateHistoryLoading}}"
|
||||
cache-config="[[cacheConfig]]"
|
||||
></ha-state-history-data>
|
||||
<paper-card
|
||||
dialog$="[[inDialog]]"
|
||||
on-click="cardTapped"
|
||||
elevation="[[computeElevation(inDialog)]]"
|
||||
>
|
||||
<div class="header">[[computeTitle(stateObj)]]</div>
|
||||
<div class="content">
|
||||
<state-history-charts hass="[[hass]]" history-data="[[stateHistory]]" is-loading-data="[[stateHistoryLoading]]" up-to-now no-single>
|
||||
<state-history-charts
|
||||
hass="[[hass]]"
|
||||
history-data="[[stateHistory]]"
|
||||
is-loading-data="[[stateHistoryLoading]]"
|
||||
up-to-now
|
||||
no-single
|
||||
>
|
||||
</state-history-charts>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -55,7 +71,7 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
|
||||
hass: Object,
|
||||
stateObj: {
|
||||
type: Object,
|
||||
observer: 'stateObjObserver',
|
||||
observer: "stateObjObserver",
|
||||
},
|
||||
inDialog: {
|
||||
type: Boolean,
|
||||
@@ -76,14 +92,19 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
|
||||
|
||||
stateObjObserver(stateObj) {
|
||||
if (!stateObj) return;
|
||||
if (this.cacheConfig.cacheKey !== stateObj.entity_id
|
||||
|| this.cacheConfig.refresh !== (stateObj.attributes.refresh || 0)
|
||||
|| this.cacheConfig.hoursToShow !== (stateObj.attributes.hours_to_show || 24)) {
|
||||
this.cacheConfig = Object.assign({}, {
|
||||
if (
|
||||
this.cacheConfig.cacheKey !== stateObj.entity_id ||
|
||||
this.cacheConfig.refresh !== (stateObj.attributes.refresh || 0) ||
|
||||
this.cacheConfig.hoursToShow !== (stateObj.attributes.hours_to_show || 24)
|
||||
) {
|
||||
this.cacheConfig = Object.assign(
|
||||
{},
|
||||
{
|
||||
refresh: stateObj.attributes.refresh || 0,
|
||||
cacheKey: stateObj.entity_id,
|
||||
hoursToShow: stateObj.attributes.hours_to_show || 24
|
||||
});
|
||||
hoursToShow: stateObj.attributes.hours_to_show || 24,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +113,7 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
computeContentClass(inDialog) {
|
||||
return inDialog ? '' : 'content';
|
||||
return inDialog ? "" : "content";
|
||||
}
|
||||
|
||||
computeHistoryEntities(stateObj) {
|
||||
@@ -104,11 +125,11 @@ class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
cardTapped(ev) {
|
||||
const mq = window.matchMedia('(min-width: 610px) and (min-height: 550px)');
|
||||
const mq = window.matchMedia("(min-width: 610px) and (min-height: 550px)");
|
||||
if (mq.matches) {
|
||||
ev.stopPropagation();
|
||||
this.fire('hass-more-info', { entityId: this.stateObj.entity_id });
|
||||
this.fire("hass-more-info", { entityId: this.stateObj.entity_id });
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('ha-history_graph-card', HaHistoryGraphCard);
|
||||
customElements.define("ha-history_graph-card", HaHistoryGraphCard);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user