Compare commits

...

155 Commits

Author SHA1 Message Date
Bram Kragten c1eabeb29f Remove light card for generated mode 2020-01-17 21:53:38 +01:00
Paulus Schoutsen 5ff8fe68ba Alert user when add-on not started (#4485) 2020-01-17 17:02:56 +01:00
David F. Mulcahey a2a039ebc5 Add group binding to the ZHA config panel and misc. cleanup (#4466)
* clean up zha device card and usage

* group binding tile

* add cluster selection to group binding tile

* fix css class name

* fix filtering

* multiselect for clusters in group binding

* pass narrow to cluster table

* fix tables

* fix device page

* address remaing comments from previous PR

* fix bad cherry-pick

* css cleanup

* consistency

* use properties

* translations

* add confirmation dialog to remove button

* fix css

* review comments

* remove noise
2020-01-17 16:39:57 +01:00
Ian Richardson 1064aed1b0 📝 make some Lovelace UI text more clear (#4500) 2020-01-17 09:54:16 +01:00
Ian Richardson 7025592e8e 🐛 fix picture glance card's camera_view option in editor (#4495) 2020-01-17 09:44:57 +01:00
HomeAssistant Azure 4966354b62 [ci skip] Translation update 2020-01-17 00:32:37 +00:00
David F. Mulcahey 68d6faf4af fix selection check (#4488) 2020-01-16 18:19:01 +01:00
Paulus Schoutsen e3346483b9 Hide device trackers from generated lovelace (#4487) 2020-01-16 08:57:41 +01:00
HomeAssistant Azure e8fb79e5ce [ci skip] Translation update 2020-01-16 00:32:40 +00:00
Alexei Chetroi d612162ab1 Fix ZHA add device path. (#4486) 2020-01-15 20:05:05 +01:00
Bram Kragten 86f8ef3a70 Styling focus menus (#4483)
* Styling menus

* Update ha-config-navigation.ts
2020-01-15 19:41:56 +01:00
Bram Kragten 0e43435362 Don't ask to choose view when only 1 view (#4480) 2020-01-15 09:05:01 -08:00
Bram Kragten aaefe0b09f Handle unknown state (#4481) 2020-01-15 09:01:59 -08:00
Bram Kragten bc731a9dc3 Add edit btn to more info for scene, script and automation (#4476) 2020-01-15 09:50:16 +01:00
Bram Kragten da25701dca Disable adoptedStyleSheets in dev (#4474) 2020-01-15 09:25:17 +01:00
Bram Kragten 21ae483dc9 Styling fixes (#4475) 2020-01-15 09:25:04 +01:00
HomeAssistant Azure 38b6e9ca10 [ci skip] Translation update 2020-01-15 00:32:57 +00:00
Bram Kragten d31245866c Add DEPRECATED to states ui (#4463)
* Add DEPRECATED to states ui

* unelevated red

* target

* Add msg in info
2020-01-14 06:35:01 -08:00
Bram Kragten 4e08d8f3b3 Fix zha back btn (#4470) 2020-01-14 07:57:00 -05:00
Bram Kragten 1e717ab33e Catch undefined cloudstatus (#4465) 2020-01-14 13:52:23 +01:00
Bram Kragten 995fb4974e Fix translations (#4469) 2020-01-14 13:20:06 +01:00
HomeAssistant Azure ffb76132f8 [ci skip] Translation update 2020-01-14 00:32:29 +00:00
Bram Kragten acba3af54b Fix back btn for Polymer (#4467) 2020-01-13 18:21:43 +01:00
Paulus Schoutsen 40ac456937 Force refresh tokens if external app (#4461) 2020-01-13 05:47:08 -08:00
Bram Kragten 5c32413bf7 Onboarding core: Display error message when saving fails (#4462) 2020-01-13 05:31:53 -08:00
Bram Kragten 22792c70c5 Change config panel navigation (#4377)
* Change config panel navigation

* Show active + don't show toolbar?

* Update ha-panel-config.ts

* Change color of menu toolbar

* Update ha-config-router.ts

* Review comments
2020-01-12 17:57:38 +01:00
Krisjanis Lejejs a8ed87298a Improved map panel and map card to ignore zones when fitting map. (#4447)
* Improved map panel and map card to ignore zones when fitting map. [#1598](https://github.com/home-assistant/home-assistant-polymer/issues/1598)

* Improved map panel and map card to ignore zones when fitting map. [#1598](https://github.com/home-assistant/home-assistant-polymer/issues/1598)

* Improved map panel and map card to ignore zones when fitting map. [#1598](https://github.com/home-assistant/home-assistant-polymer/issues/1598)

* Changed approach and created a different array for zones

* Removed zone key option for markers
2020-01-12 17:56:55 +01:00
Joakim Sørensen b15270dfe2 Use correct suffix for elevation (#4454)
* Use correct suffix for elevation

* Use correct suffix for elevation
2020-01-12 07:31:59 -08:00
Bram Kragten 58ad949bc8 Virtualize logbook (#4450)
* Virtualize logbook

* Clean

* Update ha-logbook.ts
2020-01-12 13:00:26 +01:00
HomeAssistant Azure adce40de56 [ci skip] Translation update 2020-01-12 00:33:31 +00:00
Ian Richardson 0f487ae4bf Add tabindex to lovelace elements (#4160)
* tabindex

* use action handler

* circular focus test

* address comment

* add focus styling to other elements

* add focus styling to cards

* style glance card entities

* Add back light/thermo changes that were lost in rebase

* Remove unused import

* lint

* lint

* 💄 tweak focus style for glance entities

* 💄 apply styling to focused state-label-badges
2020-01-11 11:50:43 +01:00
Joakim Sørensen 2848e3a63b Adds CCS var usage to person dialog (#4449) 2020-01-11 11:49:57 +01:00
Bram Kragten 5a172a64c5 Make entry flow dialog modal (#4440)
* Make entry flow dialog modal

* Add close button

* Update dialog-data-entry-flow.ts

* Fix aria-label
2020-01-10 16:40:19 -08:00
HomeAssistant Azure 433aa16ea6 [ci skip] Translation update 2020-01-11 00:32:34 +00:00
HomeAssistant Azure 50cb8cf3cc [ci skip] Translation update 2020-01-10 00:32:38 +00:00
Sean Mooney 4e5406b27b Typo fix in issue template (#4445)
fixes small typo, necesarry = necessary
2020-01-09 09:29:42 -06:00
Franck Nijhof 80eb80619a Add configuration for Lock Threads on closed pull requests (#4443) 2020-01-09 11:40:25 +01:00
Ian Richardson bf71b3a869 ♻️ convert ha-attributes to lit-element (#4350)
* ♻️ convert ha-attributes to lit-element

* Address comments

* inline items

* 🐛 Fix attribution display logic
2020-01-09 10:22:23 +01:00
HomeAssistant Azure ff270c4b7d [ci skip] Translation update 2020-01-09 00:32:44 +00:00
David F. Mulcahey 5415068917 Rework the ZHA config panel (#4415)
* convert zha config panel to tabs

* add spacer to prevent combobox from hitting bottom

* break clusters out into their own section

* cleanup buttons

* remove header

* make devices default tab

* convert from tabs to a list view

* convert to table on dashboard

* fix anchor on mobile safari

* cleanup CSS to fix display on mobile

* cleanup card css

* more css cleanup

* fix group page

* remove translations changes

* Update src/panels/config/zha/zha-clusters.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-config-dashboard.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-device-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-groups-dashboard.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* review comments

* fix dangling quote after commit suggestion

* css cleanup

* remove flex rules

* remove flex rules

* css  cleanup

* remove dialog per review comments

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-01-08 18:35:21 +01:00
Bram Kragten 357a67c00d Bumped version to 20200108.0 2020-01-08 18:26:20 +01:00
HomeAssistant Azure cbe4269320 [ci skip] Translation update 2020-01-08 17:25:54 +00:00
Bram Kragten fbd5185ce2 Add ability to remove Lovelace config (#4430)
* Add ability to remove Lovelace config

* Update hc-lovelace.ts
2020-01-08 18:19:10 +01:00
Bram Kragten a33cf97e2c Fix moving actions with data (#4438) 2020-01-08 18:18:53 +01:00
Pascal Vizeli 7e7da26543 Update azure-pipelines-translation.yml for Azure Pipelines 2020-01-08 16:54:09 +01:00
Bram Kragten 79058e893b Add alert when Google sync failed (#4435) 2020-01-08 15:59:22 +01:00
Bram Kragten 2eb548bb74 Merge branch 'master' into dev 2020-01-07 20:53:23 +01:00
Bram Kragten 08baf8a757 Bumped version to 20200107.0 2020-01-07 20:50:51 +01:00
Bram Kragten f02fa6a94b Add multi select to entity registry (#4424)
* Add multi select to entity registry

* Fix filter and sort on status

* Remove unused prop platform

* Review

* Update ha-config-entity-registry.ts
2020-01-07 12:29:42 +01:00
Bram Kragten 2ed6d0e73c Make modal of Lovelace editor dialogs (#4426)
Fixes #4425
2020-01-06 22:25:17 +01:00
David F. Mulcahey 35d9b2ac3c Add the ability to create new Zigbee groups to the ZHA config panel (#4384)
* add group page

* Update src/panels/config/zha/zha-add-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* fix group name handling

* Update src/panels/config/zha/zha-add-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-01-06 07:02:47 -05:00
Bram Kragten 18d09c6f04 Add UI for restored entities (#4414)
* Add UI for restored entities

* Add conformation for removal

* Apply suggestions

* Guard
2020-01-03 12:44:25 +01:00
Joakim Sørensen 70b81de49d Force rerender on update/save (#4396)
* Force rerender on update/save

* Fix linting issue

* Define properties by using @property() instead

* Add styles to disabled save button

* Change to use @customElement, and remove _generation as a property.
2020-01-02 21:15:26 +01:00
David Cramer f0808c1f54 Add ha-subppage toolbar css styles (#4409) 2020-01-02 20:55:43 +01:00
Jay e779f0747e Change TRIGGER to EXECUTE (#4413)
There's been some confusion among new users about what the `TRIGGER` button does in the automation info popup. `EXECUTE` better represents what pressing that button does since it bypasses conditions and simply runs the action like a script. The automation docs at <https://www.home-assistant.io/docs/automation/action/> also say "The action of an automation rule is what is being executed when a rule fires."
2020-01-02 20:16:39 +01:00
David F. Mulcahey bdd18775c3 Add group editing to the ZHA config panel (#4382)
* add group editing

* Update src/panels/config/zha/zha-devices-data-table.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-devices-data-table.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Update src/panels/config/zha/zha-group-page.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* review comments

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-01-02 15:59:18 +01:00
David F. Mulcahey 711d51c022 Disable ZHA device binding buttons when a device to bind isn't selected (#4407)
* only enable buttons when a device is selected

* review comments
2020-01-02 07:24:40 -05:00
David F. Mulcahey 1b0d8bba29 fix area index on ZHA device card (#4406) 2020-01-02 10:50:19 +01:00
Colin Frei 2988cc512f Fix grammatical error (#4403) 2020-01-02 10:28:36 +01:00
Joakim Sørensen a2f8e5f3e7 Hide protection mode toggle if not usable (#4392) 2020-01-02 10:20:21 +01:00
David F. Mulcahey 680bf06a4b Add group detail view to the ZHA config panel (#4380)
* add group details

* review comments
2019-12-24 10:29:22 -05:00
David F. Mulcahey ff0b1881e2 Add Zigbee group removal to the ZHA config panel (#4376)
* add remove groups function

* add ability to remove groups

* translations

* review comments

* review comments

* review comments
2019-12-24 08:12:02 -05:00
David F. Mulcahey de653e1f7b Add Zigbee group viewing to ZHA config panel (#4365)
* add ability to view zigbee groups

* review comments

* remove selectable until used
2019-12-23 10:46:34 -05:00
Bram Kragten bb41170765 Add language Iban (#4375) 2019-12-23 16:27:41 +01:00
Bram Kragten 0ed2bc93aa Remove uploading translations from Travis (#4374) 2019-12-23 13:39:47 +01:00
Bram Kragten 04770f8ee2 Add language Esperanto (#4373) 2019-12-23 13:39:31 +01:00
Bram Kragten 15a2790b9f Add support to add all device entities to Lovelace (#4356)
* Add support to add all device entities to Lovelace

* Reload config when it was changed while Lovelace was not active

* Localize

* Update ha-panel-lovelace.ts

* Move to device entities card

* Move Lovelace logic to lovelace combine with unused entities

* Unused imports

* Added suggestions and support for YAML mode
2019-12-23 10:39:17 +01:00
Jc2k 83880791b1 Add 'unignore' to DISCOVERY_SOURCES that can be ignored. (#4370) 2019-12-21 17:10:20 +01:00
HomeAssistant Azure 4dca3289f6 [ci skip] Translation update 2019-12-19 16:07:21 +00:00
Pascal Vizeli 083a3ebfc4 Run translation on dev (#4368) 2019-12-19 17:03:05 +01:00
Pascal Vizeli 6117c4e989 Add Auto Translation handling (#4339)
* Add Auto Translation handling

* Cleanup
2019-12-18 16:38:36 +01:00
Bram Kragten 609763e658 Set focus to search when opening add integration dialog (#4357)
* Set focus to search when opening add integration dialog

* Also add to flow form
2019-12-18 16:35:20 +01:00
Bram Kragten 2c57ab60f1 Add ignore discovery button (#4354)
* Add ignore discovery button

* Add seperate list for ignored integrations

* Move translations

* Add zeroconf
2019-12-18 16:22:17 +01:00
Ian Richardson dd17a153d2 Fire custom LL event (#4361) 2019-12-18 07:40:26 +01:00
Bram Kragten c2d551bb7c Merge pull request #4341 from bonanitech/patch-2
Upgrade MDI icons to 4.7.95
2019-12-12 17:20:24 +01:00
Mauricio Bonani e0b1921108 Fix version number 2019-12-09 12:40:11 -05:00
Mauricio Bonani fcf39ceb96 Upgrade MDI icons to 4.7.95 2019-12-09 12:27:03 -05:00
Mauricio Bonani 3cc979a077 Upgrade MDI icons to 4.7.95 2019-12-09 12:24:36 -05:00
Bram Kragten 9972973774 Merge pull request #4338 from home-assistant/rc
20191204.1
2019-12-09 13:41:33 +01:00
Bram Kragten 20ae32bc26 Bumped version to 20191204.1 2019-12-09 13:03:17 +01:00
Bram Kragten a29892023b Revert "Add copy entity ID/state/attributes menu button in dev tools/states" (#4337)
* Revert "Add copy entity ID/state/attributes menu button in dev tools/states (#4259)"

This reverts commit 4b56db5255.

* Update package.json
2019-12-09 13:02:41 +01:00
Bram Kragten b283fec482 Update cloud-google-assistant.ts (#4329) 2019-12-09 13:02:17 +01:00
Bram Kragten e0116a8236 Fix thingtalk automations creation (#4328) 2019-12-09 13:01:56 +01:00
Bram Kragten d1990a4bac Revert "Add copy entity ID/state/attributes menu button in dev tools/states" (#4337)
* Revert "Add copy entity ID/state/attributes menu button in dev tools/states (#4259)"

This reverts commit 4b56db5255.

* Update package.json
2019-12-09 12:59:20 +01:00
Bram Kragten cbba1849e2 Convert script and automation editor to lit (#4327)
* Convert script and automation editor to lit

* Update yarn.lock
2019-12-09 10:59:52 +01:00
Bram Kragten 43393d1647 Update cloud-google-assistant.ts (#4329) 2019-12-09 08:34:36 +01:00
Bram Kragten b47ee1051c Fix thingtalk automations creation (#4328) 2019-12-07 20:46:04 +01:00
Bram Kragten 393adacc9e Convert automation actions/scripts to Lit (#4324)
* Convert automation actions/scripts to Lit

* Update ha-automation-action-row.ts

* Comments
2019-12-06 12:14:45 +01:00
Bram Kragten 073428849e Convert automation conditions to Lit (#4321)
* Convert automation conditions to Lit

* Split condition editor and row

* Comments

* Update automation.ts

* Update automation.ts
2019-12-05 19:48:06 +01:00
Bram Kragten e6ac0258e3 Use dynamicElement directive in ha-form (#4317)
* Use dynamicContentDirective

* Turn around

* Remove attributes

* Rename to dynamicElement
2019-12-04 22:58:35 +01:00
Bram Kragten d7e7798a55 Merge pull request #4318 from home-assistant/dev
20191204.0
2019-12-04 20:02:41 +01:00
Bram Kragten 2557414b11 Merge branch 'master' into dev 2019-12-04 19:30:47 +01:00
Bram Kragten f7065fbce9 Bumped version to 20191204.0 2019-12-04 19:28:47 +01:00
Bram Kragten 016564eee9 Update translations 2019-12-04 19:22:23 +01:00
Bram Kragten ff3087c39c Convert automation trigger to litelement (#4315)
* Convert automation trigger to Lit

* Update ha-automation-trigger-row.ts

* dynamicContentDirective

* update

* Lint

* Implement other types
2019-12-04 09:57:47 -08:00
Bram Kragten 239438ee5d Add entity picker to service call action (#4310)
* Add entity picker to service call action

* Use prop instead of attr
2019-12-03 12:30:51 +01:00
Florian Gareis 5458cda31f Add new confim dialog to automation editor (#4255) 2019-12-03 12:21:51 +01:00
Bram Kragten 36f49e66fd Remove empty defaults from time patern trigger automation (#4307) 2019-12-02 11:11:05 -08:00
Bram Kragten 2bafd38ea8 Allow automation actions/scripts to be moved up/down (#4308)
* Allow automation actions/scripts to be moved up/down

* Update index.tsx
2019-12-02 11:10:44 -08:00
Bram Kragten 73b3262491 Fix editing delay action (#4309) 2019-12-02 11:08:38 -08:00
Bram Kragten 808cde033f Update bug_report.md 2019-12-02 17:39:39 +01:00
Bram Kragten fa8f6b7b91 Add yaml editor to automation actions and scripts (#4306)
* Add yaml editor to automation actions and scripts

* Add types

* Update event.tsx
2019-12-02 14:08:19 +01:00
Bram Kragten 94c120cdb1 Add yaml editor to automation conditions (#4305) 2019-12-02 12:02:35 +01:00
Bram Kragten 7b2be54f8f YAML support for automation triggers (#4289)
* WIP: Add yaml editors to automation

* Fix form overwriting yaml on switching back

* Finish triggers

* prettier
2019-12-02 11:20:09 +01:00
nicop4 4b56db5255 Add copy entity ID/state/attributes menu button in dev tools/states (#4259)
* Added button and js method to copy with copy-to-clipboard library

* Copy entity id working, tooltip added

* copy ok, use ha toast to notify ok

* cleanup code

* add translation

* removed old useless code

* Replaced copy button with menu

* Fix comparison operator & removed commented code

	modifié :         src/panels/developer-tools/state/developer-tools-state.js

* Fix spaces

	modifié :         src/panels/developer-tools/state/developer-tools-state.js

* Improve copy attributes

* only one menu & update translation

* copy attributes in yml format
use paper-icon-item instead of paper-icon-button and add yarn.lock

* removed paper-item
2019-12-02 10:35:49 +01:00
Bram Kragten 93165c9111 Area/multiple devices and name support for thingtalk automations (#4272)
* WIP: Area/multiple devices and name support

* Fix removing devices

* Don't recalc entities for all devices every time

* Use guards

* Update ha-thingtalk-placeholders.ts
2019-12-02 10:30:30 +01:00
Bram Kragten caa604d5ca Add more aria labels (#4293)
* Add aria labels

* Fix polymer binding
2019-12-02 09:29:02 +01:00
Thomas Lovén e7e9e2cf85 Allow setting temperature to 0 degrees (#4300) 2019-12-02 09:23:20 +01:00
Bram Kragten daa04e9973 Fix jumping on iOS when toggle switch (#4275) 2019-11-29 12:41:37 +01:00
Bram Kragten 5355269f5d Check if external app by object (#4280)
* Check if external app by object

* Update core.ts

* Conditional chaining

* add babel optional chaining
2019-11-27 15:44:59 -08:00
Bram Kragten 2665a75250 Don't show hidden scenes (#4285)
* Don't show hidden scenes

* Comments

* computeStateDomain
2019-11-27 15:44:28 -08:00
Bram Kragten 8a39d18323 Bump TypeScript to 3.7 (#4282)
* Bump TypeScript to 3.7

* Update prettier to support ts 3.7

* Prettier

* More prettier

* Even more prettier
2019-11-27 13:51:03 -08:00
Bram Kragten b8a026397b Don't filter attributes when saving scene (#4278)
* Add cover attributes to scene editor

* Add more

* Remove filtering of attributes

* Update ha-scene-editor.ts
2019-11-27 13:43:46 -08:00
Bram Kragten bd5fe302eb Revert "Add specific maskable icons (#4283)" (#4284)
This reverts commit de0f1b2b65.
2019-11-27 20:23:58 +01:00
Bram Kragten de0f1b2b65 Add specific maskable icons (#4283) 2019-11-27 16:43:23 +01:00
Thomas Lovén defaa2b276 Fix missing semicolons in CSS (#4281)
Introduced in #4269
2019-11-27 13:06:02 +01:00
Bram Kragten 60efe00a1f Fix styling of vaadin elements (#4276) 2019-11-26 16:57:29 +01:00
Davide Varricchio fe93b993db Change to thermostat card to reflect step_temp on set-temperature (#4221)
* Minor change to thermostat card to reflect step_temp on set-temperature

* Corrected indentation

* Resolved eslint error
2019-11-25 17:42:38 +01:00
Joakim Sørensen f6afc92d3c Adds "air" at the bottom of the page (#4267)
* Adds "air" at the bottom of the page

* Update src/panels/config/dashboard/ha-config-dashboard.ts

Co-Authored-By: Bram Kragten <mail@bramkragten.nl>

* Add margin to promo
2019-11-25 17:38:08 +01:00
Carlos Gustavo Sarmiento e4c635c855 Added new CSS property for styling of the app-header component (#4269) 2019-11-25 17:37:33 +01:00
Bram Kragten a3e59e168f Bumped version to 20191119.6 2019-11-23 21:31:19 +01:00
Thomas Lovén e56355b406 Bump round-slider version. Fix #4265 (#4266) 2019-11-23 21:30:54 +01:00
Mauricio Bonani 8ef15c50b4 Upgrade MDI icons to 4.6.95 (#4270)
* Upgrade MDI icons to 4.6.95

* Upgrade MDI icons to 4.6.95
2019-11-23 21:26:32 +01:00
Marius 81588469b8 Add secondary-info: last-triggered (#4222)
* Add secondary-info: last-triggered

add last-triggered to the currently available options 'entity-id' and 'last-changed' see:https://www.home-assistant.io/lovelace/entities/#secondary_info

* corrected omission 'attributes'

* added test for attributes.last_triggered

* Update hui-generic-entity-row.ts

* Update hui-generic-entity-row.ts
2019-11-23 21:19:26 +01:00
Joakim Sørensen 70a920af3c Add initial bg color to panels (#4268) 2019-11-23 21:18:54 +01:00
Thomas Lovén 1329e60c89 Bump round-slider version. Fix #4265 (#4266) 2019-11-23 21:12:48 +01:00
Bram Kragten 9b7c095080 Bumped version to 20191119.5 2019-11-21 17:25:50 +01:00
Bram Kragten 654ff99cd1 Bumped version to 20191119.4 2019-11-21 17:04:43 +01:00
Bram Kragten 0511bc360e iOS 9 doesn't support append (#4260) 2019-11-21 17:04:36 +01:00
Bram Kragten ea9e8cc392 iOS 9 doesn't support append (#4260) 2019-11-21 17:03:35 +01:00
Bram Kragten 8433678371 Bumped version to 20191119.3 2019-11-21 15:22:07 +01:00
Bram Kragten 757bc00854 Fix thermostat card (#4258)
* Fix thermostat card

* Change styling

* Remove margin on mode buttons
2019-11-21 15:21:45 +01:00
Bram Kragten 2551393821 Fix light card (#4257)
* Fix light card

* Remove unused class

* Fix for when entity is not available

* Fix active state
2019-11-21 15:21:20 +01:00
Bram Kragten 0acd41b7f0 Fix thermostat card (#4258)
* Fix thermostat card

* Change styling

* Remove margin on mode buttons
2019-11-21 15:18:16 +01:00
Bram Kragten 85ca73db84 Fix light card (#4257)
* Fix light card

* Remove unused class

* Fix for when entity is not available

* Fix active state
2019-11-21 15:17:55 +01:00
Bram Kragten 444cbd00d9 Update README.md 2019-11-21 15:05:42 +01:00
Bram Kragten 15b500886c Bumped version to 20191119.2 2019-11-20 11:02:01 +01:00
Thomas Lovén 3aac834e72 Version bump round-slider. Fix bad rendering in IE/Edge (#4249) 2019-11-20 11:01:32 +01:00
Thomas Lovén 6edf23b91f Version bump round-slider. Fix bad rendering in IE/Edge (#4249) 2019-11-20 10:55:06 +01:00
Bram Kragten e445251b02 Bumped version to 20191119.1 2019-11-19 21:08:29 +01:00
Bram Kragten 693151b590 Fix ha-form on edge (#4248) 2019-11-19 21:08:15 +01:00
Bram Kragten 1249c0eea9 Fix ha-form on edge (#4248) 2019-11-19 21:06:52 +01:00
Bram Kragten 3133118870 Update vaadin components (#3571)
* Update vaadin components

* Remove resolution

* Migrate person detail dialog to mwc-dialog

* Fix imports

* Update dialog-person-detail.ts
2019-11-19 11:35:37 -06:00
Bram Kragten de5c1a0545 Merge pull request #4246 from home-assistant/dev
20191119.0
2019-11-19 13:37:07 +01:00
Bram Kragten c61e2fb459 Bumped version to 20191119.0 2019-11-19 13:19:33 +01:00
Bram Kragten 64a2a19da3 Update translations 2019-11-19 13:19:27 +01:00
Bram Kragten 74fe1f820c Fix error when no entities (#4244) 2019-11-19 13:16:55 +01:00
Bram Kragten 69929f5dc3 Fix thermostat unavailable (#4245) 2019-11-19 13:16:38 +01:00
Bram Kragten fcd793fc9e Fix device filtering on mobile (#4243) 2019-11-19 11:41:56 +01:00
Bram Kragten 8a3b1d76a1 Fix removing entity in scene editor (#4241)
Fixes https://github.com/home-assistant/home-assistant-polymer/issues/4237
2019-11-19 00:35:16 +01:00
Thomas Lovén 9f520d7628 Use new scaling features of round-slider (#4172)
* Refresh light card.

* Refresh thermostat card

* Fix paddings

* Fix #4175

* Use action handler

* Address review comments

* Lint

* Padding on percentage

* Remove typo
2019-11-19 00:32:23 +01:00
Bram Kragten 258cfddc3f Remove alias from scene action (#4240)
Fixes https://github.com/home-assistant/home-assistant-polymer/issues/4239
2019-11-19 00:28:35 +01:00
Bram Kragten 3697500402 Fix min value in editor (#4236) 2019-11-19 00:28:08 +01:00
springstan b4942ad27e Fixed two-part pin code input in Manual Alarm Control Panel (#4213)
* Consolidated code input via clicking GUI pads and using a physical keyboard

* Replaced the querySelector with the query decorator

* Run the query selector once, store its result and reuse it multiple times
2019-11-19 00:27:48 +01:00
Ville Skyttä 1e217e8d2f Support email and url form input types (#4186)
* Support email and url form input types

* Lint

* Lint

* Add types
2019-11-18 18:03:19 +01:00
386 changed files with 22711 additions and 8279 deletions
+26 -1
View File
@@ -41,7 +41,32 @@ Provide details about what browser (and version) you are seeing the issue in. An
**Description of problem:**
<!--
Explain what the issue is, and how things should look/behave. If possible provide a screenshot with a description.
Explain what the issue is, and what is the current behaviour. If possible provide a screenshot with a description.
-->
**Expected behaviour:**
<!--
Explain how things should look/behave. If possible provide a screenshot with a description.
-->
**Relevant config:**
<!--
Give the config of both the integration that is used, the Lovelace config, scene, automation or otherwise relevant configuration.
-->
**Steps to reproduce this problem:**
<!--
Sum up all steps that are necessary to reproduce this bug.
For example:
1. Add a climate integration
2. Navigate to Lovelace
3. Click more info of the climate entity
4. Set the hvac action to heat
5. Set the temperature higher than the current temperature
6. Set the hvac action to cool
-->
**Javascript errors shown in the web inspector (if applicable):**
+27
View File
@@ -0,0 +1,27 @@
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
# Number of days of inactivity before a closed issue or pull request is locked
daysUntilLock: 1
# Skip issues and pull requests created before a given timestamp. Timestamp must
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
skipCreatedBefore: 2020-01-01
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
exemptLabels: []
# Label to add before locking, such as `outdated`. Set to `false` to disable
lockLabel: false
# Comment to post before locking. Set to `false` to disable
lockComment: false
# Assign `resolved` as the reason for locking. Set to `false` to disable
setLockReason: false
# Limit to only `issues` or `pulls`
only: pulls
# Optionally, specify configuration settings just for `issues` or `pulls`
issues:
daysUntilLock: 30
-9
View File
@@ -13,15 +13,6 @@ script:
- npm run test
# - xvfb-run wct --module-resolution=node --npm
# - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --module-resolution=node --npm --plugin sauce; fi'
services:
- docker
before_deploy:
- "docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21"
deploy:
provider: script
script: script/travis_deploy
"on":
branch: master
dist: trusty
addons:
sauce_connect: true
+4 -2
View File
@@ -2,9 +2,9 @@
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://home-assistant.io/demo/)
[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://demo.home-assistant.io/)
- [View demo of the Polymer frontend](https://home-assistant.io/demo/)
- [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)
@@ -31,3 +31,5 @@ It is possible to compile the project and/or run commands in the development env
## License
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variation of devices.
+70
View File
@@ -0,0 +1,70 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
branches:
include:
- dev
paths:
include:
- translations/en.json
pr: none
schedules:
- cron: "30 0 * * *"
displayName: "translation update"
branches:
include:
- dev
always: true
variables:
- group: translation
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
jobs:
- job: 'Upload'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: '12.x'
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
./script/translations_upload_base
displayName: 'Upload Translation'
- job: 'Download'
dependsOn:
- 'Upload'
condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: '12.x'
- template: templates/azp-step-git-init.yaml@azure
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
npm install
./script/translations_download
displayName: 'Download Translation'
- script: |
git checkout dev
git add translation
git commit -am "[ci skip] Translation update"
git push
displayName: 'Update translation'
+1
View File
@@ -33,6 +33,7 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => {
pragma: "h",
},
],
"@babel/plugin-proposal-optional-chaining",
[
require("@babel/plugin-proposal-decorators").default,
{ decoratorsBeforeExport: true },
+1 -1
View File
@@ -91,7 +91,7 @@ const createWebpackConfig = ({
),
].filter(Boolean),
resolve: {
extensions: [".ts", ".js", ".json", ".tsx"],
extensions: [".ts", ".js", ".json"],
alias: {
react: "preact-compat",
"react-dom": "preact-compat",
+1
View File
@@ -39,6 +39,7 @@ class HcLovelace extends LitElement {
mode: "storage",
language: "en",
saveConfig: async () => undefined,
deleteConfig: async () => undefined,
setEditMode: () => undefined,
};
return this.lovelaceConfig.views[index].panel
+3 -3
View File
@@ -175,9 +175,9 @@ export class HcMain extends HassElement {
} catch (err) {
// Generate a Lovelace config.
this._unsubLovelace = () => undefined;
const {
generateLovelaceConfigFromHass,
} = await import("../../../../src/panels/lovelace/common/generate-lovelace-config");
const { generateLovelaceConfigFromHass } = await import(
"../../../../src/panels/lovelace/common/generate-lovelace-config"
);
this._handleNewLovelaceConfig(
await generateLovelaceConfigFromHass(this.hass!)
);
+1 -1
View File
@@ -53,7 +53,7 @@ class CardModder extends LitElement {
for (var k in this._config.style) {
if (window.cardTools.hasTemplate(this._config.style[k]))
this.templated.push(k);
this.card.style.setProperty(k, '');
this.card.style.setProperty(k, "");
target.style.setProperty(
k,
window.cardTools.parseTemplate(this._config.style[k])
+3 -1
View File
@@ -12,5 +12,7 @@ import "./resources/hademo-icons";
/* polyfill for paper-dropdown */
setTimeout(() => {
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min");
import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
);
}, 1000);
+66 -61
View File
@@ -65,74 +65,79 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"];
export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI(new RegExp("history/period/.+"), (
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
mockHass.mockAPI(
new RegExp("history/period/.+"),
(
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
const results: HassEntity[][] = [];
const results: HassEntity[][] = [];
for (const entityId of entities) {
const state = hass.states[entityId];
for (const entityId of entities) {
const state = hass.states[entityId];
if (!state) {
continue;
}
if (!state) {
continue;
}
if (!state.attributes.unit_of_measurement) {
results.push(generateHistory(state, [state.state]));
continue;
}
if (!state.attributes.unit_of_measurement) {
results.push(generateHistory(state, [state.state]));
continue;
}
const numberState = Number(state.state);
const numberState = Number(state.state);
if (isNaN(numberState)) {
// tslint:disable-next-line
console.log(
"Ignoring state with unparsable state but with a unit",
entityId,
state
if (isNaN(numberState)) {
// tslint:disable-next-line
console.log(
"Ignoring state with unparsable state but with a unit",
entityId,
state
);
continue;
}
const statesToGenerate = 15;
let genFunc;
if (incrementalUnits.includes(state.attributes.unit_of_measurement)) {
let initial = Math.floor(
numberState * 0.4 + numberState * Math.random() * 0.2
);
const diff = Math.max(
1,
Math.floor((numberState - initial) / statesToGenerate)
);
genFunc = () => {
initial += diff;
return Math.min(numberState, initial);
};
} else {
const diff = Math.floor(
numberState * (numberState > 80 ? 0.05 : 0.5)
);
genFunc = () =>
numberState - diff + Math.floor(Math.random() * 2 * diff);
}
results.push(
generateHistory(
{
entity_id: state.entity_id,
attributes: state.attributes,
},
Array.from({ length: statesToGenerate }, genFunc)
)
);
continue;
}
const statesToGenerate = 15;
let genFunc;
if (incrementalUnits.includes(state.attributes.unit_of_measurement)) {
let initial = Math.floor(
numberState * 0.4 + numberState * Math.random() * 0.2
);
const diff = Math.max(
1,
Math.floor((numberState - initial) / statesToGenerate)
);
genFunc = () => {
initial += diff;
return Math.min(numberState, initial);
};
} else {
const diff = Math.floor(numberState * (numberState > 80 ? 0.05 : 0.5));
genFunc = () =>
numberState - diff + Math.floor(Math.random() * 2 * diff);
}
results.push(
generateHistory(
{
entity_id: state.entity_id,
attributes: state.attributes,
},
Array.from({ length: statesToGenerate }, genFunc)
)
);
return results;
}
return results;
});
);
};
+4 -3
View File
@@ -12,9 +12,10 @@ export const mockLovelace = (
localizePromise: Promise<LocalizeFunc>
) => {
hass.mockWS("lovelace/config", () =>
Promise.all([selectedDemoConfig, localizePromise]).then(
([config, localize]) => config.lovelace(localize)
)
Promise.all([
selectedDemoConfig,
localizePromise,
]).then(([config, localize]) => config.lovelace(localize))
);
hass.mockWS("lovelace/config/save", () => Promise.resolve());
+2 -6
View File
@@ -44,9 +44,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
selected="{{selectedInput}}"
>
<template is="dom-repeat" items="[[inputDevices]]">
<paper-item device\$="[[item.device]]"
>[[item.name]]</paper-item
>
<paper-item device$="[[item.device]]">[[item.name]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
@@ -57,9 +55,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
selected="{{selectedOutput}}"
>
<template is="dom-repeat" items="[[outputDevices]]">
<paper-item device\$="[[item.device]]"
>[[item.name]]</paper-item
>
<paper-item device$="[[item.device]]">[[item.name]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
+22 -13
View File
@@ -373,19 +373,21 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
</template>
</div>
</template>
<div class="state">
<div>
Protection mode
<span>
<iron-icon icon="hassio:information"></iron-icon>
<paper-tooltip>Grant the add-on elevated system access.</paper-tooltip>
</span>
<template is="dom-if" if="[[_computeUsesProtectedOptions(addon)]]">
<div class="state">
<div>
Protection mode
<span>
<iron-icon icon="hassio:information"></iron-icon>
<paper-tooltip>Grant the add-on elevated system access.</paper-tooltip>
</span>
</div>
<ha-switch
on-change="protectionToggled"
checked="[[addon.protected]]"
></ha-switch>
</div>
<ha-switch
on-change="protectionToggled"
checked="[[addon.protected]]"
></ha-switch>
</div>
</template>
</template>
</div>
<div class="card-actions">
@@ -569,7 +571,10 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
openChangelog() {
this.hass
.callApi("get", `hassio/addons/${this.addonSlug}/changelog`)
.then((resp) => resp, () => "Error getting changelog")
.then(
(resp) => resp,
() => "Error getting changelog"
)
.then((content) => {
showHassioMarkdownDialog(this, {
title: "Changelog",
@@ -607,6 +612,10 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
return !addon.ingress || !this._computeHA92plus(hass);
}
_computeUsesProtectedOptions(addon) {
return addon.docker_api || addon.full_access || addon.host_pid;
}
_computeHA92plus(hass) {
const [major, minor] = hass.config.version.split(".", 2);
return Number(major) > 0 || (major === "0" && Number(minor) >= 92);
+2 -6
View File
@@ -74,9 +74,7 @@ export class HassioUpdate extends LitElement {
this.supervisorInfo.version,
this.supervisorInfo.last_version,
"hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${
this.supervisorInfo.last_version
}`
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisorInfo.last_version}`
)}
${this.hassOsInfo
? this._renderUpdateCard(
@@ -84,9 +82,7 @@ export class HassioUpdate extends LitElement {
this.hassOsInfo.version,
this.hassOsInfo.version_latest,
"hassio/hassos/update",
`https://github.com//home-assistant/hassos/releases/tag/${
this.hassOsInfo.version_latest
}`
`https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}`
)
: ""}
</div>
@@ -12,7 +12,9 @@ export const showHassioMarkdownDialog = (
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-markdown",
dialogImport: () =>
import(/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"),
import(
/* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown"
),
dialogParams,
});
};
@@ -12,7 +12,9 @@ export const showHassioSnapshotDialog = (
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot",
dialogImport: () =>
import(/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"),
import(
/* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot"
),
dialogParams,
});
};
+15 -4
View File
@@ -27,6 +27,7 @@ import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
// Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-pages-with-tabs";
import { navigate } from "../../src/common/navigate";
// The register callback of the IronA11yKeysBehavior inside paper-icon-button
// is not called, causing _keyBindings to be uninitiliazed for paper-icon-button,
@@ -56,12 +57,16 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
addon: {
tag: "hassio-addon-view",
load: () =>
import(/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"),
import(
/* webpackChunkName: "hassio-addon-view" */ "./addon-view/hassio-addon-view"
),
},
ingress: {
tag: "hassio-ingress-view",
load: () =>
import(/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"),
import(
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
),
},
},
};
@@ -161,14 +166,20 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
}),
]);
if (!addon.ingress_url) {
throw new Error("Add-on does not support Ingress");
alert("Add-on does not support Ingress");
return;
}
if (addon.state !== "started") {
alert("Add-on is not running. Please start it first");
navigate(this, `/hassio/addon/${addon.slug}`, true);
return;
}
location.assign(addon.ingress_url);
// await a promise that doesn't resolve, so we show the loading screen
// while we load the next page.
await new Promise(() => undefined);
} catch (err) {
alert(`Unable to open ingress connection `);
alert("Unable to open ingress connection");
}
}
+26 -24
View File
@@ -8,7 +8,7 @@
"version": "1.0.0",
"scripts": {
"build": "script/build_frontend",
"lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'src/**/*.tsx' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'cast/src/**/*.ts' 'test-mocha/**/*.ts' && tsc",
"lint": "eslint src hassio/src gallery/src && tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'cast/src/**/*.ts' 'test-mocha/**/*.ts' && tsc",
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
"test": "npm run lint && npm run mocha",
"docker_build": "sh ./script/docker_run.sh build $npm_package_version",
@@ -19,13 +19,14 @@
"dependencies": {
"@material/chips": "^3.2.0",
"@material/data-table": "^3.2.0",
"@material/mwc-base": "^0.8.0",
"@material/mwc-button": "^0.8.0",
"@material/mwc-checkbox": "^0.8.0",
"@material/mwc-fab": "^0.8.0",
"@material/mwc-ripple": "^0.8.0",
"@material/mwc-switch": "^0.8.0",
"@mdi/svg": "4.5.95",
"@material/mwc-base": "^0.10.0",
"@material/mwc-button": "^0.10.0",
"@material/mwc-checkbox": "^0.10.0",
"@material/mwc-dialog": "^0.10.0",
"@material/mwc-fab": "^0.10.0",
"@material/mwc-ripple": "^0.10.0",
"@material/mwc-switch": "^0.10.0",
"@mdi/svg": "4.7.95",
"@polymer/app-layout": "^3.0.2",
"@polymer/app-localize-behavior": "^3.0.1",
"@polymer/app-route": "^3.0.2",
@@ -67,9 +68,9 @@
"@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "^0.2.2",
"@vaadin/vaadin-combo-box": "^4.2.8",
"@vaadin/vaadin-date-picker": "^3.3.3",
"@thomasloven/round-slider": "0.3.7",
"@vaadin/vaadin-combo-box": "^5.0.6",
"@vaadin/vaadin-date-picker": "^4.0.3",
"@webcomponents/shadycss": "^1.9.0",
"@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "~2.8.0",
@@ -88,6 +89,7 @@
"leaflet": "^1.4.0",
"lit-element": "^2.2.1",
"lit-html": "^1.1.0",
"lit-virtualizer": "^0.4.2",
"marked": "^0.6.1",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2",
@@ -104,15 +106,16 @@
"xss": "^1.0.6"
},
"devDependencies": {
"@babel/core": "^7.4.0",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.4.0",
"@babel/plugin-proposal-decorators": "^7.4.0",
"@babel/plugin-proposal-object-rest-spread": "^7.4.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@babel/preset-env": "^7.4.2",
"@babel/preset-typescript": "^7.4.0",
"@babel/core": "^7.7.4",
"@babel/plugin-external-helpers": "^7.7.4",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-decorators": "^7.7.4",
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
"@babel/plugin-proposal-optional-chaining": "^7.7.4",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/plugin-transform-react-jsx": "^7.7.4",
"@babel/preset-env": "^7.7.4",
"@babel/preset-typescript": "^7.7.4",
"@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^3.0.12",
"@types/chromecast-caf-sender": "^1.0.1",
@@ -154,18 +157,18 @@
"merge-stream": "^1.0.1",
"mocha": "^6.0.2",
"parse5": "^5.1.0",
"prettier": "^1.16.4",
"prettier": "^1.19.1",
"raw-loader": "^2.0.0",
"reify": "^0.18.1",
"require-dir": "^1.2.0",
"sinon": "^7.3.1",
"terser-webpack-plugin": "^1.2.3",
"ts-mocha": "^6.0.0",
"tslint": "^5.14.0",
"tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0",
"tslint-eslint-rules": "^5.4.0",
"tslint-plugin-prettier": "^2.0.1",
"typescript": "^3.6.3",
"typescript": "^3.7.2",
"web-component-tester": "^6.9.2",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9",
@@ -178,7 +181,6 @@
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
"resolutions": {
"@webcomponents/webcomponentsjs": "^2.2.10",
"@vaadin/vaadin-lumo-styles": "^1.4.2",
"@polymer/polymer": "3.1.0",
"lit-html": "^1.1.2"
},
+2 -2
View File
@@ -26,8 +26,8 @@ LANG_ISO=en
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "${CURRENT_BRANCH-}" != "master" ] && [ "${TRAVIS_BRANCH-}" != "master" ] ; then
echo "Please only run the translations upload script from a clean checkout of master."
if [ "${CURRENT_BRANCH-}" != "dev" ] && [ "${AZURE_BRANCH-}" != "dev" ] ; then
echo "Please only run the translations upload script from a clean checkout of dev."
exit 1
fi
-11
View File
@@ -1,11 +0,0 @@
#!/usr/bin/env bash
# Safe bash settings
# -e Exit on command fail
# -u Exit on unset variable
# -o pipefail Exit if piped command has error code
set -eu -o pipefail
cd "$(dirname "$0")/.."
script/translations_upload_base
+1 -1
View File
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20191118.0",
version="20200108.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
+9 -12
View File
@@ -98,9 +98,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
<ha-markdown
allowsvg
.content=${this.localize(
`ui.panel.page-authorize.form.providers.${
step.handler[0]
}.abort.${step.reason}`
`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${step.reason}`
)}
></ha-markdown>
`;
@@ -119,6 +117,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
.error=${step.errors}
.computeLabel=${this._computeLabelCallback(step)}
.computeError=${this._computeErrorCallback(step)}
@value-changed=${this._stepDataChanged}
></ha-form>
`;
default:
@@ -223,10 +222,12 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
}, 100);
}
private _stepDataChanged(ev: CustomEvent) {
this._stepData = ev.detail.value;
}
private _computeStepDescription(step: DataEntryFlowStepForm) {
const resourceKey = `ui.panel.page-authorize.form.providers.${
step.handler[0]
}.step.${step.step_id}.description`;
const resourceKey = `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`;
const args: string[] = [];
const placeholders = step.description_placeholders || {};
Object.keys(placeholders).forEach((key) => {
@@ -240,9 +241,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
// Returns a callback for ha-form to calculate labels per schema object
return (schema) =>
this.localize(
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${
step.step_id
}.data.${schema.name}`
`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.data.${schema.name}`
);
}
@@ -250,9 +249,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
// Returns a callback for ha-form to calculate error messages
return (error) =>
this.localize(
`ui.panel.page-authorize.form.providers.${
step.handler[0]
}.error.${error}`
`ui.panel.page-authorize.form.providers.${step.handler[0]}.error.${error}`
);
}
+3 -1
View File
@@ -11,7 +11,9 @@ import "./ha-auth-flow";
import { AuthProvider, fetchAuthProviders } from "../data/auth";
import { registerServiceWorker } from "../util/register-service-worker";
import(/* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider");
import(
/* webpackChunkName: "pick-auth-provider" */ "../auth/ha-pick-auth-provider"
);
interface QueryParams {
client_id?: string;
+2 -2
View File
@@ -10,11 +10,11 @@ function toLocaleDateStringSupportsOptions() {
return false;
}
export default (toLocaleDateStringSupportsOptions()
export default toLocaleDateStringSupportsOptions()
? (dateObj: Date, locales: string) =>
dateObj.toLocaleDateString(locales, {
year: "numeric",
month: "long",
day: "numeric",
})
: (dateObj: Date) => fecha.format(dateObj, "mediumDate"));
: (dateObj: Date) => fecha.format(dateObj, "mediumDate");
+2 -2
View File
@@ -10,7 +10,7 @@ function toLocaleStringSupportsOptions() {
return false;
}
export default (toLocaleStringSupportsOptions()
export default toLocaleStringSupportsOptions()
? (dateObj: Date, locales: string) =>
dateObj.toLocaleString(locales, {
year: "numeric",
@@ -19,4 +19,4 @@ export default (toLocaleStringSupportsOptions()
hour: "numeric",
minute: "2-digit",
})
: (dateObj: Date) => fecha.format(dateObj, "haDateTime"));
: (dateObj: Date) => fecha.format(dateObj, "haDateTime");
+2 -2
View File
@@ -10,10 +10,10 @@ function toLocaleTimeStringSupportsOptions() {
return false;
}
export default (toLocaleTimeStringSupportsOptions()
export default toLocaleTimeStringSupportsOptions()
? (dateObj: Date, locales: string) =>
dateObj.toLocaleTimeString(locales, {
hour: "numeric",
minute: "2-digit",
})
: (dateObj: Date) => fecha.format(dateObj, "shortTime"));
: (dateObj: Date) => fecha.format(dateObj, "shortTime");
+1 -1
View File
@@ -60,7 +60,7 @@ export const applyThemesOnElement = (
element.updateStyles(styles);
} else if (window.ShadyCSS) {
// implement updateStyles() method of Polymer elements
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ (element), styles);
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
}
if (!updateMeta) {
@@ -0,0 +1,33 @@
import { directive, Part, NodePart } from "lit-html";
export const dynamicElement = directive(
(tag: string, properties?: { [key: string]: any }) => (part: Part): void => {
if (!(part instanceof NodePart)) {
throw new Error(
"dynamicContentDirective can only be used in content bindings"
);
}
let element = part.value as HTMLElement | undefined;
if (
element !== undefined &&
tag.toUpperCase() === (element as HTMLElement).tagName
) {
if (properties) {
Object.entries(properties).forEach(([key, value]) => {
element![key] = value;
});
}
return;
}
element = document.createElement(tag);
if (properties) {
Object.entries(properties).forEach(([key, value]) => {
element![key] = value;
});
}
part.setValue(element);
}
);
+3 -1
View File
@@ -11,7 +11,9 @@ export const setupLeafletMap = async (
throw new Error("Cannot setup Leaflet map on disconnected element");
}
// tslint:disable-next-line
const Leaflet = (await import(/* webpackChunkName: "leaflet" */ "leaflet")) as LeafletModuleType;
const Leaflet = (await import(
/* webpackChunkName: "leaflet" */ "leaflet"
)) as LeafletModuleType;
Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/";
const map = Leaflet.map(mapElement);
-25
View File
@@ -1,25 +0,0 @@
// interface OnChangeComponent {
// props: {
// index: number;
// onChange(index: number, data: object);
// };
// }
// export function onChangeEvent(this: OnChangeComponent, prop, ev) {
export function onChangeEvent(this: any, prop, ev) {
const origData = this.props[prop];
if (ev.target.value === origData[ev.target.name]) {
return;
}
const data = { ...origData };
if (ev.target.value) {
data[ev.target.name] = ev.target.value;
} else {
delete data[ev.target.name];
}
this.props.onChange(this.props.index, data);
}
-9
View File
@@ -1,9 +0,0 @@
import { render } from "preact";
export default function unmount(mountEl) {
render(
// @ts-ignore
() => null,
mountEl
);
}
+5 -1
View File
@@ -14,7 +14,11 @@ import "@material/mwc-button";
@customElement("search-input")
class SearchInput extends LitElement {
@property() private filter?: string;
@property() public filter?: string;
public focus() {
this.shadowRoot!.querySelector("paper-input")!.focus();
}
protected render(): TemplateResult | void {
return html`
@@ -15,6 +15,7 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
id="progress"
progress="[[progress]]"
on-click="buttonTapped"
tabindex="0"
><slot></slot
></ha-progress-button>
`;
+56 -29
View File
@@ -6,8 +6,9 @@ import {
MDCDataTableFoundation,
} from "@material/data-table";
import { classMap } from "lit-html/directives/class-map";
import {
BaseElement,
html,
query,
queryAll,
@@ -15,10 +16,11 @@ import {
css,
customElement,
property,
classMap,
TemplateResult,
PropertyValues,
} from "@material/mwc-base/base-element";
} from "lit-element";
import { BaseElement } from "@material/mwc-base/base-element";
// eslint-disable-next-line import/no-webpack-loader-syntax
// @ts-ignore
@@ -73,7 +75,7 @@ export interface DataTableSortColumnData {
export interface DataTableColumnData extends DataTableSortColumnData {
title: string;
type?: "numeric" | "icon";
template?: <T>(data: any, row: T) => TemplateResult;
template?: <T>(data: any, row: T) => TemplateResult | string;
}
export interface DataTableRowData {
@@ -86,11 +88,11 @@ export class HaDataTable extends BaseElement {
@property({ type: Array }) public data: DataTableRowData[] = [];
@property({ type: Boolean }) public selectable = false;
@property({ type: String }) public id = "id";
@property({ type: String }) public filter = "";
protected mdcFoundation!: MDCDataTableFoundation;
protected readonly mdcFoundationClass = MDCDataTableFoundation;
@query(".mdc-data-table") protected mdcRoot!: HTMLElement;
@queryAll(".mdc-data-table__row") protected rowElements!: HTMLElement[];
@query("#header-checkbox") private _headerCheckbox!: HaCheckbox;
@property({ type: Boolean }) private _filterable = false;
@property({ type: Boolean }) private _headerChecked = false;
@property({ type: Boolean }) private _headerIndeterminate = false;
@@ -106,13 +108,19 @@ export class HaDataTable extends BaseElement {
private _worker: any | undefined;
private _debounceSearch = debounce(
(ev) => {
this._filter = ev.detail.value;
(value: string) => {
this._filter = value;
},
200,
false
);
public clearSelection(): void {
this._headerChecked = false;
this._headerIndeterminate = false;
this.mdcFoundation.handleHeaderRowCheckboxChange();
}
protected firstUpdated() {
super.firstUpdated();
this._worker = sortFilterWorker();
@@ -144,6 +152,10 @@ export class HaDataTable extends BaseElement {
this._sortColumns = clonedColumns;
}
if (properties.has("filter")) {
this._debounceSearch(this.filter);
}
if (
properties.has("data") ||
properties.has("columns") ||
@@ -157,14 +169,18 @@ export class HaDataTable extends BaseElement {
protected render() {
return html`
${this._filterable
? html`
<search-input
@value-changed=${this._handleSearchChange}
></search-input>
`
: ""}
<div class="mdc-data-table">
<slot name="header">
${this._filterable
? html`
<div class="table-header">
<search-input
@value-changed=${this._handleSearchChange}
></search-input>
</div>
`
: ""}
</slot>
<table class="mdc-data-table__table">
<thead>
<tr class="mdc-data-table__header-row">
@@ -176,7 +192,6 @@ export class HaDataTable extends BaseElement {
scope="col"
>
<ha-checkbox
id="header-checkbox"
class="mdc-data-table__row-checkbox"
@change=${this._handleHeaderRowCheckboxChange}
.indeterminate=${this._headerIndeterminate}
@@ -240,7 +255,9 @@ export class HaDataTable extends BaseElement {
<ha-checkbox
class="mdc-data-table__row-checkbox"
@change=${this._handleRowCheckboxChange}
.checked=${this._checkedRows.includes(row[this.id])}
.checked=${this._checkedRows.includes(
String(row[this.id])
)}
>
</ha-checkbox>
</td>
@@ -370,9 +387,10 @@ export class HaDataTable extends BaseElement {
});
}
private _handleHeaderRowCheckboxChange() {
this._headerChecked = this._headerCheckbox.checked;
this._headerIndeterminate = this._headerCheckbox.indeterminate;
private _handleHeaderRowCheckboxChange(ev: Event) {
const checkbox = ev.target as HaCheckbox;
this._headerChecked = checkbox.checked;
this._headerIndeterminate = checkbox.indeterminate;
this.mdcFoundation.handleHeaderRowCheckboxChange();
}
@@ -385,20 +403,26 @@ export class HaDataTable extends BaseElement {
}
private _handleRowClick(ev: Event) {
const rowId = (ev.target as HTMLElement)
.closest("tr")!
.getAttribute("data-row-id")!;
const target = ev.target as HTMLElement;
if (target.tagName === "HA-CHECKBOX") {
return;
}
const rowId = target.closest("tr")!.getAttribute("data-row-id")!;
fireEvent(this, "row-click", { id: rowId }, { bubbles: false });
}
private _setRowChecked(rowId: string, checked: boolean) {
if (checked && !this._checkedRows.includes(rowId)) {
this._checkedRows = [...this._checkedRows, rowId];
} else if (!checked) {
const index = this._checkedRows.indexOf(rowId);
if (index !== -1) {
this._checkedRows.splice(index, 1);
if (checked) {
if (this._checkedRows.includes(rowId)) {
return;
}
this._checkedRows = [...this._checkedRows, rowId];
} else {
const index = this._checkedRows.indexOf(rowId);
if (index === -1) {
return;
}
this._checkedRows.splice(index, 1);
}
fireEvent(this, "selection-changed", {
id: rowId,
@@ -407,7 +431,7 @@ export class HaDataTable extends BaseElement {
}
private _handleSearchChange(ev: CustomEvent): void {
this._debounceSearch(ev);
this._debounceSearch(ev.detail.value);
}
static get styles(): CSSResult {
@@ -587,6 +611,9 @@ export class HaDataTable extends BaseElement {
.mdc-data-table__header-cell:hover.not-sorted ha-icon {
left: 0px;
}
.table-header {
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
}
`;
}
}
@@ -0,0 +1,412 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import "@polymer/paper-listbox/paper-listbox";
import memoizeOne from "memoize-one";
import {
LitElement,
TemplateResult,
html,
css,
CSSResult,
customElement,
property,
PropertyValues,
} from "lit-element";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import "./ha-devices-picker";
import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../data/device_registry";
import { compare } from "../../common/string/compare";
import { PolymerChangedEvent } from "../../polymer-types";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
} from "../../data/area_registry";
import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { computeDomain } from "../../common/entity/compute_domain";
interface DevicesByArea {
[areaId: string]: AreaDevices;
}
interface AreaDevices {
id?: string;
name: string;
devices: string[];
}
const rowRenderer = (
root: HTMLElement,
_owner,
model: { item: AreaDevices }
) => {
if (!root.firstElementChild) {
root.innerHTML = `
<style>
paper-item {
width: 100%;
margin: -10px 0;
padding: 0;
}
paper-icon-button {
float: right;
}
.devices {
display: none;
}
.devices.visible {
display: block;
}
</style>
<paper-item>
<paper-item-body two-line="">
<div class='name'>[[item.name]]</div>
<div secondary>[[item.devices.length]] devices</div>
</paper-item-body>
</paper-item>
`;
}
root.querySelector(".name")!.textContent = model.item.name!;
root.querySelector(
"[secondary]"
)!.textContent = `${model.item.devices.length.toString()} devices`;
};
@customElement("ha-area-devices-picker")
export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
@property() public hass!: HomeAssistant;
@property() public label?: string;
@property() public value?: string;
@property() public area?: string;
@property() public devices?: string[];
/**
* Show only devices with entities from specific domains.
* @type {Array}
* @attr include-domains
*/
@property({ type: Array, attribute: "include-domains" })
public includeDomains?: string[];
/**
* Show no devices with entities of these domains.
* @type {Array}
* @attr exclude-domains
*/
@property({ type: Array, attribute: "exclude-domains" })
public excludeDomains?: string[];
/**
* Show only deviced with entities of these device classes.
* @type {Array}
* @attr include-device-classes
*/
@property({ type: Array, attribute: "include-device-classes" })
public includeDeviceClasses?: string[];
@property({ type: Boolean })
private _opened?: boolean;
@property() private _areaPicker = true;
@property() private _devices?: DeviceRegistryEntry[];
@property() private _areas?: AreaRegistryEntry[];
@property() private _entities?: EntityRegistryEntry[];
private _selectedDevices: string[] = [];
private _filteredDevices: DeviceRegistryEntry[] = [];
private _getDevices = memoizeOne(
(
devices: DeviceRegistryEntry[],
areas: AreaRegistryEntry[],
entities: EntityRegistryEntry[],
includeDomains: this["includeDomains"],
excludeDomains: this["excludeDomains"],
includeDeviceClasses: this["includeDeviceClasses"]
): AreaDevices[] => {
if (!devices.length) {
return [];
}
const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
let inputDevices = [...devices];
if (includeDomains) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) =>
includeDomains.includes(computeDomain(entity.entity_id))
);
});
}
if (excludeDomains) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return true;
}
return entities.every(
(entity) =>
!excludeDomains.includes(computeDomain(entity.entity_id))
);
});
}
if (includeDeviceClasses) {
inputDevices = inputDevices.filter((device) => {
const devEntities = deviceEntityLookup[device.id];
if (!devEntities || !devEntities.length) {
return false;
}
return deviceEntityLookup[device.id].some((entity) => {
const stateObj = this.hass.states[entity.entity_id];
if (!stateObj) {
return false;
}
return (
stateObj.attributes.device_class &&
includeDeviceClasses.includes(stateObj.attributes.device_class)
);
});
});
}
this._filteredDevices = inputDevices;
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
for (const area of areas) {
areaLookup[area.area_id] = area;
}
const devicesByArea: DevicesByArea = {};
for (const device of inputDevices) {
const areaId = device.area_id;
if (areaId) {
if (!(areaId in devicesByArea)) {
devicesByArea[areaId] = {
id: areaId,
name: areaLookup[areaId].name,
devices: [],
};
}
devicesByArea[areaId].devices.push(device.id);
}
}
const sorted = Object.keys(devicesByArea)
.sort((a, b) =>
compare(devicesByArea[a].name || "", devicesByArea[b].name || "")
)
.map((key) => devicesByArea[key]);
return sorted;
}
);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
this._devices = devices;
}),
subscribeAreaRegistry(this.hass.connection!, (areas) => {
this._areas = areas;
}),
subscribeEntityRegistry(this.hass.connection!, (entities) => {
this._entities = entities;
}),
];
}
protected updated(changedProps: PropertyValues) {
if (changedProps.has("area") && this.area) {
this._areaPicker = true;
this.value = this.area;
} else if (changedProps.has("devices") && this.devices) {
this._areaPicker = false;
const filteredDeviceIds = this._filteredDevices.map(
(device) => device.id
);
const selectedDevices = this.devices.filter((device) =>
filteredDeviceIds.includes(device)
);
this._setValue(selectedDevices);
}
}
protected render(): TemplateResult | void {
if (!this._devices || !this._areas || !this._entities) {
return;
}
const areas = this._getDevices(
this._devices,
this._areas,
this._entities,
this.includeDomains,
this.excludeDomains,
this.includeDeviceClasses
);
if (!this._areaPicker || areas.length === 0) {
return html`
<ha-devices-picker
@value-changed=${this._devicesPicked}
.hass=${this.hass}
.includeDomains=${this.includeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.value=${this._selectedDevices}
.pickDeviceLabel=${`Add ${this.label} device`}
.pickedDeviceLabel=${`${this.label} device`}
></ha-devices-picker>
${areas.length > 0
? html`
<mwc-button @click=${this._switchPicker}
>Choose an area</mwc-button
>
`
: ""}
`;
}
return html`
<vaadin-combo-box-light
item-value-path="id"
item-id-path="id"
item-label-path="name"
.items=${areas}
.value=${this._value}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._areaPicked}
>
<paper-input
.label=${this.label === undefined && this.hass
? this.hass.localize("ui.components.device-picker.device")
: `${this.label} in area`}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
>
${this.value
? html`
<paper-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
Clear
</paper-icon-button>
`
: ""}
${areas.length > 0
? html`
<paper-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.show_devices"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
Toggle
</paper-icon-button>
`
: ""}
</paper-input>
</vaadin-combo-box-light>
<mwc-button @click=${this._switchPicker}
>Choose individual devices</mwc-button
>
`;
}
private _clearValue(ev: Event) {
ev.stopPropagation();
this._setValue([]);
}
private get _value() {
return this.value || [];
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private async _switchPicker() {
this._areaPicker = !this._areaPicker;
}
private async _areaPicked(ev: PolymerChangedEvent<string>) {
const value = ev.detail.value;
let selectedDevices = [];
const target = ev.target as any;
if (target.selectedItem) {
selectedDevices = target.selectedItem.devices;
}
if (value !== this._value || this._selectedDevices !== selectedDevices) {
this._setValue(selectedDevices, value);
}
}
private _devicesPicked(ev: CustomEvent) {
ev.stopPropagation();
const selectedDevices = ev.detail.value;
this._setValue(selectedDevices);
}
private _setValue(selectedDevices: string[], value = "") {
this.value = value;
this._selectedDevices = selectedDevices;
setTimeout(() => {
fireEvent(this, "value-changed", { value: selectedDevices });
fireEvent(this, "change");
}, 0);
}
static get styles(): CSSResult {
return css`
paper-input > paper-icon-button {
width: 24px;
height: 24px;
padding: 2px;
color: var(--secondary-text-color);
}
[hidden] {
display: none;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-area-devices-picker": HaAreaDevicesPicker;
}
}
@@ -176,6 +176,7 @@ export abstract class HaDeviceAutomationPicker<
this.value = automation;
setTimeout(() => {
fireEvent(this, "change");
fireEvent(this, "value-changed", { value: automation });
}, 0);
}
+1 -1
View File
@@ -1,7 +1,7 @@
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import "@polymer/paper-listbox/paper-listbox";
import memoizeOne from "memoize-one";
import {
+127
View File
@@ -0,0 +1,127 @@
import {
LitElement,
TemplateResult,
property,
html,
customElement,
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button-light";
import { HomeAssistant } from "../../types";
import { PolymerChangedEvent } from "../../polymer-types";
import { fireEvent } from "../../common/dom/fire_event";
import "./ha-device-picker";
@customElement("ha-devices-picker")
class HaDevicesPicker extends LitElement {
@property() public hass?: HomeAssistant;
@property() public value?: string[];
/**
* Show entities from specific domains.
* @type {string}
* @attr include-domains
*/
@property({ type: Array, attribute: "include-domains" })
public includeDomains?: string[];
/**
* Show no entities of these domains.
* @type {Array}
* @attr exclude-domains
*/
@property({ type: Array, attribute: "exclude-domains" })
public excludeDomains?: string[];
@property({ attribute: "picked-device-label" })
@property({ type: Array, attribute: "include-device-classes" })
public includeDeviceClasses?: string[];
public pickedDeviceLabel?: string;
@property({ attribute: "pick-device-label" }) public pickDeviceLabel?: string;
protected render(): TemplateResult | void {
if (!this.hass) {
return;
}
const currentDevices = this._currentDevices;
return html`
${currentDevices.map(
(entityId) => html`
<div>
<ha-device-picker
allow-custom-entity
.curValue=${entityId}
.hass=${this.hass}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.value=${entityId}
.label=${this.pickedDeviceLabel}
@value-changed=${this._deviceChanged}
></ha-device-picker>
</div>
`
)}
<div>
<ha-device-picker
.hass=${this.hass}
.includeDomains=${this.includeDomains}
.excludeDomains=${this.excludeDomains}
.includeDeviceClasses=${this.includeDeviceClasses}
.label=${this.pickDeviceLabel}
@value-changed=${this._addDevice}
></ha-device-picker>
</div>
`;
}
private get _currentDevices() {
return this.value || [];
}
private async _updateDevices(devices) {
fireEvent(this, "value-changed", {
value: devices,
});
this.value = devices;
}
private _deviceChanged(event: PolymerChangedEvent<string>) {
event.stopPropagation();
const curValue = (event.currentTarget as any).curValue;
const newValue = event.detail.value;
if (newValue === curValue || newValue !== "") {
return;
}
if (newValue === "") {
this._updateDevices(
this._currentDevices.filter((dev) => dev !== curValue)
);
} else {
this._updateDevices(
this._currentDevices.map((dev) => (dev === curValue ? newValue : dev))
);
}
}
private async _addDevice(event: PolymerChangedEvent<string>) {
event.stopPropagation();
const toAdd = event.detail.value;
(event.currentTarget as any).value = "";
if (!toAdd) {
return;
}
const currentDevices = this._currentDevices;
if (currentDevices.includes(toAdd)) {
return;
}
this._updateDevices([...currentDevices, toAdd]);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-devices-picker": HaDevicesPicker;
}
}
+3 -1
View File
@@ -215,7 +215,9 @@ class HaChartBase extends mixinBehaviors(
}
if (scriptsLoaded === null) {
scriptsLoaded = import(/* webpackChunkName: "load_chart" */ "../../resources/ha-chart-scripts.js");
scriptsLoaded = import(
/* webpackChunkName: "load_chart" */ "../../resources/ha-chart-scripts.js"
);
}
scriptsLoaded.then((ChartModule) => {
this.ChartClass = ChartModule.default;
+1 -1
View File
@@ -2,7 +2,7 @@ import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import memoizeOne from "memoize-one";
import "./state-badge";
-91
View File
@@ -1,91 +0,0 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import hassAttributeUtil from "../util/hass-attributes-util";
class HaAttributes extends PolymerElement {
static get template() {
return html`
<style include="iron-flex iron-flex-alignment"></style>
<style>
.data-entry .value {
max-width: 200px;
overflow-wrap: break-word;
}
.attribution {
color: var(--secondary-text-color);
text-align: right;
}
</style>
<div class="layout vertical">
<template
is="dom-repeat"
items="[[computeDisplayAttributes(stateObj, filtersArray)]]"
as="attribute"
>
<div class="data-entry layout justified horizontal">
<div class="key">[[formatAttribute(attribute)]]</div>
<div class="value">
[[formatAttributeValue(stateObj, attribute)]]
</div>
</div>
</template>
<div class="attribution" hidden$="[[!computeAttribution(stateObj)]]">
[[computeAttribution(stateObj)]]
</div>
</div>
`;
}
static get properties() {
return {
stateObj: {
type: Object,
},
extraFilters: {
type: String,
value: "",
},
filtersArray: {
type: Array,
computed: "computeFiltersArray(extraFilters)",
},
};
}
computeFiltersArray(extraFilters) {
return Object.keys(hassAttributeUtil.LOGIC_STATE_ATTRIBUTES).concat(
extraFilters ? extraFilters.split(",") : []
);
}
computeDisplayAttributes(stateObj, filtersArray) {
if (!stateObj) {
return [];
}
return Object.keys(stateObj.attributes).filter(function(key) {
return filtersArray.indexOf(key) === -1;
});
}
formatAttribute(attribute) {
return attribute.replace(/_/g, " ");
}
formatAttributeValue(stateObj, attribute) {
var value = stateObj.attributes[attribute];
if (value === null) return "-";
if (Array.isArray(value)) {
return value.join(", ");
}
return value instanceof Object ? JSON.stringify(value, null, 2) : value;
}
computeAttribution(stateObj) {
return stateObj.attributes.attribution;
}
}
customElements.define("ha-attributes", HaAttributes);
+97
View File
@@ -0,0 +1,97 @@
import {
property,
LitElement,
TemplateResult,
html,
CSSResult,
css,
customElement,
} from "lit-element";
import { HassEntity } from "home-assistant-js-websocket";
import hassAttributeUtil from "../util/hass-attributes-util";
@customElement("ha-attributes")
class HaAttributes extends LitElement {
@property() public stateObj?: HassEntity;
@property() public extraFilters?: string;
protected render(): TemplateResult | void {
if (!this.stateObj) {
return html``;
}
return html`
<div>
${this.computeDisplayAttributes(
Object.keys(hassAttributeUtil.LOGIC_STATE_ATTRIBUTES).concat(
this.extraFilters ? this.extraFilters.split(",") : []
)
).map(
(attribute) => html`
<div class="data-entry">
<div class="key">${attribute.replace(/_/g, " ")}</div>
<div class="value">
${this.formatAttributeValue(attribute)}
</div>
</div>
`
)}
${this.stateObj.attributes.attribution
? html`
<div class="attribution">
${this.stateObj.attributes.attribution}
</div>
`
: ""}
</div>
`;
}
static get styles(): CSSResult {
return css`
.data-entry {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.data-entry .value {
max-width: 200px;
overflow-wrap: break-word;
}
.attribution {
color: var(--secondary-text-color);
text-align: right;
}
`;
}
private computeDisplayAttributes(filtersArray: string[]): string[] {
if (!this.stateObj) {
return [];
}
return Object.keys(this.stateObj.attributes).filter((key) => {
return filtersArray.indexOf(key) === -1;
});
}
private formatAttributeValue(attribute: string): string {
if (!this.stateObj) {
return "-";
}
const value = this.stateObj.attributes[attribute];
if (value === null) {
return "-";
}
if (Array.isArray(value)) {
return value.join(", ");
}
return value instanceof Object ? JSON.stringify(value, null, 2) : value;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-attributes": HaAttributes;
}
}
+3 -2
View File
@@ -122,8 +122,9 @@ class HaCameraStream extends LitElement {
private async _startHls(): Promise<void> {
// tslint:disable-next-line
const Hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any)
.default as HLSModule;
const Hls = ((await import(
/* webpackChunkName: "hls.js" */ "hls.js"
)) as any).default as HLSModule;
let hlsSupported = Hls.isSupported();
const videoEl = this._videoEl;
+5 -15
View File
@@ -72,9 +72,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
computeCurrentStatus(hass, stateObj) {
if (!hass || !stateObj) return null;
if (stateObj.attributes.current_temperature != null) {
return `${stateObj.attributes.current_temperature} ${
hass.config.unit_system.temperature
}`;
return `${stateObj.attributes.current_temperature} ${hass.config.unit_system.temperature}`;
}
if (stateObj.attributes.current_humidity != null) {
return `${stateObj.attributes.current_humidity} %`;
@@ -89,22 +87,16 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
stateObj.attributes.target_temp_low != null &&
stateObj.attributes.target_temp_high != null
) {
return `${stateObj.attributes.target_temp_low}-${
stateObj.attributes.target_temp_high
} ${hass.config.unit_system.temperature}`;
return `${stateObj.attributes.target_temp_low}-${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`;
}
if (stateObj.attributes.temperature != null) {
return `${stateObj.attributes.temperature} ${
hass.config.unit_system.temperature
}`;
return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`;
}
if (
stateObj.attributes.target_humidity_low != null &&
stateObj.attributes.target_humidity_high != null
) {
return `${stateObj.attributes.target_humidity_low}-${
stateObj.attributes.target_humidity_high
}%`;
return `${stateObj.attributes.target_humidity_low}-${stateObj.attributes.target_humidity_high}%`;
}
if (stateObj.attributes.humidity != null) {
return `${stateObj.attributes.humidity} %`;
@@ -121,9 +113,7 @@ class HaClimateState extends LocalizeMixin(PolymerElement) {
const stateString = localize(`state.climate.${stateObj.state}`);
return stateObj.attributes.hvac_action
? `${localize(
`state_attributes.climate.hvac_action.${
stateObj.attributes.hvac_action
}`
`state_attributes.climate.hvac_action.${stateObj.attributes.hvac_action}`
)} (${stateString})`
: stateString;
}
+1 -1
View File
@@ -3,7 +3,7 @@ import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
import { EventsMixin } from "../mixins/events-mixin";
+2 -1
View File
@@ -1,4 +1,5 @@
import { classMap, html, customElement } from "@material/mwc-base/base-element";
import { classMap } from "lit-html/directives/class-map";
import { html, customElement } from "lit-element";
import { ripple } from "@material/mwc-ripple/ripple-directive.js";
import "@material/mwc-fab";
+3 -8
View File
@@ -43,14 +43,9 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
}
private _valueChanged(ev: Event) {
fireEvent(
this,
"value-changed",
{
value: (ev.target as PaperCheckboxElement).checked,
},
{ bubbles: false }
);
fireEvent(this, "value-changed", {
value: (ev.target as PaperCheckboxElement).checked,
});
}
static get styles(): CSSResult {
+3 -8
View File
@@ -51,14 +51,9 @@ export class HaFormFloat extends LitElement implements HaFormElement {
if (this._value === value) {
return;
}
fireEvent(
this,
"value-changed",
{
value,
},
{ bubbles: false }
);
fireEvent(this, "value-changed", {
value,
});
}
}
+3 -8
View File
@@ -71,14 +71,9 @@ export class HaFormInteger extends LitElement implements HaFormElement {
if (this._value === value) {
return;
}
fireEvent(
this,
"value-changed",
{
value,
},
{ bubbles: false }
);
fireEvent(this, "value-changed", {
value,
});
}
}
@@ -96,19 +96,14 @@ export class HaFormTimePeriod extends LitElement implements HaFormElement {
value %= 60;
}
fireEvent(
this,
"value-changed",
{
value: {
hours,
minutes,
seconds: this._seconds,
...{ [unit]: value },
},
fireEvent(this, "value-changed", {
value: {
hours,
minutes,
seconds: this._seconds,
...{ [unit]: value },
},
{ bubbles: false }
);
});
}
}
+3 -8
View File
@@ -60,14 +60,9 @@ export class HaFormSelect extends LitElement implements HaFormElement {
if (!ev.detail.value) {
return;
}
fireEvent(
this,
"value-changed",
{
value: ev.detail.value.itemValue,
},
{ bubbles: false }
);
fireEvent(this, "value-changed", {
value: ev.detail.value.itemValue,
});
}
}
+16 -8
View File
@@ -56,6 +56,7 @@ export class HaFormString extends LitElement implements HaFormElement {
`
: html`
<paper-input
.type=${this._stringType}
.label=${this.label}
.value=${this.data}
.required=${this.schema.required}
@@ -75,14 +76,21 @@ export class HaFormString extends LitElement implements HaFormElement {
if (this.data === value) {
return;
}
fireEvent(
this,
"value-changed",
{
value,
},
{ bubbles: false }
);
fireEvent(this, "value-changed", {
value,
});
}
private _stringType() {
if (this.schema.format) {
if (["email", "url"].includes(this.schema.format)) {
return this.schema.format;
}
if (this.schema.format === "fqdnurl") {
return "url";
}
}
return "text";
}
}
+12 -42
View File
@@ -3,10 +3,8 @@ import {
LitElement,
html,
property,
query,
CSSResult,
css,
PropertyValues,
} from "lit-element";
import "./ha-form-string";
@@ -16,6 +14,7 @@ import "./ha-form-boolean";
import "./ha-form-select";
import "./ha-form-positive_time_period_dict";
import { fireEvent } from "../../common/dom/fire_event";
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
export type HaFormSchema =
| HaFormStringSchema
@@ -51,6 +50,7 @@ export interface HaFormFloatSchema extends HaFormBaseSchema {
export interface HaFormStringSchema extends HaFormBaseSchema {
type: "string";
format?: string;
}
export interface HaFormBooleanSchema extends HaFormBaseSchema {
@@ -99,20 +99,14 @@ export class HaForm extends LitElement implements HaFormElement {
@property() public computeError?: (schema: HaFormSchema, error) => string;
@property() public computeLabel?: (schema: HaFormSchema) => string;
@property() public computeSuffix?: (schema: HaFormSchema) => string;
@query("ha-form") private _childForm?: HaForm;
@query("#element") private _elementContainer?: HTMLDivElement;
public focus() {
const input = this._childForm
? this._childForm
: this._elementContainer
? this._elementContainer.lastChild
: undefined;
const input =
this.shadowRoot!.getElementById("child-form") ||
this.shadowRoot!.querySelector("ha-form");
if (!input) {
return;
}
(input as HTMLElement).focus();
}
@@ -150,40 +144,16 @@ export class HaForm extends LitElement implements HaFormElement {
</div>
`
: ""}
<div id="element" @value-changed=${this._valueChanged}></div>
${dynamicElement(`ha-form-${this.schema.type}`, {
schema: this.schema,
data: this.data,
label: this._computeLabel(this.schema),
suffix: this._computeSuffix(this.schema),
id: "child-form",
})}
`;
}
protected updated(changedProperties: PropertyValues) {
const schemaChanged = changedProperties.has("schema");
const oldSchema = schemaChanged
? changedProperties.get("schema")
: undefined;
if (
!Array.isArray(this.schema) &&
schemaChanged &&
(!oldSchema || (oldSchema as HaFormSchema).type !== this.schema.type)
) {
const element = document.createElement(
`ha-form-${this.schema.type}`
) as HaFormElement;
element.schema = this.schema;
element.data = this.data;
element.label = this._computeLabel(this.schema);
element.suffix = this._computeSuffix(this.schema);
if (this._elementContainer!.lastChild) {
this._elementContainer!.removeChild(this._elementContainer!.lastChild);
}
this._elementContainer!.append(element);
} else if (this._elementContainer && this._elementContainer.lastChild) {
const element = this._elementContainer!.lastChild as HaFormElement;
element.schema = this.schema;
element.data = this.data;
element.label = this._computeLabel(this.schema);
element.suffix = this._computeSuffix(this.schema);
}
}
private _computeLabel(schema: HaFormSchema) {
return this.computeLabel
? this.computeLabel(schema)
+19 -4
View File
@@ -520,10 +520,13 @@ class HaSidebar extends LitElement {
}
a {
text-decoration: none;
color: var(--sidebar-text-color);
font-weight: 500;
font-size: 14px;
text-decoration: none;
position: relative;
display: block;
outline: 0;
}
paper-icon-item {
@@ -546,7 +549,8 @@ class HaSidebar extends LitElement {
color: var(--sidebar-icon-color);
}
.iron-selected paper-icon-item:before {
.iron-selected paper-icon-item::before,
a:not(.iron-selected):focus::before {
border-radius: 4px;
position: absolute;
top: 0;
@@ -555,11 +559,22 @@ class HaSidebar extends LitElement {
left: 0;
pointer-events: none;
content: "";
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
transition: opacity 15ms linear;
will-change: opacity;
}
.iron-selected paper-icon-item::before {
background-color: var(--sidebar-selected-icon-color);
opacity: 0.12;
}
a:not(.iron-selected):focus::before {
background-color: currentColor;
opacity: var(--dark-divider-opacity);
margin: 4px 8px;
}
.iron-selected paper-icon-item:focus::before,
.iron-selected:focus paper-icon-item::before {
opacity: 0.2;
}
.iron-selected paper-icon-item[pressed]:before {
opacity: 0.37;
+2 -6
View File
@@ -58,14 +58,10 @@ class HaWaterHeaterState extends LocalizeMixin(PolymerElement) {
stateObj.attributes.target_temp_low != null &&
stateObj.attributes.target_temp_high != null
) {
return `${stateObj.attributes.target_temp_low} - ${
stateObj.attributes.target_temp_high
} ${hass.config.unit_system.temperature}`;
return `${stateObj.attributes.target_temp_low} - ${stateObj.attributes.target_temp_high} ${hass.config.unit_system.temperature}`;
}
if (stateObj.attributes.temperature != null) {
return `${stateObj.attributes.temperature} ${
hass.config.unit_system.temperature
}`;
return `${stateObj.attributes.temperature} ${hass.config.unit_system.temperature}`;
}
return "";
+96
View File
@@ -0,0 +1,96 @@
import { safeDump, safeLoad } from "js-yaml";
import "./ha-code-editor";
import { LitElement, property, customElement, html, query } from "lit-element";
import { fireEvent } from "../common/dom/fire_event";
import { afterNextRender } from "../common/util/render-status";
// tslint:disable-next-line
import { HaCodeEditor } from "./ha-code-editor";
const isEmpty = (obj: object) => {
if (typeof obj !== "object") {
return false;
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
return false;
}
}
return true;
};
@customElement("ha-yaml-editor")
export class HaYamlEditor extends LitElement {
@property() public value?: any;
@property() public isValid = true;
@property() public label?: string;
@property() private _yaml?: string;
@query("ha-code-editor") private _editor?: HaCodeEditor;
public setValue(value) {
try {
this._yaml = value && !isEmpty(value) ? safeDump(value) : "";
} catch (err) {
alert(`There was an error converting to YAML: ${err}`);
}
afterNextRender(() => {
if (this._editor?.codemirror) {
this._editor.codemirror.refresh();
}
});
}
protected firstUpdated() {
this.setValue(this.value);
}
protected render() {
if (this._yaml === undefined) {
return;
}
return html`
${this.label
? html`
<p>${this.label}</p>
`
: ""}
<ha-code-editor
.value=${this._yaml}
mode="yaml"
.error=${this.isValid === false}
@value-changed=${this._onChange}
></ha-code-editor>
`;
}
private _onChange(ev: CustomEvent) {
ev.stopPropagation();
const value = ev.detail.value;
let parsed;
let isValid = true;
if (value) {
try {
parsed = safeLoad(value);
isValid = true;
} catch (err) {
// Invalid YAML
isValid = false;
}
} else {
parsed = {};
}
this.value = parsed;
this.isValid = isValid;
if (isValid) {
fireEvent(this, "value-changed", { value: parsed });
}
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-yaml-editor": HaYamlEditor;
}
}
+156 -3
View File
@@ -4,6 +4,8 @@ import {
} from "home-assistant-js-websocket";
import { HomeAssistant } from "../types";
import { navigate } from "../common/navigate";
import { DeviceCondition, DeviceTrigger } from "./device_automation";
import { Action } from "./script";
export interface AutomationEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
@@ -15,11 +17,162 @@ export interface AutomationEntity extends HassEntityBase {
export interface AutomationConfig {
alias: string;
description: string;
trigger: any[];
condition?: any[];
action: any[];
trigger: Trigger[];
condition?: Condition[];
action: Action[];
}
export interface ForDict {
hours?: number | string;
minutes?: number | string;
seconds?: number | string;
}
export interface StateTrigger {
platform: "state";
entity_id?: string;
from?: string | number;
to?: string | number;
for?: string | number | ForDict;
}
export interface MqttTrigger {
platform: "mqtt";
topic: string;
payload?: string;
}
export interface GeoLocationTrigger {
platform: "geo_location";
source: "string";
zone: "string";
event: "enter" | "leave";
}
export interface HassTrigger {
platform: "homeassistant";
event: "start" | "shutdown";
}
export interface NumericStateTrigger {
platform: "numeric_state";
entity_id: string;
above?: number;
below?: number;
value_template?: string;
for?: string | number | ForDict;
}
export interface SunTrigger {
platform: "sun";
offset: number;
event: "sunrise" | "sunset";
}
export interface TimePatternTrigger {
platform: "time_pattern";
hours?: number | string;
minutes?: number | string;
seconds?: number | string;
}
export interface WebhookTrigger {
platform: "webhook";
webhook_id: string;
}
export interface ZoneTrigger {
platform: "zone";
entity_id: string;
zone: string;
event: "enter" | "leave";
}
export interface TimeTrigger {
platform: "time";
at: string;
}
export interface TemplateTrigger {
platform: "template";
value_template: string;
}
export interface EventTrigger {
platform: "event";
event_type: string;
event_data: any;
}
export type Trigger =
| StateTrigger
| MqttTrigger
| GeoLocationTrigger
| HassTrigger
| NumericStateTrigger
| SunTrigger
| TimePatternTrigger
| WebhookTrigger
| ZoneTrigger
| TimeTrigger
| TemplateTrigger
| EventTrigger
| DeviceTrigger;
export interface LogicalCondition {
condition: "and" | "or";
conditions: Condition[];
}
export interface StateCondition {
condition: "state";
entity_id: string;
state: string | number;
}
export interface NumericStateCondition {
condition: "numeric_state";
entity_id: string;
above?: number;
below?: number;
value_template?: string;
}
export interface SunCondition {
condition: "sun";
after_offset: number;
before_offset: number;
after: "sunrise" | "sunset";
before: "sunrise" | "sunset";
}
export interface ZoneCondition {
condition: "zone";
entity_id: string;
zone: string;
}
export interface TimeCondition {
condition: "time";
after: string;
before: string;
}
export interface TemplateCondition {
condition: "template";
value_template: string;
}
export type Condition =
| StateCondition
| NumericStateCondition
| SunCondition
| ZoneCondition
| TimeCondition
| TemplateCondition
| DeviceCondition
| LogicalCondition;
export const deleteAutomation = (hass: HomeAssistant, id: string) =>
hass.callApi("DELETE", `config/automation/config/${id}`);
+1 -3
View File
@@ -19,9 +19,7 @@ export interface Stream {
}
export const computeMJPEGStreamUrl = (entity: CameraEntity) =>
`/api/camera_proxy_stream/${entity.entity_id}?token=${
entity.attributes.access_token
}`;
`/api/camera_proxy_stream/${entity.entity_id}?token=${entity.attributes.access_token}`;
export const fetchThumbnailUrlWithCache = (
hass: HomeAssistant,
+5
View File
@@ -4,6 +4,8 @@ import { debounce } from "../common/util/debounce";
import { getCollection, Connection } from "home-assistant-js-websocket";
import { LocalizeFunc } from "../common/translations/localize";
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf"];
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
handler,
@@ -26,6 +28,9 @@ export const handleConfigFlowStep = (
data
);
export const ignoreConfigFlow = (hass: HomeAssistant, flowId: string) =>
hass.callWS({ type: "config_entries/ignore_flow", flow_id: flowId });
export const deleteConfigFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi("DELETE", `config/config_entries/flow/${flowId}`);
+1 -1
View File
@@ -3,7 +3,7 @@ import { HomeAssistant } from "../types";
interface ProcessResults {
card: { [key: string]: { [key: string]: string } };
speech: {
[SpeechType in "plain" | "ssml"]: { extra_data: any; speech: string }
[SpeechType in "plain" | "ssml"]: { extra_data: any; speech: string };
};
}
+6 -16
View File
@@ -18,7 +18,7 @@ export interface DeviceCondition extends DeviceAutomation {
}
export interface DeviceTrigger extends DeviceAutomation {
platform: string;
platform: "device";
}
export const fetchDeviceActions = (hass: HomeAssistant, deviceId: string) =>
@@ -107,9 +107,7 @@ export const localizeDeviceAutomationAction = (
state ? computeStateName(state) : "<unknown>",
"subtype",
hass.localize(
`component.${action.domain}.device_automation.action_subtype.${
action.subtype
}`
`component.${action.domain}.device_automation.action_subtype.${action.subtype}`
)
);
};
@@ -122,16 +120,12 @@ export const localizeDeviceAutomationCondition = (
? hass.states[condition.entity_id]
: undefined;
return hass.localize(
`component.${condition.domain}.device_automation.condition_type.${
condition.type
}`,
`component.${condition.domain}.device_automation.condition_type.${condition.type}`,
"entity_name",
state ? computeStateName(state) : "<unknown>",
"subtype",
hass.localize(
`component.${condition.domain}.device_automation.condition_subtype.${
condition.subtype
}`
`component.${condition.domain}.device_automation.condition_subtype.${condition.subtype}`
)
);
};
@@ -142,16 +136,12 @@ export const localizeDeviceAutomationTrigger = (
) => {
const state = trigger.entity_id ? hass.states[trigger.entity_id] : undefined;
return hass.localize(
`component.${trigger.domain}.device_automation.trigger_type.${
trigger.type
}`,
`component.${trigger.domain}.device_automation.trigger_type.${trigger.type}`,
"entity_name",
state ? computeStateName(state) : "<unknown>",
"subtype",
hass.localize(
`component.${trigger.domain}.device_automation.trigger_subtype.${
trigger.subtype
}`
`component.${trigger.domain}.device_automation.trigger_subtype.${trigger.subtype}`
)
);
};
+1 -3
View File
@@ -149,9 +149,7 @@ export const createHassioSession = async (hass: HomeAssistant) => {
"POST",
"hassio/ingress/session"
);
document.cookie = `ingress_session=${
response.data.session
};path=/api/hassio_ingress/`;
document.cookie = `ingress_session=${response.data.session};path=/api/hassio_ingress/`;
};
export const reloadHassioAddons = (hass: HomeAssistant) =>
+7
View File
@@ -0,0 +1,7 @@
export interface LogbookEntry {
when: string;
name: string;
message: string;
entity_id?: string;
domain: string;
}
+11 -1
View File
@@ -69,6 +69,10 @@ export interface NoActionConfig extends BaseActionConfig {
action: "none";
}
export interface CustomActionConfig extends BaseActionConfig {
action: "fire-dom-event";
}
export interface BaseActionConfig {
confirmation?: ConfirmationRestrictionConfig;
}
@@ -88,7 +92,8 @@ export type ActionConfig =
| NavigateActionConfig
| UrlActionConfig
| MoreInfoActionConfig
| NoActionConfig;
| NoActionConfig
| CustomActionConfig;
export const fetchConfig = (
conn: Connection,
@@ -108,6 +113,11 @@ export const saveConfig = (
config,
});
export const deleteConfig = (hass: HomeAssistant): Promise<void> =>
hass.callWS({
type: "lovelace/config/delete",
});
export const subscribeLovelaceUpdates = (
conn: Connection,
onChange: () => void
-32
View File
@@ -18,38 +18,6 @@ export const SCENE_IGNORED_DOMAINS = [
"zone",
];
export const SCENE_SAVED_ATTRIBUTES = {
light: [
"brightness",
"color_temp",
"effect",
"rgb_color",
"xy_color",
"hs_color",
],
media_player: [
"is_volume_muted",
"volume_level",
"sound_mode",
"source",
"media_content_id",
"media_content_type",
],
climate: [
"target_temperature",
"target_temperature_high",
"target_temperature_low",
"target_humidity",
"fan_mode",
"swing_mode",
"hvac_mode",
"preset_mode",
],
vacuum: ["cleaning_mode"],
fan: ["speed", "current_direction"],
water_heather: ["temperature", "operation_mode"],
};
export interface SceneEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & { id?: string };
}
+44
View File
@@ -1,5 +1,21 @@
import { HomeAssistant } from "../types";
import { computeObjectId } from "../common/entity/compute_object_id";
import { Condition } from "./automation";
import {
HassEntityBase,
HassEntityAttributeBase,
} from "home-assistant-js-websocket";
export interface ScriptEntity extends HassEntityBase {
attributes: HassEntityAttributeBase & {
last_triggered: string;
};
}
export interface ScriptConfig {
alias: string;
sequence: Action[];
}
export interface EventAction {
event: string;
@@ -7,12 +23,40 @@ export interface EventAction {
event_data_template?: { [key: string]: any };
}
export interface ServiceAction {
service: string;
entity_id?: string;
data?: { [key: string]: any };
}
export interface DeviceAction {
device_id: string;
domain: string;
entity_id: string;
}
export interface DelayAction {
delay: number;
}
export interface SceneAction {
scene: string;
}
export interface WaitAction {
wait_template: string;
timeout?: number;
}
export type Action =
| EventAction
| DeviceAction
| ServiceAction
| Condition
| DelayAction
| SceneAction
| WaitAction;
export const triggerScript = (
hass: HomeAssistant,
entityId: string,
+95
View File
@@ -51,6 +51,12 @@ export interface ReadAttributeServiceData {
manufacturer?: number;
}
export interface ZHAGroup {
name: string;
group_id: number;
members: ZHADevice[];
}
export const reconfigureNode = (
hass: HomeAssistant,
ieeeAddress: string
@@ -120,6 +126,32 @@ export const unbindDevices = (
target_ieee: targetIEEE,
});
export const bindDeviceToGroup = (
hass: HomeAssistant,
deviceIEEE: string,
groupId: number,
clusters: Cluster[]
): Promise<void> =>
hass.callWS({
type: "zha/groups/bind",
source_ieee: deviceIEEE,
group_id: groupId,
bindings: clusters,
});
export const unbindDeviceFromGroup = (
hass: HomeAssistant,
deviceIEEE: string,
groupId: number,
clusters: Cluster[]
): Promise<void> =>
hass.callWS({
type: "zha/groups/unbind",
source_ieee: deviceIEEE,
group_id: groupId,
bindings: clusters,
});
export const readAttributeValue = (
hass: HomeAssistant,
data: ReadAttributeServiceData
@@ -153,3 +185,66 @@ export const fetchClustersForZhaNode = (
type: "zha/devices/clusters",
ieee: ieeeAddress,
});
export const fetchGroups = (hass: HomeAssistant): Promise<ZHAGroup[]> =>
hass.callWS({
type: "zha/groups",
});
export const removeGroups = (
hass: HomeAssistant,
groupIdsToRemove: number[]
): Promise<ZHAGroup[]> =>
hass.callWS({
type: "zha/group/remove",
group_ids: groupIdsToRemove,
});
export const fetchGroup = (
hass: HomeAssistant,
groupId: number
): Promise<ZHAGroup> =>
hass.callWS({
type: "zha/group",
group_id: groupId,
});
export const fetchGroupableDevices = (
hass: HomeAssistant
): Promise<ZHADevice[]> =>
hass.callWS({
type: "zha/devices/groupable",
});
export const addMembersToGroup = (
hass: HomeAssistant,
groupId: number,
membersToAdd: string[]
): Promise<ZHAGroup> =>
hass.callWS({
type: "zha/group/members/add",
group_id: groupId,
members: membersToAdd,
});
export const removeMembersFromGroup = (
hass: HomeAssistant,
groupId: number,
membersToRemove: string[]
): Promise<ZHAGroup> =>
hass.callWS({
type: "zha/group/members/remove",
group_id: groupId,
members: membersToRemove,
});
export const addGroup = (
hass: HomeAssistant,
groupName: string,
membersToAdd?: string[]
): Promise<ZHAGroup> =>
hass.callWS({
type: "zha/group/add",
group_name: groupName,
members: membersToAdd,
});
@@ -98,9 +98,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
"ui.dialogs.config_entry_system_options.enable_new_entities_description",
"integration",
this.hass.localize(
`component.${
this._params.entry.domain
}.config.title`
`component.${this._params.entry.domain}.config.title`
) || this._params.entry.domain
)}
</p>
@@ -117,7 +115,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
.disabled=${this._submitting}
>
${this.hass.localize(
"ui.panel.config.entity_registry.editor.update"
"ui.panel.config.entities.editor.update"
)}
</mwc-button>
</div>
@@ -10,7 +10,9 @@ export interface ConfigEntrySystemOptionsDialogParams {
}
export const loadConfigEntrySystemOptionsDialog = () =>
import(/* webpackChunkName: "config-entry-system-options" */ "./dialog-config-entry-system-options");
import(
/* webpackChunkName: "config-entry-system-options" */ "./dialog-config-entry-system-options"
);
export const showConfigEntrySystemOptionsDialog = (
element: HTMLElement,
@@ -11,6 +11,7 @@ import {
import "@material/mwc-button";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-tooltip/paper-tooltip";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-spinner/paper-spinner";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
@@ -124,6 +125,7 @@ class DataEntryFlowDialog extends LitElement {
<ha-paper-dialog
with-backdrop
opened
modal
@opened-changed=${this._openedChanged}
>
${this._loading || (this._step === null && this._handlers === undefined)
@@ -134,53 +136,62 @@ class DataEntryFlowDialog extends LitElement {
? // When we are going to next step, we render 1 round of empty
// to reset the element.
""
: this._step === null
? // Show handler picker
html`
<step-flow-pick-handler
.flowConfig=${this._params.flowConfig}
.hass=${this.hass}
.handlers=${this._handlers}
.showAdvanced=${this._params.showAdvanced}
></step-flow-pick-handler>
`
: this._step.type === "form"
? html`
<step-flow-form
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-form>
`
: this._step.type === "external"
? html`
<step-flow-external
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-external>
`
: this._step.type === "abort"
? html`
<step-flow-abort
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-abort>
`
: this._devices === undefined || this._areas === undefined
? // When it's a create entry result, we will fetch device & area registry
html`
<step-flow-loading></step-flow-loading>
`
: html`
<step-flow-create-entry
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
.devices=${this._devices}
.areas=${this._areas}
></step-flow-create-entry>
<paper-icon-button
aria-label=${this.hass.localize(
"ui.panel.config.integrations.config_flow.dismiss"
)}
icon="hass:close"
dialog-dismiss
></paper-icon-button>
${this._step === null
? // Show handler picker
html`
<step-flow-pick-handler
.flowConfig=${this._params.flowConfig}
.hass=${this.hass}
.handlers=${this._handlers}
.showAdvanced=${this._params.showAdvanced}
></step-flow-pick-handler>
`
: this._step.type === "form"
? html`
<step-flow-form
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-form>
`
: this._step.type === "external"
? html`
<step-flow-external
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-external>
`
: this._step.type === "abort"
? html`
<step-flow-abort
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
></step-flow-abort>
`
: this._devices === undefined || this._areas === undefined
? // When it's a create entry result, we will fetch device & area registry
html`
<step-flow-loading></step-flow-loading>
`
: html`
<step-flow-create-entry
.flowConfig=${this._params.flowConfig}
.step=${this._step}
.hass=${this.hass}
.devices=${this._devices}
.areas=${this._areas}
></step-flow-create-entry>
`}
`}
</ha-paper-dialog>
`;
@@ -318,6 +329,12 @@ class DataEntryFlowDialog extends LitElement {
display: block;
padding: 0;
}
paper-icon-button {
display: inline-block;
padding: 8px;
margin: 16px 16px 0 0;
float: right;
}
`,
];
}
@@ -71,9 +71,7 @@ export const showConfigFlowDialog = (
renderShowFormStepFieldLabel(hass, step, field) {
return hass.localize(
`component.${step.handler}.config.step.${step.step_id}.data.${
field.name
}`
`component.${step.handler}.config.step.${step.step_id}.data.${field.name}`
);
},
@@ -79,7 +79,9 @@ export interface DataEntryFlowDialogParams {
}
export const loadDataEntryFlowDialog = () =>
import(/* webpackChunkName: "dialog-config-flow" */ "./dialog-data-entry-flow");
import(
/* webpackChunkName: "dialog-config-flow" */ "./dialog-data-entry-flow"
);
export const showFlowDialog = (
element: HTMLElement,
@@ -54,9 +54,7 @@ export const showOptionsFlowDialog = (
renderShowFormStepFieldLabel(hass, step, field) {
return hass.localize(
`component.${configEntry.domain}.options.step.${step.step_id}.data.${
field.name
}`
`component.${configEntry.domain}.options.step.${step.step_id}.data.${field.name}`
);
},
@@ -109,6 +109,7 @@ class StepFlowForm extends LitElement {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
setTimeout(() => this.shadowRoot!.querySelector("ha-form")!.focus(), 0);
this.addEventListener("keypress", (ev) => {
if (ev.keyCode === 13) {
this._submitStep();
@@ -19,6 +19,7 @@ import "../../components/ha-icon-next";
import "../../common/search/search-input";
import { styleMap } from "lit-html/directives/style-map";
import { FlowConfig } from "./show-dialog-data-entry-flow";
import { configFlowContentStyles } from "./styles";
interface HandlerObj {
name: string;
@@ -101,6 +102,14 @@ class StepFlowPickHandler extends LitElement {
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
setTimeout(
() => this.shadowRoot!.querySelector("search-input")!.focus(),
0
);
}
protected updated(changedProps) {
super.updated(changedProps);
// Store the width so that when we search, box doesn't jump
@@ -125,28 +134,27 @@ class StepFlowPickHandler extends LitElement {
});
}
static get styles(): CSSResult {
return css`
h2 {
margin-bottom: 2px;
padding-left: 16px;
}
div {
overflow: auto;
max-height: 600px;
}
paper-item {
cursor: pointer;
}
p {
text-align: center;
padding: 16px;
margin: 0;
}
p > a {
color: var(--primary-color);
}
`;
static get styles(): CSSResult[] {
return [
configFlowContentStyles,
css`
div {
overflow: auto;
max-height: 600px;
}
paper-item {
cursor: pointer;
}
p {
text-align: center;
padding: 16px;
margin: 0;
}
p > a {
color: var(--primary-color);
}
`,
];
}
}
@@ -117,9 +117,7 @@ class DialogDeviceRegistryDetail extends LitElement {
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
<mwc-button @click="${this._updateEntry}">
${this.hass.localize(
"ui.panel.config.entity_registry.editor.update"
)}
${this.hass.localize("ui.panel.config.entities.editor.update")}
</mwc-button>
</div>
</ha-paper-dialog>
@@ -12,7 +12,9 @@ export interface DeviceRegistryDetailDialogParams {
}
export const loadDeviceRegistryDetailDialog = () =>
import(/* webpackChunkName: "device-registry-detail-dialog" */ "./dialog-device-registry-detail");
import(
/* webpackChunkName: "device-registry-detail-dialog" */ "./dialog-device-registry-detail"
);
export const showDeviceRegistryDetailDialog = (
element: HTMLElement,
@@ -6,7 +6,9 @@ export interface HaDomainTogglerDialogParams {
}
export const loadDomainTogglerDialog = () =>
import(/* webpackChunkName: "dialog-domain-toggler" */ "./dialog-domain-toggler");
import(
/* webpackChunkName: "dialog-domain-toggler" */ "./dialog-domain-toggler"
);
export const showDomainTogglerDialog = (
element: HTMLElement,
@@ -100,7 +100,7 @@ class MoreInfoClimate extends LitElement {
</div>
`
: ""}
${stateObj.attributes.temperature
${stateObj.attributes.temperature !== undefined
? html`
<ha-climate-control
.value=${stateObj.attributes.temperature}
@@ -112,8 +112,8 @@ class MoreInfoClimate extends LitElement {
></ha-climate-control>
`
: ""}
${stateObj.attributes.target_temp_low ||
stateObj.attributes.target_temp_high
${stateObj.attributes.target_temp_low !== undefined ||
stateObj.attributes.target_temp_high !== undefined
? html`
<ha-climate-control
.value=${stateObj.attributes.target_temp_low}
@@ -2,7 +2,7 @@ import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@vaadin/vaadin-date-picker/vaadin-date-picker";
import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker";
import "../../../components/ha-relative-time";
import "../../../components/paper-time-input";
+73 -2
View File
@@ -1,6 +1,7 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-icon-button/paper-icon-button";
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@@ -11,18 +12,25 @@ import "../../state-summary/state-card-content";
import "./controls/more-info-content";
import { navigate } from "../../common/navigate";
import { computeStateName } from "../../common/entity/compute_state_name";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const";
import { EventsMixin } from "../../mixins/events-mixin";
import LocalizeMixin from "../../mixins/localize-mixin";
import { computeRTL } from "../../common/util/compute_rtl";
import { removeEntityRegistryEntry } from "../../data/entity_registry";
import { showConfirmationDialog } from "../confirmation/show-dialog-confirmation";
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
const EDITABLE_DOMAINS = ["script"];
/*
* @appliesMixin EventsMixin
*/
class MoreInfoControls extends EventsMixin(PolymerElement) {
class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
static get template() {
return html`
<style include="ha-style-dialog">
@@ -56,6 +64,10 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
padding-bottom: 16px;
}
mwc-button.warning {
--mdc-theme-primary: var(--google-red-500);
}
:host([domain="camera"]) paper-dialog-scrollable {
margin: 0 -24px -21px;
}
@@ -68,7 +80,7 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
<app-toolbar>
<paper-icon-button
aria-label="Dismiss dialog"
aria-label$="[[localize('ui.dialogs.more_info_control.dismiss')]]"
icon="hass:close"
dialog-dismiss
></paper-icon-button>
@@ -77,10 +89,18 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
</div>
<template is="dom-if" if="[[canConfigure]]">
<paper-icon-button
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
icon="hass:settings"
on-click="_gotoSettings"
></paper-icon-button>
</template>
<template is="dom-if" if="[[_computeEdit(hass, stateObj)]]">
<paper-icon-button
aria-label$="[[localize('ui.dialogs.more_info_control.edit')]]"
icon="hass:pencil"
on-click="_gotoEdit"
></paper-icon-button>
</template>
</app-toolbar>
<template is="dom-if" if="[[_computeShowStateInfo(stateObj)]]" restamp="">
@@ -115,6 +135,15 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
state-obj="[[stateObj]]"
hass="[[hass]]"
></more-info-content>
<template
is="dom-if"
if="[[_computeShowRestored(stateObj)]]"
restamp=""
>
[[localize('ui.dialogs.more_info_control.restored.not_provided')]] <br />
[[localize('ui.dialogs.more_info_control.restored.remove_intro')]] <br />
<mwc-button class="warning" on-click="_removeEntity">[[localize('ui.dialogs.more_info_control.restored.remove_action')]]</mwc-buttom>
</template>
</paper-dialog-scrollable>
`;
}
@@ -170,6 +199,10 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
return !stateObj || !DOMAINS_NO_INFO.includes(computeStateDomain(stateObj));
}
_computeShowRestored(stateObj) {
return stateObj && stateObj.attributes.restored;
}
_computeShowHistoryComponent(hass, stateObj) {
return (
hass &&
@@ -187,6 +220,16 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
return stateObj ? computeStateName(stateObj) : "";
}
_computeEdit(hass, stateObj) {
const domain = this._computeDomain(stateObj);
return (
stateObj &&
hass.user.is_admin &&
((EDITABLE_DOMAINS_WITH_ID.includes(domain) && stateObj.attributes.id) ||
EDITABLE_DOMAINS.includes(domain))
);
}
_stateObjChanged(newVal) {
if (!newVal) {
return;
@@ -200,10 +243,38 @@ class MoreInfoControls extends EventsMixin(PolymerElement) {
}
}
_removeEntity() {
showConfirmationDialog(this, {
title: this.localize(
"ui.dialogs.more_info_control.restored.confirm_remove_title"
),
text: this.localize(
"ui.dialogs.more_info_control.restored.confirm_remove_text"
),
confirmBtnText: this.localize("ui.common.yes"),
cancelBtnText: this.localize("ui.common.no"),
confirm: () =>
removeEntityRegistryEntry(this.hass, this.stateObj.entity_id),
});
}
_gotoSettings() {
this.fire("more-info-page", { page: "settings" });
}
_gotoEdit() {
const domain = this._computeDomain(this.stateObj);
navigate(
this,
`/config/${domain}/edit/${
EDITABLE_DOMAINS_WITH_ID.includes(domain)
? this.stateObj.attributes.id
: this.stateObj.entity_id
}`
);
this.fire("hass-more-info", { entityId: null });
}
_computeRTL(hass) {
return computeRTL(hass);
}
@@ -47,6 +47,7 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
<app-toolbar>
<ha-paper-icon-button-arrow-prev
aria-label$="[[localize('ui.dialogs.more_info_settings.back')]]"
on-click="_backTapped"
></ha-paper-icon-button-arrow-prev>
<div main-title="">[[_computeStateName(stateObj)]]</div>
@@ -49,10 +49,10 @@ export class HuiNotificationDrawer extends EventsMixin(
text-align: center;
}
</style>
<app-drawer id='drawer' opened="{{open}}" disable-swipe align="start">
<app-drawer id="drawer" opened="{{open}}" disable-swipe align="start">
<app-toolbar>
<div main-title>[[localize('ui.notification_drawer.title')]]</div>
<ha-paper-icon-button-prev on-click="_closeDrawer"></paper-icon-button>
<ha-paper-icon-button-prev on-click="_closeDrawer" aria-label$="[[localize('ui.notification_drawer.close')]]"></paper-icon-button>
</app-toolbar>
<div class="notifications">
<template is="dom-if" if="[[!_empty(notifications)]]">
@@ -1,7 +1,9 @@
import { fireEvent } from "../../common/dom/fire_event";
const loadVoiceCommandDialog = () =>
import(/* webpackChunkName: "ha-voice-command-dialog" */ "./ha-voice-command-dialog");
import(
/* webpackChunkName: "ha-voice-command-dialog" */ "./ha-voice-command-dialog"
);
export const showVoiceCommandDialog = (element: HTMLElement): void => {
fireEvent(element, "show-dialog", {
@@ -54,9 +54,8 @@ class DialogZHADeviceInfo extends LitElement {
class="card"
.hass=${this.hass}
.device=${this._device}
showActions
isJoinPage
@zha-device-removed=${this._onDeviceRemoved}
.showEntityDetail=${false}
></zha-device-card>
`}
</ha-paper-dialog>
@@ -5,7 +5,9 @@ export interface ZHADeviceInfoDialogParams {
}
export const loadZHADeviceInfoDialog = () =>
import(/* webpackChunkName: "dialog-zha-device-info" */ "./dialog-zha-device-info");
import(
/* webpackChunkName: "dialog-zha-device-info" */ "./dialog-zha-device-info"
);
export const showZHADeviceInfoDialog = (
element: HTMLElement,
+3 -1
View File
@@ -10,6 +10,8 @@ import "../auth/ha-authorize";
/* polyfill for paper-dropdown */
setTimeout(
() =>
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"),
import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
),
2000
);
+16 -6
View File
@@ -23,13 +23,16 @@ declare global {
}
}
const isExternal = location.search.includes("external_auth=1");
const isExternal =
window.externalApp ||
window.webkit?.messageHandlers?.getExternalAuth ||
location.search.includes("external_auth=1");
const authProm = isExternal
? () =>
import(/* webpackChunkName: "external_auth" */ "../external_app/external_auth").then(
({ createExternalAuth }) => createExternalAuth(hassUrl)
)
import(
/* webpackChunkName: "external_auth" */ "../external_app/external_auth"
).then(({ createExternalAuth }) => createExternalAuth(hassUrl))
: () =>
getAuth({
hassUrl,
@@ -52,8 +55,12 @@ const connProm = async (auth) => {
throw err;
}
// We can get invalid auth if auth tokens were stored that are no longer valid
// Clear stored tokens.
if (!isExternal) {
if (isExternal) {
// Tell the external app to force refresh the access tokens.
// This should trigger their unauthorized handling.
await auth.refreshAccessToken(true);
} else {
// Clear stored tokens.
saveTokens(null);
}
auth = await authProm();
@@ -63,6 +70,9 @@ const connProm = async (auth) => {
};
if (__DEV__) {
// Remove adoptedStyleSheets so style inspector works on shadow DOM.
// @ts-ignore
delete Document.prototype.adoptedStyleSheets;
performance.mark("hass-start");
}
window.hassConnection = authProm().then(connProm);
+17 -12
View File
@@ -11,6 +11,10 @@ interface BasePayload {
callback: string;
}
interface GetExternalAuthPayload extends BasePayload {
force?: boolean;
}
interface RefreshTokenResponse {
access_token: string;
expires_in: number;
@@ -26,7 +30,7 @@ declare global {
webkit?: {
messageHandlers: {
getExternalAuth: {
postMessage(payload: BasePayload);
postMessage(payload: GetExternalAuthPayload);
};
revokeExternalAuth: {
postMessage(payload: BasePayload);
@@ -60,8 +64,13 @@ class ExternalAuth extends Auth {
});
}
public async refreshAccessToken() {
const callbackPayload = { callback: CALLBACK_SET_TOKEN };
public async refreshAccessToken(force?: boolean) {
const payload: GetExternalAuthPayload = {
callback: CALLBACK_SET_TOKEN,
};
if (force) {
payload.force = true;
}
const callbackPromise = new Promise<RefreshTokenResponse>(
(resolve, reject) => {
@@ -73,11 +82,9 @@ class ExternalAuth extends Auth {
await 0;
if (window.externalApp) {
window.externalApp.getExternalAuth(JSON.stringify(callbackPayload));
window.externalApp.getExternalAuth(JSON.stringify(payload));
} else {
window.webkit!.messageHandlers.getExternalAuth.postMessage(
callbackPayload
);
window.webkit!.messageHandlers.getExternalAuth.postMessage(payload);
}
const tokens = await callbackPromise;
@@ -87,7 +94,7 @@ class ExternalAuth extends Auth {
}
public async revoke() {
const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN };
const payload: BasePayload = { callback: CALLBACK_REVOKE_TOKEN };
const callbackPromise = new Promise((resolve, reject) => {
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
@@ -97,11 +104,9 @@ class ExternalAuth extends Auth {
await 0;
if (window.externalApp) {
window.externalApp.revokeExternalAuth(JSON.stringify(callbackPayload));
window.externalApp.revokeExternalAuth(JSON.stringify(payload));
} else {
window.webkit!.messageHandlers.revokeExternalAuth.postMessage(
callbackPayload
);
window.webkit!.messageHandlers.revokeExternalAuth.postMessage(payload);
}
await callbackPromise;
+18 -19
View File
@@ -74,20 +74,23 @@ export const provideHass = (
restResponses.push([path, callback]);
}
mockAPI(new RegExp("states/.+"), (
// @ts-ignore
method,
path,
parameters
) => {
const [domain, objectId] = path.substr(7).split(".", 2);
if (!domain || !objectId) {
return;
mockAPI(
new RegExp("states/.+"),
(
// @ts-ignore
method,
path,
parameters
) => {
const [domain, objectId] = path.substr(7).split(".", 2);
if (!domain || !objectId) {
return;
}
addEntities(
getEntity(domain, objectId, parameters.state, parameters.attributes)
);
}
addEntities(
getEntity(domain, objectId, parameters.state, parameters.attributes)
);
});
);
const localLanguage = getLocalLanguage();
@@ -117,9 +120,7 @@ export const provideHass = (
? callback(msg)
: Promise.reject({
code: "command_not_mocked",
message: `WS Command ${
msg.type
} is not implemented in provide_hass.`,
message: `WS Command ${msg.type} is not implemented in provide_hass.`,
});
},
subscribeMessage: async (onChange, msg) => {
@@ -128,9 +129,7 @@ export const provideHass = (
? callback(msg, onChange)
: Promise.reject({
code: "command_not_mocked",
message: `WS Command ${
msg.type
} is not implemented in provide_hass.`,
message: `WS Command ${msg.type} is not implemented in provide_hass.`,
});
},
subscribeEvents: async (
+10 -3
View File
@@ -9,12 +9,14 @@ import {
} from "lit-element";
import "../components/ha-menu-button";
import "../components/ha-paper-icon-button-arrow-prev";
import { classMap } from "lit-html/directives/class-map";
@customElement("hass-subpage")
class HassSubpage extends LitElement {
@property()
public header?: string;
@property({ type: Boolean })
public showBackButton = true;
@property({ type: Boolean })
public hassio = false;
@@ -25,6 +27,7 @@ class HassSubpage extends LitElement {
aria-label="Back"
.hassio=${this.hassio}
@click=${this._backTapped}
class=${classMap({ hidden: !this.showBackButton })}
></ha-paper-icon-button-arrow-prev>
<div main-title>${this.header}</div>
@@ -53,9 +56,9 @@ class HassSubpage extends LitElement {
height: 64px;
padding: 0 16px;
pointer-events: none;
background-color: var(--primary-color);
background-color: var(--app-header-background-color);
font-weight: 400;
color: var(--text-primary-color, white);
color: var(--app-header-text-color, white);
}
ha-menu-button,
@@ -64,6 +67,10 @@ class HassSubpage extends LitElement {
pointer-events: auto;
}
ha-paper-icon-button-arrow-prev.hidden {
visibility: hidden;
}
[main-title] {
margin: 0 0 0 24px;
line-height: 20px;
+7 -4
View File
@@ -45,7 +45,9 @@ export class HomeAssistantAppEl extends HassElement {
this._initialize();
setTimeout(registerServiceWorker, 1000);
/* polyfill for paper-dropdown */
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min");
import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
);
}
protected updated(changedProps: PropertyValues): void {
@@ -55,9 +57,10 @@ export class HomeAssistantAppEl extends HassElement {
this._updateHass({ panelUrl: this._panelUrl });
}
if (changedProps.has("hass")) {
this.hassChanged(this.hass!, changedProps.get("hass") as
| HomeAssistant
| undefined);
this.hassChanged(
this.hass!,
changedProps.get("hass") as HomeAssistant | undefined
);
}
}
+39 -13
View File
@@ -12,33 +12,59 @@ import { removeInitSkeleton } from "../util/init-skeleton";
const CACHE_COMPONENTS = ["lovelace", "states", "developer-tools"];
const COMPONENTS = {
calendar: () =>
import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"),
import(
/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"
),
config: () =>
import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"),
import(
/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"
),
custom: () =>
import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"),
import(
/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"
),
"developer-tools": () =>
import(/* webpackChunkName: "panel-developer-tools" */ "../panels/developer-tools/ha-panel-developer-tools"),
import(
/* webpackChunkName: "panel-developer-tools" */ "../panels/developer-tools/ha-panel-developer-tools"
),
lovelace: () =>
import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"),
import(
/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"
),
states: () =>
import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"),
import(
/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"
),
history: () =>
import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"),
import(
/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"
),
iframe: () =>
import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"),
import(
/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"
),
kiosk: () =>
import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"),
import(
/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"
),
logbook: () =>
import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"),
import(
/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"
),
mailbox: () =>
import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"),
import(
/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"
),
map: () =>
import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map"),
profile: () =>
import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"),
import(
/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"
),
"shopping-list": () =>
import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"),
import(
/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"
),
};
const getRoutes = (panels: Panels): RouterOptions => {
+6 -2
View File
@@ -84,8 +84,12 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._fetchOnboardingSteps();
import(/* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations");
import(/* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config");
import(
/* webpackChunkName: "onboarding-integrations" */ "./onboarding-integrations"
);
import(
/* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config"
);
registerServiceWorker(false);
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
}
+8 -4
View File
@@ -116,9 +116,13 @@ class OnboardingCoreConfig extends LitElement {
@value-changed=${this._handleChange}
>
<span slot="suffix">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_meters"
)}
${this._unitSystem === "metric"
? this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_meters"
)
: this.hass.localize(
"ui.panel.config.core.section.core.core_config.elevation_feet"
)}
</span>
</paper-input>
</div>
@@ -266,7 +270,7 @@ class OnboardingCoreConfig extends LitElement {
});
} catch (err) {
this._working = false;
alert("FAIL");
alert(`Failed to save: ${err.message}`);
}
}
+3 -1
View File
@@ -124,7 +124,9 @@ class OnboardingIntegrations extends LitElement {
loadConfigFlowDialog();
this._loadConfigEntries();
/* polyfill for paper-dropdown */
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min");
import(
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
);
}
private _createFlow() {
@@ -47,9 +47,7 @@ class DialogAreaDetail extends LitElement {
<h2>
${entry
? entry.name
: this.hass.localize(
"ui.panel.config.area_registry.editor.default_name"
)}
: this.hass.localize("ui.panel.config.areas.editor.default_name")}
</h2>
<paper-dialog-scrollable>
${this._error
@@ -81,9 +79,7 @@ class DialogAreaDetail extends LitElement {
@click="${this._deleteEntry}"
.disabled=${this._submitting}
>
${this.hass.localize(
"ui.panel.config.area_registry.editor.delete"
)}
${this.hass.localize("ui.panel.config.areas.editor.delete")}
</mwc-button>
`
: html``}
@@ -92,12 +88,8 @@ class DialogAreaDetail extends LitElement {
.disabled=${nameInvalid || this._submitting}
>
${entry
? this.hass.localize(
"ui.panel.config.area_registry.editor.update"
)
: this.hass.localize(
"ui.panel.config.area_registry.editor.create"
)}
? this.hass.localize("ui.panel.config.areas.editor.update")
: this.hass.localize("ui.panel.config.areas.editor.create")}
</mwc-button>
</div>
</ha-paper-dialog>
@@ -5,6 +5,7 @@ import {
css,
CSSResult,
property,
customElement,
} from "lit-element";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
@@ -30,7 +31,8 @@ import { classMap } from "lit-html/directives/class-map";
import { computeRTL } from "../../../common/util/compute_rtl";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
class HaConfigAreaRegistry extends LitElement {
@customElement("ha-config-areas")
export class HaConfigAreas extends LitElement {
@property() public hass!: HomeAssistant;
@property() public isWide?: boolean;
@property() private _areas?: AreaRegistryEntry[];
@@ -51,24 +53,23 @@ class HaConfigAreaRegistry extends LitElement {
}
return html`
<hass-subpage
header="${this.hass.localize("ui.panel.config.area_registry.caption")}"
.header="${this.hass.localize("ui.panel.config.areas.caption")}"
.showBackButton=${!this.isWide}
>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize("ui.panel.config.area_registry.picker.header")}
${this.hass.localize("ui.panel.config.areas.picker.header")}
</span>
<span slot="introduction">
${this.hass.localize(
"ui.panel.config.area_registry.picker.introduction"
)}
${this.hass.localize("ui.panel.config.areas.picker.introduction")}
<p>
${this.hass.localize(
"ui.panel.config.area_registry.picker.introduction2"
"ui.panel.config.areas.picker.introduction2"
)}
</p>
<a href="/config/integrations/dashboard">
${this.hass.localize(
"ui.panel.config.area_registry.picker.integrations_page"
"ui.panel.config.areas.picker.integrations_page"
)}
</a>
</span>
@@ -85,13 +86,9 @@ class HaConfigAreaRegistry extends LitElement {
${this._areas.length === 0
? html`
<div class="empty">
${this.hass.localize(
"ui.panel.config.area_registry.no_areas"
)}
${this.hass.localize("ui.panel.config.areas.no_areas")}
<mwc-button @click=${this._createArea}>
${this.hass.localize(
"ui.panel.config.area_registry.create_area"
)}
${this.hass.localize("ui.panel.config.areas.create_area")}
</mwc-button>
</div>
`
@@ -103,9 +100,7 @@ class HaConfigAreaRegistry extends LitElement {
<ha-fab
?is-wide=${this.isWide}
icon="hass:plus"
title="${this.hass.localize(
"ui.panel.config.area_registry.create_area"
)}"
title="${this.hass.localize("ui.panel.config.areas.create_area")}"
@click=${this._createArea}
class="${classMap({
rtl: computeRTL(this.hass),
@@ -208,5 +203,3 @@ All devices in this area will become unassigned.`)
`;
}
}
customElements.define("ha-config-area-registry", HaConfigAreaRegistry);

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