Compare commits

..

146 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 2c1b25b00b Bumped version to 20200603.1 2020-06-03 18:18:30 +02:00
Bram Kragten 407f305d21 Fix for earlier loading the frontend (#6094) 2020-06-03 18:16:49 +02:00
Bram Kragten 5d5d6b247f Merge pull request #6093 from home-assistant/dev 2020-06-03 13:28:05 +02:00
Bram Kragten 77bd7c37c1 Merge branch 'master' into dev 2020-06-03 11:37:20 +02:00
Bram Kragten 783ea0f5c8 Bumped version to 20200603.0 2020-06-03 10:20:37 +02:00
HomeAssistant Azure 33f1b45f30 [ci skip] Translation update 2020-06-03 00:32:35 +00:00
Bram Kragten e1df50ad3b Fix refresh when starting with generated on cast (#6072) 2020-06-02 22:25:11 +02:00
Maciej Bieniek 6a9a4cf65f Add missing translation in the customize (#6074) 2020-06-02 17:26:01 +02:00
Maciej Bieniek 443634ecf0 Add missing translations in the users panel (#6082)
* Add missing translations in the users panel

* Suggested change

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Suggested change

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Suggested change

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Move error_required to ui.common

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-06-02 17:25:30 +02:00
HomeAssistant Azure 2a17870d6d [ci skip] Translation update 2020-06-02 00:32:40 +00:00
Bram Kragten 486ed7dcaa More card size tweaking (#6073) 2020-06-01 16:08:27 +02:00
Bram Kragten 19c13096dc Fix map history (#6075) 2020-06-01 16:07:06 +02:00
HomeAssistant Azure 49b4271a47 [ci skip] Translation update 2020-06-01 00:32:39 +00:00
HomeAssistant Azure 7461d8f806 [ci skip] Translation update 2020-05-31 00:32:37 +00:00
HomeAssistant Azure 362236d7df [ci skip] Translation update 2020-05-30 00:32:30 +00:00
Zack Arnett ba384b9eee Weather Card: Show 5 element Forecast Longer (#5885) 2020-05-29 15:09:35 +02:00
Maciej Bieniek d94df728e5 Add missing translations on Z-Wave management page (#5932) 2020-05-29 15:08:23 +02:00
Bram Kragten d0a53d1760 Handle starting the frontend before finished loading integrations (#6068)
Co-authored-by: J. Nick Koston <nick@koston.org>
2020-05-28 21:09:26 -07:00
HomeAssistant Azure 2e5ec1f0c1 [ci skip] Translation update 2020-05-29 00:32:33 +00:00
Paulus Schoutsen 6c8aedfb8b Fix feature detection for module browsers that do not support dynamic… (#6069) 2020-05-28 12:28:58 -07:00
Robert Chmielowiec f354e1eb0f Invert whitelist logic in deviceAutomationsEqual (#6032) 2020-05-28 16:24:22 +02:00
HomeAssistant Azure 49b1e5897e [ci skip] Translation update 2020-05-28 00:32:21 +00:00
Paulus Schoutsen 1a58c17180 Fix hassio dev under rollup (#6063) 2020-05-27 14:18:38 -07:00
Bram Kragten 7fb852893c Fix get card size for lazy elements (#6023) 2020-05-27 22:18:13 +02:00
Paulus Schoutsen aa9a354746 Preload (#6062) 2020-05-27 12:54:52 -07:00
Bram Kragten 78f5429c92 break word in dev states table (#6060) 2020-05-27 09:34:51 -07:00
J. Nick Koston 79de8e0f32 Request less data when making history api calls where possible (#5934) 2020-05-27 16:45:39 +02:00
Bram Kragten 70f59eeec6 MIgrate entity-filter-badge to updating element (#6050) 2020-05-27 15:25:28 +02:00
Paulus Schoutsen b75792a3bf Fix filtering out compatibility (#6056) 2020-05-27 00:30:23 -07:00
Paulus Schoutsen 5cb7117656 Move compatibility to the top of custom panel entrypoint (#6055) 2020-05-27 09:19:28 +02:00
HomeAssistant Azure 3a5bd7474b [ci skip] Translation update 2020-05-27 00:32:20 +00:00
Bram Kragten fb929a089c Fix aria label icon name (#5992) 2020-05-26 10:03:15 +02:00
Bram Kragten 6076a0cdc4 Bump roundslider (#6049) 2020-05-26 10:00:25 +02:00
Bram Kragten 413f1c31bb Bumped version to 20200519.5 2020-05-26 09:50:56 +02:00
Bram Kragten 20baff380b Dont recreate resize observers and replace polyfill (#6043) 2020-05-26 09:49:43 +02:00
Bram Kragten 582d159884 Remove mwc-icon-button from dev tools states (#6046) 2020-05-26 09:49:12 +02:00
Bram Kragten 9c43c5806d Fix shopping list initial data fetch (#6020) 2020-05-26 09:48:50 +02:00
Bram Kragten 6cc9ce573b Bumped version to 20200519.4 2020-05-22 18:21:00 +02:00
Bram Kragten 23192226dd Missed paper icon button (#5987) 2020-05-22 18:20:52 +02:00
Bram Kragten 736444201b Fix for icons with firefox private mode (#5985) 2020-05-22 18:20:33 +02:00
Bram Kragten 785f49b005 Picture glance: Use icon button instead of icon with button styles. (#5977) 2020-05-22 18:20:08 +02:00
Bram Kragten 55dd1f4aa1 Fix device entities not updating (#5983) 2020-05-22 18:19:47 +02:00
Bram Kragten 6d0490d7d9 Fix show password toggle (#5979) 2020-05-22 18:19:14 +02:00
Bram Kragten 9c574995ac Bumped version to 20200519.3 2020-05-21 15:15:37 +02:00
Bram Kragten 3f35c603d2 Don't set hass when not defined (#5967) 2020-05-21 15:14:48 +02:00
Bram Kragten 8e0688140e Fix weather card (#5966) 2020-05-21 15:14:21 +02:00
Bram Kragten 0c57c05a22 Bumped version to 20200519.2 2020-05-21 13:02:07 +02:00
Bram Kragten c6be3be45a Fix markdown card crashing the demo (#5962) 2020-05-21 13:01:57 +02:00
Bram Kragten 9689db9605 Upgrade lazy error card (#5955) 2020-05-21 13:01:36 +02:00
Zack Arnett 4b8f7e1fe9 Weather Card: Fix Forecast Image Spacing (#5952) 2020-05-20 21:39:45 +02:00
Bram Kragten e165a96689 Bumped version to 20200519.1 2020-05-20 21:36:11 +02:00
Bram Kragten 91a655a81e Fix resize observers and update gauge styling (#5949) 2020-05-20 21:35:52 +02:00
Bram Kragten 612811e2c2 Fix disabled styling zone panel (#5950) 2020-05-20 21:35:32 +02:00
Bram Kragten 11bd72915c Fix picture header footer upgrade (#5945) 2020-05-20 21:35:12 +02:00
Bram Kragten 339221e793 Upgrade lazy loaded elements before setting config (#5944) 2020-05-20 21:34:49 +02:00
Bram Kragten 9be864b45e Fix search for data-tables using the builtin search bar (#5937) 2020-05-20 21:34:24 +02:00
Bram Kragten fafad302ba Merge pull request #5931 from home-assistant/dev 2020-05-19 16:05:55 +02:00
Bram Kragten 0a7f610ad3 Merge pull request #5920 from home-assistant/dev 2020-05-18 20:05:22 +02:00
Bram Kragten 007f8b50b9 Merge pull request #5900 from home-assistant/dev 2020-05-15 22:01:34 +02:00
Bram Kragten 259726f5be Merge pull request #5883 from home-assistant/dev 2020-05-14 20:05:34 +02:00
Bram Kragten 6853db693a Merge pull request #5862 from home-assistant/dev 2020-05-14 01:44:59 +02:00
Bram Kragten 51e7aaa805 Merge pull request #5848 from home-assistant/dev 2020-05-13 13:14:23 +02:00
Bram Kragten de1ffe67b1 Merge pull request #5840 from home-assistant/dev 2020-05-12 22:24:40 +02:00
Bram Kragten d19acf17c2 Merge pull request #5826 from home-assistant/dev 2020-05-09 21:17:30 +02:00
Bram Kragten ae6243b7bf Merge pull request #5757 from home-assistant/dev 2020-05-05 17:38:14 +02:00
330 changed files with 10925 additions and 7139 deletions
-4
View File
@@ -1,4 +0,0 @@
node_modules
hass_frontend
hass_frontend_es5
.git
+1 -5
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,
-31
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" ]
-9
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.
+8 -8
View File
@@ -22,7 +22,10 @@ module.exports.emptyPackages = ({ latestBuild }) =>
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
// Compatibility not needed for latest builds
latestBuild &&
path.resolve(paths.polymer_dir, "src/entrypoints/compatibility.ts"),
// 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);
@@ -82,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 {
@@ -167,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"]),
+1
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"
)
);
+8 -11
View File
@@ -1,39 +1,36 @@
const del = require("del");
const gulp = require("gulp");
const config = require("../paths");
const paths = require("../paths");
require("./translations");
gulp.task(
"clean",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.app_output_root, config.build_dir]);
return del([paths.app_output_root, paths.build_dir]);
})
);
gulp.task(
"clean-demo",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.demo_output_root, config.build_dir]);
return del([paths.demo_output_root, paths.build_dir]);
})
);
gulp.task(
"clean-cast",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.cast_output_root, config.build_dir]);
return del([paths.cast_output_root, paths.build_dir]);
})
);
gulp.task(
"clean-hassio",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.hassio_output_root, config.build_dir]);
})
);
gulp.task("clean-hassio", function cleanOutputAndBuildDir() {
return del([paths.hassio_output_root, paths.build_dir]);
});
gulp.task(
"clean-gallery",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.gallery_output_root, config.build_dir]);
return del([paths.gallery_output_root, paths.build_dir]);
})
);
+8 -6
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));
});
+3 -1
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/")
+23
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"])
)
+1 -6
View File
@@ -133,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", () =>
+2 -3
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)
)
+1 -1
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"),
};
+8 -2
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;
}
-1
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 %>");
-1
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 %>");
+2 -2
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";
+3 -3
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";
+13 -6
View File
@@ -82,6 +82,7 @@ export class HcMain extends HassElement {
.hass=${this.hass}
.lovelaceConfig=${this._lovelaceConfig}
.viewPath=${this._lovelacePath}
@config-refresh=${this._generateLovelaceConfig}
></hc-lovelace>
`;
}
@@ -191,14 +192,11 @@ 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;
const { generateLovelaceConfigFromHass } = await import(
"../../../../src/panels/lovelace/common/generate-lovelace-config"
);
this._handleNewLovelaceConfig(
await generateLovelaceConfigFromHass(this.hass!)
);
await this._generateLovelaceConfig();
}
}
if (!resourcesLoaded) {
@@ -218,6 +216,15 @@ export class HcMain extends HassElement {
this._sendStatus();
}
private async _generateLovelaceConfig() {
const { generateLovelaceConfigFromHass } = await import(
"../../../../src/panels/lovelace/common/generate-lovelace-config"
);
this._handleNewLovelaceConfig(
await generateLovelaceConfigFromHass(this.hass!)
);
}
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
castContext.setApplicationState(lovelaceConfig.title!);
this._lovelaceConfig = lovelaceConfig;
+3 -6
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,
});
+1 -13
View File
@@ -5,18 +5,6 @@
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="icon" href="/static/icons/favicon.ico" />
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" />
<link
rel="preload"
href="/static/fonts/roboto/Roboto-Regular.woff2"
as="font"
crossorigin
/>
<link
rel="preload"
href="/static/fonts/roboto/Roboto-Medium.woff2"
as="font"
crossorigin
/>
<link
rel="apple-touch-icon"
sizes="180x180"
@@ -96,6 +84,7 @@
<div id="ha-init-skeleton"></div>
<ha-demo></ha-demo>
<%= renderTemplate('_js_base') %>
<%= renderTemplate('_preload_roboto') %>
<script type="module" src="<%= latestDemoJS %>"></script>
@@ -103,7 +92,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("<%= es5DemoJS %>");
+6 -4
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">
+3
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,
});
+3 -6
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 = `
+9 -1
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;
+4
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;
+4
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}
+9
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>
+8 -3
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>
+3 -6
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,
});
+9 -7
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,7 +75,8 @@
"@thomasloven/round-slider": "0.5.0",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@webcomponents/webcomponentsjs": "^2.3.4",
"@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "~2.8.0",
"chartjs-chart-timeline": "^0.3.0",
"codemirror": "^5.49.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.1.2",
"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",
@@ -203,7 +205,7 @@
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
"resolutions": {
"@webcomponents/webcomponentsjs": "^2.3.4",
"@webcomponents/webcomponentsjs": "^2.2.10",
"@polymer/polymer": "3.1.0",
"lit-html": "1.2.1",
"lit-element": "2.3.1",
-14
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
-103
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
+1 -1
View File
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20200519.0",
version="20200620.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
+8 -16
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;
}
+1 -1
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
+4 -1
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"],
+8
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;
};
@@ -8,6 +8,9 @@ class HaProgressButton extends PolymerElement {
static get template() {
return html`
<style>
:host {
outline: none;
}
.container {
position: relative;
display: inline-block;
@@ -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;
}
+228
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;
}
}
+1 -1
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;
}
`;
+3 -1
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, () => {
+16 -4
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);
+195
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;
}
}
+10 -2
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;
}
`,
];
+33
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;
}
}
+25 -7
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);
+8 -3
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);
+25 -7
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);
+25 -7
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);
+4 -11
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);
+1 -1
View File
@@ -13,7 +13,7 @@ class HaMarkdownElement extends UpdatingElement {
protected update(changedProps) {
super.update(changedProps);
if (this.content !== undefined) {
this._render();
this._render();
}
}
+1 -1
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 {
+3 -2
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 {
+41 -19
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);
}
+1 -54
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 {
-46
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);
+6
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;
+1 -1
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", {
+8
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");
+11 -10
View File
@@ -65,14 +65,15 @@ export const fetchDeviceTriggerCapabilities = (
trigger,
});
const whitelist = [
"above",
"below",
"brightness_pct",
"code",
"for",
"position",
"set_brightness",
const deviceAutomationIdentifiers = [
"device_id",
"domain",
"entity_id",
"type",
"subtype",
"event",
"condition",
"platform",
];
export const deviceAutomationsEqual = (
@@ -84,7 +85,7 @@ export const deviceAutomationsEqual = (
}
for (const property in a) {
if (whitelist.includes(property)) {
if (!deviceAutomationIdentifiers.includes(property)) {
continue;
}
if (!Object.is(a[property], b[property])) {
@@ -92,7 +93,7 @@ export const deviceAutomationsEqual = (
}
}
for (const property in b) {
if (whitelist.includes(property)) {
if (!deviceAutomationIdentifiers.includes(property)) {
continue;
}
if (!Object.is(a[property], b[property])) {
+20
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
+19 -4
View File
@@ -56,7 +56,8 @@ export const fetchRecent = (
startTime,
endTime,
skipInitialState = false,
significantChangesOnly?: boolean
significantChangesOnly?: boolean,
minimalResponse = true
): Promise<HassEntity[][]> => {
let url = "history/period";
if (startTime) {
@@ -72,6 +73,9 @@ export const fetchRecent = (
if (significantChangesOnly !== undefined) {
url += `&significant_changes_only=${Number(significantChangesOnly)}`;
}
if (minimalResponse) {
url += "&minimal_response";
}
return hass.callApi("GET", url);
};
@@ -83,14 +87,17 @@ export const fetchDate = (
): Promise<HassEntity[][]> => {
return hass.callApi(
"GET",
`history/period/${startTime.toISOString()}?end_time=${endTime.toISOString()}`
`history/period/${startTime.toISOString()}?end_time=${endTime.toISOString()}&minimal_response`
);
};
const equalState = (obj1: LineChartState, obj2: LineChartState) =>
obj1.state === obj2.state &&
// They either both have an attributes object or not
// Only compare attributes if both states have an attributes object.
// When `minimal_response` is sent, only the first and last state
// will have attributes except for domains in DOMAINS_USE_LAST_UPDATED.
(!obj1.attributes ||
!obj2.attributes ||
LINE_ATTRIBUTES_TO_KEEP.every(
(attr) => obj1.attributes![attr] === obj2.attributes![attr]
));
@@ -101,12 +108,20 @@ const processTimelineEntity = (
states: HassEntity[]
): TimelineEntity => {
const data: TimelineState[] = [];
const last_element = states.length - 1;
for (const state of states) {
if (data.length > 0 && state.state === data[data.length - 1].state) {
continue;
}
// Copy the data from the last element as its the newest
// and is only needed to localize the data
if (!state.entity_id) {
state.attributes = states[last_element].attributes;
state.entity_id = states[last_element].entity_id;
}
data.push({
state_localize: computeStateDisplay(localize, state, language),
state: state.state,
@@ -198,7 +213,7 @@ export const computeHistory = (
}
const stateWithUnit = stateInfo.find(
(state) => "unit_of_measurement" in state.attributes
(state) => state.attributes && "unit_of_measurement" in state.attributes
);
let unit: string | undefined;
+60
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}`] = {};
};
+1 -1
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",
@@ -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);
}
+91 -113
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;
}
`,
];
@@ -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 {
+14 -1
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 {
@@ -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>
+31 -45
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;
}
`,
];
}
+1 -5
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 (
@@ -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;
}
}
@@ -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,
});
};
+6
View File
@@ -28,6 +28,7 @@ import { HomeAssistant } from "../types";
declare global {
interface Window {
hassConnection: Promise<{ auth: Auth; conn: Connection }>;
hassConnectionReady?: (hassConnection: Window["hassConnection"]) => void;
}
}
@@ -82,6 +83,11 @@ window.hassConnection = (authProm() as Promise<Auth | ExternalAuth>).then(
connProm
);
// This is set if app was somehow loaded before core.
if (window.hassConnectionReady) {
window.hassConnectionReady(window.hassConnection);
}
// Start fetching some of the data that we will need.
window.hassConnection.then(({ conn }) => {
const noop = () => {
+4 -7
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" */ "../resources/compatibility"),
]);
es5Loaded = loadJS(
`${__STATIC_PATH__}polyfills/custom-elements-es5-adapter.js`
).catch(); // Swallow errors as it raises errors on old browsers.
}
return es5Loaded;
};
@@ -51,7 +49,6 @@ function initialize(panel: CustomPanelInfo, properties: {}) {
}
if (__BUILD__ === "es5") {
// Load ES5 adapter. Swallow errors as it raises errors on old browsers.
start = start.then(() => window.loadES5Adapter());
}
+2 -1
View File
@@ -1,4 +1,4 @@
import { HassConfig } from "home-assistant-js-websocket";
import { HassConfig, STATE_RUNNING } from "home-assistant-js-websocket";
export const demoConfig: HassConfig = {
location_name: "Home",
@@ -18,6 +18,7 @@ export const demoConfig: HassConfig = {
whitelist_external_dirs: [],
config_source: "storage",
safe_mode: false,
state: STATE_RUNNING,
internal_url: "http://homeassistant.local:8123",
external_url: null,
};
+16
View File
@@ -0,0 +1,16 @@
<script>
if (navigator.userAgent.indexOf("Android") === -1 &&
navigator.userAgent.indexOf("CrOS") === -1) {
function _pf(src, type) {
var el = document.createElement("link");
el.rel = "preload";
el.as = "font";
el.type = "font/woff2";
el.href = src;
el.crossOrigin = "anonymous";
document.head.append(el);
}
_pf("/static/fonts/roboto/Roboto-Regular.woff2");
_pf("/static/fonts/roboto/Roboto-Medium.woff2");
}
</script>
+1 -13
View File
@@ -3,18 +3,6 @@
<head>
<title>Home Assistant</title>
<link rel="preload" href="<%= latestPageJS %>" as="script" crossorigin="use-credentials" />
<link
rel="preload"
href="/static/fonts/roboto/Roboto-Light.woff2"
as="font"
crossorigin
/>
<link
rel="preload"
href="/static/fonts/roboto/Roboto-Regular.woff2"
as="font"
crossorigin
/>
<%= renderTemplate('_header') %>
<style>
.content {
@@ -46,6 +34,7 @@
</div>
<%= renderTemplate('_js_base') %>
<%= renderTemplate('_preload_roboto') %>
<script type="module" crossorigin="use-credentials">
import "<%= latestPageJS %>";
@@ -58,7 +47,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("<%= es5PageJS %>");
+20 -26
View File
@@ -2,18 +2,7 @@
<html>
<head>
<link rel="preload" href="<%= latestCoreJS %>" as="script" crossorigin="use-credentials" />
<link
rel="preload"
href="/static/fonts/roboto/Roboto-Regular.woff2"
as="font"
crossorigin
/>
<link
rel="preload"
href="/static/fonts/roboto/Roboto-Medium.woff2"
as="font"
crossorigin
/>
<link rel="preload" href="<%= latestAppJS %>" as="script" crossorigin="use-credentials" />
<%= renderTemplate('_header') %>
<title>Home Assistant</title>
<link
@@ -61,26 +50,28 @@
<home-assistant> </home-assistant>
<%= renderTemplate('_js_base') %>
<%= renderTemplate('_preload_roboto') %>
<script type="module" crossorigin="use-credentials">
import "<%= latestCoreJS %>";
import "<%= latestAppJS %>";
<script>
import("<%= latestCoreJS %>");
import("<%= latestAppJS %>");
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 nomodule>
(function() {
// Safari 10.1 supports type=module but ignores nomodule, so we add this check.
if (!isS101) {
<script>
if (!window.latestJS) {
window.customPanelJS = "<%= es5CustomPanelJS %>";
_ls("/static/polyfills/custom-elements-es5-adapter.js");
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = 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 %>");
});
@@ -89,11 +80,14 @@
_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 -%}
+1 -13
View File
@@ -3,18 +3,6 @@
<head>
<title>Home Assistant</title>
<link rel="preload" href="<%= latestPageJS %>" as="script" crossorigin="use-credentials" />
<link
rel="preload"
href="/static/fonts/roboto/Roboto-Light.woff2"
as="font"
crossorigin
/>
<link
rel="preload"
href="/static/fonts/roboto/Roboto-Regular.woff2"
as="font"
crossorigin
/>
<%= renderTemplate('_header') %>
<style>
.content {
@@ -48,6 +36,7 @@
</div>
<%= renderTemplate('_js_base') %>
<%= renderTemplate('_preload_roboto') %>
<script type="module" crossorigin="use-credentials">
import "<%= latestPageJS %>";
@@ -60,7 +49,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("<%= es5PageJS %>");
+12 -1
View File
@@ -93,7 +93,18 @@ export class HomeAssistantAppEl extends HassElement {
protected async _initialize() {
try {
const { auth, conn } = await window.hassConnection;
let result;
if (window.hassConnection) {
result = await window.hassConnection;
} else {
// In the edge case that
result = await new Promise((resolve) => {
window.hassConnectionReady = resolve;
});
}
const { auth, conn } = result;
this._haVersion = conn.haVersion;
this.initializeHass(auth, conn);
} catch (err) {
+68 -2
View File
@@ -8,6 +8,11 @@ import {
RouteOptions,
RouterOptions,
} from "./hass-router-page";
import {
STATE_STARTING,
STATE_NOT_RUNNING,
STATE_RUNNING,
} from "home-assistant-js-websocket";
const CACHE_URL_PATHS = ["lovelace", "developer-tools"];
const COMPONENTS = {
@@ -84,6 +89,22 @@ class PartialPanelResolver extends HassRouterPage {
@property() public narrow?: boolean;
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);
@@ -93,6 +114,15 @@ class PartialPanelResolver extends HassRouterPage {
const oldHass = changedProps.get("hass") as this["hass"];
if (
this._waitForStart &&
(this.hass.config.state === STATE_STARTING ||
this.hass.config.state === STATE_RUNNING)
) {
this._waitForStart = false;
this.rebuild();
}
if (this.hass.panels && (!oldHass || oldHass.panels !== this.hass.panels)) {
this._updateRoutes(oldHass?.panels);
}
@@ -115,19 +145,55 @@ class PartialPanelResolver extends HassRouterPage {
hass: this.hass,
narrow: this.narrow,
route: this.routeTail,
panel: hass.panels[hass.panelUrl],
panel: hass.panels[this._currentPage],
});
} else {
el.hass = hass;
el.narrow = this.narrow;
el.route = this.routeTail;
el.panel = hass.panels[hass.panelUrl];
el.panel = hass.panels[this._currentPage];
}
}
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);
if (
!this._waitForStart &&
this._currentPage &&
!this.hass.panels[this._currentPage]
) {
if (this.hass.config.state !== STATE_NOT_RUNNING) {
this._waitForStart = true;
if (this.lastChild) {
this.removeChild(this.lastChild);
}
this.appendChild(this.createLoadingScreen());
return;
}
}
if (
!oldPanels ||
!deepEqual(
+5 -1
View File
@@ -24,7 +24,11 @@ export const SubscribeMixin = <T extends Constructor<UpdatingElement>>(
if (this.__unsubs) {
while (this.__unsubs.length) {
const unsub = this.__unsubs.pop()!;
Promise.resolve(unsub).then((unsubFunc) => unsubFunc());
if (unsub instanceof Promise) {
unsub.then((unsubFunc) => unsubFunc());
} else {
unsub();
}
}
this.__unsubs = undefined;
}
+63 -24
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);
}
}
@@ -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,
});
}
@@ -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);
}
`;
}
@@ -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"
@@ -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);
}
`;
}
@@ -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>
`;
}
@@ -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>
`;
}
@@ -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`
@@ -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[];
}
);
@@ -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);
}
`;
}
@@ -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"
@@ -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>
`;
}
+13 -7
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>
`);
@@ -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
@@ -37,7 +37,7 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
</span>
<ha-entity-config
hass="[[hass]]"
label="Entity"
label="[[localize('ui.panel.config.customize.picker.entity')]]"
entities="[[entities]]"
config="[[entityConfig]]"
>

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