Compare commits

..

144 Commits

Author SHA1 Message Date
Paulus Schoutsen
8ecd54ddf2 Upgrade webcomponents polyfill 2020-05-26 16:19:10 -07:00
Paulus Schoutsen
304fad3f49 Remove unused HTML tests (#6053) 2020-05-26 16:02:05 -07:00
Bram Kragten
a3736683eb Bump roundslider (#6049) 2020-05-26 09:59:06 +02:00
Bram Kragten
8ad2bf5401 Fix shopping list initial data fetch (#6020) 2020-05-26 09:44:46 +02:00
HomeAssistant Azure
66409f0fa5 [ci skip] Translation update 2020-05-26 00:32:44 +00:00
Franck Nijhof
68172e006f Update Code of Conduct to 2.0 (#6048) 2020-05-25 16:35:57 -07:00
Bram Kragten
cf5e808a96 Remove mwc-icon-button from dev tools states (#6046) 2020-05-25 21:57:04 +02:00
Maciej Bieniek
3faebaeb4b Add missing translations to actions, triggers and conditions of device automations (#6002) 2020-05-25 19:37:44 +02:00
Maciej Bieniek
6b8cfe661c Add missing translations on the area registry page (#5998) 2020-05-25 19:37:17 +02:00
Bram Kragten
2fd64af737 Dont recreate resize observers and replace polyfill (#6043) 2020-05-25 19:36:47 +02:00
Paulus Schoutsen
050cdf3783 Integrate compatibility into other entrypoints (#6029) 2020-05-25 19:36:22 +02:00
Paulus Schoutsen
d73b3d77ea Split out HTML imports from entrypoint (#6030) 2020-05-25 09:55:50 -07:00
Bram Kragten
acc024bcf9 Fix entity marker border color (#6041) 2020-05-25 16:47:21 +02:00
Maciej Bieniek
deb179ad38 Add translation to the back button on the helpers page (#5999) 2020-05-25 14:50:58 +02:00
Maciej Bieniek
bfb11790a2 Add translations for 'No area' strings (#6000) 2020-05-25 14:46:34 +02:00
Paulus Schoutsen
af23110074 Move legacy styles to their own files (#6033) 2020-05-25 10:16:01 +02:00
Paulus Schoutsen
b8e71609db Random Rollup tweaks (#6034) 2020-05-25 10:12:42 +02:00
HomeAssistant Azure
1876b3827f [ci skip] Translation update 2020-05-25 00:32:56 +00:00
Paulus Schoutsen
38d3b8d087 Ignore proxy-polyfill in workers (#6011) 2020-05-24 10:41:05 +02:00
Paulus Schoutsen
c5ef33cc78 Upgrade Fuse (#6012) 2020-05-24 10:39:07 +02:00
Paulus Schoutsen
7427b209a7 Fix ignore plugin for ES5 builds 2020-05-23 22:08:49 -07:00
Paulus Schoutsen
71397e5199 Exclude default polymer theme (#6010) 2020-05-23 20:20:36 -07:00
HomeAssistant Azure
1bc3b3befc [ci skip] Translation update 2020-05-24 00:32:49 +00:00
Paulus Schoutsen
872e46a076 Exclude packages rollup (#6007) 2020-05-23 13:53:26 -07:00
Paulus Schoutsen
ad386c0e22 Cleanups (#5997) 2020-05-23 00:06:23 -07:00
Paulus Schoutsen
7e281f66c2 Rollup (#5995) 2020-05-22 23:05:47 -07:00
Bram Kragten
7daafcbe1b Fix aria label icon name (#5992) 2020-05-22 22:24:37 +02:00
Paulus Schoutsen
c5b223988a Split generic bundle config from webpack config (#5917) 2020-05-22 10:02:05 -07:00
Bram Kragten
06667455ae Missed paper icon button (#5987) 2020-05-22 18:16:45 +02:00
Bram Kragten
3640960486 Fix for icons with firefox private mode (#5985) 2020-05-22 17:55:28 +02:00
Bram Kragten
4ad3dbf3e2 Update release-drafter.yaml 2020-05-22 16:36:00 +02:00
Bram Kragten
10957deb1f Add release drafter action (#5984) 2020-05-22 16:33:54 +02:00
Bram Kragten
0a128db269 Picture glance: Use icon button instead of icon with button styles. (#5977) 2020-05-22 15:36:43 +02:00
Bram Kragten
6bb3b84377 Fix device entities not updating (#5983) 2020-05-22 15:36:30 +02:00
Bram Kragten
b8d2c551e0 Fix show password toggle (#5979) 2020-05-22 14:44:17 +02:00
Maciej Bieniek
34e06351fb Add translations for buttons and error string on the auth page (#5933) 2020-05-22 14:44:06 +02:00
HomeAssistant Azure
e179404a9e [ci skip] Translation update 2020-05-22 00:32:48 +00:00
Bram Kragten
5f81a204f2 Don't set hass when not defined (#5967) 2020-05-21 15:10:32 +02:00
Bram Kragten
df3b70a533 Fix weather card (#5966) 2020-05-21 14:54:10 +02:00
Bram Kragten
e4607735ff Fix markdown card crashing the demo (#5962) 2020-05-21 13:00:33 +02:00
HomeAssistant Azure
389b7def0b [ci skip] Translation update 2020-05-21 00:32:27 +00:00
Bram Kragten
e35bd30ed3 Upgrade lazy error card (#5955) 2020-05-20 23:08:49 +02:00
Zack Arnett
5cc4e2bb16 Weather Card: Fix Forecast Image Spacing (#5952) 2020-05-20 21:39:29 +02:00
Bram Kragten
3a453f5843 Fix resize observers and update gauge styling (#5949) 2020-05-20 21:27:08 +02:00
Bram Kragten
28e0384b55 Fix disabled styling zone panel (#5950) 2020-05-20 19:31:45 +02:00
Bram Kragten
dcd6c6f06f Fix picture header footer upgrade (#5945) 2020-05-20 17:34:15 +02:00
Bram Kragten
28d26065e4 Upgrade lazy loaded elements before setting config (#5944) 2020-05-20 16:23:50 +02:00
Bram Kragten
300c8d06c4 Fix search for data-tables using the builtin search bar (#5937) 2020-05-20 10:01:33 +02:00
Paulus Schoutsen
d2a1d11d16 limit manifest to entrypoints (#5936) 2020-05-19 23:55:17 -07:00
HomeAssistant Azure
6ae717bbfe [ci skip] Translation update 2020-05-20 00:32:38 +00:00
Joakim Sørensen
21296b4224 Adds more styling to markdown elements (#5904) 2020-05-19 22:20:29 +02:00
Bram Kragten
0c94ad46b2 Bumped version to 20200519.0 2020-05-19 14:29:35 +02:00
Bram Kragten
d9bb40f934 Fix data table worker (#5921) 2020-05-19 13:59:16 +02:00
Bram Kragten
bbc16b6bc8 Cache used icons in memory, use inline icon for dev-tools (#5927) 2020-05-19 13:58:08 +02:00
Bram Kragten
16154e9d8b Change disabled icon to pencil-off (#5930) 2020-05-19 13:57:53 +02:00
Jeff Rescignano
61bd536d7b Update invalid links in README.md (#5926) 2020-05-19 09:54:39 +02:00
HomeAssistant Azure
02c798a8bc [ci skip] Translation update 2020-05-19 00:33:05 +00:00
Paulus Schoutsen
c37a691b9b Remove unused deps (#5916) 2020-05-18 23:09:59 +02:00
Maciej Bieniek
23c68d17e8 Add missing translations to ha-device-entities-card (#5908) 2020-05-18 21:54:58 +02:00
Bram Kragten
c9e8bd2e5d Fix picture card (#5922) 2020-05-18 20:05:00 +02:00
Paulus Schoutsen
a66d2ca1b9 Use comlink in workers (#5915) 2020-05-18 16:51:46 +02:00
Bram Kragten
d38a0f0366 Bumped version to 20200518.0 2020-05-18 16:14:12 +02:00
Bram Kragten
29759de021 Update ha-entity-picker.ts 2020-05-18 16:14:04 +02:00
Bram Kragten
264759ddf0 Fix entity-filter-card (#5919) 2020-05-18 15:16:56 +02:00
Bram Kragten
91b0bd5b5e Check if attached on rebuild (#5918) 2020-05-18 13:27:58 +02:00
Bram Kragten
a0e2cc7a3a Fix ignored config entries (#5914) 2020-05-18 11:26:32 +02:00
Mat Strange
9e1eb41cbe Added theme to events documentation (#5903) 2020-05-18 11:26:10 +02:00
Maciej Bieniek
c37eb023b0 Add missing translation on developer tool page (#5886) 2020-05-18 11:07:00 +02:00
HomeAssistant Azure
840948ba4a [ci skip] Translation update 2020-05-18 00:33:03 +00:00
J. Nick Koston
8fbdd88b24 Cleanup new ha-config (#5906)
Remove changedProps check as its always true.
2020-05-17 11:59:50 +02:00
HomeAssistant Azure
512d35d2e0 [ci skip] Translation update 2020-05-17 00:33:05 +00:00
Zack Arnett
0321e55a42 EZ (#5901) 2020-05-16 11:15:04 +02:00
Zack Arnett
a1ee9ad48b Card Editor: Documentation per Card (#5888)
* Doc-links

* Comments

* Fix

* Remove unneeded code

* undo the change

* better
2020-05-15 21:50:28 -04:00
HomeAssistant Azure
1ad1fd28f1 [ci skip] Translation update 2020-05-16 00:32:54 +00:00
Bram Kragten
6fe8a87cca Bumped version to 20200515.0 2020-05-15 21:45:59 +02:00
Bram Kragten
5f46679d94 Fix mdc checkbox styling (#5897) 2020-05-15 17:56:47 +02:00
Zack Arnett
18a3f212f3 Calendar Panel: Popup Style (#5895) 2020-05-15 17:22:38 +02:00
Zack Arnett
67a3f5d87b Gauge Card: Fix if value is greater than max (#5887) 2020-05-15 17:21:53 +02:00
Bram Kragten
c88439ba2f Polyfill Intl.PluralRules (#5893) 2020-05-15 14:54:42 +02:00
Bram Kragten
67e17d4016 Fix conditional and custom panel updated > update (#5891) 2020-05-15 14:54:23 +02:00
Bram Kragten
b61cf60faf Migrate card preview to UpdatingElement (#5884) 2020-05-15 09:19:24 +02:00
HomeAssistant Azure
5503853445 [ci skip] Translation update 2020-05-15 00:32:48 +00:00
Bram Kragten
bec42d941b Bumped version to 20200514.1 2020-05-14 18:46:20 +02:00
Bram Kragten
dcbbaf08f9 Set lovelace when restoring view from cache, optimise picture element and thermostat (#5880) 2020-05-14 18:44:38 +02:00
Zack Arnett
349355584a Media Player Row: Fix State Translation (#5881)
* Fix for state display translation

* Comments
2020-05-14 12:38:43 -04:00
Zack Arnett
7ce0b34774 Resizer fix (#5882) 2020-05-14 12:37:38 -04:00
Zack Arnett
dd894758a4 Card Editor: Preview Card Margin fix (#5879) 2020-05-14 18:13:26 +02:00
Zack Arnett
2aa1eb97fd Markdown Card: Fix not rendering on initial load (#5864) 2020-05-14 18:13:06 +02:00
Bram Kragten
4c43ae7b2f Beta fixes (#5878) 2020-05-14 16:03:52 +02:00
Zack Arnett
34e516e0be oops (#5866) 2020-05-14 09:26:06 +02:00
HomeAssistant Azure
7bd3427e76 [ci skip] Translation update 2020-05-14 00:32:37 +00:00
Bram Kragten
252ce1e467 Show loading screen for integration config (#5863) 2020-05-14 01:44:39 +02:00
Bram Kragten
221c12bd61 Bumped version to 20200514.0 2020-05-14 01:12:57 +02:00
Paulus Schoutsen
12edd68874 Remove unused ES5 service worker (#5860) 2020-05-14 01:11:32 +02:00
Bram Kragten
f469753fb1 Dont add all childs at once to view (#5856) 2020-05-14 00:09:52 +02:00
Bram Kragten
c894ecd0e6 Optimise sourcemaps (#5859) 2020-05-14 00:06:39 +02:00
Paulus Schoutsen
2153bc536c Clean up service worker code and fix 404 (#5855) 2020-05-13 13:17:47 -07:00
Bram Kragten
86bbac430c Fix translations for integration config panel (#5854) 2020-05-13 20:49:01 +02:00
Bram Kragten
16ad8a3c01 Open newly added view after adding (#5851) 2020-05-13 19:49:03 +02:00
Bram Kragten
b2af91c83e Fixes for problems caused by not rebuilding (#5850) 2020-05-13 18:51:26 +02:00
Bram Kragten
4c0810f530 Fix typo in property method (#5852) 2020-05-13 18:51:16 +02:00
Paulus Schoutsen
f70130e21f Remove reference to Google Fonts (#5849) 2020-05-13 18:22:53 +02:00
Paulus Schoutsen
6bb7b01d00 Fix demo size_stats 2020-05-13 08:41:35 -07:00
Bram Kragten
54704e53b3 Bumped version to 20200513.0 2020-05-13 12:47:37 +02:00
Bram Kragten
cc46797576 Some fixes in focus and click handling (#5847) 2020-05-13 12:47:09 +02:00
Bram Kragten
7f1fb6f75f Set edit mode to false on card when disabling edit mode. (#5845) 2020-05-13 12:05:29 +02:00
Bram Kragten
2c2a1d204b Graph history fix (#5846) 2020-05-13 12:05:03 +02:00
Paulus Schoutsen
581fafdcc9 Workbox 5 in gulp (#5843) 2020-05-13 11:12:01 +02:00
HomeAssistant Azure
10358abbec [ci skip] Translation update 2020-05-13 00:32:49 +00:00
Zack Arnett
eb2b24d57c Weather Card: Fix overwritten changes (#5841) 2020-05-12 22:24:20 +02:00
Bram Kragten
825db8a56a Bumped version to 20200512.0 2020-05-12 21:59:06 +02:00
David F. Mulcahey
577a21fc5c Rework ZHA group adds and removes (#5602) 2020-05-12 21:42:29 +02:00
Bram Kragten
1b2841eef9 Recreate cards only on config change (#5839) 2020-05-12 11:09:30 -07:00
Ian Richardson
845511e322 Add 'brightness' as a secondary_info option (#5731) 2020-05-12 12:29:43 -04:00
Bram Kragten
96ab057853 Ignore ResizeObserver loop limit exceeded error (#5838) 2020-05-12 12:17:41 +02:00
HomeAssistant Azure
f85cf0a238 [ci skip] Translation update 2020-05-12 00:33:00 +00:00
Bram Kragten
84a2676a9c compress icons (#5836) 2020-05-11 23:59:29 +02:00
Zack Arnett
466a1af902 Weather Card/Row: Weather Icons as SVG, Themeable, user definable (#5736)
* SVG

* no-unneeded-ternary

* declared ubnused

* moving stuff around

* Few updates

* All svgs in | update row

* No slots

* Remove public/static/images/weather

* style for user defined

* few updates to missing fils

* classes

* wind color
2020-05-11 23:58:17 +02:00
Zack Arnett
ebbe7e805f Gauge Card: Convert to Round Slider (#5510)
* Use round slider for gauge

* Update guage to slider

* Add severity back

* Remove Base Unit

* fix merge

* resize observer

* Update src/panels/lovelace/cards/hui-gauge-card.ts

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

* Update Install Resize Observer to be a helper

* Type import

* Reviews

* Updates to other cards

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-05-11 17:14:02 -04:00
Zack Arnett
c861ee025e Weather Card: Ability to choose Secondary Attribute (#5820) 2020-05-11 22:11:48 +02:00
Paulus Schoutsen
6d0823328d Fix generated Lovelace translating domain names (#5803) 2020-05-11 10:28:05 -07:00
Joakim Sørensen
60be14dc77 Use manifest.issue_tracker for issues URL if not built_in (#5818) 2020-05-11 13:45:00 +02:00
Mat Strange
2d627819d9 Added translation to dev-tools-info-integrations (#5834) 2020-05-11 13:44:32 +02:00
Zack Arnett
cf575f83f5 Weather Card: Switch State and Name (#5795) 2020-05-11 13:44:15 +02:00
HomeAssistant Azure
79935b2d4c [ci skip] Translation update 2020-05-11 00:32:59 +00:00
HomeAssistant Azure
d1cceb2013 [ci skip] Translation update 2020-05-10 00:33:23 +00:00
Bram Kragten
f5da130d51 Merge pull request #5823 from matstrange/dev 2020-05-09 22:18:37 +02:00
Mat Strange
8768304ec5 Returned to no label on disarm/trigger as before 2020-05-09 20:30:44 +01:00
Bram Kragten
f10a5dcdbe Unused import 2020-05-09 21:22:46 +02:00
Bram Kragten
a27428ebcd Simplify enter handling 2020-05-09 21:17:55 +02:00
Bram Kragten
1a0bf861ee Bumped version to 20200509.0 2020-05-09 21:01:00 +02:00
Bram Kragten
db906ad4d0 Bump material (#5812) 2020-05-09 20:59:53 +02:00
Bram Kragten
75ed0f2f99 Fix translation download and add empty languages (#5824) 2020-05-09 20:59:34 +02:00
Bram Kragten
29ed1144d5 Simplify custom icon set, return only 1 icon per call (#5822) 2020-05-09 20:59:02 +02:00
Mat Strange
fa445d4066 Added translation to bade on alarm panel 2020-05-09 12:00:26 +01:00
Erik Montnemery
d10be4ef2d Show cover position slider if cover supports it (#5815) 2020-05-08 22:59:20 +02:00
Joakim Sørensen
7f66d5b8e9 Change target to currentTarget for the click event (#5816) 2020-05-08 16:31:07 -04:00
Bram Kragten
0c8cd680c2 Allow custom icon sets (#5794) 2020-05-08 21:56:25 +02:00
Maciej Bieniek
a7ba1977b4 Add translation for and word on the integration card (#5811)
* Add translation for and word on tje integration card

* Move and to ui.common
2020-05-08 17:43:21 +02:00
Bram Kragten
a960b39235 Bump lit-element and lit-html (#5810) 2020-05-08 14:00:09 +02:00
Bram Kragten
3febf059ec Codesplit Supervisor panel icons (#5809) 2020-05-08 13:10:24 +02:00
Paulus Schoutsen
20203f7bdb Bump home-assistant-js-websocket to 5.1.2 2020-05-07 18:35:14 -07:00
Franck Nijhof
a399d76d06 Add new core configuration UI for external_url & internal_url (#5755) 2020-05-07 18:32:03 -07:00
Franck Nijhof
1ca097c5a0 Add issue_tracker integration manifest property (#5744) 2020-05-07 20:51:46 -04:00
565 changed files with 12517 additions and 9781 deletions

View File

@@ -69,7 +69,7 @@
"import/extensions": [
2,
"ignorePackages",
{ "ts": "ignorePackages", "js": "ignorePackages" }
{ "ts": "never", "js": "never" }
],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": 0,

14
.github/workflows/release-drafter.yaml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Release Drafter
on:
push:
branches:
- dev
jobs:
update_release_draft:
runs-on: ubuntu-latest
steps:
- uses: release-drafter/release-drafter@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -2,79 +2,139 @@
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
Examples of behavior that contributes to a positive environment for our
community include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior by participants include:
Examples of unacceptable behavior include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
## Enforcement Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [safety@home-assistant.io][email]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
reported to the community leaders responsible for enforcement at
[safety@home-assistant.io][email] or by using the report/flag feature of
the medium used. All complaints will be reviewed and investigated promptly and
fairly.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available [here][version].
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available [here][version].
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder][mozilla].
## Adoption
This Code of Conduct was first adopted January 21st, 2017 and announced in [this][coc-blog] blog post.
This Code of Conduct was first adopted January 21st, 2017 and announced in
[this][coc-blog] blog post and has been updated on May 25th, 2020 to version
2.0 of the [Contributor Covenant][homepage] as announced in [this][coc2-blog]
blog post.
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
For answers to common questions about this code of conduct, see the FAQ at
<https://www.contributor-covenant.org/faq>. Translations are available at
<https://www.contributor-covenant.org/translations>.
[coc-blog]: /blog/2017/01/21/home-assistant-governance/
[coc2-blog]: /blog/2020/05/25/code-of-conduct-updated/
[email]: mailto:safety@home-assistant.io
[coc-blog]: https://home-assistant.io/blog/2017/01/21/home-assistant-governance/
[homepage]: http://contributor-covenant.org
[mozilla]: https://github.com/mozilla/diversity
[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html

View File

@@ -6,12 +6,12 @@ This is the repository for the official [Home Assistant](https://home-assistant.
- [View demo of Home Assistant](https://demo.home-assistant.io/)
- [More information about Home Assistant](https://home-assistant.io)
- [Frontend development instructions](https://developers.home-assistant.io/docs/en/frontend_index.html)
- [Frontend development instructions](https://developers.home-assistant.io/docs/frontend/development/)
## Development
- Initial setup: `script/setup`
- Development: [Instructions](https://developers.home-assistant.io/docs/en/frontend_development.html)
- Development: [Instructions](https://developers.home-assistant.io/docs/frontend/development/)
- Production build: `script/build_frontend`
- Gallery: `cd gallery && script/develop_gallery`
- Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html)

View File

@@ -1,40 +0,0 @@
const options = ({ latestBuild }) => ({
presets: [
!latestBuild && [require("@babel/preset-env").default, { modules: false }],
require("@babel/preset-typescript").default,
].filter(Boolean),
plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
[
"@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true },
],
// Only support the syntax, Webpack will handle it.
"@babel/syntax-dynamic-import",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
[
require("@babel/plugin-proposal-decorators").default,
{ decoratorsBeforeExport: true },
],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
],
});
module.exports.options = options;
module.exports.babelLoaderConfig = ({ latestBuild }) => {
if (latestBuild === undefined) {
throw Error("latestBuild not defined for babel loader config");
}
return {
test: /\.m?js$|\.tsx?$/,
exclude: [require.resolve("@mdi/js/mdi.js"), require.resolve("hls.js")],
use: {
loader: "babel-loader",
options: options({ latestBuild }),
},
};
};

196
build-scripts/bundle.js Normal file
View File

@@ -0,0 +1,196 @@
const path = require("path");
const env = require("./env.js");
const paths = require("./paths.js");
// Files from NPM Packages that should not be imported
module.exports.ignorePackages = ({ latestBuild }) => [
// Bloats bundle and it's not used.
path.resolve(require.resolve("moment"), "../locale"),
// Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"),
];
// Files from NPM packages that we should replace with empty file
module.exports.emptyPackages = ({ latestBuild }) =>
[
// Contains all color definitions for all material color sets.
// We don't use it
require.resolve("@polymer/paper-styles/color.js"),
require.resolve("@polymer/paper-styles/default-theme.js"),
// Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"),
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"),
// This polyfill is loaded in workers to support ES5, filter it out.
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
].filter(Boolean);
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
__DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(env.version()),
__DEMO__: false,
__BACKWARDS_COMPAT__: false,
__STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development"
),
...defineOverlay,
});
module.exports.terserOptions = (latestBuild) => ({
safari10: true,
ecma: latestBuild ? undefined : 5,
output: { comments: false },
});
module.exports.babelOptions = ({ latestBuild }) => ({
babelrc: false,
presets: [
!latestBuild && [require("@babel/preset-env").default, { modules: false }],
require("@babel/preset-typescript").default,
].filter(Boolean),
plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
[
"@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true },
],
// Only support the syntax, Webpack will handle it.
"@babel/syntax-dynamic-import",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
[
require("@babel/plugin-proposal-decorators").default,
{ decoratorsBeforeExport: true },
],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
],
});
// Are already ES5, cause warnings when babelified.
module.exports.babelExclude = () => [
require.resolve("@mdi/js/mdi.js"),
require.resolve("hls.js"),
];
const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
const publicPath = (latestBuild) =>
latestBuild ? "/frontend_latest/" : "/frontend_es5/";
/*
BundleConfig {
// Object with entrypoints that need to be bundled
entry: { [name: string]: pathToFile },
// Folder where bundled files need to be written
outputPath: string,
// absolute url-path where bundled files can be found
publicPath: string,
// extra definitions that we need to replace in source
defineOverlay: {[name: string]: value },
// if this is a production build
isProdBuild: boolean,
// If we're targeting latest browsers
latestBuild: boolean,
// If we're doing a stats build (create nice chunk names)
isStatsBuild: boolean,
// Names of entrypoints that should not be hashed
dontHash: Set<string>
}
*/
module.exports.config = {
app({ isProdBuild, latestBuild, isStatsBuild }) {
return {
entry: {
service_worker: "./src/entrypoints/service_worker.ts",
app: "./src/entrypoints/app.ts",
authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts",
core: "./src/entrypoints/core.ts",
"custom-panel": "./src/entrypoints/custom-panel.ts",
},
outputPath: outputPath(paths.app_output_root, latestBuild),
publicPath: publicPath(latestBuild),
isProdBuild,
latestBuild,
isStatsBuild,
};
},
demo({ isProdBuild, latestBuild, isStatsBuild }) {
return {
entry: {
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
},
outputPath: outputPath(paths.demo_output_root, latestBuild),
publicPath: publicPath(latestBuild),
defineOverlay: {
__VERSION__: JSON.stringify(`DEMO-${env.version()}`),
__DEMO__: true,
},
isProdBuild,
latestBuild,
isStatsBuild,
};
},
cast({ isProdBuild, latestBuild }) {
const entry = {
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
};
if (latestBuild) {
entry.receiver = path.resolve(
paths.cast_dir,
"src/receiver/entrypoint.ts"
);
}
return {
entry,
outputPath: outputPath(paths.cast_output_root, latestBuild),
publicPath: publicPath(latestBuild),
isProdBuild,
latestBuild,
defineOverlay: {
__BACKWARDS_COMPAT__: true,
},
};
},
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,
isProdBuild,
latestBuild,
dontHash: new Set(["entrypoint"]),
};
},
gallery({ isProdBuild, latestBuild }) {
return {
entry: {
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
},
outputPath: outputPath(paths.gallery_output_root, latestBuild),
publicPath: publicPath(latestBuild),
isProdBuild,
latestBuild,
};
},
};

View File

@@ -3,8 +3,13 @@ const path = require("path");
const paths = require("./paths.js");
module.exports = {
useRollup() {
return process.env.ROLLUP === "1";
},
isProdBuild() {
return process.env.NODE_ENV === "production";
return (
process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
);
},
isStatsBuild() {
return process.env.STATS === "1";

View File

@@ -1,7 +1,7 @@
// Run HA develop mode
const gulp = require("gulp");
const envVars = require("../env");
const env = require("../env");
require("./clean.js");
require("./translations.js");
@@ -11,6 +11,7 @@ require("./compress.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
gulp.task(
"develop-app",
@@ -20,14 +21,14 @@ gulp.task(
},
"clean",
gulp.parallel(
"gen-service-worker-dev",
"gen-service-worker-app-dev",
"gen-icons-json",
"gen-pages-dev",
"gen-index-app-dev",
"build-translations"
),
"copy-static",
"webpack-watch-app"
"copy-static-app",
env.useRollup() ? "rollup-watch-app" : "webpack-watch-app"
)
);
@@ -39,14 +40,14 @@ gulp.task(
},
"clean",
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static",
"webpack-prod-app",
"copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
...// Don't compress running tests
(envVars.isTest() ? [] : ["compress-app"]),
(env.isTest() ? [] : ["compress-app"]),
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",
"gen-service-worker-prod"
"gen-service-worker-app-prod"
)
)
);

View File

@@ -1,11 +1,14 @@
const gulp = require("gulp");
const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
gulp.task(
"develop-cast",
@@ -17,7 +20,7 @@ gulp.task(
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-cast",
"webpack-dev-server-cast"
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
)
);
@@ -31,7 +34,7 @@ gulp.task(
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-cast",
"webpack-prod-cast",
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
"gen-index-cast-prod"
)
);

View File

@@ -6,34 +6,34 @@ require("./translations");
gulp.task(
"clean",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.root, config.build_dir]);
return del([config.app_output_root, config.build_dir]);
})
);
gulp.task(
"clean-demo",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.demo_root, config.build_dir]);
return del([config.demo_output_root, config.build_dir]);
})
);
gulp.task(
"clean-cast",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.cast_root, config.build_dir]);
return del([config.cast_output_root, config.build_dir]);
})
);
gulp.task(
"clean-hassio",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.hassio_root, config.build_dir]);
return del([config.hassio_output_root, config.build_dir]);
})
);
gulp.task(
"clean-gallery",
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([config.gallery_root, config.build_dir]);
return del([config.gallery_output_root, config.build_dir]);
})
);

View File

@@ -8,31 +8,36 @@ const paths = require("../paths");
gulp.task("compress-app", function compressApp() {
const jsLatest = gulp
.src(path.resolve(paths.output, "**/*.js"))
.pipe(zopfli())
.pipe(gulp.dest(paths.output));
.src(path.resolve(paths.app_output_latest, "**/*.js"))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.app_output_latest));
const jsEs5 = gulp
.src(path.resolve(paths.output_es5, "**/*.js"))
.pipe(zopfli())
.pipe(gulp.dest(paths.output_es5));
.src(path.resolve(paths.app_output_es5, "**/*.js"))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.app_output_es5));
const polyfills = gulp
.src(path.resolve(paths.static, "polyfills/*.js"))
.pipe(zopfli())
.pipe(gulp.dest(path.resolve(paths.static, "polyfills")));
.src(path.resolve(paths.app_output_static, "polyfills/*.js"))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.app_output_static, "polyfills")));
const translations = gulp
.src(path.resolve(paths.static, "translations/*.json"))
.pipe(zopfli())
.pipe(gulp.dest(path.resolve(paths.static, "translations")));
.src(path.resolve(paths.app_output_static, "translations/**/*.json"))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.app_output_static, "translations")));
return merge(jsLatest, jsEs5, polyfills, translations);
const icons = gulp
.src(path.resolve(paths.app_output_static, "mdi/*.json"))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.app_output_static, "mdi")));
return merge(jsLatest, jsEs5, polyfills, translations, icons);
});
gulp.task("compress-hassio", function compressApp() {
return gulp
.src(path.resolve(paths.hassio_root, "**/*.js"))
.src(path.resolve(paths.hassio_output_root, "**/*.js"))
.pipe(zopfli())
.pipe(gulp.dest(paths.hassio_root));
.pipe(gulp.dest(paths.hassio_output_root));
});

View File

@@ -1,6 +1,8 @@
// Run demo develop mode
const gulp = require("gulp");
const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./gen-icons-json.js");
@@ -8,6 +10,7 @@ require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
gulp.task(
"develop-demo",
@@ -19,7 +22,7 @@ gulp.task(
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "gen-index-demo-dev", "build-translations"),
"copy-static-demo",
"webpack-dev-server-demo"
env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo"
)
);
@@ -34,7 +37,7 @@ gulp.task(
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-demo",
"webpack-prod-demo",
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
"gen-index-demo-prod"
)
);

View File

@@ -1,9 +1,14 @@
const del = require("del");
const gulp = require("gulp");
const fs = require("fs");
const mapStream = require("map-stream");
const inDir = "translations/frontend";
const downloadDir = inDir + "/downloads";
const inDirFrontend = "translations/frontend";
const inDirBackend = "translations/backend";
const downloadDir = "translations/downloads";
const srcMeta = "src/translations/translationMetadata.json";
const encoding = "utf8";
const tasks = [];
@@ -53,9 +58,25 @@ gulp.task(taskName, function () {
});
tasks.push(taskName);
taskName = "check-all-files-exist";
gulp.task(taskName, function () {
const file = fs.readFileSync(srcMeta, { encoding });
const meta = JSON.parse(file);
Object.keys(meta).forEach((lang) => {
if (!fs.existsSync(`${inDirFrontend}/${lang}.json`)) {
fs.writeFileSync(`${inDirFrontend}/${lang}.json`, JSON.stringify({}));
}
if (!fs.existsSync(`${inDirBackend}/${lang}.json`)) {
fs.writeFileSync(`${inDirBackend}/${lang}.json`, JSON.stringify({}));
}
});
return Promise.resolve();
});
tasks.push(taskName);
taskName = "move-downloaded-translations";
gulp.task(taskName, function () {
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDir));
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDirFrontend));
});
tasks.push(taskName);
@@ -65,6 +86,7 @@ gulp.task(
gulp.series(
"check-translations-html",
"move-downloaded-translations",
"check-all-files-exist",
"clean-downloaded-translations"
)
);

View File

@@ -6,31 +6,36 @@ const fs = require("fs-extra");
const path = require("path");
const template = require("lodash.template");
const minify = require("html-minifier").minify;
const config = require("../paths.js");
const paths = require("../paths.js");
const env = require("../env.js");
const templatePath = (tpl) =>
path.resolve(config.polymer_dir, "src/html/", `${tpl}.html.template`);
path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`);
const readFile = (pth) => fs.readFileSync(pth).toString();
const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
const compiled = template(readFile(pathFunc(pth)));
return compiled({ ...data, renderTemplate });
return compiled({
...data,
useRollup: env.useRollup(),
renderTemplate,
});
};
const renderDemoTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(config.demo_dir, "src/html/", `${tpl}.html.template`)
path.resolve(paths.demo_dir, "src/html/", `${tpl}.html.template`)
);
const renderCastTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(config.cast_dir, "src/html/", `${tpl}.html.template`)
path.resolve(paths.cast_dir, "src/html/", `${tpl}.html.template`)
);
const renderGalleryTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(config.gallery_dir, "src/html/", `${tpl}.html.template`)
path.resolve(paths.gallery_dir, "src/html/", `${tpl}.html.template`)
);
const minifyHtml = (content) =>
@@ -48,29 +53,36 @@ gulp.task("gen-pages-dev", (done) => {
const content = renderTemplate(page, {
latestPageJS: `/frontend_latest/${page}.js`,
es5Compatibility: "/frontend_es5/compatibility.js",
es5PageJS: `/frontend_es5/${page}.js`,
});
fs.outputFileSync(path.resolve(config.root, `${page}.html`), content);
fs.outputFileSync(
path.resolve(paths.app_output_root, `${page}.html`),
content
);
}
done();
});
gulp.task("gen-pages-prod", (done) => {
const latestManifest = require(path.resolve(config.output, "manifest.json"));
const es5Manifest = require(path.resolve(config.output_es5, "manifest.json"));
const latestManifest = require(path.resolve(
paths.app_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.app_output_es5,
"manifest.json"
));
for (const page of PAGES) {
const content = renderTemplate(page, {
latestPageJS: latestManifest[`${page}.js`],
es5Compatibility: es5Manifest["compatibility.js"],
es5PageJS: es5Manifest[`${page}.js`],
});
fs.outputFileSync(
path.resolve(config.root, `${page}.html`),
path.resolve(paths.app_output_root, `${page}.html`),
minifyHtml(content)
);
}
@@ -85,32 +97,39 @@ gulp.task("gen-index-app-dev", (done) => {
latestCoreJS: "/frontend_latest/core.js",
latestCustomPanelJS: "/frontend_latest/custom-panel.js",
es5Compatibility: "/frontend_es5/compatibility.js",
es5AppJS: "/frontend_es5/app.js",
es5CoreJS: "/frontend_es5/core.js",
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
}).replace(/#THEMEC/g, "{{ theme_color }}");
fs.outputFileSync(path.resolve(config.root, "index.html"), content);
fs.outputFileSync(path.resolve(paths.app_output_root, "index.html"), content);
done();
});
gulp.task("gen-index-app-prod", (done) => {
const latestManifest = require(path.resolve(config.output, "manifest.json"));
const es5Manifest = require(path.resolve(config.output_es5, "manifest.json"));
const latestManifest = require(path.resolve(
paths.app_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.app_output_es5,
"manifest.json"
));
const content = renderTemplate("index", {
latestAppJS: latestManifest["app.js"],
latestCoreJS: latestManifest["core.js"],
latestCustomPanelJS: latestManifest["custom-panel.js"],
es5Compatibility: es5Manifest["compatibility.js"],
es5AppJS: es5Manifest["app.js"],
es5CoreJS: es5Manifest["core.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
});
const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}");
fs.outputFileSync(path.resolve(config.root, "index.html"), minified);
fs.outputFileSync(
path.resolve(paths.app_output_root, "index.html"),
minified
);
done();
});
@@ -119,7 +138,7 @@ gulp.task("gen-index-cast-dev", (done) => {
latestReceiverJS: "/frontend_latest/receiver.js",
});
fs.outputFileSync(
path.resolve(config.cast_root, "receiver.html"),
path.resolve(paths.cast_output_root, "receiver.html"),
contentReceiver
);
@@ -127,14 +146,17 @@ gulp.task("gen-index-cast-dev", (done) => {
latestLauncherJS: "/frontend_latest/launcher.js",
es5LauncherJS: "/frontend_es5/launcher.js",
});
fs.outputFileSync(path.resolve(config.cast_root, "faq.html"), contentFAQ);
fs.outputFileSync(
path.resolve(paths.cast_output_root, "faq.html"),
contentFAQ
);
const contentLauncher = renderCastTemplate("launcher", {
latestLauncherJS: "/frontend_latest/launcher.js",
es5LauncherJS: "/frontend_es5/launcher.js",
});
fs.outputFileSync(
path.resolve(config.cast_root, "index.html"),
path.resolve(paths.cast_output_root, "index.html"),
contentLauncher
);
done();
@@ -142,11 +164,11 @@ gulp.task("gen-index-cast-dev", (done) => {
gulp.task("gen-index-cast-prod", (done) => {
const latestManifest = require(path.resolve(
config.cast_output,
paths.cast_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
config.cast_output_es5,
paths.cast_output_es5,
"manifest.json"
));
@@ -154,7 +176,7 @@ gulp.task("gen-index-cast-prod", (done) => {
latestReceiverJS: latestManifest["receiver.js"],
});
fs.outputFileSync(
path.resolve(config.cast_root, "receiver.html"),
path.resolve(paths.cast_output_root, "receiver.html"),
contentReceiver
);
@@ -162,14 +184,17 @@ gulp.task("gen-index-cast-prod", (done) => {
latestLauncherJS: latestManifest["launcher.js"],
es5LauncherJS: es5Manifest["launcher.js"],
});
fs.outputFileSync(path.resolve(config.cast_root, "faq.html"), contentFAQ);
fs.outputFileSync(
path.resolve(paths.cast_output_root, "faq.html"),
contentFAQ
);
const contentLauncher = renderCastTemplate("launcher", {
latestLauncherJS: latestManifest["launcher.js"],
es5LauncherJS: es5Manifest["launcher.js"],
});
fs.outputFileSync(
path.resolve(config.cast_root, "index.html"),
path.resolve(paths.cast_output_root, "index.html"),
contentLauncher
);
done();
@@ -181,32 +206,36 @@ gulp.task("gen-index-demo-dev", (done) => {
const content = renderDemoTemplate("index", {
latestDemoJS: "/frontend_latest/main.js",
es5Compatibility: "/frontend_es5/compatibility.js",
es5DemoJS: "/frontend_es5/main.js",
});
fs.outputFileSync(path.resolve(config.demo_root, "index.html"), content);
fs.outputFileSync(
path.resolve(paths.demo_output_root, "index.html"),
content
);
done();
});
gulp.task("gen-index-demo-prod", (done) => {
const latestManifest = require(path.resolve(
config.demo_output,
paths.demo_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
config.demo_output_es5,
paths.demo_output_es5,
"manifest.json"
));
const content = renderDemoTemplate("index", {
latestDemoJS: latestManifest["main.js"],
es5Compatibility: es5Manifest["compatibility.js"],
es5DemoJS: es5Manifest["main.js"],
});
const minified = minifyHtml(content);
fs.outputFileSync(path.resolve(config.demo_root, "index.html"), minified);
fs.outputFileSync(
path.resolve(paths.demo_output_root, "index.html"),
minified
);
done();
});
@@ -217,13 +246,16 @@ gulp.task("gen-index-gallery-dev", (done) => {
latestGalleryJS: "./frontend_latest/entrypoint.js",
});
fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), content);
fs.outputFileSync(
path.resolve(paths.gallery_output_root, "index.html"),
content
);
done();
});
gulp.task("gen-index-gallery-prod", (done) => {
const latestManifest = require(path.resolve(
config.gallery_output,
paths.gallery_output_latest,
"manifest.json"
));
const content = renderGalleryTemplate("index", {
@@ -231,6 +263,9 @@ gulp.task("gen-index-gallery-prod", (done) => {
});
const minified = minifyHtml(content);
fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), minified);
fs.outputFileSync(
path.resolve(paths.gallery_output_root, "index.html"),
minified
);
done();
});

View File

@@ -1,6 +1,8 @@
// Run demo develop mode
const gulp = require("gulp");
const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./gen-icons-json.js");
@@ -8,6 +10,7 @@ require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
gulp.task(
"develop-gallery",
@@ -20,7 +23,7 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-gallery",
"gen-index-gallery-dev",
"webpack-dev-server-gallery"
env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery"
)
);
@@ -34,7 +37,7 @@ gulp.task(
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-gallery",
"webpack-prod-gallery",
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
"gen-index-gallery-prod"
)
);

View File

@@ -51,6 +51,12 @@ function copyPolyfills(staticDir) {
);
}
function copyLoaderJS(staticDir) {
const staticPath = genStaticPath(staticDir);
copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js"));
copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js"));
}
function copyFonts(staticDir) {
const staticPath = genStaticPath(staticDir);
// Local fonts
@@ -72,17 +78,17 @@ function copyMapPanel(staticDir) {
);
}
gulp.task("copy-translations", (done) => {
const staticDir = paths.static;
gulp.task("copy-translations-app", async () => {
const staticDir = paths.app_output_static;
copyTranslations(staticDir);
done();
});
gulp.task("copy-static", (done) => {
const staticDir = paths.static;
gulp.task("copy-static-app", async () => {
const staticDir = paths.app_output_static;
// Basic static files
fs.copySync(polyPath("public"), paths.root);
fs.copySync(polyPath("public"), paths.app_output_root);
copyLoaderJS(staticDir);
copyPolyfills(staticDir);
copyFonts(staticDir);
copyTranslations(staticDir);
@@ -90,48 +96,50 @@ gulp.task("copy-static", (done) => {
// Panel assets
copyMapPanel(staticDir);
done();
});
gulp.task("copy-static-demo", (done) => {
gulp.task("copy-static-demo", async () => {
// Copy app static files
fs.copySync(
polyPath("public/static"),
path.resolve(paths.demo_root, "static")
path.resolve(paths.demo_output_root, "static")
);
// Copy demo static files
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_root);
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_output_root);
copyPolyfills(paths.demo_static);
copyMapPanel(paths.demo_static);
copyFonts(paths.demo_static);
copyTranslations(paths.demo_static);
copyMdiIcons(paths.demo_static);
done();
copyLoaderJS(paths.demo_output_static);
copyPolyfills(paths.demo_output_static);
copyMapPanel(paths.demo_output_static);
copyFonts(paths.demo_output_static);
copyTranslations(paths.demo_output_static);
copyMdiIcons(paths.demo_output_static);
});
gulp.task("copy-static-cast", (done) => {
gulp.task("copy-static-cast", async () => {
// Copy app static files
fs.copySync(polyPath("public/static"), paths.cast_static);
fs.copySync(polyPath("public/static"), paths.cast_output_static);
// Copy cast static files
fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_root);
fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_output_root);
copyMapPanel(paths.cast_static);
copyFonts(paths.cast_static);
copyTranslations(paths.cast_static);
copyMdiIcons(paths.cast_static);
done();
copyLoaderJS(paths.cast_output_static);
copyPolyfills(paths.cast_output_static);
copyMapPanel(paths.cast_output_static);
copyFonts(paths.cast_output_static);
copyTranslations(paths.cast_output_static);
copyMdiIcons(paths.cast_output_static);
});
gulp.task("copy-static-gallery", (done) => {
gulp.task("copy-static-gallery", async () => {
// Copy app static files
fs.copySync(polyPath("public/static"), paths.gallery_static);
fs.copySync(polyPath("public/static"), paths.gallery_output_static);
// Copy gallery static files
fs.copySync(path.resolve(paths.gallery_dir, "public"), paths.gallery_root);
fs.copySync(
path.resolve(paths.gallery_dir, "public"),
paths.gallery_output_root
);
copyMapPanel(paths.gallery_static);
copyFonts(paths.gallery_static);
copyTranslations(paths.gallery_static);
copyMdiIcons(paths.gallery_static);
done();
copyMapPanel(paths.gallery_output_static);
copyFonts(paths.gallery_output_static);
copyTranslations(paths.gallery_output_static);
copyMdiIcons(paths.gallery_output_static);
});

View File

@@ -1,11 +1,12 @@
const gulp = require("gulp");
const envVars = require("../env");
const env = require("../env");
require("./clean.js");
require("./gen-icons-json.js");
require("./webpack.js");
require("./compress.js");
require("./rollup.js");
gulp.task(
"develop-hassio",
@@ -15,7 +16,7 @@ gulp.task(
},
"clean-hassio",
"gen-icons-json",
"webpack-watch-hassio"
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
)
);
@@ -27,8 +28,8 @@ gulp.task(
},
"clean-hassio",
"gen-icons-json",
"webpack-prod-hassio",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
...// Don't compress running tests
(envVars.isTest() ? [] : ["compress-hassio"])
(env.isTest() ? [] : ["compress-hassio"])
)
);

View File

@@ -0,0 +1,151 @@
// Tasks to run Rollup
const path = require("path");
const gulp = require("gulp");
const rollup = require("rollup");
const handler = require("serve-handler");
const http = require("http");
const log = require("fancy-log");
const rollupConfig = require("../rollup");
const paths = require("../paths");
const open = require("open");
const bothBuilds = (createConfigFunc, params) =>
gulp.series(
async function buildLatest() {
await buildRollup(
createConfigFunc({
...params,
latestBuild: true,
})
);
},
async function buildES5() {
await buildRollup(
createConfigFunc({
...params,
latestBuild: false,
})
);
}
);
function createServer(serveOptions) {
const server = http.createServer((request, response) => {
return handler(request, response, {
public: serveOptions.root,
});
});
server.listen(
serveOptions.port,
serveOptions.networkAccess ? "0.0.0.0" : undefined,
() => {
log.info(`Available at http://localhost:${serveOptions.port}`);
open(`http://localhost:${serveOptions.port}`);
}
);
}
function watchRollup(createConfig, extraWatchSrc = [], serveOptions) {
const { inputOptions, outputOptions } = createConfig({
isProdBuild: false,
latestBuild: true,
});
const watcher = rollup.watch({
...inputOptions,
output: [outputOptions],
watch: {
include: ["src/**"] + extraWatchSrc,
},
});
let startedHttp = false;
watcher.on("event", (event) => {
if (event.code === "BUNDLE_END") {
log(`Build done @ ${new Date().toLocaleTimeString()}`);
} else if (event.code === "ERROR") {
log.error(event.error);
} else if (event.code === "END") {
if (startedHttp || !serveOptions) {
return;
}
startedHttp = true;
createServer(serveOptions);
}
});
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series("build-translations", "copy-translations-app")
);
}
async function buildRollup(config) {
const bundle = await rollup.rollup(config.inputOptions);
await bundle.write(config.outputOptions);
}
gulp.task("rollup-watch-app", () => {
watchRollup(rollupConfig.createAppConfig);
});
gulp.task("rollup-watch-hassio", () => {
watchRollup(rollupConfig.createHassioConfig, ["hassio/src/**"]);
});
gulp.task("rollup-dev-server-demo", () => {
watchRollup(rollupConfig.createDemoConfig, ["demo/src/**"], {
root: paths.demo_output_root,
port: 8090,
});
});
gulp.task("rollup-dev-server-cast", () => {
watchRollup(rollupConfig.createCastConfig, ["cast/src/**"], {
root: paths.cast_output_root,
port: 8080,
networkAccess: true,
});
});
gulp.task("rollup-dev-server-gallery", () => {
watchRollup(rollupConfig.createGalleryConfig, ["gallery/src/**"], {
root: paths.gallery_output_root,
port: 8100,
});
});
gulp.task(
"rollup-prod-app",
bothBuilds(rollupConfig.createAppConfig, { isProdBuild: true })
);
gulp.task(
"rollup-prod-demo",
bothBuilds(rollupConfig.createDemoConfig, { isProdBuild: true })
);
gulp.task(
"rollup-prod-cast",
bothBuilds(rollupConfig.createCastConfig, { isProdBuild: true })
);
gulp.task("rollup-prod-hassio", () =>
buildRollup(
rollupConfig.createHassioConfig({
isProdBuild: true,
latestBuild: false,
})
)
);
gulp.task("rollup-prod-gallery", () =>
buildRollup(
rollupConfig.createGalleryConfig({
isProdBuild: true,
latestBuild: true,
})
)
);

View File

@@ -5,18 +5,22 @@
const gulp = require("gulp");
const path = require("path");
const fs = require("fs-extra");
const config = require("../paths.js");
const workboxBuild = require("workbox-build");
const sourceMapUrl = require("source-map-url");
const paths = require("../paths.js");
const swPath = path.resolve(config.root, "service_worker.js");
const swDest = path.resolve(paths.app_output_root, "service_worker.js");
const writeSW = (content) => fs.outputFileSync(swPath, content.trim() + "\n");
const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n");
gulp.task("gen-service-worker-dev", (done) => {
gulp.task("gen-service-worker-app-dev", (done) => {
writeSW(
`
console.debug('Service worker disabled in development');
self.addEventListener('install', (event) => {
// This will activate the dev service worker,
// removing any prod service worker the dev might have running
self.skipWaiting();
});
`
@@ -24,10 +28,69 @@ self.addEventListener('install', (event) => {
done();
});
gulp.task("gen-service-worker-prod", (done) => {
fs.copySync(
path.resolve(config.output, "service_worker.js"),
path.resolve(config.root, "service_worker.js")
gulp.task("gen-service-worker-app-prod", async () => {
// Read bundled source file
const bundleManifestLatest = require(path.resolve(
paths.app_output_latest,
"manifest.json"
));
let serviceWorkerContent = fs.readFileSync(
paths.app_output_root + bundleManifestLatest["service_worker.js"],
"utf-8"
);
done();
// Delete old file from frontend_latest so manifest won't pick it up
fs.removeSync(
paths.app_output_root + bundleManifestLatest["service_worker.js"]
);
fs.removeSync(
paths.app_output_root + bundleManifestLatest["service_worker.js.map"]
);
// Remove ES5
const bundleManifestES5 = require(path.resolve(
paths.app_output_es5,
"manifest.json"
));
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]);
fs.removeSync(
paths.app_output_root + bundleManifestES5["service_worker.js.map"]
);
const workboxManifest = await workboxBuild.getManifest({
// Files that mach this pattern will be considered unique and skip revision check
// ignore JS files + translation files
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/,
globDirectory: paths.app_output_root,
globPatterns: [
"frontend_latest/*.js",
// Cache all English translations because we catch them as fallback
// Using pattern to match hash instead of * to avoid caching en-GB
// 'v' added as valid hash letter because in dev we hash with 'dev'
"static/translations/**/en-+([a-fv0-9]).json",
// Icon shown on splash screen
"static/icons/favicon-192x192.png",
"static/icons/favicon.ico",
// Common fonts
"static/fonts/roboto/Roboto-Light.woff2",
"static/fonts/roboto/Roboto-Medium.woff2",
"static/fonts/roboto/Roboto-Regular.woff2",
"static/fonts/roboto/Roboto-Bold.woff2",
],
});
for (const warning of workboxManifest.warnings) {
console.warn(warning);
}
// remove source map and add WB manifest
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
serviceWorkerContent = serviceWorkerContent.replace(
"WB_MANIFEST",
JSON.stringify(workboxManifest.manifestEntries)
);
// Write new file to root
fs.writeFileSync(swDest, serviceWorkerContent);
});

View File

@@ -38,9 +38,9 @@ const runDevServer = ({
const handler = (done) => (err, stats) => {
if (err) {
console.log(err.stack || err);
log.error(err.stack || err);
if (err.details) {
console.log(err.details);
log.error(err.details);
}
return;
}
@@ -48,7 +48,7 @@ const handler = (done) => (err, stats) => {
log(`Build done @ ${new Date().toLocaleTimeString()}`);
if (stats.hasErrors() || stats.hasWarnings()) {
console.log(stats.toString("minimal"));
log.warn(stats.toString("minimal"));
}
if (done) {
@@ -64,7 +64,7 @@ gulp.task("webpack-watch-app", () => {
);
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series("build-translations", "copy-translations")
gulp.series("build-translations", "copy-translations-app")
);
});
@@ -82,7 +82,7 @@ gulp.task(
gulp.task("webpack-dev-server-demo", () => {
runDevServer({
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
contentBase: paths.demo_root,
contentBase: paths.demo_output_root,
port: 8090,
});
});
@@ -103,7 +103,7 @@ gulp.task(
gulp.task("webpack-dev-server-cast", () => {
runDevServer({
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
contentBase: paths.cast_root,
contentBase: paths.cast_output_root,
port: 8080,
// Accessible from the network, because that's how Cast hits it.
listenHost: "0.0.0.0",
@@ -152,7 +152,7 @@ gulp.task("webpack-dev-server-gallery", () => {
runDevServer({
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
contentBase: paths.gallery_root,
contentBase: paths.gallery_output_root,
port: 8100,
});
});

View File

@@ -4,30 +4,36 @@ module.exports = {
polymer_dir: path.resolve(__dirname, ".."),
build_dir: path.resolve(__dirname, "../build"),
root: path.resolve(__dirname, "../hass_frontend"),
static: path.resolve(__dirname, "../hass_frontend/static"),
output: path.resolve(__dirname, "../hass_frontend/frontend_latest"),
output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
app_output_root: path.resolve(__dirname, "../hass_frontend"),
app_output_static: path.resolve(__dirname, "../hass_frontend/static"),
app_output_latest: path.resolve(
__dirname,
"../hass_frontend/frontend_latest"
),
app_output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
demo_dir: path.resolve(__dirname, "../demo"),
demo_root: path.resolve(__dirname, "../demo/dist"),
demo_static: path.resolve(__dirname, "../demo/dist/static"),
demo_output: path.resolve(__dirname, "../demo/dist/frontend_latest"),
demo_output_root: path.resolve(__dirname, "../demo/dist"),
demo_output_static: path.resolve(__dirname, "../demo/dist/static"),
demo_output_latest: path.resolve(__dirname, "../demo/dist/frontend_latest"),
demo_output_es5: path.resolve(__dirname, "../demo/dist/frontend_es5"),
cast_dir: path.resolve(__dirname, "../cast"),
cast_root: path.resolve(__dirname, "../cast/dist"),
cast_static: path.resolve(__dirname, "../cast/dist/static"),
cast_output: path.resolve(__dirname, "../cast/dist/frontend_latest"),
cast_output_root: path.resolve(__dirname, "../cast/dist"),
cast_output_static: path.resolve(__dirname, "../cast/dist/static"),
cast_output_latest: path.resolve(__dirname, "../cast/dist/frontend_latest"),
cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"),
gallery_dir: path.resolve(__dirname, "../gallery"),
gallery_root: path.resolve(__dirname, "../gallery/dist"),
gallery_output: path.resolve(__dirname, "../gallery/dist/frontend_latest"),
gallery_static: path.resolve(__dirname, "../gallery/dist/static"),
gallery_output_root: path.resolve(__dirname, "../gallery/dist"),
gallery_output_latest: path.resolve(
__dirname,
"../gallery/dist/frontend_latest"
),
gallery_output_static: path.resolve(__dirname, "../gallery/dist/static"),
hassio_dir: path.resolve(__dirname, "../hassio"),
hassio_root: path.resolve(__dirname, "../hassio/build"),
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
hassio_publicPath: "/api/hassio/app/",
translations_src: path.resolve(__dirname, "../src/translations"),

View File

@@ -0,0 +1,14 @@
module.exports = function (opts = {}) {
const dontHash = opts.dontHash || new Set();
return {
name: "dont-hash",
renderChunk(_code, chunk, _options) {
if (!chunk.isEntry || !dontHash.has(chunk.name)) {
return null;
}
chunk.fileName = `${chunk.name}.js`;
return null;
},
};
};

View File

@@ -0,0 +1,26 @@
const path = require("path");
module.exports = function (userOptions = {}) {
// Files need to be absolute paths.
// This only works if the file has no exports
// and only is imported for its side effects
const files = userOptions.files || [];
if (files.length === 0) {
return {
name: "ignore",
};
}
return {
name: "ignore",
load(id) {
return files.some((toIgnorePath) => id.startsWith(toIgnorePath))
? {
code: "",
}
: null;
},
};
};

View File

@@ -0,0 +1,34 @@
const url = require("url");
const defaultOptions = {
publicPath: "",
};
module.exports = function (userOptions = {}) {
const options = { ...defaultOptions, ...userOptions };
return {
name: "manifest",
generateBundle(outputOptions, bundle) {
const manifest = {};
for (const chunk of Object.values(bundle)) {
if (!chunk.isEntry) {
continue;
}
// Add js extension to mimic Webpack manifest.
manifest[`${chunk.name}.js`] = url.resolve(
options.publicPath,
chunk.fileName
);
}
this.emitFile({
type: "asset",
source: JSON.stringify(manifest, undefined, 2),
name: "manifest.json",
fileName: "manifest.json",
});
},
};
};

View File

@@ -0,0 +1,149 @@
// Worker plugin
// Each worker will include all of its dependencies
// instead of relying on an importer.
// Forked from v.1.4.1
// https://github.com/surma/rollup-plugin-off-main-thread
/**
* Copyright 2018 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const rollup = require("rollup");
const path = require("path");
const MagicString = require("magic-string");
const defaultOpts = {
// A RegExp to find `new Workers()` calls. The second capture group _must_
// capture the provided file name without the quotes.
workerRegexp: /new Worker\((["'])(.+?)\1(,[^)]+)?\)/g,
plugins: ["node-resolve", "commonjs", "babel", "terser", "ignore"],
};
async function getBundledWorker(workerPath, rollupOptions) {
const bundle = await rollup.rollup({
...rollupOptions,
input: {
worker: workerPath,
},
});
const { output } = await bundle.generate({
// Generates cleanest output, we shouldn't have any imports/exports
// that would be incompatible with ES5.
format: "es",
// We should not export anything. This will fail build if we are.
exports: "none",
});
let code;
for (const chunkOrAsset of output) {
if (chunkOrAsset.name === "worker") {
code = chunkOrAsset.code;
} else if (chunkOrAsset.type !== "asset") {
throw new Error("Unexpected extra output");
}
}
return code;
}
module.exports = function (opts = {}) {
opts = { ...defaultOpts, ...opts };
let rollupOptions;
let refIds;
return {
name: "hass-worker",
async buildStart(options) {
refIds = {};
rollupOptions = {
plugins: options.plugins.filter((plugin) =>
opts.plugins.includes(plugin.name)
),
};
},
async transform(code, id) {
// Copy the regexp as they are stateful and this hook is async.
const workerRegexp = new RegExp(
opts.workerRegexp.source,
opts.workerRegexp.flags
);
if (!workerRegexp.test(code)) {
return;
}
const ms = new MagicString(code);
// Reset the regexp
workerRegexp.lastIndex = 0;
while (true) {
const match = workerRegexp.exec(code);
if (!match) {
break;
}
const workerFile = match[2];
let optionsObject = {};
// Parse the optional options object
if (match[3] && match[3].length > 0) {
// FIXME: ooooof!
optionsObject = new Function(`return ${match[3].slice(1)};`)();
}
delete optionsObject.type;
if (!new RegExp("^.*/").test(workerFile)) {
this.warn(
`Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`
);
continue;
}
// Find worker file and store it as a chunk with ID prefixed for our loader
const resolvedWorkerFile = (await this.resolve(workerFile, id)).id;
let chunkRefId;
if (resolvedWorkerFile in refIds) {
chunkRefId = refIds[resolvedWorkerFile];
} else {
this.addWatchFile(resolvedWorkerFile);
const source = await getBundledWorker(
resolvedWorkerFile,
rollupOptions
);
chunkRefId = refIds[resolvedWorkerFile] = this.emitFile({
name: path.basename(resolvedWorkerFile),
source,
type: "asset",
});
}
const workerParametersStartIndex = match.index + "new Worker(".length;
const workerParametersEndIndex =
match.index + match[0].length - ")".length;
ms.overwrite(
workerParametersStartIndex,
workerParametersEndIndex,
`import.meta.ROLLUP_FILE_URL_${chunkRefId}, ${JSON.stringify(
optionsObject
)}`
);
}
return {
code: ms.toString(),
map: ms.generateMap({ hires: true }),
};
},
};
};

151
build-scripts/rollup.js Normal file
View File

@@ -0,0 +1,151 @@
const path = require("path");
const commonjs = require("@rollup/plugin-commonjs");
const resolve = require("@rollup/plugin-node-resolve");
const json = require("@rollup/plugin-json");
const babel = require("rollup-plugin-babel");
const replace = require("@rollup/plugin-replace");
const visualizer = require("rollup-plugin-visualizer");
const { string } = require("rollup-plugin-string");
const { terser } = require("rollup-plugin-terser");
const manifest = require("./rollup-plugins/manifest-plugin");
const worker = require("./rollup-plugins/worker-plugin");
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin");
const ignore = require("./rollup-plugins/ignore-plugin");
const bundle = require("./bundle");
const paths = require("./paths");
const extensions = [".js", ".ts"];
/**
* @param {Object} arg
* @param { import("rollup").InputOption } arg.input
*/
const createRollupConfig = ({
entry,
outputPath,
defineOverlay,
isProdBuild,
latestBuild,
isStatsBuild,
publicPath,
dontHash,
}) => {
return {
/**
* @type { import("rollup").InputOptions }
*/
inputOptions: {
input: entry,
// Some entry points contain no JavaScript. This setting silences a warning about that.
// https://rollupjs.org/guide/en/#preserveentrysignatures
preserveEntrySignatures: false,
plugins: [
ignore({
files: bundle.emptyPackages({ latestBuild }),
}),
resolve({
extensions,
preferBuiltins: false,
browser: true,
rootDir: paths.polymer_dir,
}),
commonjs({
namedExports: {
"js-yaml": ["safeDump", "safeLoad"],
},
}),
json(),
babel({
...bundle.babelOptions({ latestBuild }),
extensions,
exclude: bundle.babelExclude(),
}),
string({
// Import certain extensions as strings
include: [path.join(paths.polymer_dir, "node_modules/**/*.css")],
}),
replace(
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
),
manifest({
publicPath,
}),
worker(),
dontHashPlugin({ dontHash }),
isProdBuild && terser(bundle.terserOptions(latestBuild)),
isStatsBuild &&
visualizer({
// https://github.com/btd/rollup-plugin-visualizer#options
open: true,
sourcemap: true,
}),
],
},
/**
* @type { import("rollup").OutputOptions }
*/
outputOptions: {
// https://rollupjs.org/guide/en/#outputdir
dir: outputPath,
// https://rollupjs.org/guide/en/#outputformat
format: latestBuild ? "es" : "systemjs",
// https://rollupjs.org/guide/en/#outputexternallivebindings
externalLiveBindings: false,
// https://rollupjs.org/guide/en/#outputentryfilenames
// https://rollupjs.org/guide/en/#outputchunkfilenames
// https://rollupjs.org/guide/en/#outputassetfilenames
entryFileNames:
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
chunkFileNames:
isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
assetFileNames:
isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
// https://rollupjs.org/guide/en/#outputsourcemap
sourcemap: isProdBuild ? true : "inline",
},
};
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
return createRollupConfig(
bundle.config.app({
isProdBuild,
latestBuild,
isStatsBuild,
})
);
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
return createRollupConfig(
bundle.config.demo({
isProdBuild,
latestBuild,
isStatsBuild,
})
);
};
const createCastConfig = ({ isProdBuild, latestBuild }) => {
return createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) => {
return createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
return createRollupConfig(
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = {
createAppConfig,
createDemoConfig,
createCastConfig,
createHassioConfig,
createGalleryConfig,
};

View File

@@ -1,15 +1,15 @@
const webpack = require("webpack");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");
const WorkerPlugin = require("worker-plugin");
const paths = require("./paths.js");
const env = require("./env.js");
const { babelLoaderConfig } = require("./babel.js");
const bundle = require("./bundle");
const createWebpackConfig = ({
entry,
outputRoot,
outputPath,
publicPath,
defineOverlay,
isProdBuild,
latestBuild,
@@ -19,22 +19,30 @@ const createWebpackConfig = ({
if (!dontHash) {
dontHash = new Set();
}
const ignorePackages = bundle.ignorePackages({ latestBuild });
return {
mode: isProdBuild ? "production" : "development",
devtool: isProdBuild ? "source-map" : "inline-cheap-module-source-map",
devtool: isProdBuild
? "cheap-module-source-map"
: "eval-cheap-module-source-map",
entry,
node: false,
module: {
rules: [
babelLoaderConfig({ latestBuild }),
{
test: /\.js$|\.ts$/,
exclude: bundle.babelExclude(),
use: {
loader: "babel-loader",
options: bundle.babelOptions({ latestBuild }),
},
},
{
test: /\.css$/,
use: "raw-loader",
},
],
},
externals: {
esprima: "esprima",
},
optimization: {
minimizer: [
new TerserPlugin({
@@ -42,45 +50,50 @@ const createWebpackConfig = ({
parallel: true,
extractComments: true,
sourceMap: true,
terserOptions: {
safari10: true,
ecma: latestBuild ? undefined : 5,
},
terserOptions: bundle.terserOptions(latestBuild),
}),
],
},
plugins: [
new ManifestPlugin(),
new webpack.DefinePlugin({
__DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(env.version()),
__DEMO__: false,
__BACKWARDS_COMPAT__: false,
__STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development"
),
...defineOverlay,
new WorkerPlugin(),
new ManifestPlugin({
// Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
}),
new webpack.DefinePlugin(
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
),
new webpack.IgnorePlugin({
checkResource(resource, context) {
// Only use ignore to intercept imports that we don't control
// inside node_module dependencies.
if (
!context.includes("/node_modules/") ||
// calling define.amd will call require("!!webpack amd options")
resource.startsWith("!!webpack")
) {
return false;
}
let fullPath;
try {
fullPath = resource.startsWith(".")
? path.resolve(context, resource)
: require.resolve(resource);
} catch (err) {
console.error("Error in ignore plugin", resource, context);
throw err;
}
return ignorePackages.some((toIgnorePath) =>
fullPath.startsWith(toIgnorePath)
);
},
}),
// Ignore moment.js locales
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Color.js is bloated, it contains all color definitions for all material color sets.
new webpack.NormalModuleReplacementPlugin(
/@polymer\/paper-styles\/color\.js$/,
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
// Ignore roboto pointing at CDN. We use local font-roboto-local.
new webpack.NormalModuleReplacementPlugin(
/@polymer\/font-roboto\/roboto\.js$/,
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
// Ignore mwc icons pointing at CDN.
new webpack.NormalModuleReplacementPlugin(
/@material\/mwc-icon\/mwc-icon-font\.js$/,
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
].filter(Boolean),
],
resolve: {
extensions: [".ts", ".js", ".json"],
},
@@ -95,147 +108,40 @@ const createWebpackConfig = ({
isProdBuild && !isStatsBuild
? "chunk.[chunkhash].js"
: "[name].chunk.js",
path: path.resolve(
outputRoot,
latestBuild ? "frontend_latest" : "frontend_es5"
),
publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/",
// For workerize loader
path: outputPath,
publicPath,
// To silence warning in worker plugin
globalObject: "self",
},
};
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
const config = createWebpackConfig({
entry: {
app: "./src/entrypoints/app.ts",
authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts",
core: "./src/entrypoints/core.ts",
compatibility: "./src/entrypoints/compatibility.ts",
"custom-panel": "./src/entrypoints/custom-panel.ts",
},
outputRoot: paths.root,
isProdBuild,
latestBuild,
isStatsBuild,
});
if (latestBuild) {
// Create an object mapping browser urls to their paths during build
const translationMetadata = require("../build-translations/translationMetadata.json");
const workBoxTranslationsTemplatedURLs = {};
const englishFilename = `en-${translationMetadata.translations.en.hash}.json`;
// core
workBoxTranslationsTemplatedURLs[
`/static/translations/${englishFilename}`
] = `build-translations/output/${englishFilename}`;
translationMetadata.fragments.forEach((fragment) => {
workBoxTranslationsTemplatedURLs[
`/static/translations/${fragment}/${englishFilename}`
] = `build-translations/output/${fragment}/${englishFilename}`;
});
config.plugins.push(
new WorkboxPlugin.InjectManifest({
swSrc: "./src/entrypoints/service-worker-hass.js",
swDest: "service_worker.js",
importWorkboxFrom: "local",
include: [/\.js$/],
templatedURLs: {
...workBoxTranslationsTemplatedURLs,
"/static/icons/favicon-192x192.png":
"public/icons/favicon-192x192.png",
"/static/fonts/roboto/Roboto-Light.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2",
"/static/fonts/roboto/Roboto-Medium.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2",
"/static/fonts/roboto/Roboto-Regular.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2",
"/static/fonts/roboto/Roboto-Bold.woff2":
"node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2",
},
})
);
}
return config;
return createWebpackConfig(
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild })
);
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
return createWebpackConfig({
entry: {
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
compatibility: path.resolve(
paths.polymer_dir,
"src/entrypoints/compatibility.ts"
),
},
outputRoot: paths.demo_root,
defineOverlay: {
__VERSION__: JSON.stringify(`DEMO-${env.version()}`),
__DEMO__: true,
},
isProdBuild,
latestBuild,
isStatsBuild,
});
return createWebpackConfig(
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
);
};
const createCastConfig = ({ isProdBuild, latestBuild }) => {
const entry = {
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
};
if (latestBuild) {
entry.receiver = path.resolve(paths.cast_dir, "src/receiver/entrypoint.ts");
}
return createWebpackConfig({
entry,
outputRoot: paths.cast_root,
isProdBuild,
latestBuild,
defineOverlay: {
__BACKWARDS_COMPAT__: true,
},
});
return createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) => {
if (latestBuild) {
throw new Error("Hass.io does not support latest build!");
}
const config = createWebpackConfig({
entry: {
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
},
outputRoot: "",
isProdBuild,
latestBuild,
dontHash: new Set(["entrypoint"]),
});
config.output.path = paths.hassio_root;
config.output.publicPath = paths.hassio_publicPath;
return config;
return createWebpackConfig(
bundle.config.hassio({ isProdBuild, latestBuild })
);
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
const config = createWebpackConfig({
entry: {
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
},
outputRoot: paths.gallery_root,
isProdBuild,
latestBuild,
});
return config;
return createWebpackConfig(
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = {

10
cast/rollup.config.js Normal file
View File

@@ -0,0 +1,10 @@
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
const config = rollup.createCastConfig({
isProdBuild: env.isProdBuild(),
latestBuild: true,
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };

View File

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

View File

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

View File

@@ -184,7 +184,7 @@ export class HcConnect extends LitElement {
this.castManager = null;
}
);
registerServiceWorker(false);
registerServiceWorker(this, false);
}
private async _handleDemo() {

View File

@@ -1,3 +1,4 @@
import "web-animations-js/web-animations-next-lite.min";
import "../../../src/resources/roboto";
import "../../../src/resources/ha-style";
import "./layout/hc-lovelace";

View File

@@ -1,6 +1,6 @@
{
"background_color": "#FFFFFF",
"description": "Open-source home automation platform running on Python 3.",
"description": "Home automation platform that puts local control and privacy first.",
"dir": "ltr",
"display": "standalone",
"icons": [
@@ -31,7 +31,7 @@
],
"lang": "en-US",
"name": "Home Assistant Demo",
"short_name": "Demo",
"short_name": "HA Demo",
"start_url": "/?homescreen=1",
"theme_color": "#03A9F4"
}

10
demo/rollup.config.js Normal file
View File

@@ -0,0 +1,10 @@
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
const config = rollup.createDemoConfig({
isProdBuild: env.isProdBuild(),
latestBuild: true,
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };

View File

@@ -7,5 +7,5 @@ set -e
cd "$(dirname "$0")/.."
STATS=1 NODE_ENV=production ../node_modules/.bin/webpack --profile --json > compilation-stats.json
npx webpack-bundle-analyzer compilation-stats.json dist
npx webpack-bundle-analyzer compilation-stats.json dist/frontend_latest
rm compilation-stats.json

View File

@@ -63,7 +63,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
elements: [
{
style: {
"--mdc-icon-size": "100px",
"--mdc-icon-size": "100%",
top: "50%",
left: "50%",
},

View File

@@ -1,4 +1,3 @@
import "@polymer/paper-styles/typography";
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style";

View File

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

View File

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

10
gallery/rollup.config.js Normal file
View File

@@ -0,0 +1,10 @@
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
const config = rollup.createGalleryConfig({
isProdBuild: env.isProdBuild(),
latestBuild: true,
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };

View File

@@ -1,4 +1,3 @@
import "@polymer/paper-styles/typography";
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style";

View File

@@ -10,6 +10,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../src/components/ha-card";
import "../../src/managers/notification-manager";
import "../../src/styles/polymer-ha-style";
// eslint-disable-next-line no-undef
const DEMOS = require.context("./demos", true, /^(.*\.(ts$))[^.]*$/im);

10
hassio/rollup.config.js Normal file
View File

@@ -0,0 +1,10 @@
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
const config = rollup.createHassioConfig({
isProdBuild: env.isProdBuild(),
latestBuild: false,
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };

View File

@@ -1,4 +1,4 @@
import "@polymer/paper-card/paper-card";
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import {
css,
CSSResultArray,
@@ -10,6 +10,7 @@ import {
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate";
import "../../../src/components/ha-card";
import {
HassioAddonInfo,
HassioAddonRepository,
@@ -66,7 +67,7 @@ class HassioAddonRepositoryEl extends LitElement {
<div class="card-group">
${addons.map(
(addon) => html`
<paper-card
<ha-card
.addon=${addon}
class=${addon.available ? "" : "not_available"}
@click=${this._addonTapped}
@@ -78,8 +79,8 @@ class HassioAddonRepositoryEl extends LitElement {
.description=${addon.description}
.available=${addon.available}
.icon=${addon.installed && addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle"}
? mdiArrowUpBoldCircle
: mdiPuzzle}
.iconTitle=${addon.installed
? addon.installed !== addon.version
? "New version available"
@@ -111,7 +112,7 @@ class HassioAddonRepositoryEl extends LitElement {
: ""}
></hassio-card-content>
</div>
</paper-card>
</ha-card>
`
)}
</div>
@@ -127,7 +128,7 @@ class HassioAddonRepositoryEl extends LitElement {
return [
hassioStyle,
css`
paper-card {
ha-card {
cursor: pointer;
}
.not_available {

View File

@@ -1,3 +1,6 @@
import "@material/mwc-icon-button/mwc-icon-button";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import {
css,
CSSResult,
@@ -6,22 +9,21 @@ import {
PropertyValues,
} from "lit-element";
import { html, TemplateResult } from "lit-html";
import "../../../src/common/search/search-input";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-svg-icon";
import {
fetchHassioAddonsInfo,
HassioAddonInfo,
HassioAddonRepository,
reloadHassioAddons,
} from "../../../src/data/hassio/addon";
import "../../../src/components/ha-icon-button";
import "../../../src/layouts/loading-screen";
import "../../../src/layouts/hass-tabs-subpage";
import "../../../src/layouts/loading-screen";
import { HomeAssistant, Route } from "../../../src/types";
import "../../../src/common/search/search-input";
import "./hassio-addon-repository";
import { supervisorTabs } from "../hassio-panel";
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
import { supervisorTabs } from "../hassio-panel";
import "./hassio-addon-repository";
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
if (a.slug === "local") {
@@ -94,27 +96,17 @@ class HassioAddonStore extends LitElement {
.tabs=${supervisorTabs}
>
<span slot="header">Add-on store</span>
<paper-menu-button
close-on-activate
no-animations
horizontal-align="right"
horizontal-offset="-5"
slot="toolbar-icon"
>
<ha-icon-button
icon="hassio:dots-vertical"
slot="dropdown-trigger"
alt="menu"
></ha-icon-button>
<paper-listbox slot="dropdown-content" role="listbox">
<paper-item @tap=${this._manageRepositories}>
Repositories
</paper-item>
<paper-item @tap=${this.refreshData}>
Reload
</paper-item>
</paper-listbox>
</paper-menu-button>
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item @tap=${this._manageRepositories}>
Repositories
</mwc-list-item>
<mwc-list-item @tap=${this.refreshData}>
Reload
</mwc-list-item>
</ha-button-menu>
${repos.length === 0
? html`<loading-screen></loading-screen>`
: html`

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
@@ -14,6 +13,7 @@ import {
TemplateResult,
} from "lit-element";
import "web-animations-js/web-animations-next-lite.min";
import "../../../../src/components/ha-card";
import {
HassioAddonDetails,
HassioAddonSetOptionParams,
@@ -23,9 +23,9 @@ import {
fetchHassioHardwareAudio,
HassioHardwareAudioDevice,
} from "../../../../src/data/hassio/hardware";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style";
@customElement("hassio-addon-audio")
@@ -46,7 +46,7 @@ class HassioAddonAudio extends LitElement {
protected render(): TemplateResult {
return html`
<paper-card heading="Audio">
<ha-card header="Audio">
<div class="card-content">
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
@@ -92,7 +92,7 @@ class HassioAddonAudio extends LitElement {
<div class="card-actions">
<mwc-button @click=${this._saveSettings}>Save</mwc-button>
</div>
</paper-card>
</ha-card>
`;
}
@@ -102,7 +102,7 @@ class HassioAddonAudio extends LitElement {
hassioStyle,
css`
:host,
paper-card,
ha-card,
paper-dropdown-menu {
display: block;
}

View File

@@ -8,12 +8,10 @@ import {
property,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../src/types";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { hassioStyle } from "../../resources/hassio-style";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import "./hassio-addon-audio";
import "./hassio-addon-config";
import "./hassio-addon-network";

View File

@@ -1,6 +1,5 @@
import "@material/mwc-button";
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
@@ -13,6 +12,7 @@ import {
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
import {
@@ -23,9 +23,8 @@ import {
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style";
@customElement("hassio-addon-config")
class HassioAddonConfig extends LitElement {
@@ -46,7 +45,7 @@ class HassioAddonConfig extends LitElement {
return html`
<h1>${this.addon.name}</h1>
<paper-card heading="Configuration">
<ha-card header="Configuration">
<div class="card-content">
<ha-yaml-editor
@value-changed=${this._configChanged}
@@ -65,7 +64,7 @@ class HassioAddonConfig extends LitElement {
Save
</mwc-button>
</div>
</paper-card>
</ha-card>
`;
}
@@ -77,7 +76,7 @@ class HassioAddonConfig extends LitElement {
:host {
display: block;
}
paper-card {
ha-card {
display: block;
}
.card-actions {

View File

@@ -1,4 +1,3 @@
import "@polymer/paper-card/paper-card";
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
@@ -11,15 +10,15 @@ import {
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-card";
import {
HassioAddonDetails,
HassioAddonSetOptionParams,
setHassioAddonOption,
} from "../../../../src/data/hassio/addon";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style";
interface NetworkItem {
@@ -53,7 +52,7 @@ class HassioAddonNetwork extends LitElement {
}
return html`
<paper-card heading="Network">
<ha-card header="Network">
<div class="card-content">
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
@@ -90,7 +89,7 @@ class HassioAddonNetwork extends LitElement {
</mwc-button>
<mwc-button @click=${this._saveTapped}>Save</mwc-button>
</div>
</paper-card>
</ha-card>
`;
}
@@ -102,7 +101,7 @@ class HassioAddonNetwork extends LitElement {
:host {
display: block;
}
paper-card {
ha-card {
display: block;
}
.errors {

View File

@@ -1,5 +1,4 @@
import "@polymer/paper-spinner/paper-spinner-lite";
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
@@ -9,16 +8,15 @@ import {
property,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../src/types";
import {
HassioAddonDetails,
fetchHassioAddonDocumentation,
} from "../../../../src/data/hassio/addon";
import "../../../../src/components/ha-markdown";
import {
fetchHassioAddonDocumentation,
HassioAddonDetails,
} from "../../../../src/data/hassio/addon";
import "../../../../src/layouts/loading-screen";
import { hassioStyle } from "../../resources/hassio-style";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
@customElement("hassio-addon-documentation-tab")
class HassioAddonDocumentationDashboard extends LitElement {
@@ -41,14 +39,14 @@ class HassioAddonDocumentationDashboard extends LitElement {
}
return html`
<div class="content">
<paper-card>
<ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<div class="card-content">
${this._content
? html`<ha-markdown .content=${this._content}></ha-markdown>`
: html`<loading-screen></loading-screen>`}
</div>
</paper-card>
</ha-card>
</div>
`;
}
@@ -58,7 +56,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
haStyle,
hassioStyle,
css`
paper-card {
ha-card {
display: block;
}
.content {
@@ -66,6 +64,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
padding: 8px;
max-width: 1024px;
}
ha-markdown {
padding: 16px;
}
`,
];
}

View File

@@ -1,4 +1,9 @@
import "../../../src/components/ha-icon-button";
import {
mdiCogs,
mdiFileDocument,
mdiInformationVariant,
mdiMathLog,
} from "@mdi/js";
import "@polymer/paper-spinner/paper-spinner-lite";
import {
css,
@@ -14,18 +19,17 @@ import {
fetchHassioAddonInfo,
HassioAddonDetails,
} from "../../../src/data/hassio/addon";
import "../../../src/layouts/hass-tabs-subpage";
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { hassioStyle } from "../resources/hassio-style";
import "./config/hassio-addon-audio";
import "./config/hassio-addon-config";
import "./config/hassio-addon-network";
import "./hassio-addon-router";
import "./info/hassio-addon-info";
import "./log/hassio-addon-logs";
import "./config/hassio-addon-network";
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
import "../../../src/layouts/hass-tabs-subpage";
import "./hassio-addon-router";
@customElement("hassio-addon-dashboard")
class HassioAddonDashboard extends LitElement {
@@ -59,7 +63,7 @@ class HassioAddonDashboard extends LitElement {
{
name: "Info",
path: `/hassio/addon/${this.addon.slug}/info`,
icon: "hassio:information-variant",
iconPath: mdiInformationVariant,
},
];
@@ -67,7 +71,7 @@ class HassioAddonDashboard extends LitElement {
addonTabs.push({
name: "Documentation",
path: `/hassio/addon/${this.addon.slug}/documentation`,
icon: "hassio:file-document",
iconPath: mdiFileDocument,
});
}
@@ -76,12 +80,12 @@ class HassioAddonDashboard extends LitElement {
{
name: "Configuration",
path: `/hassio/addon/${this.addon.slug}/config`,
icon: "hassio:cogs",
iconPath: mdiCogs,
},
{
name: "Log",
path: `/hassio/addon/${this.addon.slug}/logs`,
icon: "hassio:math-log",
iconPath: mdiMathLog,
}
);
}
@@ -115,7 +119,6 @@ class HassioAddonDashboard extends LitElement {
css`
:host {
color: var(--primary-text-color);
--paper-card-header-color: var(--primary-text-color);
}
.content {
padding: 24px 0 32px;

View File

@@ -1,15 +1,15 @@
import { customElement, property } from "lit-element";
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
import {
HassRouterPage,
RouterOptions,
} from "../../../src/layouts/hass-router-page";
import { customElement, property } from "lit-element";
import { HomeAssistant } from "../../../src/types";
import "./config/hassio-addon-config-tab";
import "./documentation/hassio-addon-documentation-tab";
// Don't codesplit the others, because it breaks the UI when pushed to a Pi
import "./info/hassio-addon-info-tab";
import "./config/hassio-addon-config-tab";
import "./log/hassio-addon-log-tab";
import "./documentation/hassio-addon-documentation-tab";
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
@customElement("hassio-addon-router")
class HassioAddonRouter extends HassRouterPage {

View File

@@ -8,12 +8,10 @@ import {
property,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../src/types";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { hassioStyle } from "../../resources/hassio-style";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import "./hassio-addon-info";
@customElement("hassio-addon-info-tab")

View File

@@ -1,5 +1,20 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import {
mdiArrowUpBoldCircle,
mdiCheckCircle,
mdiChip,
mdiCircle,
mdiCursorDefaultClickOutline,
mdiDocker,
mdiExclamationThick,
mdiFlask,
mdiHomeAssistant,
mdiInformation,
mdiKey,
mdiNetwork,
mdiPound,
mdiShield,
} from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import {
css,
@@ -16,10 +31,11 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { navigate } from "../../../../src/common/navigate";
import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-markdown";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import "../../../../src/components/ha-icon";
import {
fetchHassioAddonChangelog,
HassioAddonDetails,
@@ -30,23 +46,23 @@ import {
setHassioAddonSecurity,
uninstallHassioAddon,
} from "../../../../src/data/hassio/addon";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-card-content";
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
import { hassioStyle } from "../../resources/hassio-style";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
const STAGE_ICON = {
stable: "mdi:check-circle",
experimental: "mdi:flask",
deprecated: "mdi:exclamation-thick",
stable: mdiCheckCircle,
experimental: mdiFlask,
deprecated: mdiExclamationThick,
};
const PERMIS_DESC = {
stage: {
title: "Add-on Stage",
description: `Add-ons can have one of three stages:\n\n<ha-icon icon='${STAGE_ICON.stable}'></ha-icon>**Stable**: These are add-ons ready to be used in production.\n<ha-icon icon='${STAGE_ICON.experimental}'></ha-icon>**Experimental**: These may contain bugs, and may be unfinished.\n<ha-icon icon='${STAGE_ICON.deprecated}'></ha-icon>**Deprecated**: These add-ons will no longer receive any updates.`,
description: `Add-ons can have one of three stages:\n\n<ha-svg-icon path='${STAGE_ICON.stable}'></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon path='${STAGE_ICON.experimental}'></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon path='${STAGE_ICON.deprecated}'></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`,
},
rating: {
title: "Add-on Security Rating",
@@ -116,7 +132,7 @@ class HassioAddonInfo extends LitElement {
return html`
${this._computeUpdateAvailable
? html`
<paper-card heading="Update available! 🎉">
<ha-card header="Update available! 🎉">
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
@@ -124,7 +140,7 @@ class HassioAddonInfo extends LitElement {
.version_latest} is available"
.description="You are currently running version ${this.addon
.version}"
icon="hassio:arrow-up-bold-circle"
icon=${mdiArrowUpBoldCircle}
iconClass="update"
></hassio-card-content>
${!this.addon.available
@@ -151,12 +167,13 @@ class HassioAddonInfo extends LitElement {
`
: ""}
</div>
</paper-card>
</ha-card>
`
: ""}
${!this.addon.protected
? html`
<paper-card heading="Warning: Protection mode is disabled!" class="warning">
<ha-card class="warning">
<div class="card-header">Warning: Protection mode is disabled!</div>
<div class="card-content">
Protection mode on this add-on is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this add-on.
</div>
@@ -164,11 +181,11 @@ class HassioAddonInfo extends LitElement {
<mwc-button @click=${this._protectionToggled}>Enable Protection mode</mwc-button>
</div>
</div>
</paper-card>
</ha-card>
`
: ""}
<paper-card>
<ha-card>
<div class="card-content">
<div class="addon-header">
${!this.narrow ? this.addon.name : ""}
@@ -177,18 +194,18 @@ class HassioAddonInfo extends LitElement {
? html`
${this._computeIsRunning
? html`
<ha-icon
<ha-svg-icon
title="Add-on is running"
class="running"
icon="hassio:circle"
></ha-icon>
path=${mdiCircle}
></ha-svg-icon>
`
: html`
<ha-icon
<ha-svg-icon
title="Add-on is stopped"
class="stopped"
icon="hassio:circle"
></ha-icon>
path=${mdiCircle}
></ha-svg-icon>
`}
`
: html` ${this.addon.version_latest} `}
@@ -232,10 +249,11 @@ class HassioAddonInfo extends LitElement {
})}
@click=${this._showMoreInfo}
id="stage"
.icon=${STAGE_ICON[this.addon.stage]}
label="stage"
description=""
></ha-label-badge>
>
<ha-svg-icon .path=${STAGE_ICON[this.addon.stage]}></ha-svg-icon>
</ha-label-badge>
<ha-label-badge
class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)),
@@ -253,10 +271,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="host_network"
icon="hassio:network"
label="host"
description=""
></ha-label-badge>
>
<ha-svg-icon path=${mdiNetwork}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.full_access
@@ -264,10 +283,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="full_access"
icon="hassio:chip"
label="hardware"
description=""
></ha-label-badge>
>
<ha-svg-icon path=${mdiChip}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.homeassistant_api
@@ -275,10 +295,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="homeassistant_api"
icon="hassio:home-assistant"
label="hass"
description=""
></ha-label-badge>
>
<ha-svg-icon path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this._computeHassioApi
@@ -286,10 +307,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="hassio_api"
icon="hassio:home-assistant"
label="hassio"
.description=${this.addon.hassio_role}
></ha-label-badge>
>
<ha-svg-icon path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.docker_api
@@ -297,10 +319,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="docker_api"
icon="hassio:docker"
label="docker"
description=""
></ha-label-badge>
>
<ha-svg-icon path=${mdiDocker}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.host_pid
@@ -308,10 +331,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="host_pid"
icon="hassio:pound"
label="host pid"
description=""
></ha-label-badge>
>
<ha-svg-icon path=${mdiPound}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.apparmor
@@ -320,10 +344,11 @@ class HassioAddonInfo extends LitElement {
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
icon="hassio:shield"
label="apparmor"
description=""
></ha-label-badge>
>
<ha-svg-icon path=${mdiShield}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.auth_api
@@ -331,10 +356,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="auth_api"
icon="hassio:key"
label="auth"
description=""
></ha-label-badge>
>
<ha-svg-icon path=${mdiKey}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.ingress
@@ -342,10 +368,13 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge
@click=${this._showMoreInfo}
id="ingress"
icon="hassio:cursor-default-click-outline"
label="ingress"
description=""
></ha-label-badge>
>
<ha-svg-icon
path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
</ha-label-badge>
`
: ""}
</div>
@@ -399,7 +428,7 @@ class HassioAddonInfo extends LitElement {
<div>
Protection mode
<span>
<ha-icon icon="hassio:information"></ha-icon>
<ha-svg-icon path=${mdiInformation}></ha-svg-icon>
<paper-tooltip>
Grant the add-on elevated system access.
</paper-tooltip>
@@ -502,17 +531,17 @@ class HassioAddonInfo extends LitElement {
</ha-progress-button>
`}
</div>
</paper-card>
</ha-card>
${this.addon.long_description
? html`
<paper-card>
<ha-card>
<div class="card-content">
<ha-markdown
.content=${this.addon.long_description}
></ha-markdown>
</div>
</paper-card>
</ha-card>
`
: ""}
`;
@@ -526,16 +555,21 @@ class HassioAddonInfo extends LitElement {
:host {
display: block;
}
paper-card {
ha-card {
display: block;
margin-bottom: 16px;
}
paper-card.warning {
ha-card.warning {
background-color: var(--google-red-500);
color: white;
--paper-card-header-color: white;
}
paper-card.warning mwc-button {
ha-card.warning .card-header {
color: white;
}
ha-card.warning .card-content {
color: white;
}
ha-card.warning mwc-button {
--mdc-theme-primary: white !important;
}
.warning {
@@ -548,7 +582,7 @@ class HassioAddonInfo extends LitElement {
.addon-header {
padding-left: 8px;
font-size: 24px;
color: var(--paper-card-header-color, --primary-text-color);
color: var(--ha-card-header-color, --primary-text-color);
}
.addon-version {
float: right;
@@ -575,7 +609,7 @@ class HassioAddonInfo extends LitElement {
width: 180px;
display: inline-block;
}
.state ha-icon {
.state ha-svg-icon {
width: 16px;
height: 16px;
color: var(--secondary-text-color);
@@ -583,10 +617,10 @@ class HassioAddonInfo extends LitElement {
ha-switch {
display: flex;
}
ha-icon.running {
ha-svg-icon.running {
color: var(--paper-green-400);
}
ha-icon.stopped {
ha-svg-icon.stopped {
color: var(--google-red-300);
}
ha-call-api-button {
@@ -596,14 +630,10 @@ class HassioAddonInfo extends LitElement {
.right {
float: right;
}
ha-markdown img {
max-width: 100%;
}
protection-enable mwc-button {
--mdc-theme-primary: white;
}
.description a,
ha-markdown a {
.description a {
color: var(--primary-color);
}
.red {
@@ -641,6 +671,9 @@ class HassioAddonInfo extends LitElement {
text-decoration: underline;
cursor: pointer;
}
ha-markdown {
padding: 16px;
}
`,
];
}
@@ -664,7 +697,7 @@ class HassioAddonInfo extends LitElement {
}
private _showMoreInfo(ev): void {
const id = ev.target.getAttribute("id");
const id = ev.currentTarget.id;
showHassioMarkdownDialog(this, {
title: PERMIS_DESC[id].title,
content: PERMIS_DESC[id].description,

View File

@@ -1,5 +1,4 @@
import "@polymer/paper-spinner/paper-spinner-lite";
import {
css,
CSSResult,
@@ -9,12 +8,10 @@ import {
property,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../src/types";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { hassioStyle } from "../../resources/hassio-style";
import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import "./hassio-addon-logs";
@customElement("hassio-addon-log-tab")

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
@@ -9,6 +8,7 @@ import {
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-card";
import {
fetchHassioAddonLogs,
HassioAddonDetails,
@@ -36,7 +36,7 @@ class HassioAddonLogs extends LitElement {
protected render(): TemplateResult {
return html`
<h1>${this.addon.name}</h1>
<paper-card>
<ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<div class="card-content">
${this._content
@@ -48,7 +48,7 @@ class HassioAddonLogs extends LitElement {
<div class="card-actions">
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
</div>
</paper-card>
</ha-card>
`;
}
@@ -58,7 +58,7 @@ class HassioAddonLogs extends LitElement {
hassioStyle,
css`
:host,
paper-card {
ha-card {
display: block;
}
.errors {

View File

@@ -1,3 +1,4 @@
import { mdiHelpCircle } from "@mdi/js";
import {
css,
CSSResult,
@@ -8,7 +9,7 @@ import {
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-relative-time";
import "../../../src/components/ha-icon";
import "../../../src/components/ha-svg-icon";
import { HomeAssistant } from "../../../src/types";
@customElement("hassio-card-content")
@@ -31,7 +32,7 @@ class HassioCardContent extends LitElement {
@property() public iconClass?: string;
@property() public icon = "hass:help-circle";
@property() public icon = mdiHelpCircle;
@property() public iconImage?: string;
@@ -48,11 +49,11 @@ class HassioCardContent extends LitElement {
</div>
`
: html`
<ha-icon
<ha-svg-icon
class=${this.iconClass}
.icon=${this.icon}
.path=${this.icon}
.title=${this.iconTitle}
></ha-icon>
></ha-svg-icon>
`}
<div>
<div class="title">
@@ -78,25 +79,25 @@ class HassioCardContent extends LitElement {
static get styles(): CSSResult {
return css`
ha-icon {
ha-svg-icon {
margin-right: 24px;
margin-left: 8px;
margin-top: 12px;
float: left;
color: var(--secondary-text-color);
}
ha-icon.update {
ha-svg-icon.update {
color: var(--paper-orange-400);
}
ha-icon.running,
ha-icon.installed {
ha-svg-icon.running,
ha-svg-icon.installed {
color: var(--paper-green-400);
}
ha-icon.hassupdate,
ha-icon.snapshot {
ha-svg-icon.hassupdate,
ha-svg-icon.snapshot {
color: var(--paper-item-icon-color);
}
ha-icon.not_available {
ha-svg-icon.not_available {
color: var(--google-red-500);
}
.title {

View File

@@ -1,13 +1,13 @@
import * as Fuse from "fuse.js";
import Fuse from "fuse.js";
import { HassioAddonInfo } from "../../../src/data/hassio/addon";
export function filterAndSort(addons: HassioAddonInfo[], filter: string) {
const options: Fuse.FuseOptions<HassioAddonInfo> = {
const options: Fuse.IFuseOptions<HassioAddonInfo> = {
keys: ["name", "description", "slug"],
caseSensitive: false,
isCaseSensitive: false,
minMatchCharLength: 2,
threshold: 0.2,
};
const fuse = new Fuse(addons, options);
return fuse.search(filter);
return fuse.search(filter).map((result) => result.item);
}

View File

@@ -1,4 +1,4 @@
import "@polymer/paper-card/paper-card";
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import {
css,
CSSResult,
@@ -10,6 +10,7 @@ import {
} from "lit-element";
import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate";
import "../../../src/components/ha-card";
import { HassioAddonInfo } from "../../../src/data/hassio/addon";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
@@ -29,19 +30,19 @@ class HassioAddons extends LitElement {
<div class="card-group">
${!this.addons
? html`
<paper-card>
<ha-card>
<div class="card-content">
You don't have any add-ons installed yet. Head over to
<a href="#" @click=${this._openStore}>the add-on store</a>
to get started!
</div>
</paper-card>
</ha-card>
`
: this.addons
.sort((a, b) => (a.name > b.name ? 1 : -1))
.map(
(addon) => html`
<paper-card .addon=${addon} @click=${this._addonTapped}>
<ha-card .addon=${addon} @click=${this._addonTapped}>
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
@@ -51,8 +52,8 @@ class HassioAddons extends LitElement {
.showTopbar=${addon.installed !== addon.version}
topbarClass="update"
.icon=${addon.installed !== addon.version
? "hassio:arrow-up-bold-circle"
: "hassio:puzzle"}
? mdiArrowUpBoldCircle
: mdiPuzzle}
.iconTitle=${addon.state !== "started"
? "Add-on is stopped"
: addon.installed !== addon.version
@@ -75,7 +76,7 @@ class HassioAddons extends LitElement {
: undefined}
></hassio-card-content>
</div>
</paper-card>
</ha-card>
`
)}
</div>
@@ -88,7 +89,7 @@ class HassioAddons extends LitElement {
haStyle,
hassioStyle,
css`
paper-card {
ha-card {
cursor: pointer;
}
`,

View File

@@ -12,14 +12,13 @@ import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import "../../../src/layouts/hass-tabs-subpage";
import { supervisorTabs } from "../hassio-panel";
import "./hassio-addons";
import "./hassio-update";
import { supervisorTabs } from "../hassio-panel";
@customElement("hassio-dashboard")
class HassioDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

View File

@@ -1,5 +1,5 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { mdiHomeAssistant } from "@mdi/js";
import {
css,
CSSResult,
@@ -10,15 +10,15 @@ import {
TemplateResult,
} from "lit-element";
import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon";
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import { haStyle } from "../../../src/resources/styles";
import "../../../src/components/ha-icon";
import { HomeAssistant } from "../../../src/types";
import "../components/hassio-card-content";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-update")
@@ -72,7 +72,7 @@ export class HassioUpdate extends LitElement {
`https://${
this.hassInfo.version_latest.includes("b") ? "rc" : "www"
}.home-assistant.io/latest-release-notes/`,
"hassio:home-assistant"
mdiHomeAssistant
)}
${this._renderUpdateCard(
"Supervisor",
@@ -107,12 +107,12 @@ export class HassioUpdate extends LitElement {
return html``;
}
return html`
<paper-card>
<ha-card>
<div class="card-content">
${icon
? html`
<div class="icon">
<ha-icon .icon=${icon}></ha-icon>
<ha-svg-icon .path=${icon}></ha-svg-icon>
</div>
`
: ""}
@@ -133,7 +133,7 @@ export class HassioUpdate extends LitElement {
Update
</ha-call-api-button>
</div>
</paper-card>
</ha-card>
`;
}

View File

@@ -1,7 +1,3 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import { PaperDialogElement } from "@polymer/paper-dialog";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "../../../../src/components/ha-icon-button";
import {
css,
CSSResult,
@@ -9,46 +5,50 @@ import {
html,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import "../../../../src/components/dialog/ha-paper-dialog";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-markdown";
import { haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown";
@customElement("dialog-hassio-markdown")
class HassioMarkdownDialog extends LitElement {
@property() public hass!: HomeAssistant;
@property() public title!: string;
@property() public content!: string;
@query("#dialog") private _dialog!: PaperDialogElement;
@property() private _opened = false;
public showDialog(params: HassioMarkdownDialogParams) {
this.title = params.title;
this.content = params.content;
this._dialog.open();
this._opened = true;
}
protected render(): TemplateResult {
if (!this._opened) {
return html``;
}
return html`
<ha-paper-dialog id="dialog" with-backdrop="">
<app-toolbar>
<ha-icon-button
icon="hassio:close"
dialog-dismiss=""
></ha-icon-button>
<div main-title="">${this.title}</div>
</app-toolbar>
<paper-dialog-scrollable>
<ha-markdown .content=${this.content || ""}></ha-markdown>
</paper-dialog-scrollable>
</ha-paper-dialog>
<ha-dialog
open
@closing=${this._closeDialog}
.heading=${createCloseHeading(this.hass, this.title)}
>
<ha-markdown .content=${this.content || ""}></ha-markdown>
</ha-dialog>
`;
}
private _closeDialog(): void {
this._opened = false;
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
@@ -90,6 +90,9 @@ class HassioMarkdownDialog extends LitElement {
color: var(--text-primary-color);
background-color: var(--primary-color);
}
ha-markdown {
padding: 16px;
}
}
`,
];

View File

@@ -1,9 +1,11 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiDelete } from "@mdi/js";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-spinner/paper-spinner";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-spinner/paper-spinner";
import {
css,
CSSResult,
@@ -16,16 +18,14 @@ import {
} from "lit-element";
import memoizeOne from "memoize-one";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-svg-icon";
import {
fetchHassioAddonsInfo,
HassioAddonRepository,
} from "../../../../src/data/hassio/addon";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import {
HassioAddonRepository,
fetchHassioAddonsInfo,
} from "../../../../src/data/hassio/addon";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
@customElement("dialog-hassio-repositories")
@@ -84,12 +84,13 @@ class HassioRepositoriesDialog extends LitElement {
<div secondary>${repo.maintainer}</div>
<div secondary>${repo.url}</div>
</paper-item-body>
<ha-icon-button
<mwc-icon-button
.slug=${repo.slug}
title="Remove"
@click=${this._removeRepository}
icon="hassio:delete"
></ha-icon-button>
>
<ha-svg-icon path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
</paper-item>
`;
})
@@ -194,7 +195,7 @@ class HassioRepositoriesDialog extends LitElement {
}
private async _removeRepository(ev: Event) {
const slug = (ev.target as any).slug;
const slug = (ev.currentTarget as any).slug;
const repositories = this._filteredRepositories(this._repos);
const repository = repositories.find((repo) => {
return repo.slug === slug;

View File

@@ -1,6 +1,6 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-repositories";
import { HassioAddonRepository } from "../../../../src/data/hassio/addon";
import "./dialog-hassio-repositories";
export interface HassioRepositoryDialogParams {
repos: HassioAddonRepository[];

View File

@@ -1,10 +1,6 @@
import "@material/mwc-button";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import { mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
import { PaperDialogElement } from "@polymer/paper-dialog";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-icon";
import "@polymer/paper-input/paper-input";
import {
css,
@@ -13,10 +9,10 @@ import {
html,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import "../../../../src/components/dialog/ha-paper-dialog";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth";
import {
fetchHassioSnapshotInfo,
@@ -76,7 +72,7 @@ class HassioSnapshotDialog extends LitElement {
@property() private _error?: string;
@property() private snapshot?: HassioSnapshotDetail;
@property() private _snapshot?: HassioSnapshotDetail;
@property() private _folders!: FolderItem[];
@@ -88,49 +84,35 @@ class HassioSnapshotDialog extends LitElement {
@property() private _restoreHass: boolean | null | undefined = true;
@query("#dialog") private _dialog!: PaperDialogElement;
public async showDialog(params: HassioSnapshotDialogParams) {
this.snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._folders = _computeFolders(
this.snapshot.folders
this._snapshot.folders
).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1));
this._addons = _computeAddons(
this.snapshot.addons
this._snapshot.addons
).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1));
this._dialogParams = params;
try {
this._dialog.open();
} catch {
await this.showDialog(params);
}
}
protected render(): TemplateResult {
if (!this.snapshot) {
if (!this._dialogParams || !this._snapshot) {
return html``;
}
return html`
<ha-paper-dialog
id="dialog"
with-backdrop=""
.on-iron-overlay-closed=${this._dialogClosed}
<ha-dialog
open
stacked
@closing=${this._closeDialog}
.heading=${createCloseHeading(this.hass, this._computeName)}
>
<app-toolbar>
<ha-icon-button
icon="hassio:close"
dialog-dismiss=""
></ha-icon-button>
<div main-title="">${this._computeName}</div>
</app-toolbar>
<div class="details">
${this.snapshot.type === "full"
${this._snapshot.type === "full"
? "Full snapshot"
: "Partial snapshot"}
(${this._computeSize})<br />
${this._formatDatetime(this.snapshot.date)}
${this._formatDatetime(this._snapshot.date)}
</div>
<div>Home Assistant:</div>
<paper-checkbox
@@ -139,7 +121,7 @@ class HassioSnapshotDialog extends LitElement {
this._restoreHass = (ev.target as PaperCheckboxElement).checked;
}}"
>
Home Assistant ${this.snapshot.homeassistant}
Home Assistant ${this._snapshot.homeassistant}
</paper-checkbox>
${this._folders.length
? html`
@@ -183,7 +165,7 @@ class HassioSnapshotDialog extends LitElement {
</paper-dialog-scrollable>
`
: ""}
${this.snapshot.protected
${this._snapshot.protected
? html`
<paper-input
autofocus=""
@@ -197,37 +179,35 @@ class HassioSnapshotDialog extends LitElement {
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
<div>Actions:</div>
<ul class="buttons">
<li>
<mwc-button @click=${this._downloadClicked}>
<ha-icon icon="hassio:download" class="icon"></ha-icon>
Download Snapshot
</mwc-button>
</li>
<li>
<mwc-button @click=${this._partialRestoreClicked}>
<ha-icon icon="hassio:history" class="icon"> </ha-icon>
Restore Selected
</mwc-button>
</li>
${this.snapshot.type === "full"
? html`
<li>
<mwc-button @click=${this._fullRestoreClicked}>
<ha-icon icon="hassio:history" class="icon"> </ha-icon>
Wipe &amp; restore
</mwc-button>
</li>
`
: ""}
<li>
<mwc-button @click=${this._deleteClicked}>
<ha-icon icon="hassio:delete" class="icon warning"> </ha-icon>
<span class="warning">Delete Snapshot</span>
</mwc-button>
</li>
</ul>
</ha-paper-dialog>
<mwc-button @click=${this._downloadClicked} slot="primaryAction">
<ha-svg-icon path=${mdiDownload} class="icon"></ha-svg-icon>
Download Snapshot
</mwc-button>
<mwc-button
@click=${this._partialRestoreClicked}
slot="secondaryAction"
>
<ha-svg-icon path=${mdiHistory} class="icon"></ha-svg-icon>
Restore Selected
</mwc-button>
${this._snapshot.type === "full"
? html`
<mwc-button
@click=${this._fullRestoreClicked}
slot="secondaryAction"
>
<ha-svg-icon path=${mdiHistory} class="icon"></ha-svg-icon>
Wipe &amp; restore
</mwc-button>
`
: ""}
<mwc-button @click=${this._deleteClicked} slot="secondaryAction">
<ha-svg-icon path=${mdiDelete} class="icon warning"></ha-svg-icon>
<span class="warning">Delete Snapshot</span>
</mwc-button>
</ha-dialog>
`;
}
@@ -235,37 +215,10 @@ class HassioSnapshotDialog extends LitElement {
return [
haStyleDialog,
css`
ha-paper-dialog {
min-width: 350px;
font-size: 14px;
border-radius: 2px;
}
app-toolbar {
margin: 0;
padding: 0 16px;
color: var(--primary-text-color);
background-color: var(--secondary-background-color);
}
app-toolbar [main-title] {
margin-left: 16px;
}
ha-paper-dialog-scrollable {
margin: 0;
}
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-paper-dialog {
max-height: 100%;
height: 100%;
}
app-toolbar {
color: var(--text-primary-color);
background-color: var(--primary-color);
}
}
.details {
color: var(--secondary-text-color);
}
@@ -336,7 +289,7 @@ class HassioSnapshotDialog extends LitElement {
folders,
};
if (this.snapshot!.protected) {
if (this._snapshot!.protected) {
data.password = this._snapshotPassword;
}
@@ -344,13 +297,13 @@ class HassioSnapshotDialog extends LitElement {
.callApi(
"POST",
`hassio/snapshots/${this.snapshot!.slug}/restore/partial`,
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
data
)
.then(
() => {
alert("Snapshot restored!");
this._dialog.close();
this._closeDialog();
},
(error) => {
this._error = error.body.message;
@@ -363,20 +316,20 @@ class HassioSnapshotDialog extends LitElement {
return;
}
const data = this.snapshot!.protected
const data = this._snapshot!.protected
? { password: this._snapshotPassword }
: undefined;
this.hass
.callApi(
"POST",
`hassio/snapshots/${this.snapshot!.slug}/restore/full`,
`hassio/snapshots/${this._snapshot!.slug}/restore/full`,
data
)
.then(
() => {
alert("Snapshot restored!");
this._dialog.close();
this._closeDialog();
},
(error) => {
this._error = error.body.message;
@@ -391,11 +344,11 @@ class HassioSnapshotDialog extends LitElement {
this.hass
.callApi("POST", `hassio/snapshots/${this.snapshot!.slug}/remove`)
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
.then(
() => {
this._dialog.close();
this._dialogParams!.onDelete();
this._closeDialog();
},
(error) => {
this._error = error.body.message;
@@ -408,7 +361,7 @@ class HassioSnapshotDialog extends LitElement {
try {
signedPath = await getSignedPath(
this.hass,
`/api/hassio/snapshots/${this.snapshot!.slug}/download`
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
);
} catch (err) {
alert(`Error: ${err.message}`);
@@ -419,19 +372,19 @@ class HassioSnapshotDialog extends LitElement {
const a = document.createElement("a");
a.href = signedPath.path;
a.download = `Hass_io_${name}.tar`;
this._dialog.appendChild(a);
this.shadowRoot!.appendChild(a);
a.click();
this._dialog.removeChild(a);
this.shadowRoot!.removeChild(a);
}
private get _computeName() {
return this.snapshot
? this.snapshot.name || this.snapshot.slug
return this._snapshot
? this._snapshot.name || this._snapshot.slug
: "Unnamed snapshot";
}
private get _computeSize() {
return Math.ceil(this.snapshot!.size * 10) / 10 + " MB";
return Math.ceil(this._snapshot!.size * 10) / 10 + " MB";
}
private _formatDatetime(datetime) {
@@ -445,9 +398,9 @@ class HassioSnapshotDialog extends LitElement {
});
}
private _dialogClosed() {
private _closeDialog() {
this._dialogParams = undefined;
this.snapshot = undefined;
this._snapshot = undefined;
this._snapshotPassword = "";
this._folders = [];
this._addons = [];

View File

@@ -3,11 +3,11 @@ import {
HassioAddonDetails,
restartHassioAddon,
} from "../../../src/data/hassio/addon";
import { HomeAssistant } from "../../../src/types";
import {
showConfirmationDialog,
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types";
export const suggestAddonRestart = async (
element: LitElement,

View File

@@ -1,4 +1,3 @@
import "../../src/components/ha-icon-button";
import { PolymerElement } from "@polymer/polymer";
import { customElement, property, PropertyValues } from "lit-element";
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
@@ -34,12 +33,6 @@ import { HomeAssistant } from "../../src/types";
// Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-panel";
// The register callback of the IronA11yKeysBehavior inside ha-icon-button
// is not called, causing _keyBindings to be uninitiliazed for ha-icon-button,
// causing an exception when added to DOM. When transpiled to ES5, this will
// break the build.
customElements.get("ha-icon-button").prototype._keyBindings = {};
@customElement("hassio-main")
class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
@property() public hass!: HomeAssistant;

View File

@@ -1,3 +1,4 @@
import { mdiBackupRestore, mdiCogs, mdiStore, mdiViewDashboard } from "@mdi/js";
import {
customElement,
html,
@@ -5,37 +6,35 @@ import {
property,
TemplateResult,
} from "lit-element";
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import {
HassioHomeAssistantInfo,
HassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
import "../../src/resources/ha-style";
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router";
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
export const supervisorTabs: PageNavigation[] = [
{
name: "Dashboard",
path: `/hassio/dashboard`,
icon: "hassio:view-dashboard",
iconPath: mdiViewDashboard,
},
{
name: "Add-on store",
path: `/hassio/store`,
icon: "hassio:store",
iconPath: mdiStore,
},
{
name: "Snapshots",
path: `/hassio/snapshots`,
icon: "hassio:backup-restore",
iconPath: mdiBackupRestore,
},
{
name: "System",
path: `/hassio/system`,
icon: "hassio:cogs",
iconPath: mdiCogs,
},
];

View File

@@ -86,9 +86,6 @@ class HassioIngressView extends LitElement {
height: 100%;
border: 0;
}
ha-icon-button {
color: var(--text-primary-color);
}
`;
}
}

View File

@@ -1,5 +1,6 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@material/mwc-icon-button";
import { mdiPackageVariant, mdiPackageVariantClosed, mdiReload } from "@mdi/js";
import "@polymer/paper-checkbox/paper-checkbox";
import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-input/paper-input";
@@ -18,6 +19,8 @@ import {
TemplateResult,
} from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon";
import {
createHassioFullSnapshot,
createHassioPartialSnapshot,
@@ -28,15 +31,14 @@ import {
reloadHassioSnapshots,
} from "../../../src/data/hassio/snapshot";
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import "../../../src/layouts/hass-tabs-subpage";
import "../components/hassio-card-content";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
import { hassioStyle } from "../resources/hassio-style";
import { supervisorTabs } from "../hassio-panel";
import { hassioStyle } from "../resources/hassio-style";
interface CheckboxItem {
slug: string;
@@ -98,12 +100,13 @@ class HassioSnapshots extends LitElement {
>
<span slot="header">Snapshots</span>
<ha-icon-button
icon="hassio:reload"
<mwc-icon-button
slot="toolbar-icon"
aria-label="Reload snapshots"
@click=${this.refreshData}
></ha-icon-button>
>
<ha-svg-icon path=${mdiReload}></ha-svg-icon>
</mwc-icon-button>
<div class="content">
<h1>
@@ -114,7 +117,7 @@ class HassioSnapshots extends LitElement {
Home Assistant instance.
</p>
<div class="card-group">
<paper-card>
<ha-card>
<div class="card-content">
<paper-input
autofocus
@@ -195,7 +198,7 @@ class HassioSnapshots extends LitElement {
Create
</mwc-button>
</div>
</paper-card>
</ha-card>
</div>
<h1>Available snapshots</h1>
@@ -204,15 +207,15 @@ class HassioSnapshots extends LitElement {
? undefined
: this._snapshots.length === 0
? html`
<paper-card>
<ha-card>
<div class="card-content">
You don't have any snapshots yet.
</div>
</paper-card>
</ha-card>
`
: this._snapshots.map(
(snapshot) => html`
<paper-card
<ha-card
class="pointer"
.snapshot=${snapshot}
@click=${this._snapshotClicked}
@@ -224,12 +227,12 @@ class HassioSnapshots extends LitElement {
.description=${this._computeDetails(snapshot)}
.datetime=${snapshot.date}
.icon=${snapshot.type === "full"
? "hassio:package-variant-closed"
: "hassio:package-variant"}
? mdiPackageVariantClosed
: mdiPackageVariant}
.icon-class="snapshot"
></hassio-card-content>
</div>
</paper-card>
</ha-card>
`
)}
</div>

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
@@ -12,23 +11,23 @@ import {
import "../../../src/components/buttons/ha-call-api-button";
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import {
changeHostOptions,
fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo as HassioHostInfoType,
rebootHost,
shutdownHost,
updateOS,
changeHostOptions,
} from "../../../src/data/hassio/host";
import {
showAlertDialog,
showConfirmationDialog,
showPromptDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import { hassioStyle } from "../resources/hassio-style";
import {
showConfirmationDialog,
showAlertDialog,
showPromptDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
@customElement("hassio-host-info")
class HassioHostInfo extends LitElement {
@@ -42,7 +41,7 @@ class HassioHostInfo extends LitElement {
public render(): TemplateResult | void {
return html`
<paper-card>
<ha-card>
<div class="card-content">
<h2>Host system</h2>
<table class="info">
@@ -113,7 +112,7 @@ class HassioHostInfo extends LitElement {
? html` <mwc-button @click=${this._updateOS}>Update</mwc-button> `
: ""}
</div>
</paper-card>
</ha-card>
`;
}
@@ -122,7 +121,7 @@ class HassioHostInfo extends LitElement {
haStyle,
hassioStyle,
css`
paper-card {
ha-card {
height: 100%;
width: 100%;
}

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
@@ -11,15 +10,16 @@ import {
} from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/components/ha-card";
import {
HassioSupervisorInfo as HassioSupervisorInfoType,
setSupervisorOption,
SupervisorOptions,
} from "../../../src/data/hassio/supervisor";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { hassioStyle } from "../resources/hassio-style";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
@customElement("hassio-supervisor-info")
class HassioSupervisorInfo extends LitElement {
@@ -31,7 +31,7 @@ class HassioSupervisorInfo extends LitElement {
public render(): TemplateResult | void {
return html`
<paper-card>
<ha-card>
<div class="card-content">
<h2>Supervisor</h2>
<table class="info">
@@ -92,7 +92,7 @@ class HassioSupervisorInfo extends LitElement {
`
: ""}
</div>
</paper-card>
</ha-card>
`;
}
@@ -101,7 +101,7 @@ class HassioSupervisorInfo extends LitElement {
haStyle,
hassioStyle,
css`
paper-card {
ha-card {
height: 100%;
width: 100%;
}

View File

@@ -2,7 +2,6 @@ import "@material/mwc-button";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-card/paper-card";
import {
css,
CSSResult,
@@ -12,14 +11,13 @@ import {
property,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/loading-screen";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
import "../components/hassio-ansi-to-html";
import { hassioStyle } from "../resources/hassio-style";
import "../../../src/layouts/loading-screen";
interface LogProvider {
key: string;
@@ -70,7 +68,7 @@ class HassioSupervisorLog extends LitElement {
public render(): TemplateResult | void {
return html`
<paper-card>
<ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${this.hass.userData?.showAdvanced
? html`
@@ -105,7 +103,7 @@ class HassioSupervisorLog extends LitElement {
<div class="card-actions">
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
</div>
</paper-card>
</ha-card>
`;
}
@@ -114,7 +112,7 @@ class HassioSupervisorLog extends LitElement {
haStyle,
hassioStyle,
css`
paper-card {
ha-card {
width: 100%;
}
pre {

View File

@@ -13,16 +13,15 @@ import {
HassioHostInfo,
} from "../../../src/data/hassio/host";
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import { haStyle } from "../../../src/resources/styles";
import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-panel";
import { hassioStyle } from "../resources/hassio-style";
import "./hassio-host-info";
import "./hassio-supervisor-info";
import "./hassio-supervisor-log";
import { supervisorTabs } from "../hassio-panel";
@customElement("hassio-system")
class HassioSystem extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;

View File

@@ -24,25 +24,25 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@fullcalendar/core": "5.0.0-beta.2",
"@fullcalendar/daygrid": "5.0.0-beta.2",
"@material/chips": "^6.0.0-canary.35a32aaea.0",
"@material/mwc-button": "0.14.1",
"@material/mwc-checkbox": "0.14.1",
"@material/mwc-dialog": "0.14.1",
"@material/mwc-fab": "0.14.1",
"@material/mwc-formfield": "0.14.1",
"@material/mwc-icon-button": "0.14.1",
"@material/mwc-list": "0.14.1",
"@material/mwc-menu": "0.14.1",
"@material/mwc-ripple": "0.14.1",
"@material/mwc-switch": "0.14.1",
"@formatjs/intl-pluralrules": "^1.5.8",
"@fullcalendar/core": "^5.0.0-beta.2",
"@fullcalendar/daygrid": "^5.0.0-beta.2",
"@material/chips": "7.0.0-canary.d92d8c93e.0",
"@material/mwc-button": "^0.15.0",
"@material/mwc-checkbox": "^0.15.0",
"@material/mwc-dialog": "^0.15.0",
"@material/mwc-fab": "^0.15.0",
"@material/mwc-formfield": "^0.15.0",
"@material/mwc-icon-button": "^0.15.0",
"@material/mwc-list": "^0.15.0",
"@material/mwc-menu": "^0.15.0",
"@material/mwc-ripple": "^0.15.0",
"@material/mwc-switch": "^0.15.0",
"@mdi/js": "4.9.95",
"@mdi/svg": "4.9.95",
"@polymer/app-layout": "^3.0.2",
"@polymer/app-route": "^3.0.2",
"@polymer/app-storage": "^3.0.2",
"@polymer/font-roboto": "^3.0.2",
"@polymer/iron-autogrow-textarea": "^3.0.1",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
@@ -51,15 +51,12 @@
"@polymer/iron-label": "^3.0.1",
"@polymer/iron-media-query": "^3.0.1",
"@polymer/iron-overlay-behavior": "^3.0.2",
"@polymer/iron-pages": "^3.0.1",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/neon-animation": "^3.0.1",
"@polymer/paper-card": "^3.0.1",
"@polymer/paper-checkbox": "^3.1.0",
"@polymer/paper-dialog": "^3.0.1",
"@polymer/paper-dialog-behavior": "^3.0.1",
"@polymer/paper-dialog-scrollable": "^3.0.1",
"@polymer/paper-drawer-panel": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.0.1",
"@polymer/paper-input": "^3.0.1",
"@polymer/paper-item": "^3.0.1",
@@ -69,7 +66,6 @@
"@polymer/paper-radio-button": "^3.0.1",
"@polymer/paper-radio-group": "^3.0.1",
"@polymer/paper-ripple": "^3.0.1",
"@polymer/paper-scroll-header-panel": "^3.0.1",
"@polymer/paper-slider": "^3.0.1",
"@polymer/paper-spinner": "^3.0.2",
"@polymer/paper-styles": "^3.0.1",
@@ -77,43 +73,46 @@
"@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.3.7",
"@thomasloven/round-slider": "0.5.0",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@webcomponents/shadycss": "^1.9.0",
"@webcomponents/webcomponentsjs": "^2.2.7",
"@webcomponents/webcomponentsjs": "^2.3.4",
"chart.js": "~2.8.0",
"chartjs-chart-timeline": "^0.3.0",
"codemirror": "^5.49.0",
"comlink": "^4.3.0",
"cpx": "^1.5.0",
"deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1",
"es6-object-assign": "^1.1.0",
"fecha": "^4.2.0",
"fuse.js": "^3.4.4",
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^0.12.4",
"home-assistant-js-websocket": "5.0.0",
"home-assistant-js-websocket": "^5.1.2",
"idb-keyval": "^3.2.0",
"intl-messageformat": "^8.3.9",
"js-yaml": "^3.13.1",
"leaflet": "^1.4.0",
"leaflet-draw": "^1.0.4",
"lit-element": "^2.2.1",
"lit-html": "^1.1.0",
"lit-element": "^2.3.1",
"lit-html": "^1.2.1",
"lit-virtualizer": "^0.4.2",
"marked": "^0.6.1",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2",
"moment": "^2.24.0",
"node-vibrant": "^3.1.5",
"proxy-polyfill": "^0.3.1",
"regenerator-runtime": "^0.13.2",
"resize-observer": "^1.0.0",
"resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0",
"superstruct": "^0.6.1",
"tslib": "^1.10.0",
"unfetch": "^4.1.0",
"web-animations-js": "^2.3.2",
"workbox-core": "^5.1.3",
"workbox-precaching": "^5.1.3",
"workbox-routing": "^5.1.3",
"workbox-strategies": "^5.1.3",
"xss": "^1.0.6"
},
"devDependencies": {
@@ -127,6 +126,10 @@
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.9.5",
"@babel/preset-typescript": "^7.9.0",
"@rollup/plugin-commonjs": "^11.1.0",
"@rollup/plugin-json": "^4.0.3",
"@rollup/plugin-node-resolve": "^7.1.3",
"@rollup/plugin-replace": "^2.3.2",
"@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^3.0.12",
"@types/codemirror": "^0.0.78",
@@ -142,7 +145,6 @@
"@typescript-eslint/parser": "^2.28.0",
"babel-loader": "^8.1.0",
"chai": "^4.2.0",
"copy-webpack-plugin": "^5.0.2",
"del": "^4.0.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-typescript": "^7.2.1",
@@ -153,63 +155,71 @@
"eslint-plugin-lit": "^1.2.0",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-wc": "^1.2.0",
"fancy-log": "^1.3.3",
"fs-extra": "^7.0.1",
"gulp": "^4.0.0",
"gulp-foreach": "^0.1.0",
"gulp-insert": "^0.5.0",
"gulp-json-transform": "^0.4.6",
"gulp-jsonminify": "^1.1.0",
"gulp-merge-json": "^1.3.1",
"gulp-rename": "^2.0.0",
"gulp-zopfli-green": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"html-minifier": "^4.0.0",
"husky": "^1.3.1",
"lint-staged": "^8.1.5",
"lit-analyzer": "^1.1.10",
"lodash.template": "^4.5.0",
"magic-string": "^0.25.7",
"map-stream": "^0.0.7",
"merge-stream": "^1.0.1",
"mocha": "^6.0.2",
"object-hash": "^2.0.3",
"parse5": "^5.1.0",
"open": "^7.0.4",
"prettier": "^2.0.4",
"raw-loader": "^2.0.0",
"reify": "^0.18.1",
"require-dir": "^1.2.0",
"rollup": "^2.8.2",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-string": "^3.0.0",
"rollup-plugin-terser": "^5.3.0",
"rollup-plugin-visualizer": "^4.0.4",
"serve": "^11.3.0",
"sinon": "^7.3.1",
"source-map-url": "^0.4.0",
"systemjs": "^6.3.2",
"terser-webpack-plugin": "^1.2.3",
"ts-lit-plugin": "^1.1.10",
"ts-mocha": "^6.0.0",
"typescript": "^3.8.3",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"web-component-tester": "^6.9.2",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.10.3",
"webpack-manifest-plugin": "^2.0.4",
"workbox-webpack-plugin": "^4.1.1",
"workerize-loader": "^1.1.0"
"workbox-build": "^5.1.3",
"worker-plugin": "^4.0.3"
},
"_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.2.10",
"@webcomponents/webcomponentsjs": "^2.3.4",
"@polymer/polymer": "3.1.0",
"lit-html": "^1.1.2",
"@material/animation": "6.0.0",
"@material/base": "6.0.0",
"@material/checkbox": "6.0.0",
"@material/density": "6.0.0",
"@material/dom": "6.0.0",
"@material/elevation": "6.0.0",
"@material/feature-targeting": "6.0.0",
"@material/ripple": "6.0.0",
"@material/rtl": "6.0.0",
"@material/shape": "6.0.0",
"@material/theme": "6.0.0",
"@material/touch-target": "6.0.0",
"@material/typography": "6.0.0"
"lit-html": "1.2.1",
"lit-element": "2.3.1",
"@material/animation": "7.0.0-canary.d92d8c93e.0",
"@material/base": "7.0.0-canary.d92d8c93e.0",
"@material/checkbox": "7.0.0-canary.d92d8c93e.0",
"@material/density": "7.0.0-canary.d92d8c93e.0",
"@material/dom": "7.0.0-canary.d92d8c93e.0",
"@material/elevation": "7.0.0-canary.d92d8c93e.0",
"@material/feature-targeting": "7.0.0-canary.d92d8c93e.0",
"@material/ripple": "7.0.0-canary.d92d8c93e.0",
"@material/rtl": "7.0.0-canary.d92d8c93e.0",
"@material/shape": "7.0.0-canary.d92d8c93e.0",
"@material/theme": "7.0.0-canary.d92d8c93e.0",
"@material/touch-target": "7.0.0-canary.d92d8c93e.0",
"@material/typography": "7.0.0-canary.d92d8c93e.0"
},
"main": "src/home-assistant.js",
"husky": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

10
rollup.config.js Normal file
View File

@@ -0,0 +1,10 @@
const rollup = require("./build-scripts/rollup.js");
const env = require("./build-scripts/env.js");
const config = rollup.createAppConfig({
isProdBuild: env.isProdBuild(),
latestBuild: true,
isStatsBuild: env.isStatsBuild(),
});
module.exports = { ...config.inputOptions, output: config.outputOptions };

View File

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

View File

@@ -83,12 +83,24 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
${this._renderStep(this._step)}
<div class="action">
<mwc-button raised @click=${this._handleSubmit}
>${this._step.type === "form" ? "Next" : "Start over"}</mwc-button
>${this._step.type === "form"
? this.localize("ui.panel.page-authorize.form.next")
: this.localize(
"ui.panel.page-authorize.form.start_over"
)}</mwc-button
>
</div>
`;
case "error":
return html` <div class="error">Error: ${this._errorMessage}</div> `;
return html`
<div class="error">
${this.localize(
"ui.panel.page-authorize.form.error",
"error",
this._errorMessage
)}
</div>
`;
case "loading":
return html` ${this.localize("ui.panel.page-authorize.form.working")} `;
default:

View File

@@ -121,7 +121,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
const tempA = document.createElement("a");
tempA.href = this.redirectUri!;
if (tempA.host === location.host) {
registerServiceWorker(false);
registerServiceWorker(this, false);
}
}

View File

@@ -1,5 +1,5 @@
import "@polymer/paper-item/paper-item.js";
import "@polymer/paper-item/paper-item-body.js";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import { html, LitElement, property } from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
import "../components/ha-icon-next";

View File

@@ -1,7 +1,7 @@
import "@polymer/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { computeStateName } from "../common/entity/compute_state_name";
import "../components/state-history-charts";
import "../data/ha-state-history-data";

View File

@@ -1,5 +1,4 @@
import "../../components/ha-icon-button";
import "@polymer/paper-input/paper-input.js";
import "@polymer/paper-input/paper-input";
import {
css,
CSSResult,
@@ -8,9 +7,11 @@ import {
property,
} from "lit-element";
import { html, TemplateResult } from "lit-html";
import { classMap } from "lit-html/directives/class-map.js";
import "../../components/ha-icon";
import { classMap } from "lit-html/directives/class-map";
import "../../components/ha-svg-icon";
import { fireEvent } from "../dom/fire_event";
import { mdiMagnify, mdiClose } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
@customElement("search-input")
class SearchInput extends LitElement {
@@ -47,17 +48,22 @@ class SearchInput extends LitElement {
@value-changed=${this._filterInputChanged}
.noLabelFloat=${this.noLabelFloat}
>
<ha-icon icon="hass:magnify" slot="prefix" class="prefix"></ha-icon>
<ha-svg-icon
path=${mdiMagnify}
slot="prefix"
class="prefix"
></ha-svg-icon>
${this.filter &&
html`
<ha-icon-button
<mwc-icon-button
slot="suffix"
class="suffix"
@click=${this._clearSearch}
icon="hass:close"
alt="Clear"
title="Clear"
></ha-icon-button>
>
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
`}
</paper-input>
`;
@@ -77,11 +83,14 @@ class SearchInput extends LitElement {
static get styles(): CSSResult {
return css`
ha-icon,
ha-icon-button {
ha-svg-icon,
mwc-icon-button {
color: var(--primary-text-color);
}
ha-icon {
mwc-icon-button {
--mdc-icon-button-size: 24px;
}
ha-svg-icon.prefix {
margin: 8px;
}
`;

View File

@@ -12,6 +12,10 @@ export interface FormatsType {
time: FormatType;
}
if (!Intl.PluralRules) {
import("@formatjs/intl-pluralrules/polyfill-locales");
}
/**
* Adapted from Polymer app-localize-behavior.
*

View File

@@ -1,6 +1,6 @@
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
import { EventsMixin } from "../../mixins/events-mixin";
import "./ha-progress-button";

View File

@@ -1,8 +1,8 @@
import "@material/mwc-button";
import "@polymer/paper-spinner/paper-spinner.js";
import { html } from "@polymer/polymer/lib/utils/html-tag.js";
import "@polymer/paper-spinner/paper-spinner";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element.js";
import { PolymerElement } from "@polymer/polymer/polymer-element";
class HaProgressButton extends PolymerElement {
static get template() {

View File

@@ -10,13 +10,10 @@ import {
query,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map.js";
import { ifDefined } from "lit-html/directives/if-defined.js";
import { styleMap } from "lit-html/directives/style-map.js";
import { classMap } from "lit-html/directives/class-map";
import { ifDefined } from "lit-html/directives/if-defined";
import { styleMap } from "lit-html/directives/style-map";
import { scroll } from "lit-virtualizer";
// @ts-ignore
// eslint-disable-next-line import/no-webpack-loader-syntax
import sortFilterWorker from "workerize-loader!./sort_filter_worker";
import { fireEvent } from "../../common/dom/fire_event";
import "../../common/search/search-input";
import { debounce } from "../../common/util/debounce";
@@ -24,6 +21,8 @@ import { nextRender } from "../../common/util/render-status";
import "../ha-checkbox";
import type { HaCheckbox } from "../ha-checkbox";
import "../ha-icon";
import { filterData, sortData } from "./sort-filter";
import memoizeOne from "memoize-one";
declare global {
// for fire event
@@ -74,6 +73,10 @@ export interface DataTableRowData {
selectable?: boolean;
}
export interface SortableColumnContainer {
[key: string]: DataTableSortColumnData;
}
@customElement("ha-data-table")
export class HaDataTable extends LitElement {
@property({ type: Object }) public columns: DataTableColumnContainer = {};
@@ -111,14 +114,10 @@ export class HaDataTable extends LitElement {
private _checkedRows: string[] = [];
private _sortColumns: {
[key: string]: DataTableSortColumnData;
} = {};
private _sortColumns: SortableColumnContainer = {};
private curRequest = 0;
private _worker: any | undefined;
private _debounceSearch = debounce(
(value: string) => {
this._filter = value;
@@ -140,11 +139,6 @@ export class HaDataTable extends LitElement {
}
}
protected firstUpdated(properties: PropertyValues) {
super.firstUpdated(properties);
this._worker = sortFilterWorker();
}
protected updated(properties: PropertyValues) {
super.updated(properties);
@@ -188,7 +182,7 @@ export class HaDataTable extends LitElement {
properties.has("_sortColumn") ||
properties.has("_sortDirection")
) {
this._filterData();
this._sortFilterData();
}
}
@@ -378,20 +372,30 @@ export class HaDataTable extends LitElement {
`;
}
private async _filterData() {
private async _sortFilterData() {
const startTime = new Date().getTime();
this.curRequest++;
const curRequest = this.curRequest;
const filterProm = this._worker.filterSortData(
this.data,
this._sortColumns,
this._filter,
this._sortDirection,
this._sortColumn
);
let filteredData = this.data;
if (this._filter) {
filteredData = await this._memFilterData(
this.data,
this._sortColumns,
this._filter
);
}
const [data] = await Promise.all([filterProm, nextRender]);
const prom = this._sortColumn
? sortData(
filteredData,
this._sortColumns,
this._sortDirection,
this._sortColumn
)
: filteredData;
const [data] = await Promise.all([prom, nextRender]);
const curTime = new Date().getTime();
const elapsed = curTime - startTime;
@@ -405,6 +409,16 @@ export class HaDataTable extends LitElement {
this._filteredData = data;
}
private _memFilterData = memoizeOne(
async (
data: DataTableRowData[],
columns: SortableColumnContainer,
filter: string
): Promise<DataTableRowData[]> => {
return filterData(data, columns, filter);
}
);
private _handleHeaderClick(ev: Event) {
const columnId = ((ev.target as HTMLElement).closest(
".mdc-data-table__header-cell"
@@ -646,6 +660,11 @@ export class HaDataTable extends LitElement {
padding: 8px;
}
.mdc-data-table__cell--icon-button {
color: var(--secondary-text-color);
text-overflow: clip;
}
.mdc-data-table__header-cell--icon-button:first-child,
.mdc-data-table__cell--icon-button:first-child {
width: 64px;
@@ -659,7 +678,7 @@ export class HaDataTable extends LitElement {
}
.mdc-data-table__cell--icon-button a {
color: var(--primary-text-color);
color: var(--secondary-text-color);
}
.mdc-data-table__header-cell {

View File

@@ -0,0 +1,36 @@
import { wrap } from "comlink";
import type { api } from "./sort_filter_worker";
type FilterDataType = api["filterData"];
type FilterDataParamTypes = Parameters<FilterDataType>;
type SortDataType = api["sortData"];
type SortDataParamTypes = Parameters<SortDataType>;
let worker: any | undefined;
export const filterData = async (
data: FilterDataParamTypes[0],
columns: FilterDataParamTypes[1],
filter: FilterDataParamTypes[2]
): Promise<ReturnType<FilterDataType>> => {
if (!worker) {
worker = wrap(new Worker("./sort_filter_worker", { type: "module" }));
}
return await worker.filterData(data, columns, filter);
};
export const sortData = async (
data: SortDataParamTypes[0],
columns: SortDataParamTypes[1],
direction: SortDataParamTypes[2],
sortColumn: SortDataParamTypes[3]
): Promise<ReturnType<SortDataType>> => {
if (!worker) {
worker = wrap(new Worker("./sort_filter_worker", { type: "module" }));
}
return await worker.sortData(data, columns, direction, sortColumn);
};

View File

@@ -1,60 +1,20 @@
import memoizeOne from "memoize-one";
// eslint-disable-next-line import/no-cycle
import {
DataTableColumnContainer,
DataTableColumnData,
// To use comlink under ES5
import "proxy-polyfill";
import { expose } from "comlink";
import type {
DataTableSortColumnData,
DataTableRowData,
SortingDirection,
SortableColumnContainer,
} from "./ha-data-table";
export const filterSortData = memoizeOne(
async (
data: DataTableRowData[],
columns: DataTableColumnContainer,
filter: string,
direction: SortingDirection,
sortColumn?: string
) =>
sortColumn
? _memSortData(
await _memFilterData(data, columns, filter),
columns,
direction,
sortColumn
)
: _memFilterData(data, columns, filter)
);
const _memFilterData = memoizeOne(
async (
data: DataTableRowData[],
columns: DataTableColumnContainer,
filter: string
) => {
if (!filter) {
return data;
}
return filterData(data, columns, filter.toUpperCase());
}
);
const _memSortData = memoizeOne(
(
data: DataTableRowData[],
columns: DataTableColumnContainer,
direction: SortingDirection,
sortColumn: string
) => {
return sortData(data, columns[sortColumn], direction, sortColumn);
}
);
export const filterData = (
const filterData = (
data: DataTableRowData[],
columns: DataTableColumnContainer,
columns: SortableColumnContainer,
filter: string
) =>
data.filter((row) => {
) => {
filter = filter.toUpperCase();
return data.filter((row) => {
return Object.entries(columns).some((columnEntry) => {
const [key, column] = columnEntry;
if (column.filterable) {
@@ -69,10 +29,11 @@ export const filterData = (
return false;
});
});
};
export const sortData = (
const sortData = (
data: DataTableRowData[],
column: DataTableColumnData,
column: DataTableSortColumnData,
direction: SortingDirection,
sortColumn: string
) =>
@@ -105,3 +66,12 @@ export const sortData = (
}
return 0;
});
const api = {
filterData,
sortData,
};
export type api = typeof api;
expose(api);

View File

@@ -1,10 +1,10 @@
import "@material/mwc-button";
import "@material/mwc-button/mwc-button";
import "../ha-icon-button";
import "@polymer/paper-input/paper-input.js";
import "@polymer/paper-item/paper-item.js";
import "@polymer/paper-item/paper-item-body.js";
import "@polymer/paper-listbox";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light.js";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-listbox/paper-listbox";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,

View File

@@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input.js";
import "@polymer/paper-item/paper-item.js";
import "@polymer/paper-item/paper-item-body.js";
import "@polymer/paper-listbox";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-listbox/paper-listbox";
import {
css,
CSSResult,

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