Compare commits

..

111 Commits

Author SHA1 Message Date
Thomas Lovén
9fbfdec546 Add some more card helpers. 2020-03-31 19:56:25 +02:00
Ian Richardson
6692fa439a hold/double actions for light-card (#5361)
* hold/double actions for light-card

* lint
2020-03-31 17:52:20 +02:00
imgbot[bot]
0535247bb3 [ImgBot] Optimize images (#5378)
*Total -- 1,745.75kb -> 1,539.81kb (11.8%)

/demo/public/assets/teachingbirds/meteogram.png -- 66.71kb -> 24.48kb (63.31%)
/demo/public/assets/teachingbirds/background_square.png -- 0.76kb -> 0.37kb (51.98%)
/demo/public/assets/arsaboo/icons/security_armed_red.png -- 6.38kb -> 3.67kb (42.5%)
/demo/public/assets/jimpower/background-15.jpg -- 231.78kb -> 159.68kb (31.11%)
/public/static/images/image-broken.svg -- 0.56kb -> 0.42kb (23.99%)
/demo/public/assets/arsaboo/icons/security_disarmed.png -- 4.21kb -> 3.25kb (22.7%)
/demo/public/assets/arsaboo/icons/automation_enabled.png -- 3.96kb -> 3.08kb (22.32%)
/demo/public/assets/jimpower/cardbackK.png -- 10.56kb -> 8.21kb (22.22%)
/demo/public/assets/arsaboo/icons/abode_enabled.png -- 3.98kb -> 3.16kb (20.62%)
/demo/public/assets/arsaboo/icons/ecobee_blank.png -- 2.18kb -> 1.78kb (18.22%)
/demo/public/assets/arsaboo/floorplans/ecobee_blank.png -- 2.17kb -> 1.78kb (17.87%)
/demo/public/assets/arsaboo/icons/automation_disabled.png -- 6.89kb -> 5.73kb (16.82%)
/demo/public/assets/jimpower/home/james_10.jpg -- 73.21kb -> 61.89kb (15.46%)
/demo/public/assets/arsaboo/icons/camera_patio_streaming.png -- 11.63kb -> 9.88kb (14.98%)
/demo/public/assets/arsaboo/icons/Harmony.png -- 3.83kb -> 3.25kb (14.95%)
/demo/public/assets/arsaboo/icons/light_off.png -- 9.53kb -> 8.51kb (10.77%)
/demo/public/assets/arsaboo/icons/tv_enabled.png -- 5.48kb -> 4.90kb (10.69%)
/demo/public/assets/jimpower/home/bus_10.jpg -- 36.36kb -> 32.58kb (10.4%)
/demo/public/assets/kernehed/bella.jpg -- 33.09kb -> 30.29kb (8.44%)
/demo/public/assets/arsaboo/images/camera.backyard.jpg -- 81.15kb -> 74.80kb (7.82%)
/demo/public/assets/kernehed/oscar.jpg -- 25.32kb -> 23.34kb (7.81%)
/demo/public/assets/arsaboo/images/camera.patio.jpg -- 63.13kb -> 58.52kb (7.29%)
/demo/public/assets/arsaboo/images/camera.porch.jpg -- 76.49kb -> 70.95kb (7.25%)
/demo/public/assets/arsaboo/icons/light_on.png -- 12.03kb -> 11.19kb (6.97%)
/demo/public/assets/jimpower/home/tina_4.jpg -- 59.69kb -> 55.92kb (6.31%)
/gallery/public/images/album_cover.jpg -- 26.11kb -> 24.46kb (6.31%)
/demo/public/assets/teachingbirds/clothes_drying_square.jpg -- 31.93kb -> 29.98kb (6.11%)
/demo/public/assets/arsaboo/images/camera.driveway.jpg -- 59.47kb -> 55.84kb (6.1%)
/demo/public/assets/teachingbirds/laundry_running_square.jpg -- 58.68kb -> 55.61kb (5.22%)
/demo/public/assets/kernehed/camera.entre.jpg -- 65.84kb -> 62.49kb (5.09%)
/demo/public/assets/teachingbirds/Stefan_square.jpg -- 11.25kb -> 10.68kb (5.06%)
/demo/public/assets/teachingbirds/isa_square.jpg -- 18.90kb -> 17.98kb (4.88%)
/demo/public/assets/jimpower/security/motion_3.jpg -- 87.09kb -> 82.99kb (4.71%)
/demo/public/assets/teachingbirds/roomba_square.jpg -- 32.23kb -> 30.82kb (4.39%)
/demo/public/assets/teachingbirds/laundry_clean_2_square.jpg -- 31.87kb -> 30.48kb (4.34%)
/demo/public/assets/teachingbirds/folded_clothes_square.jpg -- 28.60kb -> 27.41kb (4.16%)
/demo/public/assets/arsaboo/icons/abode_disabled.png -- 8.74kb -> 8.38kb (4.06%)
/gallery/public/images/netflix.jpg -- 20.19kb -> 19.39kb (4%)
/demo/public/assets/teachingbirds/roomba_bw_square.jpg -- 15.97kb -> 15.34kb (3.97%)
/demo/public/assets/teachingbirds/trash_square.jpg -- 29.50kb -> 28.39kb (3.76%)
/demo/public/assets/teachingbirds/cleaning_square.jpg -- 38.57kb -> 37.19kb (3.59%)
/demo/public/assets/teachingbirds/dryer_square.jpg -- 18.78kb -> 18.16kb (3.32%)
/gallery/public/images/album_cover_2.jpg -- 129.79kb -> 125.62kb (3.21%)
/demo/public/assets/teachingbirds/House_square.jpg -- 40.59kb -> 39.31kb (3.15%)
/demo/public/assets/arsaboo/icons/tv_disabled.png -- 10.24kb -> 9.93kb (3.08%)
/demo/public/assets/teachingbirds/washer_square.jpg -- 20.52kb -> 20.00kb (2.52%)
/demo/public/assets/teachingbirds/mailbox_square.jpg -- 42.97kb -> 41.98kb (2.3%)
/demo/public/assets/teachingbirds/trash_bear_bw_square.jpg -- 17.60kb -> 17.35kb (1.46%)
/demo/public/assets/teachingbirds/guests_square.jpg -- 49.05kb -> 48.38kb (1.38%)
/demo/public/assets/teachingbirds/mailbox_bw_square.jpg -- 19.44kb -> 19.25kb (0.98%)
/demo/public/assets/arsaboo/icons/tv_on2.png -- 0.79kb -> 0.78kb (0.25%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>

Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2020-03-31 16:50:20 +02:00
Thomas Lovén
a0a4fcaf5f Allow custom cards in card picker (#5122)
* Allow custom cards in card picker

* Lint. "Custom:" prefix to card names

* Address review comments

* Simplifications. Translation fixes.

* Less magic

* Move CUSTOM_TYPE_PREFIX

* Update hui-card-picker.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-03-31 16:20:09 +02:00
Zack Arnett
454d81facc View Editor: Badge Preview (#5335)
* Badge Preview

* Move Badge preview

* Clean

* Error card + clean

* remove try catch
2020-03-31 13:30:52 +02:00
Bram Kragten
0bfa8260fa Merge pull request #5372 from dmulcahey/dm/zha-tables-clear-selections
Clear selections in ZHA data tables
2020-03-31 13:25:50 +02:00
Bram Kragten
f2124f1c95 Merge pull request #5373 from zsarnett/entity-card-fix
Entity Card: Fix Value clipping in editor
2020-03-31 13:24:34 +02:00
HomeAssistant Azure
451bc2370a [ci skip] Translation update 2020-03-31 00:32:48 +00:00
Zack Arnett
0b17642c31 Fix editor value clipping 2020-03-30 20:19:18 -04:00
David Mulcahey
214dc25576 clear selections 2020-03-30 20:01:04 -04:00
Zack Arnett
158eddfd44 Entity Card: Card Addition (#4971)
* Review to changed Src translations

* Reviews

* side by side theme

* Allow user to specify unit

* Add unit back update for headerfooter and editor

* Clean

* Unavailable for attribute that doesn't exist

* fix merge

* Fix from rebasing

* reviews

* Localize State

* fix for localize

* css

* reviews

* Break the rules
2020-03-30 19:49:02 -04:00
Pascal Vizeli
5daa6dbd25 [skip ci] Simplify release pipeline 2020-03-30 22:52:27 +02:00
Pascal Vizeli
645ef3e61f [skip ci] Simplify release pipeline 2020-03-30 22:51:42 +02:00
Bram Kragten
5dcea51712 Fix default path for demo 2020-03-30 21:20:06 +02:00
Bram Kragten
6995968d50 Update ha-sidebar.ts 2020-03-30 20:59:12 +02:00
Bram Kragten
c4cb42f3c2 Merge branch 'master' into dev 2020-03-30 20:57:24 +02:00
Bram Kragten
0d4a7a2b3e Bumped version to 20200330.0 2020-03-30 20:53:19 +02:00
Bram Kragten
20cc9c9b42 Update user config pages (#5354)
* Update user config pages

* remove user-editor

* Update

* Update dialog-add-user.ts
2020-03-30 20:52:53 +02:00
Bram Kragten
8a6bd04543 Add default dashboard selection to profile page (#5360)
* Add default dashboard selection to profile page

* Comments

* Console.bye
2020-03-30 16:08:36 +02:00
Bram Kragten
263138a388 MIgrate scripts to data table (#5352)
* MIgrate scripts to data table

* Update imports

* Update

* Lint
2020-03-30 15:38:15 +02:00
Bram Kragten
e645342131 Allow to start with empty view on take control (#5357)
* Allow to start with empty view on take control

* Localize
2020-03-30 15:37:59 +02:00
Bram Kragten
6e4c707f9e Migrate scene config to data table (#5351)
* Migrate scene config to data table

* Remove unused styles

* Update ha-scene-dashboard.ts

* Lint + help

* Update ha-scene-dashboard.ts
2020-03-30 15:06:29 +02:00
Bram Kragten
fca286d6c0 Change automation picker to data table (#5344)
* Change automation picker to data-table

* Update ha-automation-picker.ts

* Update ha-automation-picker.ts

* Update ha-automation-picker.ts

* Add edit button + disabled tooltip

* Fix translation key

* Remove unused

* Comments and fixes

* Update ha-automation-picker.ts
2020-03-30 14:21:55 +02:00
Bram Kragten
5a2e08647f Add area config page (#5343)
* Add area config page

* Comments

* Update ha-config-area-page.ts

* Update ha-config-area-page.ts
2020-03-30 14:21:36 +02:00
Bram Kragten
f6dac98abd Update layout of device info page (#5342)
* Update layout of device info page

* Comments

* <a> and simplify layout

* Update ha-config-device-page.ts
2020-03-30 14:21:21 +02:00
Pascal Vizeli
ddb525f6cd Fix hass.io panel for older version (#5365)
* Fix hass.io panel for older version

* Fix lint
2020-03-30 11:38:54 +02:00
HomeAssistant Azure
f7ee712456 [ci skip] Translation update 2020-03-30 00:40:34 +00:00
Ian Richardson
ff873e2f71 add name option to buttons row (#5083)
* add name option to buttons row

* explicit show_name

* Update src/panels/lovelace/components/hui-buttons-base.ts

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

* Update src/panels/lovelace/components/hui-buttons-base.ts

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

* lint

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-03-29 13:49:27 +02:00
Ian Richardson
54b57e6222 add volume slider to media_player row (#4743)
*  add volume slider to media_player row

* add more controls

* flex slider

* override width

* volume buttons when narrow

* address comments

* Updates for rebase

* attempt to use debounce. not working

* remove log

* fix observer

* address some review comments

* unobserve

* address comments
2020-03-29 13:30:52 +02:00
HomeAssistant Azure
375abfb95e [ci skip] Translation update 2020-03-29 00:32:56 +00:00
HomeAssistant Azure
30a38fa6d1 [ci skip] Translation update 2020-03-28 00:34:07 +00:00
Pascal Vizeli
554c0b692d Fix supervisor panel (#5348)
* Fix supervisor panel

* Lint

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2020-03-27 11:55:55 -07:00
HomeAssistant Azure
61ac831882 [ci skip] Translation update 2020-03-27 00:33:27 +00:00
Bram Kragten
59e89a0daf Sandbox iframe 2020-03-26 11:39:01 +01:00
Zack Arnett
1e3950cd1d Add shopping list (#5339) 2020-03-26 11:32:40 +01:00
Zack Arnett
f514ea453c Delete Card: Add Card Preview to Dialog (#5325)
* Delete Card Dialog

* Update with to be the max for a column

* Comments

* remove open and wait for render to resize I think

* Review

* fire event from dialog
2020-03-26 11:13:13 +01:00
Paulus Schoutsen
cc046478e5 Catch LL config not found exception in preload (#5340) 2020-03-26 10:47:12 +01:00
HomeAssistant Azure
a17c1052cd [ci skip] Translation update 2020-03-26 00:32:45 +00:00
HomeAssistant Azure
2408f9b8fa [ci skip] Translation update 2020-03-25 00:32:35 +00:00
Aidan Timson
6aae1b3378 Fix Default Lovelace Panel Title (#5301)
* Fix lovelace panel title

* Fix types error

* Revert and workaround null type

* Update src/data/panel.ts

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

* Update panel.ts

* Update panel.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-03-24 15:08:51 +01:00
Pascal Vizeli
ed51223226 Update azure-pipelines-release.yml for Azure Pipelines 2020-03-24 14:16:21 +01:00
sdotter
013808b7f5 Fixed iframe panel unnecessary scrollbar (on 100vh -64px) (#5326)
* Fixed iframe panel unnecessary scrollbar (on 100vh -64px)

* Fixed iframe panel unnecessary scrollbar (removed margin-top)

No need for margin-top
2020-03-24 13:18:45 +01:00
HomeAssistant Azure
af584e1d12 [ci skip] Translation update 2020-03-24 00:57:08 +00:00
J. Nick Koston
3763d7a1d0 Use horizontal control icons for devices that commonly move horizontally (#5309)
* Use horizontal control icons for devices that
commonly move horizontally including:

awning
curtain
gate

* no need to check

* remove debug

* reduce - js is so permissive

* remove curtain
2020-03-23 20:33:45 +01:00
Zack Arnett
ce92add096 Footer/Header: Graph (#5273)
* Add Graph as a footerheader option

* Move get Coordinates to a new file

* await

* Comments
2020-03-23 09:46:56 -04:00
HomeAssistant Azure
40c94b6596 [ci skip] Translation update 2020-03-23 00:32:58 +00:00
Aidan Timson
c894bc2e40 Fix icon button padding (#5312) 2020-03-23 00:18:13 +01:00
HomeAssistant Azure
415a4fa1af [ci skip] Translation update 2020-03-22 00:32:46 +00:00
Bram Kragten
b9367a33a8 Invalidate theme cache when switching demo's (#5305)
* Invalidate theme cache when switching demo's

* Move to mock hass
2020-03-21 20:19:13 +01:00
Bram Kragten
7170f06c08 Add checks for arrays in compute unused entities (#5307) 2020-03-21 20:18:40 +01:00
J. Nick Koston
f2578a58b4 Add "gate" device class for DEVICE_CLASS_GATE (#5299)
* Add "gate" device class for DEVICE_CLASS_GATE

Gates are found outside of a structure and are typically part of a fence.

When opening or closing a garage door, it was impossible to tell
if you had hit the button or not even though the underlying state
was reported as "opening". This lead to confusion and multiple
clicks to open a garage door which can cause the door to stop
opening and result in frustration.

Add icons for gate and garage opening and closing states.

* Add "gate" device class for DEVICE_CLASS_GATE

Gates are found outside of a structure and are typically part of a fence.

When opening or closing a garage door, it was impossible to tell
if you had hit the button or not even though the underlying state
was reported as "opening". This lead to confusion and multiple
clicks to open a garage door which can cause the door to stop
opening and result in frustration.

Add icons for gate and garage opening and closing states.

* de-bold
2020-03-21 11:20:06 -07:00
Bram Kragten
1950656bd5 Add ability to edit panel view cards (#5257)
* Add ability to edit panel view cards

* Localize and fix some styling
2020-03-21 16:51:44 +01:00
Bram Kragten
eed3263c70 Optimize cards for horizontal stack (#5254) 2020-03-21 15:59:30 +01:00
Robert Resch
02e01626f5 fix #5300: always show attribute step (#5302) 2020-03-21 15:15:20 +01:00
HomeAssistant Azure
41a2d9604e [ci skip] Translation update 2020-03-21 00:32:47 +00:00
Bram Kragten
7d6f188bfc Change themes logic (#5232)
* Fix themes

* Update hui-view.ts

* Comments and bail out

* Update apply_themes_on_element.ts

* refactor, move meta to theme mixin, adapt lovelace theme picker

* console.bye

* Comments

* Optimizations

* Bail out early is no hex value

* Cache processed themes

* Remove hex-rgb cache
2020-03-20 21:30:20 +01:00
Bram Kragten
15a144f17a Add iframe panel mode and align aspect ratio option with map card (#5289)
* Add iframe panel mode and align aspect ratio option with map card

* lint
2020-03-20 17:53:30 +01:00
Erik Montnemery
c54f2b66da Add delete button to MQTT devices' config page (#5117)
* Add delete button to MQTT devices' config page

* Move delete button to its own card

* Fix review comments

* Fix review comments

* Fix review comments

* Use haStyle instead of haStyleDialog
2020-03-20 15:53:36 +01:00
Bram Kragten
685a0807d8 Fix alarm panel (#5291)
Fixes https://github.com/home-assistant/frontend/issues/5073
2020-03-20 15:21:25 +01:00
Zack Arnett
0d404e0e37 Stub Configs: Add Type (#5206)
* Add Type

* Add return type

* Lint

* Fix map

* map fix

* Comments
2020-03-20 12:41:28 +01:00
Zack Arnett
39bb859f57 Fix tooltip overflow (#5282) 2020-03-20 12:01:18 +01:00
Zack Arnett
90e32b7e45 Hui Unavailable: Fix Opacity (#5283)
* Fix unavailable

* opacity
2020-03-20 11:49:46 +01:00
Zack Arnett
63a2d9dd18 alphabet english (#5284) 2020-03-20 11:48:02 +01:00
HomeAssistant Azure
4982693883 [ci skip] Translation update 2020-03-20 00:32:48 +00:00
Zack Arnett
f4211e3fa3 Find Entites: Filter Unavailable - Used and unused entities required (#5208)
* Update Find Entities

* lint

* Filter until max - entitiesFill-> entitiesFallback

* Comment - Move unav - unk filter to picker

* lint

* Var name update

* remove unnessary check

* add it back and fix logic

* use typescript ? in condition

* comments

* lint

* Compute used once

* pass in hass

* Return set

* fix merge

* Optimize unused entities in find
2020-03-19 14:39:14 -04:00
Paulus Schoutsen
eacf58b5a5 Update color count in color extraction (#5270)
* Update color count in color extraction

* add the images
2020-03-18 23:12:27 -04:00
HomeAssistant Azure
f9349bc731 [ci skip] Translation update 2020-03-19 00:32:53 +00:00
Bram Kragten
3840671764 Remove app-route from hui-root (#5267)
* Remove app-route from hui-root

* Update hui-root.ts
2020-03-18 15:37:44 -07:00
Bram Kragten
fd62cf02d6 Fix button card not showing visual editor on add (#5256) 2020-03-18 23:32:03 +01:00
Bram Kragten
254744cd7d Bumped version to 20200318.1 2020-03-18 23:29:00 +01:00
Zack Arnett
595d04c922 Fix theme update (#5246) 2020-03-18 23:28:50 +01:00
Bram Kragten
a3969fe2c8 Fix stack editor in stack editor (#5255) 2020-03-18 23:28:42 +01:00
Paulus Schoutsen
220e4134b7 Fix calculating title if panels not loaded yet (#5262) 2020-03-18 23:28:35 +01:00
Bram Kragten
56e176a6f1 Fix stack editor in conditional card (#5265) 2020-03-18 23:28:30 +01:00
Bram Kragten
780c15d6b3 Fix stack editor in conditional card (#5265) 2020-03-18 21:29:09 +01:00
Bram Kragten
55ff848b78 Fix stack editor in stack editor (#5255) 2020-03-18 20:51:00 +01:00
Zack Arnett
dbe829bc7d Card Editor: Copy Card (#5249)
* Copy Card Feature

* dUplicate
2020-03-18 20:33:59 +01:00
Zack Arnett
8d0508f320 Unused Entities: Only Show Fab when selection is made (#5248)
* Only Show Fab when selection is made

* reviews
2020-03-18 20:33:36 +01:00
Paulus Schoutsen
2741bb8b38 Fix calculating title if panels not loaded yet (#5262) 2020-03-18 20:32:35 +01:00
Paulus Schoutsen
16cadd53cf Set data as property. (#5263) 2020-03-18 20:27:44 +01:00
Olivér Falvai
a8d21c6112 Add loading text for long-running integration install step (#4378)
* Added optional label for dialog-data-entry-flow

* Use correct loading element

* Update src/translations/en.json

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

* Minor template adjustment

* Revert accidental change of PR templates

* Revert accidental change of PR templates

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-03-18 17:00:08 +01:00
Bram Kragten
6b2e707653 Update en.json 2020-03-18 12:49:32 +01:00
Bram Kragten
205b7451fa Update hui-gauge-card.ts (#5241) 2020-03-18 12:48:16 +01:00
Zack Arnett
1d3aeec0de more info (#5247) 2020-03-18 12:47:59 +01:00
Zack Arnett
ac911dcd31 Fix theme update (#5246) 2020-03-18 12:47:20 +01:00
Bram Kragten
89a94b3efc Merge pull request #5252 from home-assistant/dev
20200318.0
2020-03-18 12:33:12 +01:00
Bram Kragten
71793dcfa5 Cast fix for urlPath === "lovelace" (#5242) 2020-03-18 12:19:01 +01:00
Bram Kragten
1e527a8350 Bumped version to 20200318.0 2020-03-18 12:18:24 +01:00
Bram Kragten
262b12eb93 Fix dialog conent color (#5251)
* Fix ha-dialog content color

* Fix
2020-03-18 12:17:01 +01:00
HomeAssistant Azure
7fb1e699ae [ci skip] Translation update 2020-03-18 00:32:39 +00:00
Aidan Timson
9ee647329b Fix Wrong Index for Filtered Tabs (#5221)
* Fix wrong index for filtered tabs

* Set active tab to actual tab and use path

* Update src/layouts/hass-tabs-subpage.ts

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

* Update src/layouts/hass-tabs-subpage.ts

Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io>

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-03-17 20:44:07 +01:00
HomeAssistant Azure
cd6dcec644 [ci skip] Translation update 2020-03-17 00:32:43 +00:00
Bram Kragten
d8f248c60e Merge pull request #5234 from home-assistant/dev
20200316.1
2020-03-16 21:18:04 +01:00
Aidan Timson
2925b930ad Set Title to Current Panel (#5220)
* Set title to current panel

* Move to mixin

* Naming

* guard clause
2020-03-16 20:54:55 +01:00
Bram Kragten
127aaba47b Add UI for reload resources service (#5229)
* Add UI for reload resources service

* Update hui-root.ts

* Check for config of main panel
2020-03-16 20:52:21 +01:00
Bram Kragten
8bc8761af6 Bumped version to 20200316.1 2020-03-16 20:32:59 +01:00
Bram Kragten
a88321d243 Only show url path field in advanced mode (#5233) 2020-03-16 20:32:13 +01:00
Bram Kragten
4e19232960 Merge pull request #5230 from Tediore/patch-3
Minor corrections and cosmetic improvements
2020-03-16 14:51:14 +01:00
Jay
5b95bdb6b7 Minor corrections and cosmetic improvements
Updated the correct file this time...
2020-03-16 08:15:18 -05:00
Bram Kragten
0fc59ccb16 Merge pull request #5228 from home-assistant/dev
20200316.0
2020-03-16 12:38:28 +01:00
Bram Kragten
a9daf9835a Bumped version to 20200316.0 2020-03-16 12:24:39 +01:00
Zack Arnett
2110d9c3b9 Map Card: Fix Dark Mode Toggle (#5209)
* Fix Editor

* comment
2020-03-16 12:23:13 +01:00
Zack Arnett
b77e0b8125 Card Picker: Remove Scale (#5205)
* remove scalling

* add zoom back
2020-03-16 12:22:56 +01:00
Zack Arnett
5197f102ea Webpage Card: Fix Aspect Ratio in Editor (#5210)
* fix Ifram editor

* suffix
2020-03-16 12:22:41 +01:00
Bram Kragten
447d4604c6 Require hyphen in lovelace dashboard url path (#5214)
* Require hyphen in url path

* Update dialog-lovelace-dashboard-detail.ts
2020-03-16 12:22:21 +01:00
Jason Lachowsky
5a84e34f93 Corrected minor misspellings (#5225) 2020-03-16 11:56:04 +01:00
Jay
fb6d3cccdc Cosmetic improvements, minor corrections (#5199)
Made some minor corrections:
"it's" > "its" ("its" is the possessive form of "it")
"setup" > "set up" ("setup" is a noun, "set up" is a verb)

Cosmetic improvement:
"can not" > "cannot" (both are correct, but "cannot" is usually more common, at least in the US. Strictly a matter of opinion here so no worries if you want this reversed.)
2020-03-16 11:51:43 +01:00
HomeAssistant Azure
fad3cb185b [ci skip] Translation update 2020-03-16 00:32:44 +00:00
HomeAssistant Azure
f61ce395f5 [ci skip] Translation update 2020-03-15 00:32:49 +00:00
Franck Nijhof
17f3299152 Fix brightness_pct in light device turn_on action (#5201) 2020-03-13 21:58:34 -07:00
HomeAssistant Azure
17e04589d0 [ci skip] Translation update 2020-03-14 00:32:33 +00:00
246 changed files with 7472 additions and 3037 deletions

View File

@@ -8,7 +8,7 @@ trigger:
pr: none
variables:
- name: versionWheels
value: '1.3-3.7-alpine3.10'
value: '1.10.1-3.7-alpine3.11'
- name: versionNode
value: '12.1'
- group: twine
@@ -50,15 +50,8 @@ stages:
- template: templates/azp-job-wheels.yaml@azure
parameters:
builderVersion: '$(versionWheels)'
builderApk: 'build-base'
wheelsLocal: true
wheelsRequirement: 'requirement.txt'
preBuild:
- task: NodeTool@0
displayName: "Use Node $(versionNode)"
inputs:
versionSpec: "$(versionNode)"
- script: |
set -e
yarn install
script/build_frontend
sleep 240
echo "home-assistant-frontend==$(Build.SourceBranchName)" > requirement.txt

View File

@@ -172,6 +172,9 @@ export class HcMain extends HassElement {
return;
}
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
if (msg.urlPath === "lovelace") {
msg.urlPath = null;
}
this._urlPath = msg.urlPath;
if (this._unsubLovelace) {
this._unsubLovelace();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 B

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -34,6 +34,7 @@ export const createMediaPlayerEntities = () => [
media_content_type: "movie",
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
supported_features: 33,
}),
getEntity("media_player", "living_room", "playing", {
@@ -42,6 +43,7 @@ export const createMediaPlayerEntities = () => [
media_title: "Chapter 1",
media_series_title: "House of Cards",
app_name: "Netflix",
entity_picture: "/images/netflix.jpg",
supported_features: 1,
}),
getEntity("media_player", "sonos_idle", "idle", {

View File

@@ -93,7 +93,7 @@ class HassioAddonRepositoryEl extends LitElement {
? "not_available"
: ""}
.iconImage=${atLeastVersion(
this.hass.connection.haVersion,
this.hass.config.version,
0,
105
) && addon.icon

View File

@@ -107,7 +107,7 @@ class HassioAddonInfo extends LitElement {
<hassio-card-content
.hass=${this.hass}
.title="${this.addon.name} ${this.addon
.last_version} is available"
.version_latest} is available"
.description="You are currently running version ${this.addon
.version}"
icon="hassio:arrow-up-bold-circle"
@@ -179,7 +179,7 @@ class HassioAddonInfo extends LitElement {
`}
`
: html`
${this.addon.last_version}
${this.addon.version_latest}
`}
</div>
</div>
@@ -636,7 +636,7 @@ class HassioAddonInfo extends LitElement {
this.addon &&
!this.addon.detached &&
this.addon.version &&
this.addon.version !== this.addon.last_version
this.addon.version !== this.addon.version_latest
);
}
@@ -661,8 +661,7 @@ class HassioAddonInfo extends LitElement {
private get _computeCannotIngressSidebar(): boolean {
return (
!this.addon.ingress ||
!atLeastVersion(this.hass.connection.haVersion, 0, 92)
!this.addon.ingress || !atLeastVersion(this.hass.config.version, 0, 92)
);
}

View File

@@ -67,7 +67,7 @@ class HassioAddons extends LitElement {
? "running"
: "stopped"}
.iconImage=${atLeastVersion(
this.hass.connection.haVersion,
this.hass.config.version,
0,
105
) && addon.icon

View File

@@ -40,8 +40,8 @@ export class HassioUpdate extends LitElement {
].filter((value) => {
return (
!!value &&
(value.last_version
? value.version !== value.last_version
(value.version_latest
? value.version !== value.version_latest
: value.version_latest
? value.version !== value.version_latest
: false)
@@ -68,26 +68,26 @@ export class HassioUpdate extends LitElement {
${this._renderUpdateCard(
"Home Assistant Core",
this.hassInfo.version,
this.hassInfo.last_version,
this.hassInfo.version_latest,
"hassio/homeassistant/update",
`https://${
this.hassInfo.last_version.includes("b") ? "rc" : "www"
this.hassInfo.version_latest.includes("b") ? "rc" : "www"
}.home-assistant.io/latest-release-notes/`,
"hassio:home-assistant"
)}
${this._renderUpdateCard(
"Supervisor",
this.supervisorInfo.version,
this.supervisorInfo.last_version,
this.supervisorInfo.version_latest,
"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.version_latest}`
)}
${this.hassOsInfo
? this._renderUpdateCard(
"Operating System",
this.hassOsInfo.version,
this.hassOsInfo.version_latest,
"hassio/hassos/update",
"hassio/os/update",
`https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}`
)
: ""}

View File

@@ -87,8 +87,7 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
applyThemesOnElement(
this.parentElement,
this.hass.themes,
this.hass.selectedTheme,
true
this.hass.selectedTheme || this.hass.themes.default_theme
);
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
// Paulus - March 17, 2019

View File

@@ -100,7 +100,7 @@ class HassioHostInfo extends LitElement {
<ha-call-api-button
class="warning"
.hass=${this.hass}
path="hassio/hassos/config/sync"
path="hassio/os/config/sync"
title="Load HassOS configs or updates from USB"
>Import from USB</ha-call-api-button
>
@@ -108,9 +108,7 @@ class HassioHostInfo extends LitElement {
: ""}
${this.hostInfo.version !== this.hostInfo.version_latest
? html`
<ha-call-api-button
.hass=${this.hass}
path="hassio/hassos/update"
<ha-call-api-button .hass=${this.hass} path="hassio/os/update"
>Update</ha-call-api-button
>
`

View File

@@ -41,7 +41,7 @@ class HassioSupervisorInfo extends LitElement {
</tr>
<tr>
<td>Latest version</td>
<td>${this.supervisorInfo.last_version}</td>
<td>${this.supervisorInfo.version_latest}</td>
</tr>
${this.supervisorInfo.channel !== "stable"
? html`
@@ -63,7 +63,7 @@ class HassioSupervisorInfo extends LitElement {
<ha-call-api-button .hass=${this.hass} path="hassio/supervisor/reload"
>Reload</ha-call-api-button
>
${this.supervisorInfo.version !== this.supervisorInfo.last_version
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
? html`
<ha-call-api-button
.hass=${this.hass}

View File

@@ -1 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="36" height="36" viewBox="0 0 24 24"><path fill="#505050" d="M19,3A2,2 0 0,1 21,5V11H19V13H19L17,13V15H15V17H13V19H11V21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H19M21,15V19A2,2 0 0,1 19,21H19L15,21V19H17V17H19V15H21M19,8.5A0.5,0.5 0 0,0 18.5,8H5.5A0.5,0.5 0 0,0 5,8.5V15.5A0.5,0.5 0 0,0 5.5,16H11V15H13V13H15V11H17V9H19V8.5Z" /></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="36" height="36" version="1.1" viewBox="0 0 24 24"><path fill="#505050" d="M19,3A2,2 0 0,1 21,5V11H19V13H19L17,13V15H15V17H13V19H11V21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H19M21,15V19A2,2 0 0,1 19,21H19L15,21V19H17V17H19V15H21M19,8.5A0.5,0.5 0 0,0 18.5,8H5.5A0.5,0.5 0 0,0 5,8.5V15.5A0.5,0.5 0 0,0 5.5,16H11V15H13V13H15V11H17V9H19V8.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 571 B

After

Width:  |  Height:  |  Size: 434 B

View File

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

View File

@@ -7,9 +7,6 @@
/** Icon to use when no icon specified for domain. */
export const DEFAULT_DOMAIN_ICON = "hass:bookmark";
/** Panel to show when no panel is picked. */
export const DEFAULT_PANEL = "lovelace";
/** Domains that have a state card. */
export const DOMAINS_WITH_CARD = [
"climate",

View File

@@ -1,4 +1,10 @@
import { derivedStyles } from "../../resources/styles";
import { HomeAssistant, Theme } from "../../types";
interface ProcessedTheme {
keys: { [key: string]: "" };
styles: { [key: string]: string };
}
const hexToRgb = (hex: string): string | null => {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
@@ -15,67 +21,82 @@ const hexToRgb = (hex: string): string | null => {
: null;
};
let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
/**
* Apply a theme to an element by setting the CSS variables on it.
*
* element: Element to apply theme on.
* themes: HASS Theme information
* localTheme: selected theme.
* updateMeta: boolean if we should update the theme-color meta element.
* selectedTheme: selected theme.
*/
export const applyThemesOnElement = (
element,
themes,
localTheme,
updateMeta = false
themes: HomeAssistant["themes"],
selectedTheme?: string
) => {
if (!element._themes) {
element._themes = {};
}
let themeName = themes.default_theme;
if (localTheme === "default" || (localTheme && themes.themes[localTheme])) {
themeName = localTheme;
}
const styles = { ...element._themes };
if (themeName !== "default") {
const theme = { ...derivedStyles, ...themes.themes[themeName] };
Object.keys(theme).forEach((key) => {
const prefixedKey = `--${key}`;
element._themes[prefixedKey] = "";
styles[prefixedKey] = theme[key];
if (key.startsWith("rgb")) {
return;
}
const rgbKey = `rgb-${key}`;
if (theme[rgbKey] !== undefined) {
return;
}
const prefixedRgbKey = `--${rgbKey}`;
element._themes[prefixedRgbKey] = "";
const rgbValue = hexToRgb(theme[key]);
if (rgbValue !== null) {
styles[prefixedRgbKey] = rgbValue;
}
});
}
if (element.updateStyles) {
element.updateStyles(styles);
} else if (window.ShadyCSS) {
// implement updateStyles() method of Polymer elements
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
}
const newTheme = selectedTheme
? PROCESSED_THEMES[selectedTheme] || processTheme(selectedTheme, themes)
: undefined;
if (!updateMeta) {
if (!element._themes && !newTheme) {
// No styles to reset, and no styles to set
return;
}
const meta = document.querySelector("meta[name=theme-color]");
if (meta) {
if (!meta.hasAttribute("default-content")) {
meta.setAttribute("default-content", meta.getAttribute("content")!);
}
const themeColor =
styles["--primary-color"] || meta.getAttribute("default-content");
meta.setAttribute("content", themeColor);
// Add previous set keys to reset them, and new theme
const styles = { ...element._themes, ...newTheme?.styles };
element._themes = newTheme?.keys;
// Set and/or reset styles
if (element.updateStyles) {
element.updateStyles(styles);
} else if (window.ShadyCSS) {
// Implement updateStyles() method of Polymer elements
window.ShadyCSS.styleSubtree(/** @type {!HTMLElement} */ element, styles);
}
};
const processTheme = (
themeName: string,
themes: HomeAssistant["themes"]
): ProcessedTheme | undefined => {
if (!themes.themes[themeName]) {
return;
}
const theme: Theme = {
...derivedStyles,
...themes.themes[themeName],
};
const styles = {};
const keys = {};
for (const key of Object.keys(theme)) {
const prefixedKey = `--${key}`;
const value = theme[key];
styles[prefixedKey] = value;
keys[prefixedKey] = "";
// Try to create a rgb value for this key if it is a hex color
if (!value.startsWith("#")) {
// Not a hex color
continue;
}
const rgbKey = `rgb-${key}`;
if (theme[rgbKey] !== undefined) {
// Theme has it's own rgb value
continue;
}
const rgbValue = hexToRgb(value);
if (rgbValue !== null) {
const prefixedRgbKey = `--${rgbKey}`;
styles[prefixedRgbKey] = rgbValue;
keys[prefixedRgbKey] = "";
}
}
PROCESSED_THEMES[themeName] = { styles, keys };
return { styles, keys };
};
export const invalidateThemeCache = () => {
PROCESSED_THEMES = {};
};

View File

@@ -4,17 +4,58 @@ import { domainIcon } from "./domain_icon";
export const coverIcon = (state: HassEntity): string => {
const open = state.state !== "closed";
switch (state.attributes.device_class) {
case "garage":
return open ? "hass:garage-open" : "hass:garage";
switch (state.state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
return "hass:arrow-down-box";
case "closed":
return "hass:garage";
default:
return "hass:garage-open";
}
case "gate":
switch (state.state) {
case "opening":
case "closing":
return "hass:gate-arrow-right";
case "closed":
return "hass:gate";
default:
return "hass:gate-open";
}
case "door":
return open ? "hass:door-open" : "hass:door-closed";
case "damper":
return open ? "hass:circle" : "hass:circle-slice-8";
case "shutter":
return open ? "hass:window-shutter-open" : "hass:window-shutter";
case "blind":
return open ? "hass:blinds-open" : "hass:blinds";
case "curtain":
switch (state.state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
return "hass:arrow-down-box";
case "closed":
return "hass:blinds";
default:
return "hass:blinds-open";
}
case "window":
return open ? "hass:window-open" : "hass:window-closed";
switch (state.state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
return "hass:arrow-down-box";
case "closed":
return "hass:window-closed";
default:
return "hass:window-open";
}
default:
return domainIcon("cover", state.state);
}

View File

@@ -77,7 +77,16 @@ export const domainIcon = (domain: string, state?: string): string => {
: "hass:checkbox-marked-circle";
case "cover":
return state === "closed" ? "hass:window-closed" : "hass:window-open";
switch (state) {
case "opening":
return "hass:arrow-up-box";
case "closing":
return "hass:arrow-down-box";
case "closed":
return "hass:window-closed";
default:
return "hass:window-open";
}
case "lock":
return state && state === "unlocked" ? "hass:lock-open" : "hass:lock";

View File

@@ -69,9 +69,10 @@ export interface DataTableSortColumnData {
export interface DataTableColumnData extends DataTableSortColumnData {
title: string;
type?: "numeric" | "icon";
type?: "numeric" | "icon" | "icon-button";
template?: <T>(data: any, row: T) => TemplateResult | string;
width?: string;
maxWidth?: string;
grows?: boolean;
}
@@ -227,10 +228,13 @@ export class HaDataTable extends LitElement {
const sorted = key === this._sortColumn;
const classes = {
"mdc-data-table__header-cell--numeric": Boolean(
column.type && column.type === "numeric"
column.type === "numeric"
),
"mdc-data-table__header-cell--icon": Boolean(
column.type && column.type === "icon"
column.type === "icon"
),
"mdc-data-table__header-cell--icon-button": Boolean(
column.type === "icon-button"
),
sortable: Boolean(column.sortable),
"not-sorted": Boolean(column.sortable && !sorted),
@@ -241,9 +245,8 @@ export class HaDataTable extends LitElement {
class="mdc-data-table__header-cell ${classMap(classes)}"
style=${column.width
? styleMap({
[column.grows ? "minWidth" : "width"]: String(
column.width
),
[column.grows ? "minWidth" : "width"]: column.width,
maxWidth: column.maxWidth || "",
})
: ""}
role="columnheader"
@@ -318,10 +321,13 @@ export class HaDataTable extends LitElement {
<div
class="mdc-data-table__cell ${classMap({
"mdc-data-table__cell--numeric": Boolean(
column.type && column.type === "numeric"
column.type === "numeric"
),
"mdc-data-table__cell--icon": Boolean(
column.type && column.type === "icon"
column.type === "icon"
),
"mdc-data-table__cell--icon-button": Boolean(
column.type === "icon-button"
),
grows: Boolean(column.grows),
})}"
@@ -329,7 +335,10 @@ export class HaDataTable extends LitElement {
? styleMap({
[column.grows
? "minWidth"
: "width"]: String(column.width),
: "width"]: column.width,
maxWidth: column.maxWidth
? column.maxWidth
: "",
})
: ""}
>
@@ -532,6 +541,7 @@ export class HaDataTable extends LitElement {
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
box-sizing: border-box;
}
.mdc-data-table__cell.mdc-data-table__cell--icon {
@@ -544,7 +554,7 @@ export class HaDataTable extends LitElement {
padding-left: 16px;
/* @noflip */
padding-right: 0;
width: 40px;
width: 56px;
}
[dir="rtl"] .mdc-data-table__header-cell--checkbox,
.mdc-data-table__header-cell--checkbox[dir="rtl"],
@@ -591,7 +601,7 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell--icon,
.mdc-data-table__cell--icon {
width: 24px;
width: 54px;
}
.mdc-data-table__header-cell.mdc-data-table__header-cell--icon {
@@ -610,6 +620,28 @@ export class HaDataTable extends LitElement {
margin-right: -8px;
}
.mdc-data-table__header-cell--icon-button,
.mdc-data-table__cell--icon-button {
width: 56px;
padding: 8px;
}
.mdc-data-table__header-cell--icon-button:first-child,
.mdc-data-table__cell--icon-button:first-child {
width: 64px;
padding-left: 16px;
}
.mdc-data-table__header-cell--icon-button:last-child,
.mdc-data-table__cell--icon-button:last-child {
width: 64px;
padding-right: 16px;
}
.mdc-data-table__cell--icon-button a {
color: var(--primary-text-color);
}
.mdc-data-table__header-cell {
font-family: Roboto, sans-serif;
-moz-osx-font-smoothing: grayscale;
@@ -695,6 +727,9 @@ export class HaDataTable extends LitElement {
.center {
text-align: center;
}
.secondary {
color: var(--secondary-text-color);
}
.scroller {
display: flex;
position: relative;

View File

@@ -246,7 +246,7 @@ class HaEntityPicker extends LitElement {
paper-input > paper-icon-button {
width: 24px;
height: 24px;
padding: 2px;
padding: 0px 2px;
color: var(--secondary-text-color);
}
[hidden] {

View File

@@ -19,7 +19,7 @@ class HaCoverControls extends PolymerElement {
<div class="state">
<paper-icon-button
aria-label="Open cover"
icon="hass:arrow-up"
icon="[[computeOpenIcon(stateObj)]]"
on-click="onOpenTap"
invisible$="[[!entityObj.supportsOpen]]"
disabled="[[computeOpenDisabled(stateObj, entityObj)]]"
@@ -32,7 +32,7 @@ class HaCoverControls extends PolymerElement {
></paper-icon-button>
<paper-icon-button
aria-label="Close cover"
icon="hass:arrow-down"
icon="[[computeCloseIcon(stateObj)]]"
on-click="onCloseTap"
invisible$="[[!entityObj.supportsClose]]"
disabled="[[computeClosedDisabled(stateObj, entityObj)]]"
@@ -60,6 +60,26 @@ class HaCoverControls extends PolymerElement {
return new CoverEntity(hass, stateObj);
}
computeOpenIcon(stateObj) {
switch (stateObj.attributes.device_class) {
case "awning":
case "gate":
return "hass:arrow-expand-horizontal";
default:
return "hass:arrow-up";
}
}
computeCloseIcon(stateObj) {
switch (stateObj.attributes.device_class) {
case "awning":
case "gate":
return "hass:arrow-collapse-horizontal";
default:
return "hass:arrow-down";
}
}
computeOpenDisabled(stateObj, entityObj) {
var assumedState = stateObj.attributes.assumed_state === true;
return (entityObj.isFullyOpen || entityObj.isOpening) && !assumedState;

View File

@@ -18,7 +18,6 @@ import "../components/user/ha-user-badge";
import "../components/ha-menu-button";
import { HomeAssistant, PanelInfo } from "../types";
import { fireEvent } from "../common/dom/fire_event";
import { DEFAULT_PANEL } from "../common/const";
import {
getExternalConfig,
ExternalConfig,
@@ -33,6 +32,7 @@ import { classMap } from "lit-html/directives/class-map";
import { PaperIconItemElement } from "@polymer/paper-item/paper-icon-item";
import { computeRTL } from "../common/util/compute_rtl";
import { compare } from "../common/string/compare";
import { getDefaultPanel } from "../data/panel";
const SHOW_AFTER_SPACER = ["config", "developer-tools", "hassio"];
@@ -77,7 +77,6 @@ const panelSorter = (a: PanelInfo, b: PanelInfo) => {
// both not built in, sort by title
return compare(a.title!, b.title!);
};
const DEFAULT_PAGE = localStorage.defaultPage || DEFAULT_PANEL;
const computePanels = (hass: HomeAssistant): [PanelInfo[], PanelInfo[]] => {
const panels = hass.panels;
@@ -89,7 +88,7 @@ const computePanels = (hass: HomeAssistant): [PanelInfo[], PanelInfo[]] => {
const afterSpacer: PanelInfo[] = [];
Object.values(panels).forEach((panel) => {
if (!panel.title || panel.url_path === DEFAULT_PAGE) {
if (!panel.title || panel.url_path === hass.defaultPanel) {
return;
}
(SHOW_AFTER_SPACER.includes(panel.url_path)
@@ -142,8 +141,7 @@ class HaSidebar extends LitElement {
}
}
const defaultPanel =
this.hass.panels[DEFAULT_PAGE] || this.hass.panels[DEFAULT_PANEL];
const defaultPanel = getDefaultPanel(hass);
return html`
<div class="menu">
@@ -297,7 +295,8 @@ class HaSidebar extends LitElement {
hass.panelUrl !== oldHass.panelUrl ||
hass.user !== oldHass.user ||
hass.localize !== oldHass.localize ||
hass.states !== oldHass.states
hass.states !== oldHass.states ||
hass.defaultPanel !== oldHass.defaultPanel
);
}
@@ -530,6 +529,7 @@ class HaSidebar extends LitElement {
overflow-x: hidden;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
scrollbar-width: thin;
background: none;
}
a {

View File

@@ -12,9 +12,9 @@ export const callAlarmAction = (
| "arm_night"
| "arm_custom_bypass"
| "disarm",
code: string
code?: string
) => {
hass!.callService("alarm_control_panel", "alarm_" + action, {
hass!.callService("alarm_control_panel", `alarm_${action}`, {
entity_id: entity,
code,
});

View File

@@ -25,3 +25,16 @@ export const fetchAuthProviders = () =>
fetch("/auth/providers", {
credentials: "same-origin",
});
export const createAuthForUser = async (
hass: HomeAssistant,
userId: string,
username: string,
password: string
) =>
hass.callWS({
type: "config/auth_provider/homeassistant/create",
user_id: userId,
username,
password,
});

View File

@@ -69,7 +69,7 @@ export const fetchDeviceTriggerCapabilities = (
const whitelist = [
"above",
"below",
"brightness",
"brightness_pct",
"code",
"for",
"position",

View File

@@ -53,6 +53,9 @@ export const fallbackDeviceName = (
return undefined;
};
export const devicesInArea = (devices: DeviceRegistryEntry[], areaId: string) =>
devices.filter((device) => device.area_id === areaId);
export const updateDeviceRegistryEntry = (
hass: HomeAssistant,
deviceId: string,

View File

@@ -27,6 +27,16 @@ export interface EntityRegistryEntryUpdateParams {
new_entity_id?: string;
}
export const findBatteryEntity = (
hass: HomeAssistant,
entities: EntityRegistryEntry[]
): EntityRegistryEntry | undefined =>
entities.find(
(entity) =>
hass.states[entity.entity_id] &&
hass.states[entity.entity_id].attributes.device_class === "battery"
);
export const computeEntityRegistryName = (
hass: HomeAssistant,
entry: EntityRegistryEntry

1
src/data/graph.ts Normal file
View File

@@ -0,0 +1 @@
export const strokeWidth = 5;

View File

@@ -29,7 +29,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
arch: "armhf" | "aarch64" | "i386" | "amd64";
machine: any;
homeassistant: string;
last_version: string;
version_latest: string;
boot: "auto" | "manual";
build: boolean;
options: object;

View File

@@ -23,7 +23,7 @@ export const fetchHassioHassOsInfo = async (hass: HomeAssistant) => {
return hassioApiResultExtractor(
await hass.callApi<HassioResponse<HassioHassOSInfo>>(
"GET",
"hassio/hassos/info"
"hassio/os/info"
)
);
};

View File

@@ -23,7 +23,7 @@ export const fetchHassioHomeAssistantInfo = async (hass: HomeAssistant) => {
return hassioApiResultExtractor(
await hass.callApi<HassioResponse<HassioHomeAssistantInfo>>(
"GET",
"hassio/homeassistant/info"
"hassio/core/info"
)
);
};

View File

@@ -0,0 +1,23 @@
export interface CustomCardEntry {
type: string;
name?: string;
description?: string;
preview?: boolean;
}
export interface CustomCardsWindow {
customCards?: CustomCardEntry[];
}
export const CUSTOM_TYPE_PREFIX = "custom:";
const customCardsWindow = window as CustomCardsWindow;
if (!("customCards" in customCardsWindow)) {
customCardsWindow.customCards = [];
}
export const customCards = customCardsWindow.customCards!;
export const getCustomCardEntry = (type: string) =>
customCards.find((card) => card.type === type);

View File

@@ -17,3 +17,12 @@ export const subscribeMQTTTopic = (
topic,
});
};
export const removeMQTTDeviceEntry = (
hass: HomeAssistant,
deviceId: string
): Promise<void> =>
hass.callWS({
type: "mqtt/device/remove",
device_id: deviceId,
});

41
src/data/panel.ts Normal file
View File

@@ -0,0 +1,41 @@
import { HomeAssistant, PanelInfo } from "../types";
import { fireEvent } from "../common/dom/fire_event";
/** Panel to show when no panel is picked. */
export const DEFAULT_PANEL = "lovelace";
export const getStorageDefaultPanelUrlPath = () =>
localStorage.defaultPanel
? JSON.parse(localStorage.defaultPanel)
: DEFAULT_PANEL;
export const setDefaultPanel = (element: HTMLElement, urlPath: string) => {
fireEvent(element, "hass-default-panel", { defaultPanel: urlPath });
};
export const getDefaultPanel = (hass: HomeAssistant) =>
hass.panels[hass.defaultPanel];
export const getPanelTitle = (hass: HomeAssistant): string | undefined => {
if (!hass.panels) {
return;
}
const panel = Object.values(hass.panels).find(
(p: PanelInfo): boolean => p.url_path === hass.panelUrl
);
if (!panel) {
return;
}
if (panel.url_path === "lovelace") {
return hass.localize("panel.states");
}
if (panel.url_path === "profile") {
return hass.localize("panel.profile");
}
return hass.localize(`panel.${panel.title}`) || panel.title || undefined;
};

View File

@@ -10,7 +10,7 @@ export interface LoggedError {
exception: string;
count: number;
// unix timestamp in seconds
first_occured: number;
first_occurred: number;
}
export const fetchSystemLog = (hass: HomeAssistant) =>

View File

@@ -5,6 +5,8 @@ export const SYSTEM_GROUP_ID_ADMIN = "system-admin";
export const SYSTEM_GROUP_ID_USER = "system-users";
export const SYSTEM_GROUP_ID_READ_ONLY = "system-read-only";
export const GROUPS = [SYSTEM_GROUP_ID_USER, SYSTEM_GROUP_ID_ADMIN];
export interface User {
id: string;
name: string;
@@ -15,7 +17,7 @@ export interface User {
credentials: Credential[];
}
interface UpdateUserParams {
export interface UpdateUserParams {
name?: User["name"];
group_ids?: User["group_ids"];
}
@@ -25,10 +27,16 @@ export const fetchUsers = async (hass: HomeAssistant) =>
type: "config/auth/list",
});
export const createUser = async (hass: HomeAssistant, name: string) =>
export const createUser = async (
hass: HomeAssistant,
name: string,
// tslint:disable-next-line: variable-name
group_ids?: User["group_ids"]
) =>
hass.callWS<{ user: User }>({
type: "config/auth/create",
name,
group_ids,
});
export const updateUser = async (

View File

@@ -130,7 +130,11 @@ class DataEntryFlowDialog extends LitElement {
>
${this._loading || (this._step === null && this._handlers === undefined)
? html`
<step-flow-loading></step-flow-loading>
<step-flow-loading
.label=${this.hass.localize(
"ui.panel.config.integrations.config_flow.loading_first_time"
)}
></step-flow-loading>
`
: this._step === undefined
? // When we are going to next step, we render 1 round of empty

View File

@@ -5,14 +5,22 @@ import {
css,
customElement,
CSSResult,
property,
} from "lit-element";
import "@polymer/paper-spinner/paper-spinner-lite";
@customElement("step-flow-loading")
class StepFlowLoading extends LitElement {
@property() public label?: string;
protected render(): TemplateResult {
return html`
<div class="init-spinner">
${this.label
? html`
<div>${this.label}</div>
`
: ""}
<paper-spinner-lite active></paper-spinner-lite>
</div>
`;

View File

@@ -142,6 +142,9 @@ class DialogBox extends LitElement {
min-width: initial;
}
}
a {
color: var(--primary-color);
}
p {
margin: 0;
padding-top: 6px;

View File

@@ -1,8 +1,9 @@
import { fireEvent } from "../../common/dom/fire_event";
import { TemplateResult } from "lit-html";
interface BaseDialogParams {
confirmText?: string;
text?: string;
text?: string | TemplateResult;
title?: string;
}

View File

@@ -92,12 +92,12 @@ window.hassConnection.then(({ conn }) => {
subscribeFrontendUserData(conn, "core", noop);
if (location.pathname === "/" || location.pathname.startsWith("/lovelace/")) {
(window as WindowWithLovelaceProm).llConfProm = fetchConfig(
conn,
null,
false
);
(window as WindowWithLovelaceProm).llResProm = fetchResources(conn);
const llWindow = window as WindowWithLovelaceProm;
llWindow.llConfProm = fetchConfig(conn, null, false);
llWindow.llConfProm.catch(() => {
// Ignore it, it is handled by Lovelace panel.
});
llWindow.llResProm = fetchResources(conn);
}
});

View File

@@ -11,7 +11,7 @@ export const demoConfig: HassConfig = {
temperature: "°C",
volume: "L",
},
components: ["notify.html5", "history"],
components: ["notify.html5", "history", "shopping_list"],
time_zone: "America/Los_Angeles",
config_dir: "/config",
version: "DEMO",

View File

@@ -1,4 +1,7 @@
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
import {
applyThemesOnElement,
invalidateThemeCache,
} from "../common/dom/apply_themes_on_element";
import { demoConfig } from "./demo_config";
import { demoServices } from "./demo_services";
@@ -8,6 +11,7 @@ import { HomeAssistant } from "../types";
import { HassEntities } from "home-assistant-js-websocket";
import { getLocalLanguage } from "../util/hass-translation";
import { translationMetadata } from "../resources/translations-metadata";
import { DEFAULT_PANEL } from "../data/panel";
const ensureArray = <T>(val: T | T[]): T[] =>
Array.isArray(val) ? val : [val];
@@ -169,6 +173,7 @@ export const provideHass = (
name: "Demo User",
},
panelUrl: "lovelace",
defaultPanel: DEFAULT_PANEL,
language: localLanguage,
selectedLanguage: localLanguage,
@@ -224,6 +229,7 @@ export const provideHass = (
(eventListeners[event] || []).forEach((fn) => fn(event));
},
mockTheme(theme) {
invalidateThemeCache();
hass().updateHass({
selectedTheme: theme ? "mock" : "default",
themes: {
@@ -237,8 +243,7 @@ export const provideHass = (
applyThemesOnElement(
document.documentElement,
themes,
selectedTheme,
true
selectedTheme as string
);
},

View File

@@ -88,6 +88,7 @@ export class HaTabsSubpageDataTable extends LitElement {
.route=${this.route}
.tabs=${this.tabs}
>
<div slot="toolbar-icon"><slot name="toolbar-icon"></slot></div>
${this.narrow
? html`
<div slot="header">

View File

@@ -37,12 +37,12 @@ class HassTabsSubpage extends LitElement {
@property() public route!: Route;
@property() public tabs!: PageNavigation[];
@property({ type: Boolean, reflect: true }) public narrow = false;
@property() private _activeTab: number = -1;
@property() private _activeTab?: PageNavigation;
private _getTabs = memoizeOne(
(
tabs: PageNavigation[],
activeTab: number,
activeTab: PageNavigation | undefined,
showAdvanced: boolean | undefined,
_components,
_language
@@ -56,31 +56,32 @@ class HassTabsSubpage extends LitElement {
);
return shownTabs.map(
(page, index) => html`
<div
class="tab ${classMap({
active: index === activeTab,
})}"
@click=${this._tabTapped}
.path=${page.path}
>
${this.narrow
? html`
<ha-icon .icon=${page.icon}></ha-icon>
`
: ""}
${!this.narrow || index === activeTab
? html`
<span class="name"
>${page.translationKey
? this.hass.localize(page.translationKey)
: name}</span
>
`
: ""}
<mwc-ripple></mwc-ripple>
</div>
`
(page) =>
html`
<div
class="tab ${classMap({
active: page === activeTab,
})}"
@click=${this._tabTapped}
.path=${page.path}
>
${this.narrow
? html`
<ha-icon .icon=${page.icon}></ha-icon>
`
: ""}
${!this.narrow || page === activeTab
? html`
<span class="name"
>${page.translationKey
? this.hass.localize(page.translationKey)
: name}</span
>
`
: ""}
<mwc-ripple></mwc-ripple>
</div>
`
);
}
);
@@ -88,7 +89,7 @@ class HassTabsSubpage extends LitElement {
protected updated(changedProperties: PropertyValues) {
super.updated(changedProperties);
if (changedProperties.has("route")) {
this._activeTab = this.tabs.findIndex((tab) =>
this._activeTab = this.tabs.find((tab) =>
this.route.prefix.includes(tab.path)
);
}

View File

@@ -6,11 +6,11 @@ import "./ha-init-page";
import "../resources/ha-style";
import "../resources/custom-card-support";
import { registerServiceWorker } from "../util/register-service-worker";
import { DEFAULT_PANEL } from "../common/const";
import { Route, HomeAssistant } from "../types";
import { navigate } from "../common/navigate";
import { HassElement } from "../state/hass-element";
import { getStorageDefaultPanelUrlPath } from "../data/panel";
export class HomeAssistantAppEl extends HassElement {
@property() private _route?: Route;
@@ -86,7 +86,7 @@ export class HomeAssistantAppEl extends HassElement {
this._route === undefined &&
(route.path === "" || route.path === "/")
) {
navigate(window, `/${localStorage.defaultPage || DEFAULT_PANEL}`, true);
navigate(window, `/${getStorageDefaultPanelUrlPath()}`, true);
return;
}

View File

@@ -109,9 +109,9 @@ class DialogAreaDetail extends LitElement {
name: this._name.trim(),
};
if (this._params!.entry) {
await this._params!.updateEntry(values);
await this._params!.updateEntry!(values);
} else {
await this._params!.createEntry(values);
await this._params!.createEntry!(values);
}
this._params = undefined;
} catch (err) {
@@ -124,7 +124,7 @@ class DialogAreaDetail extends LitElement {
private async _deleteEntry() {
this._submitting = true;
try {
if (await this._params!.removeEntry()) {
if (await this._params!.removeEntry!()) {
this._params = undefined;
}
} finally {

View File

@@ -0,0 +1,397 @@
import "@material/mwc-button";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-input/paper-input";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../components/dialog/ha-paper-dialog";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types";
import memoizeOne from "memoize-one";
import {
AreaRegistryEntry,
updateAreaRegistryEntry,
deleteAreaRegistryEntry,
} from "../../../data/area_registry";
import {
DeviceRegistryEntry,
devicesInArea,
computeDeviceName,
} from "../../../data/device_registry";
import { configSections } from "../ha-panel-config";
import {
showAreaRegistryDetailDialog,
loadAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import { RelatedResult, findRelated } from "../../../data/search";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ha-config-area-page")
class HaConfigAreaPage extends LitElement {
@property() public hass!: HomeAssistant;
@property() public areaId!: string;
@property() public areas!: AreaRegistryEntry[];
@property() public devices!: DeviceRegistryEntry[];
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
@property() public isWide!: boolean;
@property() public showAdvanced!: boolean;
@property() public route!: Route;
@property() private _related?: RelatedResult;
private _area = memoizeOne((areaId: string, areas: AreaRegistryEntry[]):
| AreaRegistryEntry
| undefined => areas.find((area) => area.area_id === areaId));
private _devices = memoizeOne(
(areaId: string, devices: DeviceRegistryEntry[]): DeviceRegistryEntry[] =>
devicesInArea(devices, areaId)
);
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
loadAreaRegistryDetailDialog();
}
protected updated(changedProps) {
super.updated(changedProps);
if (changedProps.has("areaId")) {
this._findRelated();
}
}
protected render(): TemplateResult {
const area = this._area(this.areaId, this.areas);
if (!area) {
return html`
<hass-error-screen
error="${this.hass.localize("ui.panel.config.areas.area_not_found")}"
></hass-error-screen>
`;
}
const devices = this._devices(this.areaId, this.devices);
return html`
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
.tabs=${configSections.integrations}
.route=${this.route}
>
${this.narrow
? html`
<span slot="header">
${area.name}
</span>
`
: ""}
<paper-icon-button
slot="toolbar-icon"
icon="hass:settings"
.entry=${area}
@click=${this._showSettings}
></paper-icon-button>
<div class="container">
${!this.narrow
? html`
<div class="fullwidth">
<h1>${area.name}</h1>
</div>
`
: ""}
<div class="column">
<ha-card
.header=${this.hass.localize("ui.panel.config.devices.caption")}
>${devices.length
? devices.map(
(device) =>
html`
<a href="/config/devices/device/${device.id}">
<paper-item>
<paper-item-body>
${computeDeviceName(device, this.hass)}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
`
)
: html`
<paper-item class="no-link"
>${this.hass.localize(
"ui.panel.config.devices.no_devices"
)}</paper-item
>
`}
</ha-card>
</div>
<div class="column">
${isComponentLoaded(this.hass, "automation")
? html`
<ha-card
.header=${this.hass.localize(
"ui.panel.config.devices.automation.automations"
)}
>${this._related?.automation?.length
? this._related.automation.map((automation) => {
const state = this.hass.states[automation];
return state
? html`
<div>
<a
href=${ifDefined(
state.attributes.id
? `/config/automation/edit/${state.attributes.id}`
: undefined
)}
>
<paper-item
.disabled=${!state.attributes.id}
>
<paper-item-body>
${computeStateName(state)}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
${!state.attributes.id
? html`
<paper-tooltip
>${this.hass.localize(
"ui.panel.config.devices.cant_edit"
)}
</paper-tooltip>
`
: ""}
</div>
`
: "";
})
: html`
<paper-item class="no-link"
>${this.hass.localize(
"ui.panel.config.devices.automation.no_automations"
)}</paper-item
>
`}
</ha-card>
`
: ""}
</div>
<div class="column">
${isComponentLoaded(this.hass, "scene")
? html`
<ha-card
.header=${this.hass.localize(
"ui.panel.config.devices.scene.scenes"
)}
>${this._related?.scene?.length
? this._related.scene.map((scene) => {
const state = this.hass.states[scene];
return state
? html`
<div>
<a
href=${ifDefined(
state.attributes.id
? `/config/scene/edit/${state.attributes.id}`
: undefined
)}
>
<paper-item
.disabled=${!state.attributes.id}
>
<paper-item-body>
${computeStateName(state)}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
${!state.attributes.id
? html`
<paper-tooltip
>${this.hass.localize(
"ui.panel.config.devices.cant_edit"
)}
</paper-tooltip>
`
: ""}
</div>
`
: "";
})
: html`
<paper-item class="no-link"
>${this.hass.localize(
"ui.panel.config.devices.scene.no_scenes"
)}</paper-item
>
`}
</ha-card>
`
: ""}
${isComponentLoaded(this.hass, "script")
? html`
<ha-card
.header=${this.hass.localize(
"ui.panel.config.devices.script.scripts"
)}
>${this._related?.script?.length
? this._related.script.map((script) => {
const state = this.hass.states[script];
return state
? html`
<a
href=${ifDefined(
state.attributes.id
? `/config/script/edit/${state.attributes.id}`
: undefined
)}
>
<paper-item>
<paper-item-body>
${computeStateName(state)}
</paper-item-body>
<ha-icon-next></ha-icon-next>
</paper-item>
</a>
`
: "";
})
: html`
<paper-item class="no-link">
${this.hass.localize(
"ui.panel.config.devices.script.no_scripts"
)}</paper-item
>
`}
</ha-card>
`
: ""}
</div>
</div>
</hass-tabs-subpage>
`;
}
private async _findRelated() {
this._related = await findRelated(this.hass, "area", this.areaId);
}
private _showSettings(ev: MouseEvent) {
const entry: AreaRegistryEntry = (ev.currentTarget! as any).entry;
this._openDialog(entry);
}
private _openDialog(entry?: AreaRegistryEntry) {
showAreaRegistryDetailDialog(this, {
entry,
updateEntry: async (values) =>
updateAreaRegistryEntry(this.hass!, entry!.area_id, values),
removeEntry: async () => {
if (
!(await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.areas.delete.confirmation_title"
),
text: this.hass.localize(
"ui.panel.config.areas.delete.confirmation_text"
),
dismissText: this.hass.localize("ui.common.no"),
confirmText: this.hass.localize("ui.common.yes"),
}))
) {
return false;
}
try {
await deleteAreaRegistryEntry(this.hass!, entry!.area_id);
return true;
} catch (err) {
return false;
}
},
});
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
h1 {
margin-top: 0;
font-family: var(--paper-font-headline_-_font-family);
-webkit-font-smoothing: var(
--paper-font-headline_-_-webkit-font-smoothing
);
font-size: var(--paper-font-headline_-_font-size);
font-weight: var(--paper-font-headline_-_font-weight);
letter-spacing: var(--paper-font-headline_-_letter-spacing);
line-height: var(--paper-font-headline_-_line-height);
opacity: var(--dark-primary-opacity);
}
.container {
display: flex;
flex-wrap: wrap;
margin: auto;
max-width: 1000px;
margin-top: 32px;
margin-bottom: 32px;
}
.column {
padding: 8px;
box-sizing: border-box;
width: 33%;
flex-grow: 1;
}
.fullwidth {
padding: 8px;
width: 100%;
}
.column > *:not(:first-child) {
margin-top: 16px;
}
:host([narrow]) .column {
width: 100%;
}
:host([narrow]) .container {
margin-top: 0;
}
paper-item {
cursor: pointer;
}
a {
text-decoration: none;
color: var(--primary-text-color);
}
paper-item.no-link {
cursor: default;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-area-page": HaConfigAreaPage;
}
}

View File

@@ -0,0 +1,200 @@
import {
LitElement,
TemplateResult,
html,
css,
CSSResult,
property,
customElement,
} from "lit-element";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import { HomeAssistant, Route } from "../../../types";
import {
AreaRegistryEntry,
createAreaRegistryEntry,
} from "../../../data/area_registry";
import "../../../components/ha-fab";
import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table";
import "../ha-config-section";
import {
showAreaRegistryDetailDialog,
loadAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail";
import { configSections } from "../ha-panel-config";
import memoizeOne from "memoize-one";
import {
DataTableColumnContainer,
RowClickedEvent,
} from "../../../components/data-table/ha-data-table";
import {
devicesInArea,
DeviceRegistryEntry,
} from "../../../data/device_registry";
import { navigate } from "../../../common/navigate";
import { HASSDomEvent } from "../../../common/dom/fire_event";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
@customElement("ha-config-areas-dashboard")
export class HaConfigAreasDashboard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public isWide?: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
@property() public areas!: AreaRegistryEntry[];
@property() public devices!: DeviceRegistryEntry[];
private _areas = memoizeOne(
(areas: AreaRegistryEntry[], devices: DeviceRegistryEntry[]) => {
return areas.map((area) => {
return {
...area,
devices: devicesInArea(devices, area.area_id).length,
};
});
}
);
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer =>
narrow
? {
name: {
title: this.hass.localize(
"ui.panel.config.areas.data_table.area"
),
sortable: true,
filterable: true,
grows: true,
direction: "asc",
},
}
: {
name: {
title: this.hass.localize(
"ui.panel.config.areas.data_table.area"
),
sortable: true,
filterable: true,
grows: true,
direction: "asc",
},
devices: {
title: this.hass.localize(
"ui.panel.config.areas.data_table.devices"
),
sortable: true,
type: "numeric",
width: "20%",
direction: "asc",
},
}
);
protected render(): TemplateResult {
return html`
<hass-tabs-subpage-data-table
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.tabs=${configSections.integrations}
.route=${this.route}
.columns=${this._columns(this.narrow)}
.data=${this._areas(this.areas, this.devices)}
@row-click=${this._handleRowClicked}
.noDataText=${this.hass.localize(
"ui.panel.config.areas.picker.no_areas"
)}
id="area_id"
>
<paper-icon-button
slot="toolbar-icon"
icon="hass:help-circle"
@click=${this._showHelp}
></paper-icon-button>
</hass-tabs-subpage-data-table>
<ha-fab
?is-wide=${this.isWide}
?narrow=${this.narrow}
icon="hass:plus"
title="${this.hass.localize(
"ui.panel.config.areas.picker.create_area"
)}"
@click=${this._createArea}
></ha-fab>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
loadAreaRegistryDetailDialog();
}
private _createArea() {
this._openDialog();
}
private _showHelp() {
showAlertDialog(this, {
title: this.hass.localize("ui.panel.config.areas.caption"),
text: html`
${this.hass.localize("ui.panel.config.areas.picker.introduction")}
<p>
${this.hass.localize("ui.panel.config.areas.picker.introduction2")}
</p>
<a href="/config/integrations/dashboard">
${this.hass.localize(
"ui.panel.config.areas.picker.integrations_page"
)}
</a>
`,
});
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const areaId = ev.detail.id;
navigate(this, `/config/areas/area/${areaId}`);
}
private _openDialog(entry?: AreaRegistryEntry) {
showAreaRegistryDetailDialog(this, {
entry,
createEntry: async (values) =>
createAreaRegistryEntry(this.hass!, values),
});
}
static get styles(): CSSResult {
return css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
ha-fab {
position: fixed;
bottom: 16px;
right: 16px;
z-index: 1;
}
ha-fab[is-wide] {
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
}
ha-fab.rtl {
right: auto;
left: 16px;
}
ha-fab[is-wide].rtl {
bottom: 24px;
right: auto;
left: 24px;
}
`;
}
}

View File

@@ -1,226 +1,120 @@
import "./ha-config-areas-dashboard";
import "./ha-config-area-page";
import { compare } from "../../../common/string/compare";
import {
LitElement,
TemplateResult,
html,
css,
CSSResult,
property,
customElement,
} from "lit-element";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import { HomeAssistant, Route } from "../../../types";
import {
AreaRegistryEntry,
updateAreaRegistryEntry,
deleteAreaRegistryEntry,
createAreaRegistryEntry,
subscribeAreaRegistry,
AreaRegistryEntry,
} from "../../../data/area_registry";
import "../../../components/ha-card";
import "../../../components/ha-fab";
import "../../../layouts/hass-tabs-subpage";
import "../../../layouts/hass-loading-screen";
import "../ha-config-section";
import {
showAreaRegistryDetailDialog,
loadAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail";
import { classMap } from "lit-html/directives/class-map";
import { computeRTL } from "../../../common/util/compute_rtl";
HassRouterPage,
RouterOptions,
} from "../../../layouts/hass-router-page";
import { property, customElement, PropertyValues } from "lit-element";
import { HomeAssistant } from "../../../types";
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
import {
DeviceRegistryEntry,
subscribeDeviceRegistry,
} from "../../../data/device_registry";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { configSections } from "../ha-panel-config";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
@customElement("ha-config-areas")
export class HaConfigAreas extends LitElement {
class HaConfigAreas extends HassRouterPage {
@property() public hass!: HomeAssistant;
@property() public isWide?: boolean;
@property() public narrow!: boolean;
@property() public route!: Route;
@property() private _areas?: AreaRegistryEntry[];
private _unsubAreas?: UnsubscribeFunc;
@property() public isWide!: boolean;
@property() public showAdvanced!: boolean;
protected routerOptions: RouterOptions = {
defaultPage: "dashboard",
routes: {
dashboard: {
tag: "ha-config-areas-dashboard",
cache: true,
},
area: {
tag: "ha-config-area-page",
},
},
};
@property() private _configEntries: ConfigEntry[] = [];
@property() private _deviceRegistryEntries: DeviceRegistryEntry[] = [];
@property() private _areas: AreaRegistryEntry[] = [];
private _unsubs?: UnsubscribeFunc[];
public connectedCallback() {
super.connectedCallback();
if (!this.hass) {
return;
}
this._loadData();
}
public disconnectedCallback() {
super.disconnectedCallback();
if (this._unsubAreas) {
this._unsubAreas();
if (this._unsubs) {
while (this._unsubs.length) {
this._unsubs.pop()!();
}
this._unsubs = undefined;
}
}
protected render(): TemplateResult {
if (!this.hass || this._areas === undefined) {
return html`
<hass-loading-screen></hass-loading-screen>
`;
}
return html`
<hass-tabs-subpage
.hass=${this.hass}
.narrow=${this.narrow}
back-path="/config"
.route=${this.route}
.tabs=${configSections.integrations}
>
<ha-config-section .isWide=${this.isWide}>
<span slot="header">
${this.hass.localize("ui.panel.config.areas.picker.header")}
</span>
<span slot="introduction">
${this.hass.localize("ui.panel.config.areas.picker.introduction")}
<p>
${this.hass.localize(
"ui.panel.config.areas.picker.introduction2"
)}
</p>
<a href="/config/integrations/dashboard">
${this.hass.localize(
"ui.panel.config.areas.picker.integrations_page"
)}
</a>
</span>
<ha-card>
${this._areas.map((entry) => {
return html`
<paper-item @click=${this._openEditEntry} .entry=${entry}>
<paper-item-body>
${entry.name}
</paper-item-body>
</paper-item>
`;
})}
${this._areas.length === 0
? html`
<div class="empty">
${this.hass.localize("ui.panel.config.areas.no_areas")}
<mwc-button @click=${this._createArea}>
${this.hass.localize("ui.panel.config.areas.create_area")}
</mwc-button>
</div>
`
: html``}
</ha-card>
</ha-config-section>
</hass-tabs-subpage>
<ha-fab
?is-wide=${this.isWide}
?narrow=${this.narrow}
icon="hass:plus"
title="${this.hass.localize("ui.panel.config.areas.create_area")}"
@click=${this._createArea}
class="${classMap({
rtl: computeRTL(this.hass),
})}"
></ha-fab>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
loadAreaRegistryDetailDialog();
}
protected updated(changedProps) {
super.updated(changedProps);
if (!this._unsubAreas) {
this._unsubAreas = subscribeAreaRegistry(
this.hass.connection,
(areas) => {
this._areas = areas;
}
);
}
}
private _createArea() {
this._openDialog();
}
private _openEditEntry(ev: MouseEvent) {
const entry: AreaRegistryEntry = (ev.currentTarget! as any).entry;
this._openDialog(entry);
}
private _openDialog(entry?: AreaRegistryEntry) {
showAreaRegistryDetailDialog(this, {
entry,
createEntry: async (values) =>
createAreaRegistryEntry(this.hass!, values),
updateEntry: async (values) =>
updateAreaRegistryEntry(this.hass!, entry!.area_id, values),
removeEntry: async () => {
if (
!(await showConfirmationDialog(this, {
title: this.hass.localize(
"ui.panel.config.areas.delete.confirmation_title"
),
text: this.hass.localize(
"ui.panel.config.areas.delete.confirmation_text"
),
dismissText: this.hass.localize("ui.common.no"),
confirmText: this.hass.localize("ui.common.yes"),
}))
) {
return false;
}
try {
await deleteAreaRegistryEntry(this.hass!, entry!.area_id);
return true;
} catch (err) {
return false;
}
},
this.addEventListener("hass-reload-entries", () => {
this._loadData();
});
}
static get styles(): CSSResult {
return css`
hass-loading-screen {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
}
a {
color: var(--primary-color);
}
ha-card {
max-width: 600px;
margin: 16px auto;
overflow: hidden;
}
.empty {
text-align: center;
}
paper-item {
cursor: pointer;
padding-top: 4px;
padding-bottom: 4px;
}
ha-fab {
position: fixed;
bottom: 16px;
right: 16px;
z-index: 1;
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!this._unsubs && changedProps.has("hass")) {
this._loadData();
}
}
ha-fab[is-wide] {
bottom: 24px;
right: 24px;
}
ha-fab[narrow] {
bottom: 84px;
}
ha-fab.rtl {
right: auto;
left: 16px;
}
protected updatePageEl(pageEl) {
pageEl.hass = this.hass;
ha-fab[is-wide].rtl {
bottom: 24px;
right: auto;
left: 24px;
}
`;
if (this._currentPage === "area") {
pageEl.areaId = this.routeTail.path.substr(1);
}
pageEl.entries = this._configEntries;
pageEl.devices = this._deviceRegistryEntries;
pageEl.areas = this._areas;
pageEl.narrow = this.narrow;
pageEl.isWide = this.isWide;
pageEl.showAdvanced = this.showAdvanced;
pageEl.route = this.routeTail;
}
private _loadData() {
getConfigEntries(this.hass).then((configEntries) => {
this._configEntries = configEntries.sort((conf1, conf2) =>
compare(conf1.title, conf2.title)
);
});
if (this._unsubs) {
return;
}
this._unsubs = [
subscribeAreaRegistry(this.hass.connection, (areas) => {
this._areas = areas;
}),
subscribeDeviceRegistry(this.hass.connection, (entries) => {
this._deviceRegistryEntries = entries;
}),
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-areas": HaConfigAreas;
}
}

View File

@@ -6,11 +6,11 @@ import {
export interface AreaRegistryDetailDialogParams {
entry?: AreaRegistryEntry;
createEntry: (values: AreaRegistryEntryMutableParams) => Promise<unknown>;
updateEntry: (
createEntry?: (values: AreaRegistryEntryMutableParams) => Promise<unknown>;
updateEntry?: (
updates: Partial<AreaRegistryEntryMutableParams>
) => Promise<unknown>;
removeEntry: () => Promise<boolean>;
removeEntry?: () => Promise<boolean>;
}
export const loadAreaRegistryDetailDialog = () =>

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