Compare commits

..

82 Commits

Author SHA1 Message Date
Paulus Schoutsen
368973b668 Add app alias 2020-06-21 12:37:12 -07:00
Paulus Schoutsen
b3b42b741d Upgrade to latest terser webpack plugin (#6199) 2020-06-20 22:51:29 -07:00
HomeAssistant Azure
020f115d7c [ci skip] Translation update 2020-06-21 00:32:58 +00:00
Bram Kragten
6891f1df1c Bumped version to 20200620.0 2020-06-20 15:56:19 +02:00
Bram Kragten
497494620d Log cast config fetch errors (#6197) 2020-06-20 15:40:29 +02:00
Bram Kragten
7a13242077 Logbook + History allow date/time filter (#6192)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-06-20 15:39:52 +02:00
Bram Kragten
b9d6973a79 ZHA: Bring back clusters UI (#6191) 2020-06-20 15:38:27 +02:00
Bram Kragten
ed0e8c5e8d Move MQTT dev tools to integrations (#6189) 2020-06-20 14:52:56 +02:00
HomeAssistant Azure
d8f530f8ac [ci skip] Translation update 2020-06-20 00:32:36 +00:00
HomeAssistant Azure
a46874b7ff [ci skip] Translation update 2020-06-19 00:32:29 +00:00
Bram Kragten
cf68f25a03 Don't throw errors in card picker (#6188) 2020-06-18 16:26:31 -07:00
Joakim Sørensen
16c604937e Display docker version (#5989) 2020-06-18 11:54:21 +02:00
Paulus Schoutsen
5cbffb23fd Remove check if config is loaded (#6123) 2020-06-18 11:18:33 +02:00
HomeAssistant Azure
6de38d3b85 [ci skip] Translation update 2020-06-18 00:32:18 +00:00
rajlaud
b34ce577d9 Add discovery to list of configuration flow sources whose entries can be ignored (#6185)
Add "discovery" to the list of configuration flow sources whose config flow entries can be ignored. For example, https://github.com/home-assistant/core/pull/35669. I've tested it on the squeezebox integration and it works.
2020-06-17 16:08:26 -07:00
Paulus Schoutsen
4b9fcd7de7 Bumped version to 20200617.0 2020-06-17 10:33:16 -07:00
Paulus Schoutsen
cc71ccaafa Keep auth params when onboarding (#6182) 2020-06-17 10:32:27 -07:00
marawan31
9ff2eece3a Added precipitation probability to forcast (#6131) 2020-06-17 09:02:44 +02:00
HomeAssistant Azure
2bc97cc9c8 [ci skip] Translation update 2020-06-17 00:32:22 +00:00
Bram Kragten
a87570cf5a Add link to integrations page if zha or zwave are loaded. (#6159) 2020-06-16 13:58:05 -07:00
Bram Kragten
9ffd25e3a0 Fix enter behavior of card editor (#6179) 2020-06-16 13:30:27 -07:00
Bram Kragten
61fdab294a Fix ha-card outline box shadow in firefox (#6174)
* Fix ha-card outline box shadow in firefox

* Add fallback for markdown code background
2020-06-16 13:29:38 -07:00
Bram Kragten
d4e137bb58 Keep add integration dialog same size while searching (#6158) 2020-06-16 13:29:11 -07:00
Bram Kragten
c251e4f241 Prevent add card dialog to jump on search (#6180) 2020-06-16 13:20:10 -07:00
Bram Kragten
a55d0f347b Fix reload script (#6181) 2020-06-16 13:19:07 -07:00
J. Nick Koston
f15cc0b424 Show the user that made the change in logbook (#6173)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-06-16 16:00:55 +02:00
HomeAssistant Azure
4e17875011 [ci skip] Translation update 2020-06-16 00:32:24 +00:00
Bram Kragten
256b64b6b3 Remove filtering for attribute hidden (#6171) 2020-06-15 15:41:21 -07:00
Paulus Schoutsen
8c0c0592e2 Move Jinja directives to own script block (#6166) 2020-06-15 16:18:58 +02:00
Thomas Lovén
f53f81dbc4 Fix translation in device options (#6172) 2020-06-15 16:17:07 +02:00
Paulus Schoutsen
d6c85719c9 Fix preload Roboto on older devices (#6165) 2020-06-14 21:08:10 -07:00
HomeAssistant Azure
c51c80bf47 [ci skip] Translation update 2020-06-15 00:32:32 +00:00
HomeAssistant Azure
544832756d [ci skip] Translation update 2020-06-14 00:32:29 +00:00
Bram Kragten
1afc2b3518 Merge branch 'master' into dev 2020-06-13 11:42:53 +02:00
Bram Kragten
17352ea5bd Bumped version to 20200613.0 2020-06-13 11:41:18 +02:00
Bram Kragten
6f5e3c2711 Close websocket connection after being hidden for 5 minutes (#6149)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-06-13 11:00:22 +02:00
Bram Kragten
bee21cd3fe Bumped version to 20200603.3 2020-06-13 10:56:44 +02:00
Bram Kragten
c4340e05d2 Disable pointer events when disabled (#6155) 2020-06-13 10:56:30 +02:00
Bram Kragten
6d6eef4e97 Disable pointer events when disabled (#6155) 2020-06-13 08:58:21 +02:00
Paulus Schoutsen
71137032df Custom Panel to allow passing two builds (#6104) 2020-06-12 21:01:09 -07:00
Bram Kragten
ffff4dc03d Add z index to dialog (#6145) 2020-06-12 21:00:37 -07:00
HomeAssistant Azure
65b16c763e [ci skip] Translation update 2020-06-13 00:32:26 +00:00
Bram Kragten
33af3de4a3 Disconnect panel after 5 minutes hidden (#6152) 2020-06-12 20:44:22 +02:00
marawan31
a822c1eb2f Reduce HLS buffer length to 1 minute instead of the default infinity (#6134)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-06-12 14:01:48 +02:00
Bram Kragten
4eb46bc275 Move integration config panels to integrations (#6122) 2020-06-12 11:51:00 +02:00
HomeAssistant Azure
ccc9b73f9b [ci skip] Translation update 2020-06-12 00:32:44 +00:00
HomeAssistant Azure
f5f8ad0e02 [ci skip] Translation update 2020-06-11 00:32:27 +00:00
Bram Kragten
0864aeb9c6 Convert config server control to Lit (#6141) 2020-06-10 21:21:04 +02:00
Bram Kragten
cda6310373 Migrate dialog-box to ha-dialog (#6140) 2020-06-10 21:19:42 +02:00
Bram Kragten
26a87e9280 Changes ha-card to new material design rules (#6137) 2020-06-10 12:01:37 +02:00
Bram Kragten
0b16a4880a Move info and log panels (#6127) 2020-06-10 11:59:05 +02:00
HomeAssistant Azure
db68c5852c [ci skip] Translation update 2020-06-10 00:32:12 +00:00
Bram Kragten
256aec5308 Remove slot from ha-switch (#6133) 2020-06-09 22:37:43 +02:00
Bram Kragten
20dd3ca21c data-entry-flow: replace paper-dialog with mwc-dialog (#6129) 2020-06-09 22:31:27 +02:00
Bram Kragten
25cc76e022 Replace paper-menu-button (#6132) 2020-06-09 22:30:36 +02:00
Bram Kragten
168cc607aa Move lovelace card edit dialog to mwc-dialog (#6130) 2020-06-09 22:30:18 +02:00
Bram Kragten
edc4601f8e Run prettier (#6128) 2020-06-09 14:06:42 +02:00
Robert
8f86a7ad8c Removed docker build method. (#6126)
The docker build method doesn't work anymore. Removed all
scripts/references related to it.
2020-06-09 11:52:43 +02:00
HomeAssistant Azure
4d7ad0dc51 [ci skip] Translation update 2020-06-09 00:32:32 +00:00
dependabot[bot]
34ac80567e Bump websocket-extensions from 0.1.3 to 0.1.4 (#6117)
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-06-08 09:56:45 +02:00
HomeAssistant Azure
23bdc03f29 [ci skip] Translation update 2020-06-08 00:32:36 +00:00
HomeAssistant Azure
3099748a6d [ci skip] Translation update 2020-06-07 00:32:42 +00:00
Paulus Schoutsen
e384f76ac1 Make two builds of hassio (#6105) 2020-06-05 21:56:56 -07:00
HomeAssistant Azure
7050d19be7 [ci skip] Translation update 2020-06-06 00:32:28 +00:00
Bram Kragten
ca678330d3 Bumped version to 20200603.2 2020-06-05 23:46:48 +02:00
Bram Kragten
10a5b3f9c3 Glance height fix (Again): A row could be zero height (#6110) 2020-06-05 23:46:41 +02:00
Bram Kragten
0fcedc5046 Glance height fix (Again): A row could be zero height (#6110) 2020-06-05 23:46:16 +02:00
Bram Kragten
f558f7fb8c Set min width to dev states columns (#6108) 2020-06-05 23:28:25 +02:00
Bram Kragten
06207defe7 Correct glance card size (#6109) 2020-06-05 23:03:15 +02:00
Bram Kragten
986f9d7633 Fix for config undefined (#6102) 2020-06-05 23:02:54 +02:00
Bram Kragten
81277fd12e Set min width to dev states columns (#6108) 2020-06-05 22:15:06 +02:00
Bram Kragten
30442b25c0 Correct glance card size (#6109) 2020-06-05 22:14:39 +02:00
Paulus Schoutsen
67ac3b4ba3 Drop Custom Elements ES5 adapter (#6107) 2020-06-05 11:04:19 +02:00
Paulus Schoutsen
f819e2cf8d Cleanup of builds (#6106) 2020-06-05 11:03:11 +02:00
Bram Kragten
a376f4525b Fix alarm card animation (#6096)
fixes #5074
2020-06-04 21:52:14 -07:00
Bram Kragten
5c1553286a Fix for config undefined (#6102) 2020-06-04 21:51:31 -07:00
HomeAssistant Azure
2c5d3f7492 [ci skip] Translation update 2020-06-05 00:32:27 +00:00
Paulus Schoutsen
58f01ba11a Fix webpack dev server (#6100) 2020-06-04 10:25:12 +02:00
HomeAssistant Azure
3fdf9a2e28 [ci skip] Translation update 2020-06-04 00:32:29 +00:00
Paulus Schoutsen
4cbd8e7673 Include compatibility in Hass.io (#6098) 2020-06-03 17:18:04 -07:00
Bram Kragten
0d4c51f26e Merge pull request #6095 from home-assistant/dev 2020-06-03 18:19:54 +02:00
Bram Kragten
5d5d6b247f Merge pull request #6093 from home-assistant/dev 2020-06-03 13:28:05 +02:00
277 changed files with 8802 additions and 6549 deletions

View File

@@ -1,4 +0,0 @@
node_modules
hass_frontend
hass_frontend_es5
.git

View File

@@ -66,11 +66,7 @@
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"import/no-cycle": 0,
"import/extensions": [
2,
"ignorePackages",
{ "ts": "never", "js": "never" }
],
"import/extensions": 0,
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": 0,
"default-case": 0,

View File

@@ -1,31 +0,0 @@
FROM node:8.11.1-alpine
# install yarn
ENV PATH /root/.yarn/bin:$PATH
## Install/force base tools
RUN apk update \
&& apk add make g++ curl bash binutils tar git python2 python3 \
&& rm -rf /var/cache/apk/* \
&& /bin/bash \
&& touch ~/.bashrc
## Install yarn
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
## Setup the project
RUN mkdir -p /frontend
WORKDIR /frontend
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
COPY script/docker_entrypoint.sh /usr/bin/docker_entrypoint.sh
RUN chmod +x /usr/bin/docker_entrypoint.sh
CMD [ "docker_entrypoint.sh" ]

View File

@@ -22,15 +22,6 @@ This is the repository for the official [Home Assistant](https://home-assistant.
A complete guide can be found at the following [link](https://www.home-assistant.io/developers/frontend/). It describes a short guide for the build of project.
### Docker environment
It is possible to compile the project and/or run commands in the development environment having only the [Docker](https://www.docker.com) pre-installed in the system. On the root of project you can do:
- `sh ./script/docker_run.sh build` Build all the project with one command
- `sh ./script/docker_run.sh bash` Open an interactive shell (the same environment generated by the _classic environment_) where you can run commands. This bash work on your project directory and any change on your file is automatically present within your build bash.
**Note**: if you have installed `npm` in addition to the `docker`, you can use the commands `npm run docker_build` and `npm run bash` to get a full build or bash as explained above
## License
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.

View File

@@ -20,7 +20,13 @@ module.exports.emptyPackages = ({ latestBuild }) =>
// Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
// Polyfill only needed for ES5 workers so filter out in latestBuild
// Compatibility not needed for latest builds
latestBuild &&
// wrapped in require.resolve so it blows up if file no longer exists
require.resolve(
path.resolve(paths.polymer_dir, "src/resources/compatibility.ts")
),
// This polyfill is loaded in workers to support ES5, filter it out.
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
].filter(Boolean);
@@ -79,8 +85,8 @@ module.exports.babelExclude = () => [
const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
const publicPath = (latestBuild) =>
latestBuild ? "/frontend_latest/" : "/frontend_es5/";
const publicPath = (latestBuild, root = "") =>
latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`;
/*
BundleConfig {
@@ -112,7 +118,6 @@ module.exports.config = {
authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts",
core: "./src/entrypoints/core.ts",
compatibility: "./src/entrypoints/compatibility.ts",
"custom-panel": "./src/entrypoints/custom-panel.ts",
},
outputPath: outputPath(paths.app_output_root, latestBuild),
@@ -127,10 +132,6 @@ module.exports.config = {
return {
entry: {
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
compatibility: path.resolve(
paths.polymer_dir,
"src/entrypoints/compatibility.ts"
),
},
outputPath: outputPath(paths.demo_output_root, latestBuild),
publicPath: publicPath(latestBuild),
@@ -169,15 +170,12 @@ module.exports.config = {
},
hassio({ isProdBuild, latestBuild }) {
if (latestBuild) {
throw new Error("Hass.io does not support latest build!");
}
return {
entry: {
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
},
outputPath: paths.hassio_output_root,
publicPath: paths.hassio_publicPath,
outputPath: outputPath(paths.hassio_output_root, latestBuild),
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
isProdBuild,
latestBuild,
dontHash: new Set(["entrypoint"]),

View File

@@ -20,6 +20,7 @@ gulp.task(
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-cast",
"gen-index-cast-dev",
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
)
);

View File

@@ -6,30 +6,32 @@ const merge = require("merge-stream");
const path = require("path");
const paths = require("../paths");
const zopfliOptions = { threshold: 150 };
gulp.task("compress-app", function compressApp() {
const jsLatest = gulp
.src(path.resolve(paths.app_output_latest, "**/*.js"))
.pipe(zopfli({ threshold: 150 }))
.pipe(zopfli(zopfliOptions))
.pipe(gulp.dest(paths.app_output_latest));
const jsEs5 = gulp
.src(path.resolve(paths.app_output_es5, "**/*.js"))
.pipe(zopfli({ threshold: 150 }))
.pipe(zopfli(zopfliOptions))
.pipe(gulp.dest(paths.app_output_es5));
const polyfills = gulp
.src(path.resolve(paths.app_output_static, "polyfills/*.js"))
.pipe(zopfli({ threshold: 150 }))
.pipe(zopfli(zopfliOptions))
.pipe(gulp.dest(path.resolve(paths.app_output_static, "polyfills")));
const translations = gulp
.src(path.resolve(paths.app_output_static, "translations/**/*.json"))
.pipe(zopfli({ threshold: 150 }))
.pipe(zopfli(zopfliOptions))
.pipe(gulp.dest(path.resolve(paths.app_output_static, "translations")));
const icons = gulp
.src(path.resolve(paths.app_output_static, "mdi/*.json"))
.pipe(zopfli({ threshold: 150 }))
.pipe(zopfli(zopfliOptions))
.pipe(gulp.dest(path.resolve(paths.app_output_static, "mdi")));
return merge(jsLatest, jsEs5, polyfills, translations, icons);
@@ -38,6 +40,6 @@ gulp.task("compress-app", function compressApp() {
gulp.task("compress-hassio", function compressApp() {
return gulp
.src(path.resolve(paths.hassio_output_root, "**/*.js"))
.pipe(zopfli())
.pipe(zopfli(zopfliOptions))
.pipe(gulp.dest(paths.hassio_output_root));
});

View File

@@ -53,7 +53,6 @@ gulp.task("gen-pages-dev", (done) => {
const content = renderTemplate(page, {
latestPageJS: `/frontend_latest/${page}.js`,
es5Compatibility: "/frontend_es5/compatibility.js",
es5PageJS: `/frontend_es5/${page}.js`,
});
@@ -79,7 +78,6 @@ gulp.task("gen-pages-prod", (done) => {
const content = renderTemplate(page, {
latestPageJS: latestManifest[`${page}.js`],
es5Compatibility: es5Manifest["compatibility.js"],
es5PageJS: es5Manifest[`${page}.js`],
});
@@ -99,7 +97,6 @@ gulp.task("gen-index-app-dev", (done) => {
latestCoreJS: "/frontend_latest/core.js",
latestCustomPanelJS: "/frontend_latest/custom-panel.js",
es5Compatibility: "/frontend_es5/compatibility.js",
es5AppJS: "/frontend_es5/app.js",
es5CoreJS: "/frontend_es5/core.js",
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
@@ -123,7 +120,6 @@ gulp.task("gen-index-app-prod", (done) => {
latestCoreJS: latestManifest["core.js"],
latestCustomPanelJS: latestManifest["custom-panel.js"],
es5Compatibility: es5Manifest["compatibility.js"],
es5AppJS: es5Manifest["app.js"],
es5CoreJS: es5Manifest["core.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
@@ -210,7 +206,6 @@ gulp.task("gen-index-demo-dev", (done) => {
const content = renderDemoTemplate("index", {
latestDemoJS: "/frontend_latest/main.js",
es5Compatibility: "/frontend_es5/compatibility.js",
es5DemoJS: "/frontend_es5/main.js",
});
@@ -233,7 +228,6 @@ gulp.task("gen-index-demo-prod", (done) => {
const content = renderDemoTemplate("index", {
latestDemoJS: latestManifest["main.js"],
es5Compatibility: es5Manifest["compatibility.js"],
es5DemoJS: es5Manifest["main.js"],
});
const minified = minifyHtml(content);

View File

@@ -36,11 +36,13 @@ function copyMdiIcons(staticDir) {
function copyPolyfills(staticDir) {
const staticPath = genStaticPath(staticDir);
// Web Component polyfills and adapters
// For custom panels using ES5 builds that don't use Babel 7+
copyFileDir(
npmPath("@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"),
staticPath("polyfills/")
);
// Web Component polyfills and adapters
copyFileDir(
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js"),
staticPath("polyfills/")

View File

@@ -1,6 +1,9 @@
const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const env = require("../env");
const paths = require("../paths");
require("./clean.js");
require("./gen-icons-json.js");
@@ -8,6 +11,24 @@ require("./webpack.js");
require("./compress.js");
require("./rollup.js");
async function writeEntrypointJS() {
// We ship two builds and we need to do feature detection on what version to load.
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
try {
new Function("import('${paths.hassio_publicPath}/frontend_latest/entrypoint.js')")();
} catch (err) {
var el = document.createElement('script');
el.src = '${paths.hassio_publicPath}/frontend_es5/entrypoint.js';
document.body.appendChild(el);
}
`,
{ encoding: "utf-8" }
);
}
gulp.task(
"develop-hassio",
gulp.series(
@@ -16,6 +37,7 @@ gulp.task(
},
"clean-hassio",
"gen-icons-json",
writeEntrypointJS,
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
)
);
@@ -29,6 +51,7 @@ gulp.task(
"clean-hassio",
"gen-icons-json",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
writeEntrypointJS,
...// Don't compress running tests
(env.isTest() ? [] : ["compress-hassio"])
)

View File

@@ -92,11 +92,7 @@ gulp.task("rollup-watch-app", () => {
});
gulp.task("rollup-watch-hassio", () => {
watchRollup(
// Force latestBuild = false for hassio config.
(conf) => rollupConfig.createHassioConfig({ ...conf, latestBuild: false }),
["hassio/src/**"]
);
watchRollup(rollupConfig.createHassioConfig, ["hassio/src/**"]);
});
gulp.task("rollup-dev-server-demo", () => {
@@ -137,12 +133,7 @@ gulp.task(
);
gulp.task("rollup-prod-hassio", () =>
buildRollup(
rollupConfig.createHassioConfig({
isProdBuild: true,
latestBuild: false,
})
)
bothBuilds(rollupConfig.createHassioConfig, { isProdBuild: true })
);
gulp.task("rollup-prod-gallery", () =>

View File

@@ -129,7 +129,7 @@ gulp.task("webpack-watch-hassio", () => {
webpack(
createHassioConfig({
isProdBuild: false,
latestBuild: false,
latestBuild: true,
})
).watch({}, handler());
});
@@ -139,9 +139,8 @@ gulp.task(
() =>
new Promise((resolve) =>
webpack(
createHassioConfig({
bothBuilds(createHassioConfig, {
isProdBuild: true,
latestBuild: false,
}),
handler(resolve)
)

View File

@@ -34,7 +34,7 @@ module.exports = {
hassio_dir: path.resolve(__dirname, "../hassio"),
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
hassio_publicPath: "/api/hassio/app/",
hassio_publicPath: "/api/hassio/app",
translations_src: path.resolve(__dirname, "../src/translations"),
};

View File

@@ -14,32 +14,6 @@ module.exports = function (userOptions = {}) {
return {
name: "ignore",
resolveId(importee, importer) {
// Only use ignore to intercept imports that we don't control
// inside node_module dependencies.
if (
importee.endsWith("commonjsHelpers.js") ||
importee.endsWith("rollupPluginBabelHelpers.js") ||
importee.endsWith("?commonjs-proxy") ||
!importer ||
!importer.includes("/node_modules/")
) {
return null;
}
let fullPath;
try {
fullPath = importee.startsWith(".")
? path.resolve(importee, importer)
: require.resolve(importee);
} catch (err) {
console.error("Error in ignore plugin", { importee, importer }, err);
throw err;
}
return files.some((toIgnorePath) => fullPath.startsWith(toIgnorePath))
? fullPath
: null;
},
load(id) {
return files.some((toIgnorePath) => id.startsWith(toIgnorePath))

View File

@@ -70,7 +70,9 @@ const createWebpackConfig = ({
if (
!context.includes("/node_modules/") ||
// calling define.amd will call require("!!webpack amd options")
resource.startsWith("!!webpack")
resource.startsWith("!!webpack") ||
// loaded by webpack dev server but doesn't exist.
resource === "webpack/hot"
) {
return false;
}
@@ -80,7 +82,11 @@ const createWebpackConfig = ({
? path.resolve(context, resource)
: require.resolve(resource);
} catch (err) {
console.error("Error in ignore plugin", resource, context);
console.error(
"Error in Home Assistant ignore plugin",
resource,
context
);
throw err;
}

View File

@@ -45,7 +45,6 @@
(function() {
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>");

View File

@@ -36,7 +36,6 @@
(function() {
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>");

View File

@@ -1,3 +1,3 @@
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto";
import "~app/resources/ha-style";
import "~app/resources/roboto";
import "./layout/hc-connect";

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-undef */
import { CAST_NS } from "../../../src/cast/const";
import { HassMessage } from "../../../src/cast/receiver_messages";
import "../../../src/resources/custom-card-support";
import { CAST_NS } from "~app/cast/const";
import { HassMessage } from "~app/cast/receiver_messages";
import "~app/resources/custom-card-support";
import { castContext } from "./cast_context";
import { HcMain } from "./layout/hc-main";
import { ReceivedMessage } from "./types";

View File

@@ -192,6 +192,8 @@ export class HcMain extends HassElement {
this._handleNewLovelaceConfig(lovelaceConfig)
);
} catch (err) {
// eslint-disable-next-line
console.log("Error fetching Lovelace configuration", err, msg);
// Generate a Lovelace config.
this._unsubLovelace = () => undefined;
await this._generateLovelaceConfig();

View File

@@ -1,11 +1,8 @@
const { createCastConfig } = require("../build-scripts/webpack.js");
const { isProdBuild } = require("../build-scripts/env.js");
// File just used for stats builds
const latestBuild = true;
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
module.exports = createCastConfig({
isProdBuild: isProdBuild(),
latestBuild,
isStatsBuild: isStatsBuild(),
latestBuild: true,
});

View File

@@ -1,3 +1,4 @@
import "../../src/resources/compatibility";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate";
import {

View File

@@ -92,15 +92,11 @@
(function() {
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5Compatibility %>").then(function() {
System.import("<%= es5DemoJS %>");
});
System.import("<%= es5DemoJS %>");
};
<% } else { %>
_ls("<%= es5Compatibility %>");
_ls("<%= es5DemoJS %>");
<% } %>
}

View File

@@ -2,7 +2,8 @@ import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-switch";
import "~app/components/ha-switch";
import "~app/components/ha-formfield";
import "./demo-card";
class DemoCards extends PolymerElement {
@@ -26,9 +27,10 @@ class DemoCards extends PolymerElement {
</style>
<app-toolbar>
<div class="filters">
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
Show config
</ha-switch>
<ha-formfield label="Show config">
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
</ha-switch>
</ha-formfield>
</div>
</app-toolbar>
<div class="cards">

View File

@@ -1,5 +1,8 @@
const { createGalleryConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
module.exports = createGalleryConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
latestBuild: true,
});

View File

@@ -1,9 +1,6 @@
window.loadES5Adapter().then(() => {
// eslint-disable-next-line
import(/* webpackChunkName: "roboto" */ "../../src/resources/roboto");
// eslint-disable-next-line
import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
});
import "~app/resources/compatibility";
import "~app/resources/roboto";
import "./hassio-main";
const styleEl = document.createElement("style");
styleEl.innerHTML = `

View File

@@ -14,7 +14,9 @@ import {
createHassioSession,
fetchHassioHomeAssistantInfo,
fetchHassioSupervisorInfo,
fetchHassioInfo,
HassioHomeAssistantInfo,
HassioInfo,
HassioPanelInfo,
HassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
@@ -75,6 +77,8 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
@property() private _hostInfo: HassioHostInfo;
@property() private _hassioInfo?: HassioInfo;
@property() private _hassOsInfo?: HassioHassOSInfo;
@property() private _hassInfo: HassioHomeAssistantInfo;
@@ -147,6 +151,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
hass: this.hass,
narrow: this.narrow,
supervisorInfo: this._supervisorInfo,
hassioInfo: this._hassioInfo,
hostInfo: this._hostInfo,
hassInfo: this._hassInfo,
hassOsInfo: this._hassOsInfo,
@@ -156,6 +161,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
el.hass = this.hass;
el.narrow = this.narrow;
el.supervisorInfo = this._supervisorInfo;
el.hassioInfo = this._hassioInfo;
el.hostInfo = this._hostInfo;
el.hassInfo = this._hassInfo;
el.hassOsInfo = this._hassOsInfo;
@@ -169,12 +175,14 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
return;
}
const [supervisorInfo, hostInfo, hassInfo] = await Promise.all([
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
fetchHassioInfo(this.hass),
]);
this._supervisorInfo = supervisorInfo;
this._hassioInfo = hassioInfo;
this._hostInfo = hostInfo;
this._hassInfo = hassInfo;

View File

@@ -3,6 +3,7 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
HassioInfo,
} from "../../src/data/hassio/supervisor";
import {
HassRouterPage,
@@ -26,6 +27,8 @@ class HassioPanelRouter extends HassRouterPage {
@property({ attribute: false }) public supervisorInfo: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property({ attribute: false }) public hostInfo: HassioHostInfo;
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
@@ -54,6 +57,7 @@ class HassioPanelRouter extends HassRouterPage {
el.route = this.route;
el.narrow = this.narrow;
el.supervisorInfo = this.supervisorInfo;
el.hassioInfo = this.hassioInfo;
el.hostInfo = this.hostInfo;
el.hassInfo = this.hassInfo;
el.hassOsInfo = this.hassOsInfo;

View File

@@ -10,6 +10,7 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
HassioInfo,
} from "../../src/data/hassio/supervisor";
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../src/types";
@@ -48,6 +49,8 @@ class HassioPanel extends LitElement {
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property({ attribute: false }) public hostInfo!: HassioHostInfo;
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
@@ -61,6 +64,7 @@ class HassioPanel extends LitElement {
.hass=${this.hass}
.narrow=${this.narrow}
.supervisorInfo=${this.supervisorInfo}
.hassioInfo=${this.hassioInfo}
.hostInfo=${this.hostInfo}
.hassInfo=${this.hassInfo}
.hassOsInfo=${this.hassOsInfo}

View File

@@ -19,6 +19,7 @@ import {
shutdownHost,
updateOS,
} from "../../../src/data/hassio/host";
import { HassioInfo } from "../../../src/data/hassio/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
@@ -35,6 +36,8 @@ class HassioHostInfo extends LitElement {
@property() public hostInfo!: HassioHostInfoType;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property() public hassOsInfo!: HassioHassOSInfo;
@property() private _errors?: string;
@@ -54,6 +57,12 @@ class HassioHostInfo extends LitElement {
<td>System</td>
<td>${this.hostInfo.operating_system}</td>
</tr>
${!this.hostInfo.features.includes("hassos")
? html`<tr>
<td>Docker version</td>
<td>${this.hassioInfo.docker}</td>
</tr>`
: ""}
${this.hostInfo.deployment
? html`
<tr>

View File

@@ -1,4 +1,3 @@
import "@polymer/paper-menu-button/paper-menu-button";
import {
css,
CSSResult,
@@ -12,7 +11,10 @@ import {
HassioHassOSInfo,
HassioHostInfo,
} from "../../../src/data/hassio/host";
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import {
HassioSupervisorInfo,
HassioInfo,
} from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
@@ -32,9 +34,11 @@ class HassioSystem extends LitElement {
@property() public supervisorInfo!: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property() public hostInfo!: HassioHostInfo;
@property() public hassOsInfo!: HassioHassOSInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
public render(): TemplateResult | void {
return html`
@@ -56,6 +60,7 @@ class HassioSystem extends LitElement {
></hassio-supervisor-info>
<hassio-host-info
.hass=${this.hass}
.hassioInfo=${this.hassioInfo}
.hostInfo=${this.hostInfo}
.hassOsInfo=${this.hassOsInfo}
></hassio-host-info>

View File

@@ -1,11 +1,8 @@
const { createHassioConfig } = require("../build-scripts/webpack.js");
const { isProdBuild } = require("../build-scripts/env.js");
// File just used for stats builds
const latestBuild = false;
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
module.exports = createHassioConfig({
isProdBuild: isProdBuild(),
latestBuild,
isStatsBuild: isStatsBuild(),
latestBuild: true,
});

View File

@@ -17,13 +17,12 @@
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
"format": "npm run format:eslint && npm run format:prettier",
"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"
"test": "npm run lint && npm run mocha"
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"~app": "file:./src",
"@formatjs/intl-pluralrules": "^1.5.8",
"@fullcalendar/core": "^5.0.0-beta.2",
"@fullcalendar/daygrid": "^5.0.0-beta.2",
@@ -76,6 +75,7 @@
"@thomasloven/round-slider": "0.5.0",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "~2.8.0",
"chartjs-chart-timeline": "^0.3.0",
@@ -89,7 +89,7 @@
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^0.12.4",
"home-assistant-js-websocket": "^5.2.1",
"home-assistant-js-websocket": "^5.3.0",
"idb-keyval": "^3.2.0",
"intl-messageformat": "^8.3.9",
"js-yaml": "^3.13.1",
@@ -108,6 +108,8 @@
"roboto-fontface": "^0.10.0",
"superstruct": "^0.6.1",
"unfetch": "^4.1.0",
"vue": "^2.6.11",
"vue2-daterange-picker": "^0.5.1",
"web-animations-js": "^2.3.2",
"workbox-core": "^5.1.3",
"workbox-precaching": "^5.1.3",
@@ -187,7 +189,7 @@
"sinon": "^7.3.1",
"source-map-url": "^0.4.0",
"systemjs": "^6.3.2",
"terser-webpack-plugin": "^1.2.3",
"terser-webpack-plugin": "^3.0.6",
"ts-lit-plugin": "^1.1.10",
"ts-mocha": "^6.0.0",
"typescript": "^3.8.3",

View File

@@ -1,14 +0,0 @@
#!/bin/bash
# Docker entry point inspired by travis build and script/build_frontend
# Stop on errors
set -e
# Build the frontend but not used the npm run build
/bin/bash script/build_frontend
# TEST
npm run test
#
#xvfb-run wct

View File

@@ -1,103 +0,0 @@
#!/bin/bash
# Basic Docker Management scripts
# With this script you can build software, or enter an agnostic development environment and run commands interactively.
check_mandatory_tools(){
if [ "x$(which docker)" == "x" ]; then
echo "UNKNOWN - Missing docker binary! Are you sure it is installed and reachable?"
exit 3
fi
docker info > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "UNKNOWN - Unable to talk to the docker daemon! Maybe the docker daemon is not running"
exit 3
fi
}
check_dev_image(){
if [[ "$(docker images -q ${IMAGE_NAME}:$IMAGE_TAG 2> /dev/null)" == "" ]]; then
echo "UNKNOWN - Can't find the development docker image ${IMAGE_NAME}:$IMAGE_TAG"
while true; do
read -p "Do you want to create it now?" yn
case $yn in
[Yy]* ) create_image; break;;
[Nn]* ) exit 3;;
* ) echo "Please answer y or n";;
esac
done
fi
}
# Building the basic image for compiling the production frontend
create_image(){
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
}
#
# Execute interactive bash on basic image
#
run_bash_on_docker(){
check_dev_image
docker run -it \
-v $PWD/:/frontend/ \
-v /frontend/node_modules \
-v /frontend/bower_components \
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash
}
#
# Execute the basic image for compiling the production frontend
#
build_all(){
check_dev_image
docker run -it \
-v $PWD/:/frontend/ \
-v /frontend/node_modules \
-v /frontend/bower_components \
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash script/build_frontend
}
# Init Global Variable
IMAGE_NAME=home_assistant_fe_image
IMAGE_TAG=${2:-latest}
check_mandatory_tools
case "$1" in
setup_env)
create_image
;;
bash)
run_bash_on_docker
;;
build)
build_all
;;
*)
echo "NAME"
echo " Docker Management."
echo ""
echo "SYNOPSIS"
echo " ${0} command [version]"
echo ""
echo "DESCRIPTION"
echo " With this script you can build software, or enter an agnostic development environment and run commands interactively."
echo ""
echo " The command are:"
echo " setup_env Create develop images"
echo " bash Run bash on develop enviroments"
echo " build Run silent build"
echo ""
echo " The version is optional, if not inserted it assumes \"latest\". "
exit 1
;;
esac
exit 0

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20200603.1",
version="20200620.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",

View File

@@ -6,19 +6,18 @@ import {
property,
PropertyValues,
} from "lit-element";
import { AuthProvider, fetchAuthProviders } from "../data/auth";
import {
AuthProvider,
fetchAuthProviders,
AuthUrlSearchParams,
} from "../data/auth";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { registerServiceWorker } from "../util/register-service-worker";
import "./ha-auth-flow";
import { extractSearchParamsObject } from "../common/url/search-params";
import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider");
interface QueryParams {
client_id?: string;
redirect_uri?: string;
state?: string;
}
class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
@property() public clientId?: string;
@@ -33,14 +32,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
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]);
}
}
const query = extractSearchParamsObject() as AuthUrlSearchParams;
if (query.client_id) {
this.clientId = query.client_id;
}
@@ -145,7 +137,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
response.status === 400 &&
authProviders.code === "onboarding_required"
) {
location.href = "/?";
location.href = `/onboarding.html${location.search}`;
return;
}

View File

@@ -6,7 +6,7 @@ export const isValidEntityId = (entityId: string) =>
export const createValidEntityId = (input: string) =>
input
.toLowerCase()
.replace(/\s|'/g, "_") // replace spaces and quotes with underscore
.replace(/\s|'|\./g, "_") // replace spaces, points and quotes with underscore
.replace(/\W/g, "") // remove not allowed chars
.replace(/_{2,}/g, "_") // replace multiple underscores with 1
.replace(/_$/, ""); // remove underscores at the end

View File

@@ -57,9 +57,12 @@ export const iconColorCSS = css`
0% {
opacity: 1;
}
100% {
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
ha-icon[data-domain="plant"][data-state="problem"],

View File

@@ -0,0 +1,8 @@
export const extractSearchParamsObject = (): { [key: string]: string } => {
const query = {};
const searchParams = new URLSearchParams(location.search);
for (const [key, value] of searchParams.entries()) {
query[key] = value;
}
return query;
};

View File

@@ -8,6 +8,9 @@ class HaProgressButton extends PolymerElement {
static get template() {
return html`
<style>
:host {
outline: none;
}
.container {
position: relative;
display: inline-block;

View File

@@ -619,6 +619,11 @@ export class HaDataTable extends LitElement {
text-transform: inherit;
}
.mdc-data-table__cell a {
color: inherit;
text-decoration: none;
}
.mdc-data-table__cell--numeric {
text-align: right;
}

View File

@@ -0,0 +1,228 @@
import Vue from "vue";
import wrap from "@vue/web-component-wrapper";
import DateRangePicker from "vue2-daterange-picker";
// @ts-ignore
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
import { fireEvent } from "../common/dom/fire_event";
import { Constructor } from "../types";
import { customElement } from "lit-element/lib/decorators";
const Component = Vue.extend({
props: {
twentyfourHours: {
type: Boolean,
default: true,
},
disabled: {
type: Boolean,
default: false,
},
ranges: {
type: Boolean,
default: true,
},
startDate: {
type: [String, Date],
default() {
return new Date();
},
},
endDate: {
type: [String, Date],
default() {
return new Date();
},
},
},
render(createElement) {
// @ts-ignore
return createElement(DateRangePicker, {
props: {
"time-picker": true,
"auto-apply": false,
opens: "right",
"show-dropdowns": false,
"time-picker24-hour": this.twentyfourHours,
disabled: this.disabled,
ranges: this.ranges ? {} : false,
},
model: {
value: {
startDate: this.startDate,
endDate: this.endDate,
},
callback: (value) => {
// @ts-ignore
fireEvent(this.$el as HTMLElement, "change", value);
},
expression: "dateRange",
},
scopedSlots: {
input() {
return createElement("slot", {
domProps: { name: "input" },
});
},
header() {
return createElement("slot", {
domProps: { name: "header" },
});
},
ranges() {
return createElement("slot", {
domProps: { name: "ranges" },
});
},
footer() {
return createElement("slot", {
domProps: { name: "footer" },
});
},
},
});
},
});
const WrappedElement: Constructor<HTMLElement> = wrap(Vue, Component);
@customElement("date-range-picker")
class DateRangePickerElement extends WrappedElement {
constructor() {
super();
const style = document.createElement("style");
style.innerHTML = `
${dateRangePickerStyles}
.calendars {
display: flex;
}
.daterangepicker {
left: 0px !important;
top: auto;
background-color: var(--card-background-color);
border: none;
border-radius: var(--ha-card-border-radius, 4px);
box-shadow: var(
--ha-card-box-shadow,
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
);
color: var(--primary-text-color);
min-width: initial !important;
}
.daterangepicker:after {
border-bottom: 6px solid var(--card-background-color);
}
.daterangepicker .calendar-table {
background-color: var(--card-background-color);
border: none;
}
.daterangepicker .calendar-table td,
.daterangepicker .calendar-table th {
background-color: transparent;
color: var(--secondary-text-color);
border-radius: 0;
outline: none;
width: 32px;
height: 32px;
}
.daterangepicker td.off,
.daterangepicker td.off.end-date,
.daterangepicker td.off.in-range,
.daterangepicker td.off.start-date {
background-color: var(--secondary-background-color);
color: var(--disabled-text-color);
}
.daterangepicker td.in-range {
background-color: var(--light-primary-color);
color: var(--primary-text-color);
}
.daterangepicker td.active,
.daterangepicker td.active:hover {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
.daterangepicker td.start-date.end-date {
border-radius: 50%;
}
.daterangepicker td.start-date {
border-radius: 50% 0 0 50%;
}
.daterangepicker td.end-date {
border-radius: 0 50% 50% 0;
}
.reportrange-text {
background: none !important;
padding: 0 !important;
border: none !important;
}
.daterangepicker .calendar-table .next span,
.daterangepicker .calendar-table .prev span {
border: solid var(--primary-text-color);
border-width: 0 2px 2px 0;
}
.daterangepicker .ranges li {
outline: none;
}
.daterangepicker .ranges li:hover {
background-color: var(--secondary-background-color);
}
.daterangepicker .ranges li.active {
background-color: var(--primary-color);
color: var(--text-primary-color);
}
.daterangepicker select.ampmselect,
.daterangepicker select.hourselect,
.daterangepicker select.minuteselect,
.daterangepicker select.secondselect {
background: transparent;
border: 1px solid var(--divider-color);
color: var(--primary-color);
}
.daterangepicker .drp-buttons .btn {
border: 1px solid var(--primary-color);
background-color: transparent;
color: var(--primary-color);
border-radius: 4px;
padding: 8px;
cursor: pointer;
}
.calendars-container {
flex-direction: column;
align-items: center;
}
.drp-calendar.col.right .calendar-table {
display: none;
}
.daterangepicker.show-ranges .drp-calendar.left {
border-left: 0px;
}
.daterangepicker .drp-calendar.left {
padding: 8px;
}
.daterangepicker.show-calendar .ranges {
margin-top: 0;
padding-top: 8px;
border-right: 1px solid var(--divider-color);
}
@media only screen and (max-width: 800px) {
.calendars {
flex-direction: column;
}
}
.calendar-table {
padding: 0 !important;
}
`;
const shadowRoot = this.shadowRoot!;
shadowRoot.appendChild(style);
// Stop click events from reaching the document, otherwise it will close the picker immediately.
shadowRoot.addEventListener("click", (ev) => ev.stopPropagation());
}
}
declare global {
interface HTMLElementTagNameMap {
"date-range-picker": DateRangePickerElement;
}
}

View File

@@ -40,7 +40,7 @@ export class HaButtonMenu extends LitElement {
static get styles(): CSSResult {
return css`
:host {
display: block;
display: inline-block;
position: relative;
}
`;

View File

@@ -176,7 +176,9 @@ class HaCameraStream extends LitElement {
Hls: HLSModule,
url: string
) {
const hls = new Hls();
const hls = new Hls({
liveBackBufferLength: 60,
});
this._hlsPolyfillInstance = hls;
hls.attachMedia(videoEl);
hls.on(Hls.Events.MEDIA_ATTACHED, () => {

View File

@@ -12,6 +12,8 @@ import {
class HaCard extends LitElement {
@property() public header?: string;
@property({ type: Boolean, reflect: true }) public outlined = false;
static get styles(): CSSResult {
return css`
:host {
@@ -19,12 +21,12 @@ class HaCard extends LitElement {
--ha-card-background,
var(--paper-card-background-color, white)
);
border-radius: var(--ha-card-border-radius, 2px);
border-radius: var(--ha-card-border-radius, 4px);
box-shadow: var(
--ha-card-box-shadow,
0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12),
0 3px 1px -2px rgba(0, 0, 0, 0.2)
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
);
color: var(--primary-text-color);
display: block;
@@ -32,6 +34,16 @@ class HaCard extends LitElement {
position: relative;
}
:host([outlined]) {
box-shadow: none;
border-width: 1px;
border-style: solid;
border-color: var(
--ha-card-border-color,
var(--divider-color, #e0e0e0)
);
}
.card-header,
:host ::slotted(.card-header) {
color: var(--ha-card-header-color, --primary-text-color);

View File

@@ -0,0 +1,195 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
PropertyValues,
} from "lit-element";
import { HomeAssistant } from "../types";
import { mdiCalendar } from "@mdi/js";
import { formatDateTime } from "../common/datetime/format_date_time";
import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item";
import "./ha-svg-icon";
import "@polymer/paper-input/paper-input";
import "@material/mwc-list/mwc-list";
import "./date-range-picker";
export interface DateRangePickerRanges {
[key: string]: [Date, Date];
}
@customElement("ha-date-range-picker")
export class HaDateRangePicker extends LitElement {
@property() public hass!: HomeAssistant;
@property() public startDate!: Date;
@property() public endDate!: Date;
@property() public ranges?: DateRangePickerRanges;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) private _hour24format = false;
protected updated(changedProps: PropertyValues) {
if (changedProps.has("hass")) {
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.language !== this.hass.language) {
this._hour24format = this._compute24hourFormat();
}
}
}
protected render(): TemplateResult {
return html`
<date-range-picker
?disabled=${this.disabled}
twentyfour-hours=${this._hour24format}
start-date=${this.startDate}
end-date=${this.endDate}
?ranges=${this.ranges !== undefined}
>
<div slot="input" class="date-range-inputs">
<ha-svg-icon path=${mdiCalendar}></ha-svg-icon>
<paper-input
.value=${formatDateTime(this.startDate, this.hass.language)}
.label=${this.hass.localize(
"ui.components.date-range-picker.start_date"
)}
.disabled=${this.disabled}
@click=${this._handleInputClick}
readonly
></paper-input>
<paper-input
.value=${formatDateTime(this.endDate, this.hass.language)}
label=${this.hass.localize(
"ui.components.date-range-picker.end_date"
)}
.disabled=${this.disabled}
@click=${this._handleInputClick}
readonly
></paper-input>
</div>
${this.ranges
? html`<div slot="ranges" class="date-range-ranges">
<mwc-list @click=${this._setDateRange}>
${Object.entries(this.ranges).map(
([name, dates]) => html`<mwc-list-item
.activated=${this.startDate.getTime() ===
dates[0].getTime() &&
this.endDate.getTime() === dates[1].getTime()}
.startDate=${dates[0]}
.endDate=${dates[1]}
>
${name}
</mwc-list-item>`
)}
</mwc-list>
</div>`
: ""}
<div slot="footer" class="date-range-footer">
<mwc-button @click=${this._cancelDateRange}
>${this.hass.localize("ui.common.cancel")}</mwc-button
>
<mwc-button @click=${this._applyDateRange}
>${this.hass.localize(
"ui.components.date-range-picker.select"
)}</mwc-button
>
</div>
</date-range-picker>
`;
}
private _compute24hourFormat() {
return (
new Intl.DateTimeFormat(this.hass.language, {
hour: "numeric",
})
.formatToParts(new Date(2020, 0, 1, 13))
.find((part) => part.type === "hour")!.value.length === 2
);
}
private _setDateRange(ev: Event) {
const target = ev.target as any;
const startDate = target.startDate;
const endDate = target.endDate;
const dateRangePicker = this._dateRangePicker;
dateRangePicker.clickRange([startDate, endDate]);
dateRangePicker.clickedApply();
}
private _cancelDateRange() {
this._dateRangePicker.clickCancel();
}
private _applyDateRange() {
this._dateRangePicker.clickedApply();
}
private get _dateRangePicker() {
const dateRangePicker = this.shadowRoot!.querySelector(
"date-range-picker"
) as any;
return dateRangePicker.vueComponent.$children[0];
}
private _handleInputClick() {
// close the date picker, so it will open again on the click event
if (this._dateRangePicker.open) {
this._dateRangePicker.open = false;
}
}
static get styles(): CSSResult {
return css`
ha-svg-icon {
margin-right: 8px;
}
.date-range-inputs {
display: flex;
align-items: center;
}
.date-range-ranges {
border-right: 1px solid var(--divider-color);
}
@media only screen and (max-width: 800px) {
.date-range-ranges {
border-right: none;
border-bottom: 1px solid var(--divider-color);
}
}
.date-range-footer {
display: flex;
justify-content: flex-end;
padding: 8px;
border-top: 1px solid var(--divider-color);
}
paper-input {
display: inline-block;
max-width: 200px;
}
paper-input:last-child {
margin-left: 8px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-date-range-picker": HaDateRangePicker;
}
}

View File

@@ -13,7 +13,7 @@ export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
<mwc-icon-button
aria-label=${hass.localize("ui.dialogs.generic.close")}
dialogAction="close"
class="close_button"
class="header_button"
>
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
@@ -25,6 +25,9 @@ export class HaDialog extends MwcDialog {
return [
style,
css`
.mdc-dialog {
z-index: var(--dialog-z-index, 7);
}
.mdc-dialog__actions {
justify-content: var(--justify-action-buttons, flex-end);
}
@@ -35,10 +38,15 @@ export class HaDialog extends MwcDialog {
display: block;
height: 20px;
}
.close_button {
.mdc-dialog__content {
padding: var(--dialog-content-padding, 20px 24px);
}
.header_button {
position: absolute;
right: 16px;
top: 12px;
text-decoration: none;
color: inherit;
}
`,
];

View File

@@ -0,0 +1,33 @@
import "@material/mwc-formfield";
import type { Formfield } from "@material/mwc-formfield";
import { style } from "@material/mwc-formfield/mwc-formfield-css";
import { css, CSSResult, customElement } from "lit-element";
import { Constructor } from "../types";
const MwcFormfield = customElements.get("mwc-formfield") as Constructor<
Formfield
>;
@customElement("ha-formfield")
export class HaFormfield extends MwcFormfield {
protected static get styles(): CSSResult[] {
return [
style,
css`
::slotted(ha-switch) {
margin-right: 10px;
}
[dir="rtl"] ::slotted(ha-switch),
::slotted(ha-switch)[dir="rtl"] {
margin-left: 10px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-formfield": HaFormfield;
}
}

View File

@@ -1,17 +1,37 @@
import { HaIconButton } from "./ha-icon-button";
import {
LitElement,
property,
TemplateResult,
html,
customElement,
} from "lit-element";
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
@customElement("ha-icon-button-arrow-next")
export class HaIconButtonArrowNext extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property() private _icon = mdiArrowRight;
export class HaIconButtonArrowNext extends HaIconButton {
public connectedCallback() {
super.connectedCallback();
// wait to check for direction since otherwise direction is wrong even though top level is RTL
setTimeout(() => {
this.icon =
this._icon =
window.getComputedStyle(this).direction === "ltr"
? "hass:arrow-right"
: "hass:arrow-left";
? mdiArrowRight
: mdiArrowLeft;
}, 100);
}
protected render(): TemplateResult {
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
}
declare global {
@@ -19,5 +39,3 @@ declare global {
"ha-icon-button-arrow-next": HaIconButtonArrowNext;
}
}
customElements.define("ha-icon-button-arrow-next", HaIconButtonArrowNext);

View File

@@ -1,8 +1,15 @@
import { LitElement, property, TemplateResult, html } from "lit-element";
import {
LitElement,
property,
TemplateResult,
html,
customElement,
} from "lit-element";
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
@customElement("ha-icon-button-arrow-prev")
export class HaIconButtonArrowPrev extends LitElement {
@property({ type: Boolean }) public disabled = false;
@@ -32,5 +39,3 @@ declare global {
"ha-icon-button-arrow-prev": HaIconButtonArrowPrev;
}
}
customElements.define("ha-icon-button-arrow-prev", HaIconButtonArrowPrev);

View File

@@ -1,17 +1,37 @@
import { HaIconButton } from "./ha-icon-button";
import {
LitElement,
property,
TemplateResult,
html,
customElement,
} from "lit-element";
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
import "@material/mwc-icon-button";
import "./ha-svg-icon";
@customElement("ha-icon-button-next")
export class HaIconButtonNext extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property() private _icon = mdiChevronRight;
export class HaIconButtonNext extends HaIconButton {
public connectedCallback() {
super.connectedCallback();
// wait to check for direction since otherwise direction is wrong even though top level is RTL
setTimeout(() => {
this.icon =
this._icon =
window.getComputedStyle(this).direction === "ltr"
? "hass:chevron-right"
: "hass:chevron-left";
? mdiChevronRight
: mdiChevronLeft;
}, 100);
}
protected render(): TemplateResult {
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
}
declare global {
@@ -19,5 +39,3 @@ declare global {
"ha-icon-button-next": HaIconButtonNext;
}
}
customElements.define("ha-icon-button-next", HaIconButtonNext);

View File

@@ -1,17 +1,37 @@
import { HaIconButton } from "./ha-icon-button";
import {
LitElement,
property,
TemplateResult,
html,
customElement,
} from "lit-element";
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
@customElement("ha-icon-button-prev")
export class HaIconButtonPrev extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property() private _icon = mdiChevronLeft;
export class HaIconButtonPrev extends HaIconButton {
public connectedCallback() {
super.connectedCallback();
// wait to check for direction since otherwise direction is wrong even though top level is RTL
setTimeout(() => {
this.icon =
this._icon =
window.getComputedStyle(this).direction === "ltr"
? "hass:chevron-left"
: "hass:chevron-right";
? mdiChevronLeft
: mdiChevronRight;
}, 100);
}
protected render(): TemplateResult {
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
}
declare global {
@@ -19,5 +39,3 @@ declare global {
"ha-icon-button-prev": HaIconButtonPrev;
}
}
customElements.define("ha-icon-button-prev", HaIconButtonPrev);

View File

@@ -24,28 +24,21 @@ export class HaIconButton extends LitElement {
protected render(): TemplateResult {
return html`
<mwc-icon-button
.label=${this.label}
?disabled=${this.disabled}
@click=${this._handleClick}
>
<mwc-icon-button .label=${this.label} .disabled=${this.disabled}>
<ha-icon .icon=${this.icon}></ha-icon>
</mwc-icon-button>
`;
}
private _handleClick(ev) {
if (this.disabled) {
ev.stopPropagation();
}
}
static get styles(): CSSResult {
return css`
:host {
display: inline-block;
outline: none;
}
:host([disabled]) {
pointer-events: none;
}
mwc-icon-button {
--mdc-theme-on-primary: currentColor;
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);

View File

@@ -13,7 +13,7 @@ class HaMarkdownElement extends UpdatingElement {
protected update(changedProps) {
super.update(changedProps);
if (this.content !== undefined) {
this._render();
this._render();
}
}

View File

@@ -54,7 +54,7 @@ class HaMarkdown extends LitElement {
}
ha-markdown-element code,
pre {
background-color: var(--markdown-code-background-color, #f6f8fa);
background-color: var(--markdown-code-background-color, none);
border-radius: 3px;
}
ha-markdown-element code {

View File

@@ -1,3 +1,5 @@
import "@material/mwc-icon-button";
import { mdiMenu } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
@@ -12,8 +14,7 @@ import { fireEvent } from "../common/dom/fire_event";
import { computeDomain } from "../common/entity/compute_domain";
import { subscribeNotifications } from "../data/persistent_notification";
import { HomeAssistant } from "../types";
import "./ha-icon-button";
import { mdiMenu } from "@mdi/js";
import "./ha-svg-icon";
@customElement("ha-menu-button")
class HaMenuButton extends LitElement {

View File

@@ -1,6 +1,12 @@
import { mdiBell, mdiCellphoneSettingsVariant } from "@mdi/js";
import "@material/mwc-icon-button";
import {
mdiBell,
mdiCellphoneSettingsVariant,
mdiMenuOpen,
mdiMenu,
mdiViewDashboard,
} from "@mdi/js";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "./ha-icon-button";
import "@polymer/paper-item/paper-icon-item";
import type { PaperIconItemElement } from "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item";
@@ -29,9 +35,9 @@ import {
getExternalConfig,
} from "../external_app/external_config";
import type { HomeAssistant, PanelInfo } from "../types";
import "./ha-svg-icon";
import "./ha-icon";
import "./ha-menu-button";
import "./ha-svg-icon";
import "./user/ha-user-badge";
const SHOW_AFTER_SPACER = ["config", "developer-tools", "hassio"];
@@ -153,13 +159,16 @@ class HaSidebar extends LitElement {
<div class="menu">
${!this.narrow
? html`
<ha-icon-button
aria-label=${hass.localize("ui.sidebar.sidebar_toggle")}
.icon=${hass.dockedSidebar === "docked"
? "hass:menu-open"
: "hass:menu"}
<mwc-icon-button
.label=${hass.localize("ui.sidebar.sidebar_toggle")}
@click=${this._toggleSidebar}
></ha-icon-button>
>
<ha-svg-icon
.path=${hass.dockedSidebar === "docked"
? mdiMenuOpen
: mdiMenu}
></ha-svg-icon>
</mwc-icon-button>
`
: ""}
<span class="title">Home Assistant</span>
@@ -174,14 +183,16 @@ class HaSidebar extends LitElement {
>
${this._renderPanel(
defaultPanel.url_path,
defaultPanel.icon || "hass:view-dashboard",
defaultPanel.title || hass.localize("panel.states")
defaultPanel.title || hass.localize("panel.states"),
defaultPanel.icon,
!defaultPanel.icon ? mdiViewDashboard : undefined
)}
${beforeSpacer.map((panel) =>
this._renderPanel(
panel.url_path,
hass.localize(`panel.${panel.title}`) || panel.title,
panel.icon,
hass.localize(`panel.${panel.title}`) || panel.title
undefined
)
)}
<div class="spacer" disabled></div>
@@ -189,8 +200,9 @@ class HaSidebar extends LitElement {
${afterSpacer.map((panel) =>
this._renderPanel(
panel.url_path,
hass.localize(`panel.${panel.title}`) || panel.title,
panel.icon,
hass.localize(`panel.${panel.title}`) || panel.title
undefined
)
)}
${this._externalConfig && this._externalConfig.hasSettingsScreen
@@ -443,7 +455,12 @@ class HaSidebar extends LitElement {
fireEvent(this, "hass-toggle-menu");
}
private _renderPanel(urlPath, icon, title) {
private _renderPanel(
urlPath: string,
title: string | null,
icon?: string | null,
iconPath?: string | null
) {
return html`
<a
aria-role="option"
@@ -454,7 +471,12 @@ class HaSidebar extends LitElement {
@mouseleave=${this._itemMouseLeave}
>
<paper-icon-item>
<ha-icon slot="item-icon" .icon="${icon}"></ha-icon>
${iconPath
? html`<ha-svg-icon
slot="item-icon"
.path=${iconPath}
></ha-svg-icon>`
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
<span class="item-text">${title}</span>
</paper-icon-item>
</a>
@@ -496,13 +518,13 @@ class HaSidebar extends LitElement {
width: 256px;
}
.menu ha-icon-button {
.menu mwc-icon-button {
color: var(--sidebar-icon-color);
}
:host([expanded]) .menu ha-icon-button {
:host([expanded]) .menu mwc-icon-button {
margin-right: 23px;
}
:host([expanded][_rtl]) .menu ha-icon-button {
:host([expanded][_rtl]) .menu mwc-icon-button {
margin-right: 0px;
margin-left: 23px;
}
@@ -714,7 +736,7 @@ class HaSidebar extends LitElement {
font-weight: 500;
}
:host([_rtl]) .menu ha-icon-button {
:host([_rtl]) .menu mwc-icon-button {
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
}

View File

@@ -1,15 +1,7 @@
import { ripple } from "@material/mwc-ripple/ripple-directive";
import "@material/mwc-switch";
import type { Switch } from "@material/mwc-switch";
import { style } from "@material/mwc-switch/mwc-switch-css";
import {
css,
CSSResult,
customElement,
html,
property,
query,
} from "lit-element";
import { css, CSSResult, customElement, property } from "lit-element";
import { forwardHaptic } from "../data/haptics";
import { Constructor } from "../types";
@@ -22,18 +14,12 @@ export class HaSwitch extends MwcSwitch {
// Do not add haptic when a user is required to press save.
@property({ type: Boolean }) public haptic = false;
@query("slot") private _slot!: HTMLSlotElement;
protected firstUpdated() {
super.firstUpdated();
this.style.setProperty(
"--mdc-theme-secondary",
"var(--switch-checked-color)"
);
this.classList.toggle(
"slotted",
Boolean(this._slot.assignedNodes().length)
);
this.addEventListener("change", () => {
if (this.haptic) {
forwardHaptic("light");
@@ -41,40 +27,10 @@ export class HaSwitch extends MwcSwitch {
});
}
protected render() {
return html`
<div class="mdc-switch">
<div class="mdc-switch__track"></div>
<div
class="mdc-switch__thumb-underlay"
.ripple="${ripple({
interactionNode: this,
})}"
>
<div class="mdc-switch__thumb">
<input
type="checkbox"
id="basic-switch"
class="mdc-switch__native-control"
role="switch"
@change="${this._haChangeHandler}"
/>
</div>
</div>
</div>
<label for="basic-switch"><slot></slot></label>
`;
}
protected static get styles(): CSSResult[] {
return [
style,
css`
:host {
display: flex;
flex-direction: row;
align-items: center;
}
.mdc-switch.mdc-switch--checked .mdc-switch__thumb {
background-color: var(--switch-checked-button-color);
border-color: var(--switch-checked-button-color);
@@ -91,18 +47,9 @@ export class HaSwitch extends MwcSwitch {
background-color: var(--switch-unchecked-track-color);
border-color: var(--switch-unchecked-track-color);
}
:host(.slotted) .mdc-switch {
margin-right: 24px;
}
`,
];
}
private _haChangeHandler(e: Event) {
this.mdcFoundation.handleChange(e);
// catch "click" event and sync properties
this.checked = this.formElement.checked;
}
}
declare global {

View File

@@ -1,46 +0,0 @@
/*
Wrapper for paper-textarea.
paper-textarea crashes on iOS when created programmatically. This only impacts
our automation and script editors as they are using Preact. Polymer is using
template elements and does not have this issue.
paper-textarea issue: https://github.com/PolymerElements/paper-input/issues/556
WebKit issue: https://bugs.webkit.org/show_bug.cgi?id=174629
*/
import "@polymer/paper-input/paper-textarea";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
class HaTextarea extends PolymerElement {
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<paper-textarea
label="[[label]]"
placeholder="[[placeholder]]"
value="{{value}}"
></paper-textarea>
`;
}
static get properties() {
return {
name: String,
label: String,
placeholder: String,
value: {
type: String,
notify: true,
},
};
}
}
customElements.define("ha-textarea", HaTextarea);

View File

@@ -1,5 +1,11 @@
import { HomeAssistant } from "../types";
export interface AuthUrlSearchParams {
client_id?: string;
redirect_uri?: string;
state?: string;
}
export interface AuthProvider {
name: string;
id: string;

View File

@@ -5,7 +5,7 @@ import { HomeAssistant } from "../types";
import { DataEntryFlowProgress, DataEntryFlowStep } from "./data_entry_flow";
import { domainToName } from "./integration";
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf"];
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf", "discovery"];
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {

View File

@@ -12,6 +12,11 @@ export interface ConfigUpdateValues {
internal_url?: string | null;
}
export interface CheckConfigResult {
result: "valid" | "invalid";
errors: string | null;
}
export const saveCoreConfig = (
hass: HomeAssistant,
values: Partial<ConfigUpdateValues>
@@ -25,3 +30,6 @@ export const detectCoreConfig = (hass: HomeAssistant) =>
hass.callWS<Partial<ConfigUpdateValues>>({
type: "config/core/detect",
});
export const checkCoreConfig = (hass: HomeAssistant) =>
hass.callApi<CheckConfigResult>("POST", "config/core/check_config");

View File

@@ -4,6 +4,20 @@ import { hassioApiResultExtractor, HassioResponse } from "./common";
export type HassioHomeAssistantInfo = any;
export type HassioSupervisorInfo = any;
export type HassioInfo = {
arch: string;
channel: string;
docker: string;
hassos?: string;
homeassistant: string;
hostname: string;
logging: string;
maching: string;
supervisor: string;
supported_arch: string[];
timezone: string;
};
export type HassioPanelInfo = PanelInfo<
| undefined
| {
@@ -38,6 +52,12 @@ export const fetchHassioSupervisorInfo = async (hass: HomeAssistant) => {
);
};
export const fetchHassioInfo = async (hass: HomeAssistant) => {
return hassioApiResultExtractor(
await hass.callApi<HassioResponse<HassioInfo>>("GET", "hassio/info")
);
};
export const fetchHassioLogs = async (
hass: HomeAssistant,
provider: string

View File

@@ -1,7 +1,67 @@
import { HomeAssistant } from "../types";
export interface LogbookEntry {
when: string;
name: string;
message: string;
entity_id?: string;
domain: string;
context_user_id?: string;
}
const DATA_CACHE: {
[cacheKey: string]: { [entityId: string]: Promise<LogbookEntry[]> };
} = {};
export const getLogbookData = (
hass: HomeAssistant,
startDate: string,
endDate: string,
entityId?: string
) => {
const ALL_ENTITIES = "*";
if (!entityId) {
entityId = ALL_ENTITIES;
}
const cacheKey = `${startDate}${endDate}`;
if (!DATA_CACHE[cacheKey]) {
DATA_CACHE[cacheKey] = {};
}
if (DATA_CACHE[cacheKey][entityId]) {
return DATA_CACHE[cacheKey][entityId];
}
if (entityId !== ALL_ENTITIES && DATA_CACHE[cacheKey][ALL_ENTITIES]) {
return DATA_CACHE[cacheKey][ALL_ENTITIES].then((entities) =>
entities.filter((entity) => entity.entity_id === entityId)
);
}
DATA_CACHE[cacheKey][entityId] = getLogbookDataFromServer(
hass,
startDate,
endDate,
entityId !== ALL_ENTITIES ? entityId : undefined
).then((entries) => entries.reverse());
return DATA_CACHE[cacheKey][entityId];
};
const getLogbookDataFromServer = async (
hass: HomeAssistant,
startDate: string,
endDate: string,
entityId?: string
) => {
const url = `logbook/${startDate}?end_time=${endDate}${
entityId ? `&entity=${entityId}` : ""
}`;
return hass.callApi<LogbookEntry[]>("GET", url);
};
export const clearLogbookCache = (startDate, endDate) => {
DATA_CACHE[`${startDate}${endDate}`] = {};
};

View File

@@ -51,7 +51,7 @@ export const onboardCoreConfigStep = (hass: HomeAssistant) =>
export const onboardIntegrationStep = (
hass: HomeAssistant,
params: { client_id: string }
params: { client_id: string; redirect_uri: string }
) =>
hass.callApi<OnboardingIntegrationStepResponse>(
"POST",

View File

@@ -11,6 +11,7 @@ import {
} from "lit-element";
import "../../components/dialog/ha-paper-dialog";
import "../../components/ha-switch";
import "../../components/ha-formfield";
import type { HaSwitch } from "../../components/ha-switch";
import {
getConfigEntrySystemOptions,
@@ -82,13 +83,8 @@ class DialogConfigEntrySystemOptions extends LitElement {
? html` <div class="error">${this._error}</div> `
: ""}
<div class="form">
<ha-switch
.checked=${!this._disableNewEntities}
@change=${this._disableNewEntitiesChanged}
.disabled=${this._submitting}
>
<div>
<p>
<ha-formfield
.label=${html`<p>
${this.hass.localize(
"ui.dialogs.config_entry_system_options.enable_new_entities_label"
)}
@@ -101,9 +97,15 @@ class DialogConfigEntrySystemOptions extends LitElement {
`component.${this._params.entry.domain}.title`
) || this._params.entry.domain
)}
</p>
</div>
</ha-switch>
</p>`}
>
<ha-switch
.checked=${!this._disableNewEntities}
@change=${this._disableNewEntitiesChanged}
.disabled=${this._submitting}
>
</ha-switch>
</ha-formfield>
</div>
`}
</paper-dialog-scrollable>
@@ -172,9 +174,6 @@ class DialogConfigEntrySystemOptions extends LitElement {
padding-bottom: 24px;
color: var(--primary-text-color);
}
p {
margin: 0;
}
.secondary {
color: var(--secondary-text-color);
}

View File

@@ -14,8 +14,7 @@ import {
PropertyValues,
TemplateResult,
} from "lit-element";
import "../../components/dialog/ha-paper-dialog";
import type { HaPaperDialog } from "../../components/dialog/ha-paper-dialog";
import "../../components/ha-dialog";
import "../../components/ha-form/ha-form";
import "../../components/ha-markdown";
import {
@@ -27,7 +26,6 @@ import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../data/device_registry";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
@@ -90,7 +88,6 @@ class DataEntryFlowDialog extends LitElement {
// We only load the handlers once
if (this._handlers === undefined) {
this._loading = true;
this.updateComplete.then(() => this._scheduleCenterDialog());
try {
this._handlers = await params.flowConfig.getFlowHandlers(this.hass);
} finally {
@@ -98,7 +95,6 @@ class DataEntryFlowDialog extends LitElement {
}
}
await this.updateComplete;
this._scheduleCenterDialog();
return;
}
@@ -115,9 +111,6 @@ class DataEntryFlowDialog extends LitElement {
this._processStep(step);
this._loading = false;
// When the flow changes, center the dialog.
// Don't do it on each step or else the dialog keeps bouncing.
this._scheduleCenterDialog();
}
protected render(): TemplateResult {
@@ -126,80 +119,84 @@ class DataEntryFlowDialog extends LitElement {
}
return html`
<ha-paper-dialog
with-backdrop
opened
modal
@opened-changed=${this._openedChanged}
<ha-dialog
open
@closing=${this._close}
scrimClickAction
escapeKeyAction
hideActions
>
${this._loading || (this._step === null && this._handlers === undefined)
? html`
<step-flow-loading
.label=${this.hass.localize(
"ui.panel.config.integrations.config_flow.loading_first_time"
)}
></step-flow-loading>
`
: this._step === undefined
? // When we are going to next step, we render 1 round of empty
// to reset the element.
""
: html`
<ha-icon-button
aria-label=${this.hass.localize(
"ui.panel.config.integrations.config_flow.dismiss"
)}
icon="hass:close"
dialog-dismiss
></ha-icon-button>
${this._step === null
? // Show handler picker
html`
<step-flow-pick-handler
.flowConfig=${this._params.flowConfig}
.hass=${this.hass}
.handlers=${this._handlers}
.showAdvanced=${this._params.showAdvanced}
></step-flow-pick-handler>
`
: this._step.type === "form"
? html`
<step-flow-form
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-form>
`
: this._step.type === "external"
? html`
<step-flow-external
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-external>
`
: this._step.type === "abort"
? html`
<step-flow-abort
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-abort>
`
: this._devices === undefined || this._areas === undefined
? // When it's a create entry result, we will fetch device & area registry
html` <step-flow-loading></step-flow-loading> `
: html`
<step-flow-create-entry
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
.devices=${this._devices}
.areas=${this._areas}
></step-flow-create-entry>
`}
`}
</ha-paper-dialog>
<div>
${this._loading ||
(this._step === null && this._handlers === undefined)
? html`
<step-flow-loading
.label=${this.hass.localize(
"ui.panel.config.integrations.config_flow.loading_first_time"
)}
></step-flow-loading>
`
: this._step === undefined
? // When we are going to next step, we render 1 round of empty
// to reset the element.
""
: html`
<ha-icon-button
aria-label=${this.hass.localize(
"ui.panel.config.integrations.config_flow.dismiss"
)}
icon="hass:close"
dialogAction="close"
></ha-icon-button>
${this._step === null
? // Show handler picker
html`
<step-flow-pick-handler
.flowConfig=${this._params.flowConfig}
.hass=${this.hass}
.handlers=${this._handlers}
.showAdvanced=${this._params.showAdvanced}
></step-flow-pick-handler>
`
: this._step.type === "form"
? html`
<step-flow-form
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-form>
`
: this._step.type === "external"
? html`
<step-flow-external
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-external>
`
: this._step.type === "abort"
? html`
<step-flow-abort
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-abort>
`
: this._devices === undefined || this._areas === undefined
? // When it's a create entry result, we will fetch device & area registry
html` <step-flow-loading></step-flow-loading> `
: html`
<step-flow-create-entry
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
.devices=${this._devices}
.areas=${this._areas}
></step-flow-create-entry>
`}
`}
</div>
</ha-dialog>
`;
}
@@ -225,18 +222,6 @@ class DataEntryFlowDialog extends LitElement {
this._areas = [];
}
}
if (changedProps.has("_devices") && this._dialog) {
this._scheduleCenterDialog();
}
}
private _scheduleCenterDialog() {
setTimeout(() => this._dialog.center(), 0);
}
private get _dialog(): HaPaperDialog {
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
}
private async _fetchDevices(configEntryId) {
@@ -310,16 +295,13 @@ class DataEntryFlowDialog extends LitElement {
}
}
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
// Closed dialog by clicking on the overlay
if (!ev.detail.value) {
if (this._step) {
this._flowDone();
} else if (this._step === null) {
// Flow aborted during picking flow
this._step = undefined;
this._params = undefined;
}
private _close(): void {
if (this._step) {
this._flowDone();
} else if (this._step === null) {
// Flow aborted during picking flow
this._step = undefined;
this._params = undefined;
}
}
@@ -327,18 +309,14 @@ class DataEntryFlowDialog extends LitElement {
return [
haStyleDialog,
css`
ha-paper-dialog {
max-width: 600px;
}
ha-paper-dialog > * {
margin: 0;
display: block;
padding: 0;
ha-dialog {
--dialog-content-padding: 0;
}
ha-icon-button {
display: inline-block;
padding: 8px;
float: right;
padding: 16px;
position: absolute;
top: 0;
right: 0;
}
`,
];

View File

@@ -42,6 +42,8 @@ class StepFlowPickHandler extends LitElement {
private _width?: number;
private _height?: number;
private _getHandlers = memoizeOne(
(h: string[], filter?: string, _localize?: LocalizeFunc) => {
const handlers: HandlerObj[] = h.map((handler) => {
@@ -82,7 +84,10 @@ class StepFlowPickHandler extends LitElement {
@value-changed=${this._filterChanged}
></search-input>
<div
style=${styleMap({ width: `${this._width}px` })}
style=${styleMap({
width: `${this._width}px`,
height: `${this._height}px`,
})}
class=${classMap({ advanced: Boolean(this.showAdvanced) })}
>
${handlers.map(
@@ -139,13 +144,20 @@ class StepFlowPickHandler extends LitElement {
protected updated(changedProps) {
super.updated(changedProps);
// Store the width so that when we search, box doesn't jump
// Store the width and height so that when we search, box doesn't jump
const div = this.shadowRoot!.querySelector("div")!;
if (!this._width) {
const width = this.shadowRoot!.querySelector("div")!.clientWidth;
const width = div.clientWidth;
if (width) {
this._width = width;
}
}
if (!this._height) {
const height = div.clientHeight;
if (height) {
this._height = height;
}
}
}
private async _filterChanged(e) {
@@ -166,8 +178,8 @@ class StepFlowPickHandler extends LitElement {
configFlowContentStyles,
css`
img {
max-width: 40px;
max-height: 40px;
width: 40px;
height: 40px;
}
search-input {
display: block;
@@ -180,12 +192,12 @@ class StepFlowPickHandler extends LitElement {
overflow: auto;
max-height: 600px;
}
@media all and (max-height: 1px) {
@media all and (max-height: 900px) {
div {
max-height: calc(100vh - 205px);
max-height: calc(100vh - 134px);
}
div.advanced {
max-height: calc(100vh - 300px);
max-height: calc(100vh - 250px);
}
}
paper-icon-item {

View File

@@ -2,8 +2,21 @@ import { css } from "lit-element";
export const configFlowContentStyles = css`
h2 {
margin-top: 24px;
margin: 24px 0 0;
padding: 0 24px;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-family: var(
--mdc-typography-headline6-font-family,
var(--mdc-typography-font-family, Roboto, sans-serif)
);
font-size: var(--mdc-typography-headline6-font-size, 1.25rem);
line-height: var(--mdc-typography-headline6-line-height, 2rem);
font-weight: var(--mdc-typography-headline6-font-weight, 500);
letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em);
text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
text-transform: var(--mdc-typography-headline6-text-transform, inherit);
box-sizing: border-box;
}
.content {

View File

@@ -66,7 +66,7 @@ class DialogDeviceRegistryDetail extends LitElement {
<paper-input
.value=${this._nameByUser}
@value-changed=${this._nameChanged}
.label=${this.hass.localize("ui.dialogs.devices.name")}
.label=${this.hass.localize("ui.panel.config.devices.name")}
.placeholder=${device.name || ""}
.disabled=${this._submitting}
></paper-input>

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button/mwc-button";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-input/paper-input";
import {
css,
@@ -11,7 +10,7 @@ import {
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "../../components/dialog/ha-paper-dialog";
import "../../components/ha-dialog";
import "../../components/ha-switch";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
@@ -41,21 +40,17 @@ class DialogBox extends LitElement {
const confirmPrompt = this._params.confirmation || this._params.prompt;
return html`
<ha-paper-dialog
with-backdrop
opened
modal
@opened-changed="${this._openedChanged}"
<ha-dialog
open
scrimClickAction
escapeKeyAction
@close=${this._close}
.heading=${this._params.title
? this._params.title
: this._params.confirmation &&
this.hass.localize("ui.dialogs.generic.default_confirmation_title")}
>
<h2>
${this._params.title
? this._params.title
: this._params.confirmation &&
this.hass.localize(
"ui.dialogs.generic.default_confirmation_title"
)}
</h2>
<paper-dialog-scrollable>
<div>
${this._params.text
? html`
<p
@@ -83,23 +78,21 @@ class DialogBox extends LitElement {
></paper-input>
`
: ""}
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
${confirmPrompt &&
html`
<mwc-button @click="${this._dismiss}">
${this._params.dismissText
? this._params.dismissText
: this.hass.localize("ui.dialogs.generic.cancel")}
</mwc-button>
`}
<mwc-button @click="${this._confirm}">
${this._params.confirmText
? this._params.confirmText
: this.hass.localize("ui.dialogs.generic.ok")}
</mwc-button>
</div>
</ha-paper-dialog>
${confirmPrompt &&
html`
<mwc-button @click=${this._dismiss} slot="secondaryAction">
${this._params.dismissText
? this._params.dismissText
: this.hass.localize("ui.dialogs.generic.cancel")}
</mwc-button>
`}
<mwc-button @click=${this._confirm} slot="primaryAction">
${this._params.confirmText
? this._params.confirmText
: this.hass.localize("ui.dialogs.generic.ok")}
</mwc-button>
</ha-dialog>
`;
}
@@ -127,10 +120,8 @@ class DialogBox extends LitElement {
this._dismiss();
}
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!(ev.detail as any).value) {
this._params = undefined;
}
private _close(): void {
this._params = undefined;
}
static get styles(): CSSResult[] {
@@ -141,15 +132,6 @@ class DialogBox extends LitElement {
pointer-events: initial !important;
cursor: initial !important;
}
ha-paper-dialog {
min-width: 400px;
max-width: 500px;
}
@media (max-width: 400px) {
ha-paper-dialog {
min-width: initial;
}
}
a {
color: var(--primary-color);
}
@@ -165,6 +147,10 @@ class DialogBox extends LitElement {
.secondary {
color: var(--secondary-text-color);
}
ha-dialog {
/* Place above other dialogs */
--dialog-z-index: 104;
}
`,
];
}

View File

@@ -86,7 +86,7 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
<div class="main-title" main-title="" on-click="enlarge">
[[_computeStateName(stateObj)]]
</div>
<template is="dom-if" if="[[_computeConfig(hass)]]">
<template is="dom-if" if="[[hass.user.is_admin]]">
<ha-icon-button
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
icon="hass:settings"
@@ -219,10 +219,6 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
return stateObj ? computeStateName(stateObj) : "";
}
_computeConfig(hass) {
return hass.user.is_admin && isComponentLoaded(hass, "config");
}
_computeEdit(hass, stateObj) {
const domain = this._computeDomain(stateObj);
return (

View File

@@ -1,113 +0,0 @@
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../components/dialog/ha-paper-dialog";
import type { HaPaperDialog } from "../../components/dialog/ha-paper-dialog";
import { fetchZHADevice, ZHADevice } from "../../data/zha";
import "../../panels/config/zha/zha-device-card";
import type { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import type { ZHADeviceInfoDialogParams } from "./show-dialog-zha-device-info";
@customElement("dialog-zha-device-info")
class DialogZHADeviceInfo extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _params?: ZHADeviceInfoDialogParams;
@property() private _error?: string;
@property() private _device?: ZHADevice;
public async showDialog(params: ZHADeviceInfoDialogParams): Promise<void> {
this._params = params;
this._device = await fetchZHADevice(this.hass, params.ieee);
await this.updateComplete;
this._dialog.open();
}
protected render(): TemplateResult {
if (!this._params || !this._device) {
return html``;
}
return html`
<ha-paper-dialog
with-backdrop
opened
@opened-changed=${this._openedChanged}
>
${this._error
? html` <div class="error">${this._error}</div> `
: html`
<zha-device-card
class="card"
.hass=${this.hass}
.device=${this._device}
@zha-device-removed=${this._onDeviceRemoved}
.showEntityDetail=${false}
.showActions="${this._device.device_type !== "Coordinator"}"
></zha-device-card>
`}
</ha-paper-dialog>
`;
}
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!ev.detail.value) {
this._params = undefined;
this._error = undefined;
this._device = undefined;
}
}
private _onDeviceRemoved(): void {
this._closeDialog();
}
private get _dialog(): HaPaperDialog {
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
}
private _closeDialog() {
this._dialog.close();
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
ha-paper-dialog > * {
margin: 0;
display: block;
padding: 0;
}
.card {
box-sizing: border-box;
display: flex;
flex: 1 0 300px;
min-width: 0;
max-width: 600px;
word-wrap: break-word;
}
.error {
color: var(--google-red-500);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-zha-device-info": DialogZHADeviceInfo;
}
}

View File

@@ -1,21 +0,0 @@
import { fireEvent } from "../../common/dom/fire_event";
export interface ZHADeviceInfoDialogParams {
ieee: string;
}
export const loadZHADeviceInfoDialog = () =>
import(
/* webpackChunkName: "dialog-zha-device-info" */ "./dialog-zha-device-info"
);
export const showZHADeviceInfoDialog = (
element: HTMLElement,
zhaDeviceInfoParams: ZHADeviceInfoDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-zha-device-info",
dialogImport: loadZHADeviceInfoDialog,
dialogParams: zhaDeviceInfoParams,
});
};

View File

@@ -1,3 +1,5 @@
// Compat needs to be first import
import "../resources/compatibility";
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../auth/ha-authorize";

View File

@@ -1,3 +1,5 @@
// Compat needs to be first import
import "../resources/compatibility";
import {
Auth,
Connection,

View File

@@ -1,3 +1,4 @@
import "../resources/compatibility";
import { PolymerElement } from "@polymer/polymer";
import { fireEvent } from "../common/dom/fire_event";
import { loadJS } from "../common/dom/load_resource";
@@ -17,12 +18,9 @@ let es5Loaded: Promise<unknown> | undefined;
window.loadES5Adapter = () => {
if (!es5Loaded) {
es5Loaded = Promise.all([
loadJS(
`${__STATIC_PATH__}polyfills/custom-elements-es5-adapter.js`
).catch(),
import(/* webpackChunkName: "compat" */ "./compatibility"),
]);
es5Loaded = loadJS(
`${__STATIC_PATH__}polyfills/custom-elements-es5-adapter.js`
).catch(); // Swallow errors as it raises errors on old browsers.
}
return es5Loaded;
};

View File

@@ -1,3 +1,5 @@
// Compat needs to be first import
import "../resources/compatibility";
import "../onboarding/ha-onboarding";
import "../resources/ha-style";
import "../resources/roboto";

View File

@@ -2,7 +2,7 @@
if (navigator.userAgent.indexOf("Android") === -1 &&
navigator.userAgent.indexOf("CrOS") === -1) {
function _pf(src, type) {
const el = document.createElement("link");
var el = document.createElement("link");
el.rel = "preload";
el.as = "font";
el.type = "font/woff2";

View File

@@ -47,15 +47,11 @@
(function() {
// Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5Compatibility %>").then(function() {
System.import("<%= es5PageJS %>");
});
System.import("<%= es5PageJS %>");
}
<% } else { %>
_ls("<%= es5Compatibility %>");
_ls("<%= es5PageJS %>");
<% } %>
}

View File

@@ -58,34 +58,36 @@
window.customPanelJS = "<%= latestCustomPanelJS %>";
window.latestJS = true;
</script>
<script>
{% for extra_module in extra_modules -%}
<script type="module" crossorigin="use-credentials" src="{{ extra_module }}"></script>
import("{{ extra_module }}");
{% endfor -%}
</script>
<script>
(function() {
if (!window.latestJS) {
window.customPanelJS = "<%= es5CustomPanelJS %>";
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5Compatibility %>").then(function() {
return System.import("<%= es5CoreJS %>");
}).then(function() {
// Although core and app can load in any order, we need to
// force loading core first because it contains polyfills
return System.import("<%= es5CoreJS %>").then(function() {
System.import("<%= es5AppJS %>");
});
}
<% } else { %>
_ls("<%= es5Compatibility %>");
_ls("<%= es5CoreJS %>");
_ls("<%= es5AppJS %>");
<% } %>
{% for extra_script in extra_js_es5 -%}
_ls("{{ extra_script }}");
{% endfor -%}
}
})();
</script>
<script>
if (!window.latestJS) {
{% for extra_script in extra_js_es5 -%}
_ls("{{ extra_script }}");
{% endfor -%}
}
</script>
{% for extra_url in extra_urls -%}

View File

@@ -49,15 +49,11 @@
(function() {
// Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5Compatibility %>").then(function() {
System.import("<%= es5PageJS %>");
});
System.import("<%= es5PageJS %>");
}
<% } else { %>
_ls("<%= es5Compatibility %>");
_ls("<%= es5PageJS %>");
<% } %>
}

View File

@@ -91,6 +91,20 @@ class PartialPanelResolver extends HassRouterPage {
private _waitForStart = false;
private _disconnectedPanel?: ChildNode;
private _hiddenTimeout?: number;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
document.addEventListener(
"visibilitychange",
() => this._handleVisibilityChange(),
false
);
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
@@ -141,6 +155,27 @@ class PartialPanelResolver extends HassRouterPage {
}
}
private _handleVisibilityChange() {
if (document.hidden) {
this._hiddenTimeout = window.setTimeout(() => {
this._hiddenTimeout = undefined;
if (this.lastChild) {
this._disconnectedPanel = this.lastChild;
this.removeChild(this.lastChild);
}
}, 300000);
} else {
if (this._hiddenTimeout) {
clearTimeout(this._hiddenTimeout);
this._hiddenTimeout = undefined;
}
if (this._disconnectedPanel) {
this.appendChild(this._disconnectedPanel);
this._disconnectedPanel = undefined;
}
}
}
private async _updateRoutes(oldPanels?: HomeAssistant["panels"]) {
this.routerOptions = getRoutes(this.hass.panels);

View File

@@ -1,9 +1,9 @@
import {
Auth,
createConnection,
genClientId,
getAuth,
subscribeConfig,
genClientId,
} from "home-assistant-js-websocket";
import {
customElement,
@@ -14,12 +14,12 @@ import {
} from "lit-element";
import { HASSDomEvent } from "../common/dom/fire_event";
import { subscribeOne } from "../common/util/subscribe-one";
import { hassUrl } from "../data/auth";
import { hassUrl, AuthUrlSearchParams } from "../data/auth";
import {
fetchOnboardingOverview,
OnboardingResponses,
OnboardingStep,
ValidOnboardingStep,
onboardIntegrationStep,
} from "../data/onboarding";
import { subscribeUser } from "../data/ws-user";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
@@ -28,19 +28,28 @@ import { HomeAssistant } from "../types";
import { registerServiceWorker } from "../util/register-service-worker";
import "./onboarding-create-user";
import "./onboarding-loading";
import { extractSearchParamsObject } from "../common/url/search-params";
interface OnboardingEvent<T extends ValidOnboardingStep> {
type: T;
result: OnboardingResponses[T];
}
type OnboardingEvent =
| {
type: "user";
result: OnboardingResponses["user"];
}
| {
type: "core_config";
result: OnboardingResponses["core_config"];
}
| {
type: "integration";
};
declare global {
interface HASSDomEvents {
"onboarding-step": OnboardingEvent<ValidOnboardingStep>;
"onboarding-step": OnboardingEvent;
}
interface GlobalEventHandlersEventMap {
"onboarding-step": HASSDomEvent<OnboardingEvent<ValidOnboardingStep>>;
"onboarding-step": HASSDomEvent<OnboardingEvent>;
}
}
@@ -150,9 +159,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
}
}
private async _handleStepDone(
ev: HASSDomEvent<OnboardingEvent<ValidOnboardingStep>>
) {
private async _handleStepDone(ev: HASSDomEvent<OnboardingEvent>) {
const stepResult = ev.detail;
this._steps = this._steps!.map((step) =>
step.step === stepResult.type ? { ...step, done: true } : step
@@ -176,9 +183,41 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
} else if (stepResult.type === "core_config") {
// We do nothing
} else if (stepResult.type === "integration") {
const result = stepResult.result as OnboardingResponses["integration"];
this._loading = true;
// Determine if oauth redirect has been provided
const externalAuthParams = extractSearchParamsObject() as AuthUrlSearchParams;
const authParams =
externalAuthParams.client_id && externalAuthParams.redirect_uri
? externalAuthParams
: {
client_id: genClientId(),
redirect_uri: `${location.protocol}//${location.host}/?auth_callback=1`,
state: btoa(
JSON.stringify({
hassUrl: `${location.protocol}//${location.host}`,
clientId: genClientId(),
})
),
};
let result: OnboardingResponses["integration"];
try {
result = await onboardIntegrationStep(this.hass!, {
client_id: authParams.client_id!,
redirect_uri: authParams.redirect_uri!,
});
} catch (err) {
this.hass!.connection.close();
await this.hass!.auth.revoke();
alert(`Unable to finish onboarding: ${err.message}`);
document.location.assign("/?");
return;
}
// If we don't close the connection manually, the connection will be
// closed when we navigate away from the page. Firefox allows JS to
// continue to execute, and so HAWS will automatically reconnect once
@@ -191,17 +230,17 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
// Revoke current auth token.
await this.hass!.auth.revoke();
const state = btoa(
JSON.stringify({
hassUrl: `${location.protocol}//${location.host}`,
clientId: genClientId(),
})
);
document.location.assign(
`/?auth_callback=1&code=${encodeURIComponent(
result.auth_code
)}&state=${state}`
);
// Build up the url to redirect to
let redirectUrl = authParams.redirect_uri!;
redirectUrl +=
(redirectUrl.includes("?") ? "&" : "?") +
`code=${encodeURIComponent(result.auth_code)}`;
if (authParams.state) {
redirectUrl += `&state=${encodeURIComponent(authParams.state)}`;
}
document.location.assign(redirectUrl);
}
}

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button/mwc-button";
import { genClientId } from "home-assistant-js-websocket";
import {
css,
CSSResult,
@@ -21,7 +20,6 @@ import {
} from "../data/config_flow";
import { DataEntryFlowProgress } from "../data/data_entry_flow";
import { domainToName } from "../data/integration";
import { onboardIntegrationStep } from "../data/onboarding";
import {
loadConfigFlowDialog,
showConfigFlowDialog,
@@ -169,12 +167,8 @@ class OnboardingIntegrations extends LitElement {
}
private async _finish() {
const result = await onboardIntegrationStep(this.hass, {
client_id: genClientId(),
});
fireEvent(this, "onboarding-step", {
type: "integration",
result,
});
}

View File

@@ -1,9 +1,12 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "../../../../components/ha-icon-button";
import "@material/mwc-list/mwc-list-item";
import "@material/mwc-icon-button";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-svg-icon";
import { mdiDotsVertical, mdiArrowUp, mdiArrowDown } from "@mdi/js";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-menu-button/paper-menu-button";
import {
css,
CSSResult,
@@ -96,56 +99,64 @@ export default class HaAutomationActionRow extends LitElement {
<div class="card-menu">
${this.index !== 0
? html`
<ha-icon-button
icon="hass:arrow-up"
<mwc-icon-button
.title=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_up"
)}
@click=${this._moveUp}
></ha-icon-button>
>
<ha-svg-icon path=${mdiArrowUp}></ha-svg-icon>
</mwc-icon-button>
`
: ""}
${this.index !== this.totalActions - 1
? html`
<ha-icon-button
icon="hass:arrow-down"
<mwc-icon-button
.title=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
.label=${this.hass.localize(
"ui.panel.config.automation.editor.move_down"
)}
@click=${this._moveDown}
></ha-icon-button>
>
<ha-svg-icon path=${mdiArrowDown}></ha-svg-icon>
</mwc-icon-button>
`
: ""}
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
close-on-activate
>
<ha-icon-button
icon="hass:dots-vertical"
slot="dropdown-trigger"
></ha-icon-button>
<paper-listbox slot="dropdown-content">
<paper-item
@tap=${this._switchYamlMode}
.disabled=${selected === -1}
>
${yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</paper-item>
<paper-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</paper-item>
<paper-item @tap=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</paper-item>
</paper-listbox>
</paper-menu-button>
<ha-button-menu corner="BOTTOM_START">
<mwc-icon-button
slot="trigger"
.title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")}
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item
@tap=${this._switchYamlMode}
.disabled=${selected === -1}
>
${yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item @tap=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</div>
${yamlMode
? html`
@@ -259,14 +270,17 @@ export default class HaAutomationActionRow extends LitElement {
top: 0;
right: 0;
z-index: 3;
color: var(--primary-text-color);
--mdc-theme-text-primary-on-background: var(--primary-text-color);
}
.rtl .card-menu {
right: auto;
left: 0;
}
.card-menu paper-item {
cursor: pointer;
ha-button-menu {
margin: 8px;
}
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
}
`;
}

View File

@@ -1,4 +1,5 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-input/paper-textarea";
import { customElement, LitElement, property } from "lit-element";
import { html } from "lit-html";
import { WaitAction } from "../../../../../data/script";
@@ -19,7 +20,7 @@ export class HaWaitAction extends LitElement implements ActionElement {
const { wait_template, timeout } = this.action;
return html`
<ha-textarea
<paper-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.wait_template.wait_template"
)}
@@ -27,7 +28,7 @@ export class HaWaitAction extends LitElement implements ActionElement {
.value=${wait_template}
@value-changed=${this._valueChanged}
dir="ltr"
></ha-textarea>
></paper-textarea>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.actions.type.wait_template.timeout"

View File

@@ -1,6 +1,8 @@
import "../../../../components/ha-icon-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-menu-button/paper-menu-button";
import "@material/mwc-list/mwc-list-item";
import "../../../../components/ha-button-menu";
import { mdiDotsVertical } from "@mdi/js";
import {
css,
CSSResult,
@@ -61,39 +63,33 @@ export default class HaAutomationConditionRow extends LitElement {
<ha-card>
<div class="card-content">
<div class="card-menu">
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
close-on-activate
>
<ha-icon-button
icon="hass:dots-vertical"
slot="dropdown-trigger"
></ha-icon-button>
<paper-listbox slot="dropdown-content">
<paper-item @tap=${this._switchYamlMode}>
${this._yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</paper-item>
<paper-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.duplicate"
)}
</paper-item>
<paper-item @tap=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.conditions.delete"
)}
</paper-item>
</paper-listbox>
</paper-menu-button>
<ha-button-menu corner="BOTTOM_START">
<mwc-icon-button
.title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")}
slot="trigger"
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon
></mwc-icon-button>
<mwc-list-item @tap=${this._switchYamlMode}>
${this._yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item @tap=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</div>
<ha-automation-condition-editor
.yamlMode=${this._yamlMode}
@@ -129,14 +125,17 @@ export default class HaAutomationConditionRow extends LitElement {
top: 0;
right: 0;
z-index: 3;
color: var(--primary-text-color);
--mdc-theme-text-primary-on-background: var(--primary-text-color);
}
.rtl .card-menu {
right: auto;
left: 0;
}
.card-menu paper-item {
cursor: pointer;
ha-button-menu {
margin: 8px;
}
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
}
`;
}

View File

@@ -2,7 +2,7 @@ import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-textarea";
import "@polymer/paper-input/paper-textarea";
import { NumericStateCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { handleChangeEvent } from "../ha-automation-condition-row";
@@ -45,7 +45,7 @@ export default class HaNumericStateCondition extends LitElement {
.value=${below}
@value-changed=${this._valueChanged}
></paper-input>
<ha-textarea
<paper-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.numeric_state.value_template"
)}
@@ -53,7 +53,7 @@ export default class HaNumericStateCondition extends LitElement {
.value=${value_template}
@value-changed=${this._valueChanged}
dir="ltr"
></ha-textarea>
></paper-textarea>
`;
}

View File

@@ -1,5 +1,5 @@
import { customElement, html, LitElement, property } from "lit-element";
import "../../../../../components/ha-textarea";
import "@polymer/paper-input/paper-textarea";
import { TemplateCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { handleChangeEvent } from "../ha-automation-condition-row";
@@ -17,7 +17,7 @@ export class HaTemplateCondition extends LitElement {
protected render() {
const { value_template } = this.condition;
return html`
<ha-textarea
<paper-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.conditions.type.template.value_template"
)}
@@ -25,7 +25,7 @@ export class HaTemplateCondition extends LitElement {
.value=${value_template}
@value-changed=${this._valueChanged}
dir="ltr"
></ha-textarea>
></paper-textarea>
`;
}

View File

@@ -1,5 +1,6 @@
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-input/paper-textarea";
import "../../../components/ha-icon-button";
import {
css,
@@ -117,7 +118,7 @@ export class HaAutomationEditor extends LitElement {
@value-changed=${this._valueChanged}
>
</paper-input>
<ha-textarea
<paper-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.description.label"
)}
@@ -127,7 +128,7 @@ export class HaAutomationEditor extends LitElement {
name="description"
.value=${this._config.description}
@value-changed=${this._valueChanged}
></ha-textarea>
></paper-textarea>
</div>
${stateObj
? html`

View File

@@ -54,9 +54,7 @@ class HaConfigAutomation extends HassRouterPage {
private _getAutomations = memoizeOne(
(states: HassEntities): AutomationEntity[] => {
return Object.values(states).filter(
(entity) =>
computeStateDomain(entity) === "automation" &&
!entity.attributes.hidden
(entity) => computeStateDomain(entity) === "automation"
) as AutomationEntity[];
}
);

View File

@@ -2,8 +2,10 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "../../../../components/ha-icon-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "@material/mwc-list/mwc-list-item";
import "../../../../components/ha-button-menu";
import { mdiDotsVertical } from "@mdi/js";
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-menu-button/paper-menu-button";
import {
css,
CSSResult,
@@ -90,42 +92,36 @@ export default class HaAutomationTriggerRow extends LitElement {
<ha-card>
<div class="card-content">
<div class="card-menu">
<paper-menu-button
no-animations
horizontal-align="right"
horizontal-offset="-5"
vertical-offset="-5"
close-on-activate
>
<ha-icon-button
icon="hass:dots-vertical"
slot="dropdown-trigger"
></ha-icon-button>
<paper-listbox slot="dropdown-content">
<paper-item
@tap=${this._switchYamlMode}
.disabled=${selected === -1}
>
${yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</paper-item>
<paper-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.duplicate"
)}
</paper-item>
<paper-item @tap=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.triggers.delete"
)}
</paper-item>
</paper-listbox>
</paper-menu-button>
<ha-button-menu corner="BOTTOM_START">
<mwc-icon-button
slot="trigger"
.title=${this.hass.localize("ui.common.menu")}
.label=${this.hass.localize("ui.common.overflow_menu")}
><ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon
></mwc-icon-button>
<mwc-list-item
@tap=${this._switchYamlMode}
.disabled=${selected === -1}
>
${yamlMode
? this.hass.localize(
"ui.panel.config.automation.editor.edit_ui"
)
: this.hass.localize(
"ui.panel.config.automation.editor.edit_yaml"
)}
</mwc-list-item>
<mwc-list-item disabled>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.duplicate"
)}
</mwc-list-item>
<mwc-list-item @tap=${this._onDelete}>
${this.hass.localize(
"ui.panel.config.automation.editor.actions.delete"
)}
</mwc-list-item>
</ha-button-menu>
</div>
${yamlMode
? html`
@@ -232,14 +228,17 @@ export default class HaAutomationTriggerRow extends LitElement {
top: 0;
right: 0;
z-index: 3;
color: var(--primary-text-color);
--mdc-theme-text-primary-on-background: var(--primary-text-color);
}
.rtl .card-menu {
right: auto;
left: 0;
}
.card-menu paper-item {
cursor: pointer;
ha-button-menu {
margin: 8px;
}
mwc-list-item[disabled] {
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
}
`;
}

View File

@@ -2,7 +2,7 @@ import "@polymer/paper-input/paper-input";
import { customElement, html, LitElement, property } from "lit-element";
import { fireEvent } from "../../../../../common/dom/fire_event";
import "../../../../../components/entity/ha-entity-picker";
import "../../../../../components/ha-textarea";
import "@polymer/paper-input/paper-textarea";
import { ForDict, NumericStateTrigger } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { handleChangeEvent } from "../ha-automation-trigger-row";
@@ -61,7 +61,7 @@ export default class HaNumericStateTrigger extends LitElement {
.value=${below}
@value-changed=${this._valueChanged}
></paper-input>
<ha-textarea
<paper-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.numeric_state.value_template"
)}
@@ -69,7 +69,7 @@ export default class HaNumericStateTrigger extends LitElement {
.value=${value_template}
@value-changed=${this._valueChanged}
dir="ltr"
></ha-textarea>
></paper-textarea>
<paper-input
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.state.for"

View File

@@ -1,5 +1,5 @@
import { customElement, html, LitElement, property } from "lit-element";
import "../../../../../components/ha-textarea";
import "@polymer/paper-input/paper-textarea";
import { TemplateTrigger } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import { handleChangeEvent } from "../ha-automation-trigger-row";
@@ -17,7 +17,7 @@ export class HaTemplateTrigger extends LitElement {
protected render() {
const { value_template } = this.trigger;
return html`
<ha-textarea
<paper-textarea
.label=${this.hass.localize(
"ui.panel.config.automation.editor.triggers.type.template.value_template"
)}
@@ -25,7 +25,7 @@ export class HaTemplateTrigger extends LitElement {
.value=${value_template}
@value-changed=${this._valueChanged}
dir="ltr"
></ha-textarea>
></paper-textarea>
`;
}

View File

@@ -33,6 +33,7 @@ import { showDomainTogglerDialog } from "../../../../dialogs/domain-toggler/show
import "../../../../layouts/hass-loading-screen";
import "../../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../../types";
import "../../../../components/ha-formfield";
const DEFAULT_CONFIG_EXPOSE = true;
const IGNORE_INTERFACES = ["Alexa.EndpointHealth"];
@@ -127,14 +128,19 @@ class CloudAlexa extends LitElement {
)
.join(", ")}
</state-info>
<ha-switch
.entityId=${entity.entity_id}
.disabled=${!emptyFilter}
.checked=${isExposed}
@change=${this._exposeChanged}
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.cloud.alexa.expose"
)}
>
${this.hass!.localize("ui.panel.config.cloud.alexa.expose")}
</ha-switch>
<ha-switch
.entityId=${entity.entity_id}
.disabled=${!emptyFilter}
.checked=${isExposed}
@change=${this._exposeChanged}
>
</ha-switch>
</ha-formfield>
</div>
</ha-card>
`);

View File

@@ -38,6 +38,7 @@ import "../../../../layouts/hass-loading-screen";
import "../../../../layouts/hass-subpage";
import type { HomeAssistant } from "../../../../types";
import { showToast } from "../../../../util/toast";
import "../../../../components/ha-formfield";
const DEFAULT_CONFIG_EXPOSE = true;
@@ -127,14 +128,19 @@ class CloudGoogleAssistant extends LitElement {
.map((trait) => trait.substr(trait.lastIndexOf(".") + 1))
.join(", ")}
</state-info>
<ha-switch
.entityId=${entity.entity_id}
.disabled=${!emptyFilter}
.checked=${isExposed}
@change=${this._exposeChanged}
<ha-formfield
.label=${this.hass!.localize(
"ui.panel.config.cloud.google.expose"
)}
>
${this.hass!.localize("ui.panel.config.cloud.google.expose")}
</ha-switch>
<ha-switch
.entityId=${entity.entity_id}
.disabled=${!emptyFilter}
.checked=${isExposed}
@change=${this._exposeChanged}
>
</ha-switch>
</ha-formfield>
${entity.might_2fa
? html`
<ha-switch

View File

@@ -20,6 +20,7 @@ import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { configSections } from "../ha-panel-config";
import "./ha-config-navigation";
import { mdiCloudLock } from "@mdi/js";
@customElement("ha-config-dashboard")
class HaConfigDashboard extends LitElement {
@@ -66,7 +67,7 @@ class HaConfigDashboard extends LitElement {
path: "/config/cloud",
translationKey: "ui.panel.config.cloud.caption",
info: this.cloudStatus,
icon: "hass:cloud-lock",
iconPath: mdiCloudLock,
},
]}
></ha-config-navigation>
@@ -84,6 +85,36 @@ class HaConfigDashboard extends LitElement {
</ha-card>
`
)}
${isComponentLoaded(this.hass, "zha")
? html`
<div class="promo-advanced">
${this.hass.localize(
"ui.panel.config.integration_panel_move.missing_zha",
"integrations_page",
html`<a href="/config/integrations">
${this.hass.localize(
"ui.panel.config.integration_panel_move.link_integration_page"
)}
</a>`
)}
</div>
`
: ""}
${isComponentLoaded(this.hass, "zwave")
? html`
<div class="promo-advanced">
${this.hass.localize(
"ui.panel.config.integration_panel_move.missing_zwave",
"integrations_page",
html`<a href="/config/integrations">
${this.hass.localize(
"ui.panel.config.integration_panel_move.link_integration_page"
)}
</a>`
)}
</div>
`
: ""}
${!this.showAdvanced
? html`
<div class="promo-advanced">

View File

@@ -38,7 +38,10 @@ class HaConfigNavigation extends LitElement {
tabindex="-1"
>
<paper-icon-item>
<ha-icon .icon=${page.icon} slot="item-icon"></ha-icon>
<ha-svg-icon
.path=${page.iconPath}
slot="item-icon"
></ha-svg-icon>
<paper-item-body two-line>
${this.hass.localize(
page.translationKey ||
@@ -88,7 +91,7 @@ class HaConfigNavigation extends LitElement {
display: block;
outline: 0;
}
ha-icon,
ha-svg-icon,
ha-icon-next {
color: var(--secondary-text-color);
}

Some files were not shown because too many files have changed in this diff Show More