Compare commits

..

380 Commits

Author SHA1 Message Date
Paul Bottein
932120869b Add checkbox mode to boolean selector 2024-08-26 19:14:31 +02:00
Eric Shtivelberg
061521a979 Fix duplicate and non lazy loading of hui-calendar-card (#21788)
* fix: hui-calendar-card is not lazy loaded

* reorder imports
2024-08-26 17:42:33 +02:00
renovate[bot]
d3f73baa36 Update vaadinWebComponents monorepo to v24.4.6 (#21794)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-26 11:06:02 -04:00
Paul Bottein
3037bf494c Add description to service translations (#21759) 2024-08-26 11:33:51 +02:00
Paulus Schoutsen
b4dd953128 Hide tag entities from default dashboard (#21793)
Tag integration now creates entities. They need to be hidden from the default dashboard.
2024-08-26 09:44:52 +02:00
renovate[bot]
430a28f350 Update dependency webpack to v5.94.0 (#21791)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-25 13:47:03 -04:00
Paul Bottein
0eacf3fdac Show error badge when an unknown entity is used in badges (#21757)
* Show error badge when wrong entity set in badges
2024-08-25 13:48:05 +02:00
karwosts
7f9bf69a08 Fix tile alarm modes when wrong code entered (#21779) 2024-08-25 13:22:19 +02:00
renovate[bot]
32cca9e30c Update babel monorepo to v7.25.4 (#21789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-25 13:11:20 +02:00
renovate[bot]
d7d62307b8 Update dependency @material/web to v2.1.0 (#21785)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-25 08:13:34 +02:00
renovate[bot]
12bfa5dab2 Update dependency eslint-plugin-wc to v2.1.1 (#21784) 2024-08-24 23:04:00 -04:00
renovate[bot]
c0a728bc66 Update dependency tinykeys to v3 (#21773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-24 14:17:27 +02:00
renovate[bot]
19e8667349 Update dependency chart.js to v4.4.4 (#21771)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-24 13:23:52 +02:00
dependabot[bot]
f3688b95d4 Bump micromatch from 4.0.7 to 4.0.8 (#21770)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.7 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/4.0.8/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-23 22:07:45 +02:00
karwosts
01f692f05c Render the label on the target selector (#21769)
* Render the label on the target selector

* use label
2024-08-23 20:04:09 +00:00
renovate[bot]
d652f6382d Update dependency @bundle-stats/plugin-webpack-filter to v4.14.2 (#21768)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-23 21:53:31 +02:00
renovate[bot]
c0f96d9473 Update dependency husky to v9.1.5 (#21767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-23 17:54:56 +02:00
renovate[bot]
f1a2af24b3 Update dependency core-js to v3.38.1 (#21764)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-23 17:54:17 +02:00
renovate[bot]
8501098bd1 Lock file maintenance (#21734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-22 21:18:12 +02:00
Gourav Soni
a235f76985 fix: Add destructive styling to delete button in dashboard config rem… (#21729)
* fix: Add destructive styling to delete button in dashboard config removal dialog

* changed translation key 'remove' to 'delete'

* Update src/translations/en.json

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-08-21 18:25:42 +00:00
Paul Bottein
968c0de141 Fix migration from call-service to perform-action (#21746) 2024-08-21 20:13:09 +02:00
Paul Bottein
77d8aff1f4 Add missing label_id and floor_id key in action struct (#21753)
* Add missing label_id key in action struct

* Add missing floor_id key in action struct
2024-08-21 19:59:02 +02:00
Steve Repsher
5e486d9cf0 Correct serving modern build to macOS companion app (#21724) 2024-08-21 11:51:07 -04:00
Michael Arthur
f730761b3f add returning lawn mower state (#21740) 2024-08-21 16:34:33 +02:00
Steve Repsher
46fc9c1a33 Remove old ha-form-style (#21751) 2024-08-21 06:29:13 +02:00
Denis Shulyaka
8ed68bf295 Add check for conversation entity (#21736)
* Add check for conversation entity

* Use true as default
2024-08-20 13:53:07 +02:00
renovate[bot]
5622180d42 Update dependency @octokit/rest to v21.0.2 (#21739) 2024-08-19 20:30:33 -04:00
renovate[bot]
ef1f9b371d Update dependency @types/chromecast-caf-receiver to v6.0.17 (#21733)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-18 23:08:06 -04:00
renovate[bot]
0b79684cf1 Update dependency @codemirror/legacy-modes to v6.4.1 (#21731)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-18 23:07:03 -04:00
renovate[bot]
bc68d8df11 Update dependency marked to v14 (#21654)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-17 12:59:26 +02:00
renovate[bot]
ebbade2fb7 Update dependency @lezer/highlight to v1.2.1 (#21719) 2024-08-16 21:59:36 -04:00
renovate[bot]
2b5f778f2e Update dependency @bundle-stats/plugin-webpack-filter to v4.14.1 (#21718) 2024-08-16 21:58:23 -04:00
Paul Bottein
91fc2383cb Add badges for sidebar view (#21715) 2024-08-16 16:52:52 +02:00
Paul Bottein
1080a8c961 Add missing box shadow theme variable to entity badge (#21714) 2024-08-16 16:18:29 +02:00
renovate[bot]
6144049f8c Update dependency lint-staged to v15.2.9 (#21712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-16 12:42:11 +02:00
renovate[bot]
4ad3ad6e93 Update dependency @codemirror/view to v6.32.0 (#21694)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-16 06:41:35 +02:00
karwosts
16e84296da Use defined response_variable for json copy_to_clipboard button (#21705) 2024-08-16 06:40:56 +02:00
karwosts
3e1ea8d236 fix ha-card-condition-state invert selector (#21711) 2024-08-16 06:39:55 +02:00
Martin Vyšňovský
8c9996fc81 Make delete button red when removing todo item (#21466) (#21708) 2024-08-16 06:39:27 +02:00
Charles Garwood
336b5fb547 Update styling on Template dev-tools (#21661)
* Update styling on Template dev-tools

* Remove unnecessary divs
2024-08-15 09:14:27 +02:00
G Johansson
3f0f3affb6 Remove deprecated mailbox (#21689)
Remove mailbox
2024-08-14 12:38:06 +02:00
renovate[bot]
6754b8893b Update dependency eslint-plugin-unused-imports to v4.1.3 (#21669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-13 16:00:05 +00:00
Adam Kapos
6ec4323c76 Enable background transparency & effects on badges (#21667)
Enable background effects on badges
2024-08-13 09:23:09 +02:00
dependabot[bot]
20408392d2 Bump actions/upload-artifact from 4.3.5 to 4.3.6 (#21671)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 08:46:05 +02:00
dependabot[bot]
b030a5d1f0 Bump relative-ci/agent-action from 2.1.11 to 2.1.12 (#21672)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-12 08:44:59 +02:00
Franck Nijhof
e26e6d7c2d Bumped version to 20240809.0 2024-08-09 19:58:41 +02:00
Simon Lamon
e0c98e4524 Fix left over "script" in default config when using perform action (#21650) 2024-08-09 19:55:57 +02:00
Simon Lamon
2832f501d1 Fix scripted blueprints (#21649) 2024-08-09 19:38:33 +02:00
renovate[bot]
d2f2d682a9 Update CodeMirror (#21628)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-09 06:25:41 +00:00
renovate[bot]
54b2121273 Update dependency @lit-labs/virtualizer to v2.0.14 (#21637)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-09 08:15:58 +02:00
renovate[bot]
c5ca731e05 Update dependency qrcode to v1.5.4 (#21638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-09 08:15:11 +02:00
renovate[bot]
fd9c6c5449 Update dependency @bundle-stats/plugin-webpack-filter to v4.14.0 (#21639)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-09 08:13:02 +02:00
renovate[bot]
39f4355c1a Update dependency core-js to v3.38.0 (#21617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-08 15:57:47 -04:00
Vaibhav Tank
35fed0b0e2 Fix logout button text color on confirmation dialog (#21595) 2024-08-08 09:18:38 +02:00
Madelena Mak
f0f0aefca1 Clean up OHF badge for splash screen (#21593)
* Clean up OHF badge for splash screen

* Fix demo logo size

* Fixed the code to Bram's feedback

* Pixel pushing
2024-08-08 07:56:51 +02:00
karwosts
b60864086f Localize some column headers (#21622) 2024-08-08 07:55:15 +02:00
Jesse Hills
6629f372fc Fix typo in assist-render-pipeline-events.ts (#21620) 2024-08-08 07:54:35 +02:00
dependabot[bot]
ec9a95dde0 Bump @75lb/deep-merge from 1.1.1 to 1.1.2 (#21616)
Bumps [@75lb/deep-merge](https://github.com/75lb/deep-merge) from 1.1.1 to 1.1.2.
- [Commits](https://github.com/75lb/deep-merge/compare/v1.1.1...v1.1.2)

---
updated-dependencies:
- dependency-name: "@75lb/deep-merge"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 12:16:34 +00:00
renovate[bot]
3cd0c49c78 Update dependency luxon to v3.5.0 (#21609)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-07 14:03:12 +02:00
karwosts
e1ae95dd9f Fix webhook textfield icon color for dark mode (#21610) 2024-08-07 08:47:26 +02:00
Franck Nijhof
9c6aef033d Add indicator/alert to YAML integrations on integration page (#21607) 2024-08-06 16:48:29 +02:00
Bram Kragten
b23aacef84 Condition testing display fix (#21606) 2024-08-06 16:33:54 +02:00
Bram Kragten
7d218c89ae Improve state badge migration (#21603) 2024-08-06 16:13:12 +02:00
Bram Kragten
ffa96d789f Update weather card layout options (#21604) 2024-08-06 16:12:36 +02:00
Franck Nijhof
5eae163796 Bumped version to 20240806.0 2024-08-06 12:09:23 +02:00
Franck Nijhof
9e7f01a009 Only migrate automation actions if there are any (#21599) 2024-08-06 11:39:52 +02:00
Simon Lamon
eeffa3561c Add badge label (#21598) 2024-08-06 11:38:08 +02:00
renovate[bot]
f096eb2031 Update dependency lint-staged to v15.2.8 (#21597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-06 10:25:36 +02:00
renovate[bot]
3aad7431da Migrate renovate config (#21594)
Migrate config renovate.json

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-06 10:08:09 +02:00
renovate[bot]
a4f167559c Update Yarn to v4.4.0 (#21592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 23:15:32 -04:00
renovate[bot]
44a0582e83 Update vaadinWebComponents monorepo to v24.4.5 (#21585)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-05 23:12:45 -04:00
Bram Kragten
94c28ea534 Bumped version to 20240805.1 2024-08-05 17:16:24 +02:00
karwosts
5b7c20af33 fix broken perform-action translation in gauge card editor (#21589) 2024-08-05 15:15:39 +00:00
Paul Bottein
4719775926 Allow name in state content for badges (#21576) 2024-08-05 17:14:10 +02:00
Bram Kragten
5c30c1647c Show name for badges without config (#21588)
* Show name for badges without config

* also for state label type
2024-08-05 15:12:28 +00:00
karwosts
3ba572ee37 Fix pointer cursor issues (#21587)
* Fix pointer cursor issues

* one more
2024-08-05 17:07:03 +02:00
karwosts
0adee7d189 Use getStubConfig for picture-elements (and more fixes) (#21579) 2024-08-05 17:05:31 +02:00
Bram Kragten
51b6d7758d Bumped version to 20240805.0 2024-08-05 16:36:50 +02:00
karwosts
0f21dfadf1 Dont load unnecessary translations for helper dialog (#21573) 2024-08-05 16:33:36 +02:00
Robert Resch
54e8a34f21 Change yaml integration icon (#21586) 2024-08-05 16:32:04 +02:00
Steve Repsher
dfbf4abd5d Link old service worker to new name (#21581) 2024-08-05 15:52:46 +02:00
dependabot[bot]
62d8434596 Bump actions/upload-artifact from 4.3.4 to 4.3.5 (#21583) 2024-08-05 08:36:50 +02:00
renovate[bot]
d6df228e17 Update dependency @babel/preset-env to v7.25.3 (#21572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-03 13:08:44 +00:00
Simon Lamon
00faa16349 Add some other tile cards to design page (#21563) 2024-08-03 15:06:41 +02:00
renovate[bot]
3c92fe4170 Update typescript-eslint monorepo to v7.18.0 (#21546)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 20:58:26 +02:00
renovate[bot]
02a9d67c7d Update babel monorepo to v7.25.2 (#21548)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-02 20:57:20 +02:00
Bram Kragten
88718bca23 Bumped version to 20240802.0 2024-08-02 15:56:25 +02:00
Bram Kragten
33931b29a1 Try to force refresh if old service worker is used (#21561)
* Try to force refresh if old service worker is used

* Update register-service-worker.ts
2024-08-02 15:32:21 +02:00
Bram Kragten
a9310fdde0 fix hui-action-editor (#21560) 2024-08-02 14:27:05 +02:00
Bram Kragten
d634317438 Fix tabs not shown in edit card dialog (#21559) 2024-08-02 13:46:25 +02:00
Bram Kragten
a41978f647 Await starting audio recorder (#21557) 2024-08-02 12:57:57 +02:00
Bram Kragten
7e799bf639 migrate script sequence to new format (#21556) 2024-08-02 12:47:41 +02:00
Paulus Schoutsen
73617711ff Show in Assist dialog if assistant is unable to control Home Assistant (#21549)
* Show in Assist dialog if assistant is unable to control Home Assistant

* Update src/translations/en.json
2024-08-02 12:31:06 +02:00
karwosts
edbfc22bf8 Add a warning for improper usage of the template selector (#21545)
* Add a warning when adding template yaml in the template selector

* refinement
2024-08-02 12:30:09 +02:00
karwosts
5e75f6a6c2 Add a destructive confirmation when deleting a picture-elements element (#21541) 2024-08-02 12:28:37 +02:00
karwosts
6dc80306e8 Add help links to helper integration config flow forms (#21537) 2024-08-02 12:27:48 +02:00
Simon Lamon
acd1b04b0a Fix width of numeric features in tile card (#21543) 2024-08-01 23:57:02 +02:00
renovate[bot]
7e9d09e11c Update dependency magic-string to v0.30.11 (#21540)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 18:37:07 +02:00
renovate[bot]
038f7ec000 Update dependency husky to v9.1.4 (#21539)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 18:36:30 +02:00
renovate[bot]
08959cbabf Update dependency @codemirror/view to v6.29.1 (#21532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 18:16:37 +02:00
Bram Kragten
b611674ebd Minify ohf logo (#21528) 2024-08-01 12:29:16 +02:00
Bram Kragten
2f696bd511 Fix demo service worker (#21527)
* fix demo service worker

* cast too
2024-08-01 09:11:27 +00:00
Bram Kragten
9e3284a7db Add ping to mock hass (#21526)
add ping to mock hass
2024-08-01 08:54:43 +00:00
Bram Kragten
0c3471e0b7 Skip database migration check on demo (#21525) 2024-08-01 08:54:14 +00:00
karwosts
1ca0b58aca Picture elements editor minor adjustments (#21512)
* Picture elements editor minor adjustments

* image selector for image element

* revert change to expanded
2024-08-01 10:43:08 +02:00
renovate[bot]
adceed689b Update dependency marked to v13.0.3 (#21513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-31 21:32:03 +02:00
Bram Kragten
574fc99889 Rename service button to action button (#21511) 2024-07-31 16:19:32 +02:00
Bram Kragten
3a83cb36a1 Bumped version to 20240731.0 2024-07-31 15:27:19 +02:00
Paulus Schoutsen
a458ccf995 Update OHF logo on loading screen (#21509)
* Update OHF logo on loading screen

* tweak

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-07-31 13:24:30 +00:00
Bram Kragten
560e2c9438 Migrate service call element to use action key (#21506)
Migrate service call element
2024-07-31 14:54:01 +02:00
Bram Kragten
78becb5440 Allow and migrate action key in service action (#21503) 2024-07-31 14:36:14 +02:00
Bram Kragten
da2e530601 Migrate state label badge to entity badge (#21507) 2024-07-31 14:34:51 +02:00
Bram Kragten
a88a7c5236 Change yaml config of UI service call actions (#21508) 2024-07-31 14:34:43 +02:00
karwosts
0a095c6f21 Add enable_millisecond to duration selector (#21498)
Add enable_milliseconds to duration selector
2024-07-31 12:34:57 +02:00
Bram Kragten
e8dd835eeb rename actions in UI editors to interactions (#21505) 2024-07-31 11:39:32 +02:00
karwosts
e05c66444a Picture Elements Visual editor (#19718)
* preliminary edits

* more functional prototype

* all types implemented

* mostly style and localization updates

* fix empty conditional case

* Move getConfigElement to elements themselves

* drop unneeded imports

* move struct validation to individual element editors

* description for unknown types

* Update src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

* Update src/panels/lovelace/editor/config-elements/elements/hui-state-icon-element-editor.ts

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

* Update src/panels/lovelace/editor/config-elements/elements/hui-service-button-element-editor.ts

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

* Update src/panels/lovelace/editor/config-elements/elements/hui-state-badge-element-editor.ts

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

* Update hui-picture-elements-card-row-editor.ts

* Fix merge mistake

* Update src/panels/lovelace/create-element/create-picture-element.ts

remove comment

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

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-07-31 11:16:57 +02:00
Paulus Schoutsen
d0e61ca31a Show OHF logo on loading and about screen (#21504)
* Show OHF logo on loading screen

* Also add OHF logo to about page
2024-07-31 09:12:11 +00:00
Simon Lamon
1f90a0c2e5 Add translation for unknown entity (#21484)
* unknown entity

* prettier
2024-07-31 11:05:14 +02:00
illuzn
dbd84901f8 Implement show_empty functionality for the markdown card (#21379)
* Implement show_empty functionality

* Implement show_empty functionality

* import fireEvent

* Order imports correctly

* Lint with prettier

* Unhide card when appropriate

* only run show code if card is hidden

* Commit coderabbit code cleanup

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* linting

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-07-31 08:58:45 +00:00
Simon Lamon
0aa25dbed9 Don't eat spaces while searching (#21479) 2024-07-31 10:58:39 +02:00
Steve Repsher
dd74a35d3f Use Brotli compression for modern build (#17906) 2024-07-31 10:57:52 +02:00
Simon Lamon
b1d8ec0fe4 Call a service: Split remaining service_data's into data and target (#21440)
* service_data into data and target

* Add other possible targets
2024-07-31 10:46:00 +02:00
Simon Lamon
cd4af674a3 Move some polymer paper-tabs to mwc-tabs (#21390)
* dev-tools

* entity card editor

* edit section dialog

* edit view

* Set the page from the router back in dev tools

* Remove changes to developer tools

* Prettier
2024-07-31 10:44:52 +02:00
renovate[bot]
7a6491a901 Update babel monorepo to v7.25.0 (#21497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-30 16:59:34 +02:00
Bram Kragten
8d20303d54 Show helpers setup in YAML also in the UI (#21500) 2024-07-30 15:13:05 +02:00
Bram Kragten
a5786b4761 Fix translation loading and manifest fetching for integration page (#21501) 2024-07-30 12:48:28 +02:00
Bram Kragten
4ade39543d Add created/modified to registry tables (#21494) 2024-07-30 11:18:50 +02:00
renovate[bot]
a85dda3365 Update dependency husky to v9.1.3 (#21443)
* Update dependency husky to v9.1.3

* Follow migration guide (options removed in 10.x, but deprecation notice right now)

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2024-07-30 07:44:43 +00:00
renovate[bot]
e578904ff7 Update dependency @material/web to v2 (#21489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-29 17:56:22 +02:00
renovate[bot]
09f0da1ead Update dependency @codemirror/view to v6.29.0 (#21488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-29 17:38:14 +02:00
Mike Degatano
2faa8fec17 Music Assistant repository is now built in (#21496) 2024-07-29 15:09:33 +00:00
renovate[bot]
945c4a66b1 Update dependency tar to v7.4.3 (#21491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-29 11:43:43 +02:00
renovate[bot]
5d794e7e88 Update dependency tar to v7.4.2 (#21482)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-28 08:02:34 +02:00
renovate[bot]
88a33bee14 Update dependency typescript to v5.5.4 (#21474) 2024-07-26 02:09:07 -04:00
renovate[bot]
35ec9af23f Update typescript-eslint monorepo to v7.17.0 (#21472) 2024-07-26 02:07:16 -04:00
renovate[bot]
dd331173ad Update dependency tar to v7.4.1 (#21469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-25 16:22:04 +00:00
renovate[bot]
885ccb84cb Update dependency eslint-plugin-unused-imports to v4.0.1 (#21468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-25 18:06:19 +02:00
schelv
0358fe5614 Clearable time selector (#18590)
* initial attempt at clearable time selector

* only show clear button if there is a value

* use null instead of undefined.

* leave event value undefined

* move clear button into input

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

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-07-25 18:05:59 +02:00
renovate[bot]
89d842c2a8 Update dependency @bundle-stats/plugin-webpack-filter to v4.13.4 (#21457)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-24 18:44:04 +02:00
Erik Montnemery
8911b55316 Adjust message about offline database migration (#21460)
Tweak message about offline database migration
2024-07-24 13:32:27 +02:00
Paul Bottein
6791e85625 Set 56px row height for new section button and title (#21456) 2024-07-23 13:13:35 +02:00
Erik Montnemery
79618ce114 Fix offline db migration support (#21452)
* Fix offline db migration support

* Add error handling
2024-07-23 11:47:47 +02:00
Quentame
87ba0e73dd picture cards: add person image support (#20593)
* picture cards: add person image support

* fix: person attributes typing

* review: apply comment from @coderabbitai

* fix lint:types

* review: put person domain in image_entity config

* add picture card compatibility & exemple in gallery

* fix lint

* Allow only image or person domains on image_entity editor config

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

* fix domain type

* gracefully use the default config.image if the person don't have an image

* gracefully use the default config.image if the person don't have an image (that works)

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2024-07-23 09:50:34 +02:00
renovate[bot]
567a2ea019 Update dependency @braintree/sanitize-url to v7.1.0 (#21451)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-23 09:23:46 +02:00
karwosts
811c34b489 Button to copy service response as json for templates (#21226)
* Button to copy service response as json for templates
2024-07-22 18:06:48 +02:00
Paul Bottein
dd22ae446a Don't show badge container if all badges are hidden (#21449) 2024-07-22 15:45:06 +02:00
Bram Kragten
d96ddf968c Show yaml setup integrations in the UI (#21447)
* Show yaml setup integrations in the UI

* Update en.json

* Move config entry logic to memoize function
2024-07-22 15:44:08 +02:00
karwosts
bbb64870a1 Leave climate humidity mode if entity stops supporting it (#21423)
* Leave climate humidity mode if entity stops supporting it

* check changedProps
2024-07-22 10:28:51 +02:00
dependabot[bot]
f05ddd3fcd Bump softprops/action-gh-release from 2.0.6 to 2.0.8 (#21446)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-22 10:05:35 +02:00
renovate[bot]
0612e25d9f Lock file maintenance (#21430)
* Lock file maintenance

* bump vaadin to 24.4.4

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Steve Repsher <steverep@users.noreply.github.com>
2024-07-20 13:32:20 -04:00
renovate[bot]
559ecf3eae Update dependency @lokalise/node-api to v12.7.0 (#21442)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-20 15:57:09 +00:00
renovate[bot]
ddb31a8342 Update dependency @codemirror/view to v6.28.6 (#21441)
* Update dependency @codemirror/view to v6.28.5

* Update dependency @codemirror/view to v6.28.6

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-20 15:56:25 +00:00
Onne
1c978b7cce Fix last point of line charts. And small other fixes to line charts. (#21235)
* Fixes last point of graphs.

Especially for items that don't often change state, the last state could
be essentially be ignored for quite a while, using the average of the
last series of entries instead.

Also for detailed graphs, the initial values could be off, because the
initial last value would be computed on bad data, resulting in NaN.

And no longer adds a double entry at end if graph has only one point.
2024-07-20 08:20:52 +00:00
renovate[bot]
9d9624c960 Update dependency @octokit/rest to v21.0.1 (#21439)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-20 08:08:00 +02:00
Paul Bottein
179245e1aa Add picker for badges (#21436) 2024-07-19 20:56:29 +02:00
Steve Repsher
345000a0e9 Redefine browser requirements for modern (latest) builds (#16506)
* Redefine browser requirements for modern (latest) builds

* Rename babel class properties plugin

* Fix only allowing latest Android

* Add browsers released in last year

* Use at or above for utilization (no change to browsers currently)

* Only use time query (no effective change)

* Add transform for private methods

* Fix some typos in browserslist config

Co-authored-by: Quentame <polletquentin74@me.com>

* bump browserslist-useragent-regex

* Add fallback feature detection for Array.prototype.findLast

---------

Co-authored-by: Quentame <polletquentin74@me.com>
2024-07-19 10:49:19 -04:00
Paul Bottein
d94d5f96c3 Fix state content options when opening (#21437) 2024-07-19 16:48:03 +02:00
Bram Kragten
0fa3538db5 Change abi of wheel to cp312 (#21435) 2024-07-19 15:50:50 +02:00
Jan-Philipp Benecke
1749725229 Remember hidden detailed energy device statistics (#21406)
* Remember hidden detailed energy device statistics

* Do not subscribe
2024-07-19 13:56:38 +02:00
Bram Kragten
83e94d32e3 Bumped version to 20240719.0 2024-07-19 12:49:36 +02:00
Bruno Pantaleão Gonçalves
7fed4e6b37 Tell mobile app to scan for Improv devices when 'Add Integration' tap (#21420) 2024-07-19 12:48:51 +02:00
leonardmgh
677cffd650 Fix halfopen websocket (#18934)
* initial suggestion

* Refactored implementation

* Updated implementation

* Cancel existing inverval
2024-07-19 10:07:55 +00:00
Paul Bottein
1faa1480e4 Remove badges configuration from view settings (#21433) 2024-07-19 10:03:18 +00:00
karwosts
8cb63ac36d Fix quirks in URL field in view editor (#21410) 2024-07-19 11:54:35 +02:00
Paul Bottein
6e29b77e94 Use layout property for panel view (#21418) 2024-07-19 11:53:46 +02:00
dougiteixeira
38e7b8c467 Allow use trigger/condition selector in config flows (#21431) 2024-07-19 09:39:06 +00:00
karwosts
d078807255 Show a fallback string when repair translation fails (#21400) 2024-07-19 11:05:03 +02:00
Steve Repsher
82d84de426 Enable service worker for legacy build (#21177) 2024-07-19 10:54:27 +02:00
Paul Bottein
729a12af0c Add new badges design with UI editor (#21401)
* Add new entity badge

* Improve badge render

* Add edit mode

* Add editor

* Increase height

* Use hui-badge

* Add editor

* Add drag and drop

* Fix editor translations

* Fix icon

* Fix inactive color

* Add state content

* Add default config

* Fix types

* Add custom badge support to editor

* Fix custom badges

* Add new badges to masonry view

* fix lint

* Fix inactive color

* Fix entity filter card

* Add display type option

* Add support for picture

* Improve focus style

* Add visibility editor

* Fix visibility

* Fix add/delete card inside section

* Fix translations

* Add error badge

* Rename classes

* Fix badge type

* Remove badges from section type

* Add missing types
2024-07-19 10:52:22 +02:00
Paul Bottein
ce43774b5f Fix tile card padding in vertical mode (#21409) 2024-07-18 21:03:21 +02:00
karwosts
e63d82d291 Fix persistent notification count on server restart (#21405)
* Fix persistent notification count on server restart
2024-07-18 20:53:17 +02:00
Stefan Agner
d997cfcef0 Add error handling to device delete (#21403)
Currently, if device delete fails, the frontend stays silent. The
user might get hints in the Core logs, but nothing shown on the
frontend.

This adds error handling similar to other places.
2024-07-18 20:50:46 +02:00
renovate[bot]
219f548261 Update typescript-eslint monorepo to v7.16.1 (#21426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 20:34:51 +02:00
karwosts
ee2b10912c Stop closed event propagating in automation editor elements (#21424) 2024-07-18 20:34:13 +02:00
renovate[bot]
e3b0630797 Update dependency @babel/core to v7.24.9 (#21422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-18 20:15:53 +02:00
Joost Lekkerkerker
30d0293a4b Add model_id to device info card (#21417)
* Add model_id to device info card

* Update src/panels/config/devices/device-detail/ha-device-info-card.ts

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>

* Add model_id to device info card

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-07-17 18:09:25 +02:00
Stefan Agner
7468ab985a Use Thread BR extended address in WS API calls (#21172)
* Use extended address in WS API calls

This follows the HA Core change and passes the extended address to the
OTBR WS API calls where necessary. It also follows the new OTBR info
format which potentially includes multiple OTBRs.

This allows to support multiple OTBR managed by a single system.

Note: There is one corner case when none of the OTBR is found via
discovery. In this case we offer to reset the OTBR. Currently we simply
offer this for the primary or first one found.

* Fix lint error

* Update src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts

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

* Update src/panels/config/integrations/integration-panels/thread/thread-config-panel.ts

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

* Use Record type to map OBTR data

* Add labels to Thread network operation icons

* Apply suggestions from code review

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

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-07-17 09:06:09 +02:00
renovate[bot]
ef3758da55 Update dependency prettier to v3.3.3 (#21408)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2024-07-16 18:32:13 +02:00
renovate[bot]
2a970b8416 Update fullcalendar monorepo to v6.1.15 (#21404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-16 17:54:29 +02:00
Paul Bottein
f70126eb62 Adjust row height in grid (#21311)
* Set row height to 56px

* Adjust padding and sizes

* Adjust margin

* Fix pointer-events

* Fix image size

* Clean code
2024-07-15 14:11:03 +02:00
Paul Bottein
f87296d978 Add state content component (#21370)
* Move state content into its own component for reusability

* Add entity state content selector

* Use live timer

* Rename live timer to remaining time and remove remaining attribute from state content list

* Move default in state content component

* Fix picker
2024-07-15 14:08:00 +02:00
renovate[bot]
2890d5c8cf Update vaadinWebComponents monorepo to v24.4.3 (#21399)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-15 09:45:05 +00:00
Simon Lamon
15a7ace278 Add on primary color to ha-slider (#21389)
add on primary
2024-07-15 11:38:48 +02:00
dependabot[bot]
71a2c40dd7 Bump home-assistant/wheels from 2024.01.0 to 2024.07.1 (#21398)
Bumps [home-assistant/wheels](https://github.com/home-assistant/wheels) from 2024.01.0 to 2024.07.1.
- [Release notes](https://github.com/home-assistant/wheels/releases)
- [Commits](https://github.com/home-assistant/wheels/compare/2024.01.0...2024.07.1)

---
updated-dependencies:
- dependency-name: home-assistant/wheels
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 09:00:16 +02:00
dependabot[bot]
a08bbcd1b4 Bump actions/setup-node from 4.0.2 to 4.0.3 (#21397)
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.0.2 to 4.0.3.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.0.2...v4.0.3)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-15 06:59:05 +00:00
renovate[bot]
5ec257fa18 Update dependency webpack to v5.93.0 (#21394)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-14 22:30:44 +02:00
renovate[bot]
9b01c0b2f0 Update dependency eslint-plugin-lit-a11y to v4.1.4 (#21392)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-14 17:21:05 +02:00
renovate[bot]
7dd860c539 Update babel monorepo to v7.24.8 (#21391)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-14 17:20:19 +02:00
karwosts
dbc2db2591 Make config flow previews more generic (#21382)
* Make config flow previews more generic
2024-07-14 17:01:21 +02:00
renovate[bot]
29aa57229c Update dependency gulp-zopfli-green to v6.0.2 (#21385) 2024-07-13 20:40:32 -04:00
renovate[bot]
8d74174be1 Update dependency glob to v11 (#21375)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-13 11:39:17 +02:00
Paul Bottein
a60242f042 Fix state color for locking and unlocking state (#21369) 2024-07-13 11:35:03 +02:00
renovate[bot]
db314522d7 Update dependency glob to v10.4.5 (#21374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-13 01:18:34 +02:00
renovate[bot]
7b66ee06eb Update vaadinWebComponents monorepo to v24.4.2 (#21376)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-13 01:15:19 +02:00
renovate[bot]
3f34dacec9 Update typescript-eslint monorepo to v7.16.0 (#21372)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-11 21:16:54 +02:00
Bram Kragten
277650e1c1 Update en.json 2024-07-11 12:26:23 +02:00
Paul Bottein
e59c04c685 Display live remaining time for timer on tile card (#21290)
Display timer by default on tile card
2024-07-11 11:09:14 +02:00
Bram Kragten
d9583582e6 Rename service call to action (#21362) 2024-07-11 09:00:15 +02:00
Bram Kragten
5ead5ed058 Fix unhiding default hidden column (#21358)
the first commit is the right commit...
2024-07-10 18:25:57 +02:00
renovate[bot]
f2993602f9 Update dependency superstruct to v2 (#21324)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-10 17:09:06 +02:00
Simon Lamon
11b490d145 Dev tools events: Add a clear events button (#21353)
Clear events
2024-07-10 15:01:56 +02:00
Bram Kragten
cd4937b539 Fix unhiding default hidden columns (#21354)
* Fix unhiding default hidden columns

* Update dialog-data-table-settings.ts
2024-07-10 14:58:26 +02:00
Paul Bottein
daa36788e0 Reload the card when changing the configuration in editor (#21351) 2024-07-10 12:39:50 +02:00
Paul Bottein
7edc4efc95 Bumped version to 20240710.0 2024-07-10 08:21:12 +02:00
Bram Kragten
144d278e4a Don't block interaction on disabled automation selector (#21347) 2024-07-09 17:03:40 +02:00
Paul Bottein
541453c245 Fix hidden conditional card in editor preview (#21344) 2024-07-09 15:37:05 +02:00
G Johansson
ca53af5c41 Add turn_on/off to FanEntity (#21339) 2024-07-09 10:26:42 +02:00
renovate[bot]
bd54eb40a7 Update dependency glob to v10.4.3 (#21343)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-09 09:59:36 +02:00
renovate[bot]
8ff2396a53 Update vaadinWebComponents monorepo to v24.4.1 (#21334)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-08 17:42:39 +02:00
Bram Kragten
c85e29f2bb Use localize func in table settings dialog (#21335) 2024-07-08 17:41:59 +02:00
karwosts
e7a749ef7d Add title to stack editor UI (#21328)
Add title to stack editor UI
2024-07-08 14:13:29 +02:00
dependabot[bot]
bef53aef57 Bump actions/upload-artifact from 4.3.3 to 4.3.4 (#21332) 2024-07-08 08:42:44 +02:00
G Johansson
877d0db1bb Add defrosting to HVAC actions for ClimateEntity (#21330) 2024-07-07 18:54:58 +02:00
renovate[bot]
aa49d6ef6b Update dependency marked to v13 (#21094)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-07 13:32:07 +02:00
renovate[bot]
d646ce4995 Update dependency @codemirror/view to v6.28.4 (#21321)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-06 17:42:38 +02:00
renovate[bot]
d6e6844f23 Update dependency @codemirror/autocomplete to v6.17.0 (#21320)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-06 17:23:49 +02:00
renovate[bot]
66e26e1a27 Update dependency @braintree/sanitize-url to v7.0.4 (#21314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-05 21:19:50 +02:00
Paul Bottein
b7473b58fb Fix sensor card display (#21313) 2024-07-05 21:01:19 +02:00
Bram Kragten
42b5fbec9b Bumped version to 20240705.0 2024-07-05 13:22:23 +02:00
Paul Bottein
f7072c247e Improve sensor card graph inside section grid (#21289) 2024-07-05 12:17:31 +02:00
Matthias de Baat
f995f19f06 Clarify remove vs. delete (#21297)
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-07-05 09:00:19 +02:00
renovate[bot]
7f50504908 Update dependency typescript to v5.5.3 (#21300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-04 19:21:19 +00:00
Paul Bottein
dc93a0ce54 Fix energy selection date card in grid layout (#21293)
Fix energy selection card in grid layout
2024-07-04 21:18:50 +02:00
Simon Lamon
3e4d06fca3 Change delete/remove wording in tag area to be consistent (#21299)
delete tags
2024-07-04 21:09:34 +02:00
Paul Bottein
050bef0564 Better handle auto height in card resize editor (#21260)
* Add auto-height option to resize editor

* Use min instead of max

* Remove auto height
2024-07-04 21:09:10 +02:00
renovate[bot]
1abebdae21 Update typescript-eslint monorepo to v7.15.0 (#21296)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-04 20:07:42 +02:00
renovate[bot]
b411ae0286 Update dependency @lokalise/node-api to v12.6.0 (#21291)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-04 19:18:37 +02:00
Paul Bottein
202bd148ef Fix iframe card overflow in vertical stack (#21282) 2024-07-04 12:29:17 +02:00
renovate[bot]
15589927c8 Update dependency @codemirror/view to v6.28.3 (#21277)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-04 11:46:55 +02:00
Bram Kragten
df7b5b08cf Allow custom localize function for datatable (#21270) 2024-07-04 11:46:10 +02:00
Steve Repsher
8b9fa9bc39 Clean up imports and unopened tag on device config page (#21274) 2024-07-04 10:01:01 +02:00
Bram Kragten
c07e1122e1 Tweak demo, add some translations, tweak media players (#21271)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2024-07-04 08:11:39 +02:00
Paul Bottein
1ceef7c3d3 Set min column size to 1 for vertical tile card (#21275) 2024-07-03 22:32:56 +02:00
karwosts
e332364ec0 Fix automation picker overflow menu for keyboard (#21048)
* Fix automation picker overflow menu for keyboard

* some updates from review

* typing

* no removeEventListener

* updates from review
2024-07-03 11:56:44 -04:00
Bram Kragten
97c4cf9391 Hide some things in demo (#21268) 2024-07-03 13:33:00 +00:00
Bram Kragten
522f66423b Fix demo map panel (#21265) 2024-07-03 13:26:19 +00:00
Bram Kragten
57e48e2561 Bumped version to 20240703.0 2024-07-03 14:23:31 +02:00
Bram Kragten
37af77dabe Make sure unhidden columns are put at the correct place (#21262) 2024-07-03 14:23:05 +02:00
Paulus Schoutsen
2b5fba4a30 Add support for capability attributes in demo (#21263) 2024-07-03 14:08:38 +02:00
Paulus Schoutsen
d833910796 Fix demo development inside a dev container (#21261) 2024-07-03 13:52:53 +02:00
Paul Bottein
81c796beb4 Fix area card background and improve grid support (#21259) 2024-07-03 13:16:55 +02:00
Paul Bottein
19ee150395 Remove layout options for media player (#21258) 2024-07-03 11:54:11 +02:00
Steve Repsher
82329833f5 Remove Safari 14.0 patch for delegatesFocus (#21247) 2024-07-03 06:28:57 +02:00
Bram Kragten
ab3b8593f4 Bumped version to 20240702.0 2024-07-02 21:21:25 +02:00
Paul Bottein
094203f0b4 Add min/max row/columns to resize card editor (#21244)
* Add min/max row/columns to resize card editor

* Add humidifier and thermostat card

* Removed unused condition

* Don't set max rows

* Add media card

* Add button card

* Use same rule if there is footer

* Don't show disabled cell

* Add min rows to sensor card

* Update sizes

* Update sizes

* Update sizes

* Add min rows to weather forecast card
2024-07-02 21:19:29 +02:00
Bram Kragten
9a2051a679 Change take control of blueprint UX (#21254)
* Change take control of blueprint UX

* Add margin to ha-alert

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-07-02 21:18:58 +02:00
Paul Bottein
09accb3071 Ignore aspect ratio in grid section (#21248)
* Ignore aspect ratio in grid section

* Feedback
2024-07-02 18:50:42 +02:00
Simon Lamon
7d432cd11a Fix logbook card display/reloading issues (#21253)
remove await logic
2024-07-02 18:44:36 +02:00
Paulus Schoutsen
7258e31348 Tweak first section in section demo (#21249)
* Tweak first section in section demo

* Allow automation entities be toggled
2024-07-02 17:11:16 +02:00
Steve Repsher
5707ca0016 Fix English only translations build (#21245) 2024-07-02 15:13:04 +02:00
Paulus Schoutsen
76abfea6ed Hide demo card when showing demo from frontpage (#21243)
* Hide demo card when showing demo from frontpage

* Store in constant on load

* reverse

* Remove filter

* move constnat

* Make Home Assistant title
2024-07-02 14:59:52 +02:00
Steve Repsher
d01377da3c Fix Webpack bundling of recorder worklet (#21239)
* Fix Webpack bundling of recorder worklet
2024-07-01 16:31:22 +02:00
Simon Lamon
e97be57e3b Application credentials: small improvements (#21233)
* small improvements

* Update ha-config-application-credentials.ts

* Update ha-config-application-credentials.ts

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-07-01 13:11:52 +02:00
Paulus Schoutsen
c71a051b6d Remove ga.js (#21242) 2024-07-01 12:11:26 +02:00
Paul Bottein
f41fab6968 Improve take control of automation/script wording (#21241) 2024-07-01 11:12:15 +02:00
renovate[bot]
bda61da666 Update dependency @material/web to v1.5.1 (#21224)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-30 09:34:43 +02:00
renovate[bot]
93445ced74 Update dependency eslint-plugin-lit-a11y to v4.1.3 (#21227)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-29 13:30:05 -04:00
Paul Bottein
b81314fc1f Bumped version to 20240628.0 2024-06-28 21:11:07 +02:00
Simon Lamon
9beb4c39ff Implement search in application credentials table (#21219)
Implement search functions
2024-06-28 21:10:24 +02:00
Simon Lamon
18a6f8d64d Add credential to user after creation (#21221) 2024-06-28 21:08:51 +02:00
Paul Bottein
beec720b9b Use display contents in horizontal stack only (#21217) 2024-06-28 20:56:01 +02:00
Paul Bottein
85865af0c3 Fix update config mecanism in hui-card (#21218) 2024-06-28 20:17:57 +02:00
Simon Lamon
d33cf4f199 Reload application credentials after single delete (#21216)
Reload application credentials after delete
2024-06-28 14:46:27 +02:00
Simon Lamon
4a1087c969 Add storage variables for application credentials config table (#21215)
Implement storage variables
2024-06-28 14:45:33 +02:00
Simon Lamon
cbc95a5e2d Fix icon header labels in Automations, Scene and Script tables (#21214)
Change icon header labels
2024-06-28 14:44:57 +02:00
Simon Lamon
dcd4c39978 Fix add application credential keyboard handling (#21205)
double primaryAction
2024-06-28 14:44:30 +02:00
renovate[bot]
11d832c2ea Update dependency @bundle-stats/plugin-webpack-filter to v4.13.3 (#21197) 2024-06-27 23:00:52 -04:00
renovate[bot]
3b15d26500 Update typescript-eslint monorepo to v7.14.1 (#21195)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-27 21:57:31 +02:00
renovate[bot]
df65038341 Update dependency mocha to v10.5.0 (#21194)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-27 14:55:43 -04:00
Paul Bottein
da2865d8bf Fix size of cards inside stack cards (#21190)
* Fix size of cards inside stack cards

* Apply style everywhere

* Fix stack

* Fix grid stack

* Set block only for square
2024-06-27 19:31:55 +02:00
Bram Kragten
fd64d17d88 Bumped version to 20240627.0 2024-06-27 19:30:48 +02:00
Paul Bottein
5273293cd6 Add last updated property to tile card state content (#21191) 2024-06-27 18:46:39 +02:00
Bram Kragten
49c42fc757 Add support for native QR code scanner (#21187) 2024-06-27 17:15:33 +02:00
Paul Bottein
7603fa3aa8 Don't set hass to undefined in lovelace cards. (#21189)
* Wait for hass and config before building the card

* Don't use setter

* Improve code readability

* Use hasupdated

* Rename build to load
2024-06-27 16:49:10 +02:00
karwosts
7aa005e0ce Fix device integration filter for entityless devices (#21136)
* Fix device integration filter for entityless devices

* code review
2024-06-27 12:40:09 +02:00
renovate[bot]
b2a55dd737 Update dependency @types/chromecast-caf-receiver to v6.0.16 (#21183)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-27 06:34:34 +02:00
Paul Bottein
ccad1afcf0 Bumped version to 20240626.2 2024-06-26 23:18:57 +02:00
Paul Bottein
231c923776 Bumped version to 20240626.1 2024-06-26 23:08:56 +02:00
Paul Bottein
b08b67179e Fix hass object in nested hui-card (#21178) 2024-06-26 21:05:47 +00:00
Paul Bottein
d9f1b06199 Fix undefined value in search (#21175) 2024-06-26 20:37:07 +02:00
Bram Kragten
4b7526c8a3 Merge branch 'master' into dev 2024-06-26 12:38:37 +02:00
Bram Kragten
6267ab5ed3 Bumped version to 20240626.0 2024-06-26 12:36:57 +02:00
Paul Bottein
ae94231800 Use resize controller for weather card (#19806)
* Use resize controller for weather card

* Don't use observe
2024-06-26 12:35:49 +02:00
Bram Kragten
7d28f3f585 Allow to hide and sort columns in data tables (#21168)
* Allow to hide and sort columns in data tables

* fix unused

* store
2024-06-26 11:51:32 +02:00
Bram Kragten
adea384f40 Update logo_x.svg 2024-06-26 11:50:18 +02:00
Bram Kragten
55b66250f4 Take convert of blueprint automation and script (#21151)
* substituteBlueprint

* WIP ux

* Simplify feature

* Add take control to scripts

* Add translations and catch error

* Clean import

---------

Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-06-26 11:24:47 +02:00
Philip Allgaier
182111912c Rename "Twitter" to "X (formerly Twitter)" (#20694)
* Rename "Twitter" to "X (formerly Twitter)"

* Add translations

* Show x logo on light mode

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2024-06-26 11:13:52 +02:00
renovate[bot]
8a0924bf1f Update dependency typescript to v5.5.2 (#21144)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-26 10:50:25 +02:00
Simon Lamon
94dc9308ea Replace octal escape sequences (#21156)
* replace octal escape characters

* Add disable eslint
2024-06-26 10:19:52 +02:00
Paul Bottein
f42a9ac070 Ignore diacritics (accents) in search (#21150)
* Ignore diacritics in search

* Rename variable
2024-06-26 10:07:31 +02:00
Paul Bottein
1acada625f Improve user and person dialogs (#21162)
* Improve user dialog

* Update person dialog

* Improve add user dialog

* Fix secondary option
2024-06-26 10:03:43 +02:00
Paul Bottein
128dbbcfef Better resizing support for thermostat card (#21120)
* Better resizing support for thermostat card

* Use resize controller

* Fix typings

* Don't use query

* Use render to set style
2024-06-26 10:03:10 +02:00
Bram Kragten
57d8544dbf Add menu with remove option to application credentials (#21139) 2024-06-26 09:47:46 +02:00
Bram Kragten
76daa2bb7f Add support for sections in filters (#21157) 2024-06-25 20:01:10 +02:00
renovate[bot]
9cbb51549b Update dependency @types/mocha to v10.0.7 (#21163)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 12:42:54 +00:00
Paul Bottein
bd1ede4145 Fix grid size picker size (#21161) 2024-06-25 12:24:04 +02:00
Paul Bottein
321a085c0e Resize card editor (#21115) 2024-06-24 22:10:31 +02:00
Bram Kragten
6a3041988a Allow to change username (#21152) 2024-06-24 18:51:43 +02:00
renovate[bot]
23fcdf876c Update dependency @codemirror/view to v6.28.2 (#21154)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-24 12:24:01 -04:00
Bram Kragten
00f325e961 Support expandable in initial form data (#21153) 2024-06-24 17:30:43 +02:00
renovate[bot]
d00b3cfc61 Update Yarn to v4.3.1 (#21149)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-24 13:33:19 +02:00
karwosts
4cc9e74ea8 Fix 'Move to view' operation (#21142) 2024-06-24 11:56:06 +02:00
dependabot[bot]
a56b9a96ce Bump softprops/action-gh-release from 2.0.5 to 2.0.6 (#21148)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-24 08:37:48 +02:00
renovate[bot]
d4b5f4bc14 Update dependency @octokit/rest to v21 (#21146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-23 23:22:08 -04:00
renovate[bot]
cf1523ee73 Update dependency @types/chromecast-caf-receiver to v6.0.15 (#21138)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-23 12:54:15 +02:00
renovate[bot]
f5d571ca84 Update dependency webpack to v5.92.1 (#21134)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 20:54:24 +02:00
karwosts
362e92f313 Add some weather attribute icons and units (#21133) 2024-06-22 15:42:26 +02:00
Jan-Philipp Benecke
5ddf72b973 Add preview to Threshold config & option flow (#19845)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-06-22 13:32:40 +02:00
renovate[bot]
6e78c28f51 Update dependency @codemirror/autocomplete to v6.16.3 (#21130)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 09:10:52 +02:00
renovate[bot]
772f0bb669 Update dependency tar to v7.4.0 (#21129)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 08:51:43 +02:00
renovate[bot]
846c2a848f Update dependency glob to v10.4.2 (#21127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-22 08:51:08 +02:00
renovate[bot]
8495757005 Update dependency @braintree/sanitize-url to v7.0.3 (#21126) 2024-06-21 22:09:25 -04:00
karwosts
9960d38b91 Offer to delete no-longer-recorded statistics (#21119) 2024-06-21 11:18:24 +02:00
Simon Lamon
d3222f8bb0 Various fixes in dialogs (#20935)
* allow escape and scrim action for repair dialogs

* improve delete entity dialogs

* reiterate refresh token dialog wordings (kept refresh token for now)

* improve device delete dialogs

* Improve deletable text and invalidation
2024-06-21 11:15:01 +02:00
Simon Lamon
2e5cce5409 Replace paper-listbox in cast frontend (#19954)
* hc-cast

* Update cast/src/launcher/layout/hc-cast.ts

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

* Update cast/src/launcher/layout/hc-cast.ts

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

* Update cast/src/launcher/layout/hc-cast.ts

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

* Fixes

---------

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2024-06-21 11:13:13 +02:00
Paul Bottein
f78946447f Show card preview inside section in card editor (#21065)
* Show card inside section in card editor

* Replace edit mode by preview

* Add backward compatibility for custom cards

* Re-order props
2024-06-21 11:12:18 +02:00
Jay Turner
eb0579ddc5 Use EnergyCardBaseConfig where appropriate (#20896)
* Use EnergyCardBaseConfig where appropriate

* Update type key

* Rename class

* Run prettier
2024-06-21 11:11:41 +02:00
Steve Repsher
686424fc70 Add CoreJS polyfills to modern build (#20676) 2024-06-21 11:07:39 +02:00
renovate[bot]
039e9b40bd Update typescript-eslint monorepo to v7.13.1 (#21121)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-20 14:41:07 -04:00
karwosts
8272bef890 Sort filter-domains on translated name (#21116) 2024-06-19 17:31:04 +02:00
Kevin Jahrens
62528b2413 Fix back paths (#21112)
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
2024-06-19 07:08:14 +00:00
renovate[bot]
fa24f529e0 Lock file maintenance (#21114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 08:58:38 +02:00
dependabot[bot]
43a54f6cda Bump ws from 7.5.9 to 7.5.10 (#21111) 2024-06-18 20:56:53 -04:00
Paulus Schoutsen
9c153bbd58 Split out service entities (#21076)
* Hide notify entiites from generated dashboard

* Split out service entities on device info page

* Update src/panels/lovelace/common/generate-lovelace-config.ts

* Split service -> notify/assist
2024-06-18 08:58:45 +02:00
Bram Kragten
27afe9ecb7 Wrap code editor for template selector (#21104)
* Wrap code editor for template selector
2024-06-17 19:15:55 +00:00
dependabot[bot]
72f989e2bd Bump actions/checkout from 4.1.6 to 4.1.7 (#21102) 2024-06-17 10:19:48 +02:00
renovate[bot]
a6ef46565f Update dependency @codemirror/view to v6.28.1 (#21099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-16 09:43:11 +02:00
renovate[bot]
a35ac09688 Update dependency lint-staged to v15.2.7 (#21098)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 20:55:19 +02:00
Douwe
27024135ea Update hui-grid-section.ts (#21088)
Add variable to align title
2024-06-15 18:21:42 +02:00
Franck Nijhof
8759ed740a Make the radius of the home zone configurable (#21096) 2024-06-15 14:41:09 +02:00
Franck Nijhof
bb3e8ae33d Update home-assistant-js-websocket to 9.4.0 (#21097) 2024-06-15 08:03:55 -04:00
renovate[bot]
b5b60c9bf0 Update vaadinWebComponents monorepo to v24.4.0 (#21089)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 00:12:37 -04:00
renovate[bot]
3b6a2cf7d8 Update dependency @codemirror/view to v6.28.0 (#21081)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 00:09:09 -04:00
renovate[bot]
7e10e14102 Update dependency lint-staged to v15.2.6 (#21092)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 21:21:25 +02:00
renovate[bot]
a580abab4a Update dependency webpack to v5.92.0 (#21090)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 20:21:11 +02:00
renovate[bot]
11523c08c4 Update Yarn to v4.3.0 (#21084) 2024-06-14 12:29:02 -04:00
renovate[bot]
7a8988528b Update dependency prettier to v3.3.2 (#21087)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 08:42:55 +02:00
renovate[bot]
2a6380f083 Update typescript-eslint monorepo to v7.13.0 (#21082)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 08:29:55 +02:00
Paul Bottein
f43319a3ae Bumped version to 20240610.1 2024-06-13 20:49:43 +02:00
Paul Bottein
e8eefaf1d3 Fix current mode not selected in card feature (#21063) 2024-06-13 20:46:50 +02:00
Steve Repsher
06d82a4925 Do not inject Intl polyfill into ecma402-abstract package (#21074) 2024-06-13 20:45:50 +02:00
Paulus Schoutsen
4991d52fcc Hide notify entities from generated dashboard (#21075)
Hide notify entiites from generated dashboard
2024-06-13 20:45:35 +02:00
Simon Lamon
0b391eafcf Fix diagnostic download not downloading (#21078) 2024-06-13 20:45:12 +02:00
Paul Bottein
0bb34830f8 Fix selected state for selected config entries (#21079) 2024-06-13 20:45:02 +02:00
Paul Bottein
29881c8bb4 Fix selected state for selected config entries (#21079) 2024-06-13 18:26:45 +02:00
Simon Lamon
56254ddf03 Fix diagnostic download not downloading (#21078) 2024-06-13 13:31:30 +02:00
Paulus Schoutsen
007ba70641 Hide notify entities from generated dashboard (#21075)
Hide notify entiites from generated dashboard
2024-06-13 08:04:27 +02:00
Steve Repsher
3e1227b064 Do not inject Intl polyfill into ecma402-abstract package (#21074) 2024-06-12 14:45:57 -04:00
Matthias Alphart
067e179f26 Translation support for device automation extra fields (#20567)
* Translation support for device trigger extra fields

* Prefer component translation over default

* Move device trigger extra_fields translations to backend

* move translations for extra_fields of conditions and actions too
2024-06-12 14:09:50 +02:00
AlCalzone
9a3f7df25e Z-Wave: Prevent closing the Add Device dialog when user input is required (#20999)
* prevent closing the Z-Wave Add node dialog when user input is required

* ask user for confirmation before leaving page during bootstrapping

* fix: no non-null assertion
2024-06-12 13:48:40 +02:00
Paul Bottein
c7b4e8f37c Fix current mode not selected in card feature (#21063) 2024-06-12 13:40:44 +02:00
Paul Bottein
bfa8b886ab Make update actions sticky on more info (#21053) 2024-06-12 13:39:04 +02:00
Paul Bottein
433c00b73a Move card loading logic into hui-card (#21018)
* Move card rebuild to hui-card

* Use hui card in stack card

* add once to event

* Do not use state

* Use hui card in conditional card

* Use editMode instead of lovelace in hui card

* Fix edit mode

* Use hui-card in card dialog and panel todo

* Fix edit mode

* Fix types

* Migrate entity filter card

* Update demo card

* Fix UI view

* Allow edit mode attribute

* Remove unused condition

* Remove unused section preview code

* Remove useless check for config
2024-06-12 13:38:21 +02:00
Stefan Agner
a497f42f73 Move send credentials to phone to main Thread configuration panel (#21066)
Move send credentials to phone to main view

Currently the button is hidden in the more info dialog, and even there
it seems that it is currently not rendered correctly.

This moves the button to the main view, make it more obvious while
still keeping it out of the way if the feature is not applicable.
2024-06-12 13:35:29 +02:00
Stefan Agner
165723cb5b Clarify Thread credentials transfer direction (#21067)
"Import credentials" on a phone can be missunderstood as importing
credentials from Home Assistant to the phone, but this is not what
this command is doing.

Use "Send credentials to Home Assistant" to make it clear what the
direction of the transfer is.
2024-06-12 13:32:51 +02:00
Bruno Pantaleão Gonçalves
42b5fa696a Fix "canImportKeychain" boolean for thread panel (#21062) 2024-06-11 10:04:13 +02:00
renovate[bot]
59062d96a8 Update dependency @rollup/plugin-commonjs to v26 (#21037)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-10 21:56:01 +02:00
Paulus Schoutsen
d36bbfe07d Hide hidden updates from sidebar/settings (#21058) 2024-06-10 15:52:24 -04:00
Bram Kragten
4a8bb5034d Merge branch 'dev' 2024-06-10 19:50:24 +02:00
Bram Kragten
0d489213a4 Bumped version to 20240610.0 2024-06-10 19:50:10 +02:00
koostamas
c54acc9369 Fix automation describeCondition for number state type (#21052)
When using a number state condition in an automation, the UI used an incorrect evaluation when trying to describe the condition which made the label default to the default value.
To fix this, I just changed the evaluation to check directly for `undefined` value.
2024-06-10 19:19:26 +02:00
Simon Lamon
562bc084f0 Revert fullcalendar back to v6.1.11 (#21039)
* Drop fullcalendar back to v6.1.11

* Add resolution
2024-06-10 09:23:53 +02:00
karwosts
6fce2f35a5 Add a title to triggered dialog (#21046) 2024-06-09 21:33:58 +02:00
karwosts
f4e24bed2e Fix cancel button in section edit (#21045) 2024-06-09 12:54:38 +00:00
renovate[bot]
09969c0e2d Update babel monorepo to v7.24.7 (#21035)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 19:52:09 +02:00
renovate[bot]
4b0181774b Update CodeMirror (#21022)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 19:51:08 +02:00
renovate[bot]
272db5e9e8 Update dependency @rollup/plugin-replace to v5.0.7 (#21036)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 19:50:43 +02:00
renovate[bot]
9ae3a824d9 Update dependency prettier to v3.3.1 (#21033)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 12:29:12 +02:00
renovate[bot]
9db55c9391 Update dependency @lit-labs/virtualizer to v2.0.13 (#21029)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 09:42:52 +02:00
renovate[bot]
59697127c0 Update dependency @rollup/plugin-replace to v5.0.6 (#21031)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-08 08:50:00 +02:00
renovate[bot]
565600e945 Update dependency @codemirror/view to v6.26.4 (#21021)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-07 16:08:38 +02:00
karwosts
721eebf367 Add floor selector to script fields (#21016) 2024-06-07 13:02:58 +02:00
renovate[bot]
f5ae842167 Update typescript-eslint monorepo to v7.12.0 (#21014) 2024-06-06 20:35:13 -04:00
renovate[bot]
3575734ed0 Update dependency @codemirror/language to v6.10.2 (#21008) 2024-06-06 20:34:16 -04:00
karwosts
4e8de1f64d Todo button menu: add stop propagation (#20996) 2024-06-05 11:43:21 +02:00
Bram Kragten
a8366c6416 Merge branch 'dev' 2024-06-05 11:42:10 +02:00
Bram Kragten
cd73b8ac29 Bumped version to 20240605.0 2024-06-05 11:41:51 +02:00
Paul Bottein
74eca6b1f5 Increase check update timeout to 15s (#20998) 2024-06-05 10:13:28 +02:00
Paulus Schoutsen
9ef0bd6e46 Fix Assist styling (#20997) 2024-06-05 09:53:40 +02:00
renovate[bot]
53eb7f771f Update dependency prettier to v3.3.0 (#20992)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-04 20:27:20 +02:00
469 changed files with 18100 additions and 9064 deletions

View File

@@ -1,28 +1,25 @@
[modern] [modern]
# Support for dynamic import is the main litmus test for serving modern builds. # Modern builds target recent browsers supporting the latest features to minimize transpilation, polyfills, etc.
# Although officially a ES2020 feature, browsers implemented it early, so this # It is served to browsers meeting the following requirements:
# enables all of ES2017 and some features in ES2018. # - released in the last year + current alpha/beta versions
supports es6-module-dynamic-import # - Firefox extended support release (ESR)
# - with global utilization at or above 0.5%
# Exclude Safari 11-12 because of a bug in tagged template literals # - must support dynamic import of ES modules
# https://bugs.webkit.org/show_bug.cgi?id=190756 # - exclude browsers no longer being maintained
# Note: Dropping version 11 also enables several more ES2018 features # - exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data
not Safari < 13 unreleased versions
not iOS < 13 last 1 year
Firefox ESR
# Exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data >= 0.5% and supports es6-module-dynamic-import
# Babel ignores these automatically, but we need here for Webpack to output ESM with dynamic imports not dead
not KaiOS > 0 not KaiOS > 0
not QQAndroid > 0 not QQAndroid > 0
not UCAndroid > 0 not UCAndroid > 0
# Exclude unsupported browsers
not dead
[legacy] [legacy]
# Legacy builds are served when modern requirements are not met and support browsers: # Legacy builds are served when modern requirements are not met and support browsers:
# - released in the last 7 years + current alpha/beta versionss # - released in the last 7 years + current alpha/beta versionss
# - with global utilization above 0.05% # - with global utilization at or above 0.05%
# The lattermost query ensures that support for popular old browsers is not dropped too early # The lattermost query ensures that support for popular old browsers is not dropped too early
# (e.g. IE 11, Android 4.4, or Samsung 4). # (e.g. IE 11, Android 4.4, or Samsung 4).
# #
@@ -36,4 +33,10 @@ not dead
# As of May 2023, only web sockets must be added to the query. # As of May 2023, only web sockets must be added to the query.
unreleased versions unreleased versions
last 7 years last 7 years
> 0.05% and supports websockets >= 0.05% and supports websockets
[legacy-sw]
# Same as legacy plus supports service workers
unreleased versions
last 7 years
>= 0.05% and supports websockets and supports serviceworkers

View File

@@ -8,6 +8,7 @@
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev", "postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
"postStartCommand": "script/bootstrap", "postStartCommand": "script/bootstrap",
"containerEnv": { "containerEnv": {
"DEV_CONTAINER": "1",
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
}, },
"customizations": { "customizations": {

View File

@@ -21,12 +21,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
with: with:
ref: dev ref: dev
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -57,12 +57,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
with: with:
ref: master ref: master
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn

View File

@@ -24,9 +24,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -58,9 +58,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -76,9 +76,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -89,7 +89,7 @@ jobs:
env: env:
IS_TEST: "true" IS_TEST: "true"
- name: Upload bundle stats - name: Upload bundle stats
uses: actions/upload-artifact@v4.3.3 uses: actions/upload-artifact@v4.3.6
with: with:
name: frontend-bundle-stats name: frontend-bundle-stats
path: build/stats/*.json path: build/stats/*.json
@@ -100,9 +100,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -113,7 +113,7 @@ jobs:
env: env:
IS_TEST: "true" IS_TEST: "true"
- name: Upload bundle stats - name: Upload bundle stats
uses: actions/upload-artifact@v4.3.3 uses: actions/upload-artifact@v4.3.6
with: with:
name: supervisor-bundle-stats name: supervisor-bundle-stats
path: build/stats/*.json path: build/stats/*.json

View File

@@ -23,7 +23,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
with: with:
# We must fetch at least the immediate parents so that if this is # We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head. # a pull request then we can checkout the head.

View File

@@ -22,12 +22,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
with: with:
ref: dev ref: dev
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -58,12 +58,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
with: with:
ref: master ref: master
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn

View File

@@ -16,10 +16,10 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn

View File

@@ -21,10 +21,10 @@ jobs:
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview') if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn

View File

@@ -20,7 +20,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5 uses: actions/setup-python@v5
@@ -28,7 +28,7 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -57,14 +57,14 @@ jobs:
run: tar -czvf translations.tar.gz translations run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts - name: Upload build artifacts
uses: actions/upload-artifact@v4.3.3 uses: actions/upload-artifact@v4.3.6
with: with:
name: wheels name: wheels
path: dist/home_assistant_frontend*.whl path: dist/home_assistant_frontend*.whl
if-no-files-found: error if-no-files-found: error
- name: Upload translations - name: Upload translations
uses: actions/upload-artifact@v4.3.3 uses: actions/upload-artifact@v4.3.6
with: with:
name: translations name: translations
path: translations.tar.gz path: translations.tar.gz

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send bundle stats and build information to RelativeCI - name: Send bundle stats and build information to RelativeCI
uses: relative-ci/agent-action@v2.1.11 uses: relative-ci/agent-action@v2.1.12
with: with:
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }} key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
token: ${{ github.token }} token: ${{ github.token }}

View File

@@ -23,7 +23,7 @@ jobs:
contents: write # Required to upload release assets contents: write # Required to upload release assets
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Verify version - name: Verify version
uses: home-assistant/actions/helpers/verify-version@master uses: home-assistant/actions/helpers/verify-version@master
@@ -34,7 +34,7 @@ jobs:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4.0.2 uses: actions/setup-node@v4.0.3
with: with:
node-version-file: ".nvmrc" node-version-file: ".nvmrc"
cache: yarn cache: yarn
@@ -55,7 +55,7 @@ jobs:
script/release script/release
- name: Upload release assets - name: Upload release assets
uses: softprops/action-gh-release@v2.0.5 uses: softprops/action-gh-release@v2.0.8
with: with:
files: | files: |
dist/*.whl dist/*.whl
@@ -74,9 +74,9 @@ jobs:
echo "home-assistant-frontend==$version" > ./requirements.txt echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2024.01.0 uses: home-assistant/wheels@2024.07.1
with: with:
abi: cp311 abi: cp312
tag: musllinux_1_2 tag: musllinux_1_2
arch: amd64 arch: amd64
wheels-key: ${{ secrets.WHEELS_KEY }} wheels-key: ${{ secrets.WHEELS_KEY }}

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.7
- name: Upload Translations - name: Upload Translations
run: | run: |

View File

@@ -1,4 +1 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn run lint-staged --relative --shell "/bin/bash" yarn run lint-staged --relative --shell "/bin/bash"

View File

@@ -0,0 +1,55 @@
diff --git a/build/inject-manifest.js b/build/inject-manifest.js
index 60e3d2bb51c11a19fbbedbad65e101082ec41c36..fed6026630f43f86e25446383982cf6fb694313b 100644
--- a/build/inject-manifest.js
+++ b/build/inject-manifest.js
@@ -104,7 +104,7 @@ async function injectManifest(config) {
replaceString: manifestString,
searchString: options.injectionPoint,
});
- filesToWrite[options.swDest] = source;
+ filesToWrite[options.swDest] = source.replace(url, encodeURI(upath_1.default.basename(destPath)));
filesToWrite[destPath] = map;
}
else {
diff --git a/build/lib/translate-url-to-sourcemap-paths.js b/build/lib/translate-url-to-sourcemap-paths.js
index 3220c5474eeac6e8a56ca9b2ac2bd9be48529e43..5f003879a904d4840529a42dd056d288fd213771 100644
--- a/build/lib/translate-url-to-sourcemap-paths.js
+++ b/build/lib/translate-url-to-sourcemap-paths.js
@@ -22,7 +22,7 @@ function translateURLToSourcemapPaths(url, swSrc, swDest) {
const possibleSrcPath = upath_1.default.resolve(upath_1.default.dirname(swSrc), url);
if (fs_extra_1.default.existsSync(possibleSrcPath)) {
srcPath = possibleSrcPath;
- destPath = upath_1.default.resolve(upath_1.default.dirname(swDest), url);
+ destPath = `${swDest}.map`;
}
else {
warning = `${errors_1.errors['cant-find-sourcemap']} ${possibleSrcPath}`;
diff --git a/src/inject-manifest.ts b/src/inject-manifest.ts
index 8795ddcaa77aea7b0356417e4bc4b19e2b3f860c..fcdc68342d9ac53936c9ed40a9ccfc2f5070cad3 100644
--- a/src/inject-manifest.ts
+++ b/src/inject-manifest.ts
@@ -129,7 +129,10 @@ export async function injectManifest(
searchString: options.injectionPoint!,
});
- filesToWrite[options.swDest] = source;
+ filesToWrite[options.swDest] = source.replace(
+ url!,
+ encodeURI(upath.basename(destPath)),
+ );
filesToWrite[destPath] = map;
} else {
// If there's no sourcemap associated with swSrc, a simple string
diff --git a/src/lib/translate-url-to-sourcemap-paths.ts b/src/lib/translate-url-to-sourcemap-paths.ts
index 072eac40d4ef5d095a01cb7f7e392a9e034853bd..f0bbe69e88ef3a415de18a7e9cb264daea273d71 100644
--- a/src/lib/translate-url-to-sourcemap-paths.ts
+++ b/src/lib/translate-url-to-sourcemap-paths.ts
@@ -28,7 +28,7 @@ export function translateURLToSourcemapPaths(
const possibleSrcPath = upath.resolve(upath.dirname(swSrc), url);
if (fse.existsSync(possibleSrcPath)) {
srcPath = possibleSrcPath;
- destPath = upath.resolve(upath.dirname(swDest), url);
+ destPath = `${swDest}.map`;
} else {
warning = `${errors['cant-find-sourcemap']} ${possibleSrcPath}`;
}

File diff suppressed because one or more lines are too long

925
.yarn/releases/yarn-4.4.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -6,4 +6,4 @@ enableGlobalCache: false
nodeLinker: node-modules nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.2.2.cjs yarnPath: .yarn/releases/yarn-4.4.0.cjs

View File

@@ -47,7 +47,7 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
__DEV__: !isProdBuild, __DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), __BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"),
__VERSION__: JSON.stringify(env.version()), __VERSION__: JSON.stringify(env.version()),
__DEMO__: false, __DEMO__: false,
__SUPERVISOR__: false, __SUPERVISOR__: false,
@@ -79,7 +79,12 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
sourceMap: !isTestBuild, sourceMap: !isTestBuild,
}); });
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ module.exports.babelOptions = ({
latestBuild,
isProdBuild,
isTestBuild,
sw,
}) => ({
babelrc: false, babelrc: false,
compact: false, compact: false,
assumptions: { assumptions: {
@@ -87,13 +92,13 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
setPublicClassFields: true, setPublicClassFields: true,
setSpreadProperties: true, setSpreadProperties: true,
}, },
browserslistEnv: latestBuild ? "modern" : "legacy", browserslistEnv: latestBuild ? "modern" : `legacy${sw ? "-sw" : ""}`,
presets: [ presets: [
[ [
"@babel/preset-env", "@babel/preset-env",
{ {
useBuiltIns: latestBuild ? false : "usage", useBuiltIns: "usage",
corejs: latestBuild ? false : dependencies["core-js"], corejs: dependencies["core-js"],
bugfixes: true, bugfixes: true,
shippedProposals: true, shippedProposals: true,
}, },
@@ -135,8 +140,14 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
"@babel/plugin-transform-runtime", "@babel/plugin-transform-runtime",
{ version: dependencies["@babel/runtime"] }, { version: dependencies["@babel/runtime"] },
], ],
// Support some proposals still in TC39 process // Transpile decorators (still in TC39 process)
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], // Modern browsers support class fields and private methods, but transform is required with the older decorator version dictated by Lit
[
"@babel/plugin-proposal-decorators",
{ version: "2018-09", decoratorsBeforeExport: true },
],
"@babel/plugin-transform-class-properties",
"@babel/plugin-transform-private-methods",
].filter(Boolean), ].filter(Boolean),
exclude: [ exclude: [
// \\ for Windows, / for Mac OS and Linux // \\ for Windows, / for Mac OS and Linux
@@ -157,7 +168,7 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
exclude: [ exclude: [
path.join(paths.polymer_dir, "src/resources/polyfills"), path.join(paths.polymer_dir, "src/resources/polyfills"),
...[ ...[
"@formatjs/intl-\\w+", "@formatjs/(?:ecma402-abstract|intl-\\w+)",
"@lit-labs/virtualizer/polyfills", "@lit-labs/virtualizer/polyfills",
"@webcomponents/scoped-custom-element-registry", "@webcomponents/scoped-custom-element-registry",
"element-internals-polyfill", "element-internals-polyfill",
@@ -215,7 +226,13 @@ module.exports.config = {
return { return {
name: "frontend" + nameSuffix(latestBuild), name: "frontend" + nameSuffix(latestBuild),
entry: { entry: {
service_worker: "./src/entrypoints/service_worker.ts", "service-worker":
!env.useRollup() && !latestBuild
? {
import: "./src/entrypoints/service-worker.ts",
layer: "sw",
}
: "./src/entrypoints/service-worker.ts",
app: "./src/entrypoints/app.ts", app: "./src/entrypoints/app.ts",
authorize: "./src/entrypoints/authorize.ts", authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts", onboarding: "./src/entrypoints/onboarding.ts",

View File

@@ -32,4 +32,7 @@ module.exports = {
} }
return version[1]; return version[1];
}, },
isDevContainer() {
return process.env.DEV_CONTAINER === "1";
},
}; };

View File

@@ -1,19 +1,54 @@
// Tasks to compress // Tasks to compress
import { constants } from "node:zlib";
import gulp from "gulp"; import gulp from "gulp";
import brotli from "gulp-brotli";
import zopfli from "gulp-zopfli-green"; import zopfli from "gulp-zopfli-green";
import paths from "../paths.cjs"; import paths from "../paths.cjs";
const filesGlob = "*.{js,json,css,svg,xml}";
const brotliOptions = {
skipLarger: true,
params: {
[constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,
},
};
const zopfliOptions = { threshold: 150 }; const zopfliOptions = { threshold: 150 };
const compressDist = (rootDir) => const compressDistBrotli = (rootDir, modernDir) =>
gulp gulp
.src([ .src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
`${rootDir}/**/*.{js,json,css,svg,xml}`, base: rootDir,
`${rootDir}/{authorize,onboarding}.html`, })
]) .pipe(brotli(brotliOptions))
.pipe(gulp.dest(rootDir));
const compressDistZopfli = (rootDir, modernDir) =>
gulp
.src(
[
`${rootDir}/**/${filesGlob}`,
`!${modernDir}/**/${filesGlob}`,
`!${rootDir}/{sw-modern,service_worker}.js`,
`${rootDir}/{authorize,onboarding}.html`,
],
{ base: rootDir }
)
.pipe(zopfli(zopfliOptions)) .pipe(zopfli(zopfliOptions))
.pipe(gulp.dest(rootDir)); .pipe(gulp.dest(rootDir));
gulp.task("compress-app", () => compressDist(paths.app_output_root)); const compressAppBrotli = () =>
gulp.task("compress-hassio", () => compressDist(paths.hassio_output_root)); compressDistBrotli(paths.app_output_root, paths.app_output_latest);
const compressHassioBrotli = () =>
compressDistBrotli(paths.hassio_output_root, paths.hassio_output_latest);
const compressAppZopfli = () =>
compressDistZopfli(paths.app_output_root, paths.app_output_latest);
const compressHassioZopfli = () =>
compressDistZopfli(paths.hassio_output_root, paths.hassio_output_latest);
gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli));
gulp.task(
"compress-hassio",
gulp.parallel(compressHassioBrotli, compressHassioZopfli)
);

View File

@@ -1,28 +1,76 @@
// Tasks to generate entry HTML // Tasks to generate entry HTML
import {
applyVersionsToRegexes,
compileRegex,
getPreUserAgentRegexes,
} from "browserslist-useragent-regexp";
import fs from "fs-extra"; import fs from "fs-extra";
import gulp from "gulp"; import gulp from "gulp";
import { minify } from "html-minifier-terser"; import { minify } from "html-minifier-terser";
import template from "lodash.template"; import template from "lodash.template";
import path from "path"; import { dirname, extname, resolve } from "node:path";
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs"; import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
import env from "../env.cjs"; import env from "../env.cjs";
import paths from "../paths.cjs"; import paths from "../paths.cjs";
// macOS companion app has no way to obtain the Safari version used by WKWebView,
// and it is not in the default user agent string. So we add an additional regex
// to serve modern based on a minimum macOS version. We take the minimum Safari
// major version from browserslist and manually map that to a supported macOS
// version. Note this assumes the user has kept Safari updated.
const HA_MACOS_REGEX =
/Home Assistant\/[\d.]+ \(.+; macOS (\d+)\.(\d+)(?:\.(\d+))?\)/;
const SAFARI_TO_MACOS = {
15: [10, 15, 0],
16: [11, 0, 0],
17: [12, 0, 0],
18: [13, 0, 0],
};
const getCommonTemplateVars = () => {
const browserRegexes = getPreUserAgentRegexes({
env: "modern",
allowHigherVersions: true,
mobileToDesktop: true,
throwOnMissing: true,
});
const minSafariVersion = browserRegexes.find(
(regex) => regex.family === "safari"
)?.matchedVersions[0][0];
const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
if (!minMacOSVersion) {
throw Error(
`Could not find minimum MacOS version for Safari ${minSafariVersion}.`
);
}
const haMacOSRegex = applyVersionsToRegexes(
[
{
family: "ha_macos",
regex: HA_MACOS_REGEX,
matchedVersions: [minMacOSVersion],
requestVersions: [minMacOSVersion],
},
],
{ ignorePatch: true, allowHigherVersions: true }
);
return {
useRollup: env.useRollup(),
useWDS: env.useWDS(),
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
};
};
const renderTemplate = (templateFile, data = {}) => { const renderTemplate = (templateFile, data = {}) => {
const compiled = template( const compiled = template(
fs.readFileSync(templateFile, { encoding: "utf-8" }) fs.readFileSync(templateFile, { encoding: "utf-8" })
); );
return compiled({ return compiled({
...data, ...data,
useRollup: env.useRollup(),
useWDS: env.useWDS(),
// Resolve any child/nested templates relative to the parent and pass the same data // Resolve any child/nested templates relative to the parent and pass the same data
renderTemplate: (childTemplate) => renderTemplate: (childTemplate) =>
renderTemplate( renderTemplate(resolve(dirname(templateFile), childTemplate), data),
path.resolve(path.dirname(templateFile), childTemplate),
data
),
}); });
}; };
@@ -56,10 +104,12 @@ const genPagesDevTask =
publicRoot = "" publicRoot = ""
) => ) =>
async () => { async () => {
const commonVars = getCommonTemplateVars();
for (const [page, entries] of Object.entries(pageEntries)) { for (const [page, entries] of Object.entries(pageEntries)) {
const content = renderTemplate( const content = renderTemplate(
path.resolve(inputRoot, inputSub, `${page}.template`), resolve(inputRoot, inputSub, `${page}.template`),
{ {
...commonVars,
latestEntryJS: entries.map((entry) => latestEntryJS: entries.map((entry) =>
useWDS useWDS
? `http://localhost:8000/src/entrypoints/${entry}.ts` ? `http://localhost:8000/src/entrypoints/${entry}.ts`
@@ -74,7 +124,7 @@ const genPagesDevTask =
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`, es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
} }
); );
fs.outputFileSync(path.resolve(outputRoot, page), content); fs.outputFileSync(resolve(outputRoot, page), content);
} }
}; };
@@ -91,16 +141,18 @@ const genPagesProdTask =
) => ) =>
async () => { async () => {
const latestManifest = fs.readJsonSync( const latestManifest = fs.readJsonSync(
path.resolve(outputLatest, "manifest.json") resolve(outputLatest, "manifest.json")
); );
const es5Manifest = outputES5 const es5Manifest = outputES5
? fs.readJsonSync(path.resolve(outputES5, "manifest.json")) ? fs.readJsonSync(resolve(outputES5, "manifest.json"))
: {}; : {};
const commonVars = getCommonTemplateVars();
const minifiedHTML = []; const minifiedHTML = [];
for (const [page, entries] of Object.entries(pageEntries)) { for (const [page, entries] of Object.entries(pageEntries)) {
const content = renderTemplate( const content = renderTemplate(
path.resolve(inputRoot, inputSub, `${page}.template`), resolve(inputRoot, inputSub, `${page}.template`),
{ {
...commonVars,
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]), latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]), es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
latestCustomPanelJS: latestManifest["custom-panel.js"], latestCustomPanelJS: latestManifest["custom-panel.js"],
@@ -108,8 +160,8 @@ const genPagesProdTask =
} }
); );
minifiedHTML.push( minifiedHTML.push(
minifyHtml(content, path.extname(page)).then((minified) => minifyHtml(content, extname(page)).then((minified) =>
fs.outputFileSync(path.resolve(outputRoot, page), minified) fs.outputFileSync(resolve(outputRoot, page), minified)
) )
); );
} }

View File

@@ -1,20 +1,19 @@
// Generate service worker. // Generate service workers
// Based on manifest, create a file with the content as service_worker.js
import fs from "fs-extra"; import { deleteAsync } from "del";
import gulp from "gulp"; import gulp from "gulp";
import path from "path"; import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
import sourceMapUrl from "source-map-url"; import { basename, join, relative } from "node:path";
import workboxBuild from "workbox-build"; import { injectManifest } from "workbox-build";
import paths from "../paths.cjs"; import paths from "../paths.cjs";
const swDest = path.resolve(paths.app_output_root, "service_worker.js"); const SW_MAP = {
[paths.app_output_latest]: "modern",
[paths.app_output_es5]: "legacy",
};
const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n"); const SW_DEV =
`
gulp.task("gen-service-worker-app-dev", (done) => {
writeSW(
`
console.debug('Service worker disabled in development'); console.debug('Service worker disabled in development');
self.addEventListener('install', (event) => { self.addEventListener('install', (event) => {
@@ -22,72 +21,67 @@ self.addEventListener('install', (event) => {
// removing any prod service worker the dev might have running // removing any prod service worker the dev might have running
self.skipWaiting(); self.skipWaiting();
}); });
` `.trim() + "\n";
gulp.task("gen-service-worker-app-dev", async () => {
await mkdir(paths.app_output_root, { recursive: true });
await Promise.all(
Object.values(SW_MAP).map((build) =>
writeFile(join(paths.app_output_root, `sw-${build}.js`), SW_DEV, {
encoding: "utf-8",
})
)
); );
done();
}); });
gulp.task("gen-service-worker-app-prod", async () => { gulp.task("gen-service-worker-app-prod", () =>
// Read bundled source file Promise.all(
const bundleManifestLatest = fs.readJsonSync( Object.entries(SW_MAP).map(async ([outPath, build]) => {
path.resolve(paths.app_output_latest, "manifest.json") const manifest = JSON.parse(
); await readFile(join(outPath, "manifest.json"), "utf-8")
let serviceWorkerContent = fs.readFileSync( );
paths.app_output_root + bundleManifestLatest["service_worker.js"], const swSrc = join(paths.app_output_root, manifest["service-worker.js"]);
"utf-8" const swDest = join(paths.app_output_root, `sw-${build}.js`);
); const buildDir = relative(paths.app_output_root, outPath);
const { warnings } = await injectManifest({
// Delete old file from frontend_latest so manifest won't pick it up swSrc,
fs.removeSync( swDest,
paths.app_output_root + bundleManifestLatest["service_worker.js"] injectionPoint: "__WB_MANIFEST__",
); // Files that mach this pattern will be considered unique and skip revision check
fs.removeSync( // ignore JS files + translation files
paths.app_output_root + bundleManifestLatest["service_worker.js.map"] dontCacheBustURLsMatching: new RegExp(
); `(?:${buildDir}/.+|static/translations/.+)`
),
// Remove ES5 globDirectory: paths.app_output_root,
const bundleManifestES5 = fs.readJsonSync( globPatterns: [
path.resolve(paths.app_output_es5, "manifest.json") `${buildDir}/*.js`,
); // Cache all English translations because we catch them as fallback
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]); // Using pattern to match hash instead of * to avoid caching en-GB
fs.removeSync( // 'v' added as valid hash letter because in dev we hash with 'dev'
paths.app_output_root + bundleManifestES5["service_worker.js.map"] "static/translations/**/en-+([a-fv0-9]).json",
); // Icon shown on splash screen
"static/icons/favicon-192x192.png",
const workboxManifest = await workboxBuild.getManifest({ "static/icons/favicon.ico",
// Files that mach this pattern will be considered unique and skip revision check // Common fonts
// ignore JS files + translation files "static/fonts/roboto/Roboto-Light.woff2",
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/, "static/fonts/roboto/Roboto-Medium.woff2",
"static/fonts/roboto/Roboto-Regular.woff2",
globDirectory: paths.app_output_root, "static/fonts/roboto/Roboto-Bold.woff2",
globPatterns: [ ],
"frontend_latest/*.js", globIgnores: [`${buildDir}/service-worker*`],
// Cache all English translations because we catch them as fallback });
// Using pattern to match hash instead of * to avoid caching en-GB if (warnings.length > 0) {
// 'v' added as valid hash letter because in dev we hash with 'dev' console.warn(
"static/translations/**/en-+([a-fv0-9]).json", `Problems while injecting ${build} service worker:\n`,
// Icon shown on splash screen warnings.join("\n")
"static/icons/favicon-192x192.png", );
"static/icons/favicon.ico", }
// Common fonts await deleteAsync(`${swSrc}?(.map)`);
"static/fonts/roboto/Roboto-Light.woff2", // Needed to install new SW from a cached HTML
"static/fonts/roboto/Roboto-Medium.woff2", if (build === "modern") {
"static/fonts/roboto/Roboto-Regular.woff2", const swOld = join(paths.app_output_root, "service_worker.js");
"static/fonts/roboto/Roboto-Bold.woff2", await symlink(basename(swDest), swOld);
], }
}); })
)
for (const warning of workboxManifest.warnings) { );
console.warn(warning);
}
// remove source map and add WB manifest
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
serviceWorkerContent = serviceWorkerContent.replace(
"WB_MANIFEST",
JSON.stringify(workboxManifest.manifestEntries)
);
// Write new file to root
fs.writeFileSync(swDest, serviceWorkerContent);
});

View File

@@ -244,11 +244,11 @@ const createTranslations = async () => {
// TODO: This is a naive interpretation of BCP47 that should be improved. // TODO: This is a naive interpretation of BCP47 that should be improved.
// Will be OK for now as long as we don't have anything more complicated // Will be OK for now as long as we don't have anything more complicated
// than a base translation + region. // than a base translation + region.
gulp const masterStream = gulp
.src(`${workDir}/en.json`) .src(`${workDir}/en.json`)
.pipe(new PassThrough({ objectMode: true })) .pipe(new PassThrough({ objectMode: true }));
.pipe(hashStream, { end: false }); masterStream.pipe(hashStream, { end: false });
const mergesFinished = []; const mergesFinished = [finished(masterStream)];
for (const translationFile of translationFiles) { for (const translationFile of translationFiles) {
const locale = basename(translationFile, ".json"); const locale = basename(translationFile, ".json");
const subtags = locale.split("-"); const subtags = locale.split("-");

View File

@@ -40,8 +40,12 @@ const runDevServer = async ({
compiler, compiler,
contentBase, contentBase,
port, port,
listenHost = "localhost", listenHost = undefined,
}) => { }) => {
if (listenHost === undefined) {
// For dev container, we need to listen on all hosts
listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost";
}
const server = new WebpackDevServer( const server = new WebpackDevServer(
{ {
hot: false, hot: false,

View File

@@ -63,17 +63,25 @@ const createWebpackConfig = ({
rules: [ rules: [
{ {
test: /\.m?js$|\.ts$/, test: /\.m?js$|\.ts$/,
use: { use: (info) => ({
loader: "babel-loader", loader: "babel-loader",
options: { options: {
...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }), ...bundle.babelOptions({
latestBuild,
isProdBuild,
isTestBuild,
sw: info.issuerLayer === "sw",
}),
cacheDirectory: !isProdBuild, cacheDirectory: !isProdBuild,
cacheCompression: false, cacheCompression: false,
}, },
}, }),
resolve: { resolve: {
fullySpecified: false, fullySpecified: false,
}, },
parser: {
worker: ["*context.audioWorklet.addModule()", "..."],
},
}, },
{ {
test: /\.css$/, test: /\.css$/,
@@ -92,11 +100,15 @@ const createWebpackConfig = ({
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
splitChunks: { splitChunks: {
// Disable splitting for web workers with ESM output // Disable splitting for web workers and worklets because imports of
// Imports of external chunks are broken // external chunks are broken for:
chunks: latestBuild // - ESM output: https://github.com/webpack/webpack/issues/17014
? (chunk) => !chunk.canBeInitial() && !/^.+-worker$/.test(chunk.name) // - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543
: undefined, chunks: (chunk) =>
!chunk.canBeInitial() &&
!new RegExp(`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`).test(
chunk.name
),
}, },
}, },
plugins: [ plugins: [
@@ -228,6 +240,7 @@ const createWebpackConfig = ({
), ),
}, },
experiments: { experiments: {
layers: true,
outputModule: true, outputModule: true,
}, },
}; };

5
cast/public/sw-legacy.js Normal file
View File

@@ -0,0 +1,5 @@
"use strict";
self.addEventListener("fetch", (event) => {
event.respondWith(fetch(event.request));
});

View File

@@ -36,13 +36,7 @@
</head> </head>
<body> <body>
<%= renderTemplate("../../../src/html/_js_base.html.template") %> <%= renderTemplate("../../../src/html/_js_base.html.template") %>
<script> <%= renderTemplate("../../../src/html/_script_loader.html.template") %>
<% for (const entry of latestEntryJS) { %>
import("<%= entry %>");
<% } %>
window.latestJS = true;
</script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<hc-layout subtitle="FAQ"> <hc-layout subtitle="FAQ">
<style> <style>
a { a {
@@ -232,17 +226,5 @@ http:
</p> </p>
</div> </div>
</hc-layout> </hc-layout>
<script>
var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]];
(function (d, t) {
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
g.src =
("https:" == location.protocol ? "//ssl" : "//www") +
".google-analytics.com/ga.js";
s.parentNode.insertBefore(g, s);
})(document, "script");
</script>
</body> </body>
</html> </html>

View File

@@ -13,15 +13,9 @@
<%= renderTemplate("_social_meta.html.template") %> <%= renderTemplate("_social_meta.html.template") %>
</head> </head>
<body> <body>
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
<hc-connect></hc-connect> <hc-connect></hc-connect>
<script> <%= renderTemplate("../../../src/html/_js_base.html.template") %>
<% for (const entry of latestEntryJS) { %> <%= renderTemplate("../../../src/html/_script_loader.html.template") %>
import("<%= entry %>");
<% } %>
window.latestJS = true;
</script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script> <script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),

View File

@@ -14,22 +14,10 @@
--background-color: #41bdf5; --background-color: #41bdf5;
} }
</style> </style>
<script>
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
s.parentNode.insertBefore(g,s)}(document,'script'));
</script>
</head> </head>
<body> <body>
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
<cast-media-player></cast-media-player> <cast-media-player></cast-media-player>
<script> <%= renderTemplate("../../../src/html/_js_base.html.template") %>
<% for (const entry of latestEntryJS) { %> <%= renderTemplate("../../../src/html/_script_loader.html.template") %>
import("<%= entry %>");
<% } %>
window.latestJS = true;
</script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
</body> </body>
</html> </html>

View File

@@ -11,10 +11,4 @@
font-size: initial; font-size: initial;
} }
</style> </style>
<script>
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
s.parentNode.insertBefore(g,s)}(document,'script'));
</script>
</html> </html>

View File

@@ -1,4 +1,3 @@
import "../../../src/resources/safari-14-attachshadow-patch";
import "./layout/hc-connect"; import "./layout/hc-connect";
import("../../../src/resources/ha-style"); import("../../../src/resources/ha-style");

View File

@@ -1,7 +1,6 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { ActionDetail } from "@material/mwc-list/mwc-list";
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js"; import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket"; import { Auth, Connection } from "home-assistant-js-websocket";
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
@@ -28,6 +27,7 @@ import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
import "../../../../src/layouts/hass-loading-screen"; import "../../../../src/layouts/hass-loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config"; import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout"; import "./hc-layout";
import "../../../../src/components/ha-list-item";
@customElement("hc-cast") @customElement("hc-cast")
class HcCast extends LitElement { class HcCast extends LitElement {
@@ -83,37 +83,37 @@ class HcCast extends LitElement {
` `
: html` : html`
<div class="section-header">PICK A VIEW</div> <div class="section-header">PICK A VIEW</div>
<paper-listbox <mwc-list @action=${this._handlePickView} activatable>
attr-for-selected="data-path"
.selected=${this.castManager.status.lovelacePath || ""}
>
${( ${(
this.lovelaceViews ?? [ this.lovelaceViews ?? [
generateDefaultViewConfig({}, {}, {}, {}, () => ""), generateDefaultViewConfig({}, {}, {}, {}, () => ""),
] ]
).map( ).map(
(view, idx) => html` (view, idx) =>
<paper-icon-item html`<ha-list-item
@click=${this._handlePickView} graphic="avatar"
data-path=${view.path || idx} .activated=${this.castManager.status?.lovelacePath ===
(view.path ?? idx)}
.selected=${this.castManager.status?.lovelacePath ===
(view.path ?? idx)}
> >
${view.title || view.path || "Unnamed view"}
${view.icon ${view.icon
? html` ? html`
<ha-icon <ha-icon
.icon=${view.icon} .icon=${view.icon}
slot="item-icon" slot="graphic"
></ha-icon> ></ha-icon>
` `
: html`<ha-svg-icon : html`<ha-svg-icon
slot="item-icon" slot="item-icon"
.path=${mdiViewDashboard} .path=${mdiViewDashboard}
></ha-svg-icon>`} ></ha-svg-icon>`}</ha-list-item
${view.title || view.path || "Unnamed view"} > `
</paper-icon-item> )}</mwc-list
` >
)}
</paper-listbox>
`} `}
<div class="card-actions"> <div class="card-actions">
${this.castManager.status ${this.castManager.status
? html` ? html`
@@ -185,8 +185,8 @@ class HcCast extends LitElement {
this.castManager.requestSession(); this.castManager.requestSession();
} }
private async _handlePickView(ev: Event) { private async _handlePickView(ev: CustomEvent<ActionDetail>) {
const path = (ev.currentTarget as any).getAttribute("data-path"); const path = this.lovelaceViews![ev.detail.index].path ?? ev.detail.index;
await ensureConnectedCastSession(this.castManager!, this.auth!); await ensureConnectedCastSession(this.castManager!, this.auth!);
castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path); castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
} }
@@ -249,26 +249,14 @@ class HcCast extends LitElement {
height: 18px; height: 18px;
} }
paper-listbox { ha-list-item ha-icon,
padding-top: 0; ha-list-item ha-svg-icon {
}
paper-listbox ha-icon,
paper-listbox ha-svg-icon {
padding: 12px; padding: 12px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
paper-icon-item { :host([hide-icons]) ha-icon {
cursor: pointer; display: none;
}
paper-icon-item[disabled] {
cursor: initial;
}
:host([hide-icons]) paper-icon-item {
--paper-item-icon-width: 0px;
} }
.spacer { .spacer {

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

5
demo/public/sw-legacy.js Normal file
View File

@@ -0,0 +1,5 @@
"use strict";
self.addEventListener("fetch", (event) => {
event.respondWith(fetch(event.request));
});

View File

@@ -1,7 +1,7 @@
import { convertEntities } from "../../../../src/fake_data/entity"; import { convertEntities } from "../../../../src/fake_data/entity";
import { DemoConfig } from "../types"; import { DemoConfig } from "../types";
export const demoEntitiesSections: DemoConfig["entities"] = () => export const demoEntitiesSections: DemoConfig["entities"] = (localize) =>
convertEntities({ convertEntities({
"cover.living_room_garden_shutter": { "cover.living_room_garden_shutter": {
entity_id: "cover.living_room_garden_shutter", entity_id: "cover.living_room_garden_shutter",
@@ -113,11 +113,30 @@ export const demoEntitiesSections: DemoConfig["entities"] = () =>
}, },
"media_player.living_room_nest_mini": { "media_player.living_room_nest_mini": {
entity_id: "media_player.living_room_nest_mini", entity_id: "media_player.living_room_nest_mini",
state: "off", state: "on",
attributes: { attributes: {
device_class: "speaker", device_class: "speaker",
friendly_name: "Living room Nest Mini", volume_level: 0.18,
supported_features: 152461, is_volume_muted: false,
media_content_type: "music",
media_duration: 300,
media_position: 0,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
media_title: "I Wasn't Born To Follow",
media_artist: "The Byrds",
media_album_name: "The Notorious Byrd Brothers",
source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"],
shuffle: false,
night_sound: false,
speech_enhance: false,
friendly_name: localize(
"ui.panel.page-demo.config.sections.entities.media_player.living_room_nest_mini"
),
entity_picture: "/assets/sections/images/media_player_family_room.jpg",
supported_features: 64063,
}, },
}, },
"cover.kitchen_shutter": { "cover.kitchen_shutter": {
@@ -168,8 +187,27 @@ export const demoEntitiesSections: DemoConfig["entities"] = () =>
state: "on", state: "on",
attributes: { attributes: {
device_class: "speaker", device_class: "speaker",
friendly_name: "Kitchen Nest Audio", volume_level: 0.18,
supported_features: 152461, is_volume_muted: false,
media_content_type: "music",
media_duration: 300,
media_position: 0,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
media_title: "I Wasn't Born To Follow",
media_artist: "The Byrds",
media_album_name: "The Notorious Byrd Brothers",
source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"],
shuffle: false,
night_sound: false,
speech_enhance: false,
friendly_name: localize(
"ui.panel.page-demo.config.sections.entities.media_player.kitchen_nest_audio"
),
entity_picture: "/assets/sections/images/media_player_family_room.jpg",
supported_features: 64063,
}, },
}, },
"binary_sensor.tesla_wall_connector_vehicle_connected": { "binary_sensor.tesla_wall_connector_vehicle_connected": {
@@ -333,8 +371,28 @@ export const demoEntitiesSections: DemoConfig["entities"] = () =>
entity_id: "media_player.study_nest_hub", entity_id: "media_player.study_nest_hub",
state: "off", state: "off",
attributes: { attributes: {
friendly_name: "Study Nest Hub", device_class: "speaker",
supported_features: 152461, volume_level: 0.18,
is_volume_muted: false,
media_content_type: "music",
media_duration: 300,
media_position: 0,
media_position_updated_at: new Date(
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
media_title: "I Wasn't Born To Follow",
media_artist: "The Byrds",
media_album_name: "The Notorious Byrd Brothers",
source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"],
shuffle: false,
night_sound: false,
speech_enhance: false,
friendly_name: localize(
"ui.panel.page-demo.config.sections.entities.media_player.study_nest_hub"
),
entity_picture: "/assets/sections/images/media_player_family_room.jpg",
supported_features: 64063,
}, },
}, },
"sensor.standing_desk_height": { "sensor.standing_desk_height": {

View File

@@ -1,40 +1,25 @@
import { isFrontpageEmbed } from "../../util/is_frontpage";
import { DemoConfig } from "../types"; import { DemoConfig } from "../types";
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({
title: "Home Assistant Demo", title: "Home Assistant Demo",
views: [ views: [
{ {
type: "sections", type: "sections",
title: "Demo", title: isFrontpageEmbed ? "Home Assistant" : "Demo",
path: "home", path: "home",
icon: "mdi:home-assistant", icon: "mdi:home-assistant",
sections: [ sections: [
{ ...(isFrontpageEmbed
title: "Welcome 👋", ? []
cards: [{ type: "custom:ha-demo-card" }], : [
}, {
title: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`,
cards: [{ type: "custom:ha-demo-card" }],
},
]),
{ {
cards: [ cards: [
{
type: "tile",
entity: "cover.living_room_garden_shutter",
name: "Garden",
},
{
type: "tile",
entity: "cover.living_room_graveyard_shutter",
name: "Rear",
},
{
type: "tile",
entity: "cover.living_room_left_shutter",
name: "Left",
},
{
type: "tile",
entity: "cover.living_room_right_shutter",
name: "Right",
},
{ {
type: "tile", type: "tile",
entity: "light.floor_lamp", entity: "light.floor_lamp",
@@ -60,13 +45,17 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
detail: 1, detail: 1,
name: "Temperature", name: "Temperature",
}, },
{
type: "tile",
entity: "cover.living_room_garden_shutter",
name: "Blinds",
},
{ {
type: "tile", type: "tile",
entity: "media_player.living_room_nest_mini", entity: "media_player.living_room_nest_mini",
name: "Nest Mini",
}, },
], ],
title: "🛋️ Living room ", title: `🛋️ ${localize("ui.panel.page-demo.config.sections.titles.living_room")} `,
}, },
{ {
type: "grid", type: "grid",
@@ -99,10 +88,9 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
{ {
type: "tile", type: "tile",
entity: "media_player.kitchen_nest_audio", entity: "media_player.kitchen_nest_audio",
name: "Nest Audio",
}, },
], ],
title: "👩‍🍳 Kitchen", title: `👩‍🍳 ${localize("ui.panel.page-demo.config.sections.titles.kitchen")}`,
}, },
{ {
type: "grid", type: "grid",
@@ -144,7 +132,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
color: "dark-grey", color: "dark-grey",
}, },
], ],
title: "⚡️ Energy", title: `⚡️ ${localize("ui.panel.page-demo.config.sections.titles.energy")}`,
}, },
{ {
type: "grid", type: "grid",
@@ -181,7 +169,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
state_content: ["preset_mode", "current_temperature"], state_content: ["preset_mode", "current_temperature"],
}, },
], ],
title: "🌤️ Climate", title: `🌤️ ${localize("ui.panel.page-demo.config.sections.titles.climate")}`,
}, },
{ {
type: "grid", type: "grid",
@@ -199,7 +187,6 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
{ {
type: "tile", type: "tile",
entity: "media_player.study_nest_hub", entity: "media_player.study_nest_hub",
name: "Nest Hub",
}, },
{ {
type: "tile", type: "tile",
@@ -209,7 +196,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
icon: "mdi:desk", icon: "mdi:desk",
}, },
], ],
title: "🧑‍💻 Study", title: `🧑‍💻 ${localize("ui.panel.page-demo.config.sections.titles.study")}`,
}, },
{ {
type: "grid", type: "grid",
@@ -243,7 +230,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
name: "Illuminance", name: "Illuminance",
}, },
], ],
title: "🌳 Outdoor", title: `🌳 ${localize("ui.panel.page-demo.config.sections.titles.outdoor")}`,
}, },
{ {
type: "grid", type: "grid",
@@ -273,7 +260,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
icon: "mdi:home-assistant", icon: "mdi:home-assistant",
}, },
], ],
title: "🎉 Updates", title: `🎉 ${localize("ui.panel.page-demo.config.sections.titles.updates")}`,
}, },
], ],
}, },

View File

@@ -1,8 +1,9 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { until } from "lit/directives/until"; import { until } from "lit/directives/until";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-button";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card"; import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
@@ -11,7 +12,6 @@ import {
demoConfigs, demoConfigs,
selectedDemoConfig, selectedDemoConfig,
selectedDemoConfigIndex, selectedDemoConfigIndex,
setDemoConfig,
} from "../configs/demo-configs"; } from "../configs/demo-configs";
@customElement("ha-demo-card") @customElement("ha-demo-card")
@@ -64,9 +64,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
)} )}
</div> </div>
<mwc-button @click=${this._nextConfig} .disabled=${this._switching}> <ha-button @click=${this._nextConfig} .disabled=${this._switching}>
${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")} ${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
</mwc-button> </ha-button>
</div> </div>
<div class="content"> <div class="content">
<p class="small-hidden"> <p class="small-hidden">
@@ -87,9 +87,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
</div> </div>
<div class="actions small-hidden"> <div class="actions small-hidden">
<a href="https://www.home-assistant.io" target="_blank"> <a href="https://www.home-assistant.io" target="_blank">
<mwc-button> <ha-button>
${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")} ${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
</mwc-button> </ha-button>
</a> </a>
</div> </div>
</ha-card> </ha-card>
@@ -113,13 +113,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
private async _updateConfig(index: number) { private async _updateConfig(index: number) {
this._switching = true; this._switching = true;
try { fireEvent(this, "set-demo-config" as any, { index });
await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err: any) {
alert("Failed to switch config :-(");
} finally {
this._switching = false;
}
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@@ -149,7 +143,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
height: 60px; height: 60px;
} }
.picker mwc-button { .picker ha-button {
margin-right: 8px; margin-right: 8px;
} }

View File

@@ -1,4 +1,4 @@
import "../../src/resources/safari-14-attachshadow-patch"; import "./util/is_frontpage";
import "./ha-demo"; import "./ha-demo";
import("../../src/resources/ha-style"); import("../../src/resources/ha-style");

View File

@@ -82,6 +82,8 @@ export class HaDemo extends HomeAssistantAppEl {
has_entity_name: false, has_entity_name: false,
unique_id: "co2_intensity", unique_id: "co2_intensity",
options: null, options: null,
created_at: 0,
modified_at: 0,
}, },
{ {
config_entry_id: "co2signal", config_entry_id: "co2signal",
@@ -100,6 +102,8 @@ export class HaDemo extends HomeAssistantAppEl {
has_entity_name: false, has_entity_name: false,
unique_id: "grid_fossil_fuel_percentage", unique_id: "grid_fossil_fuel_percentage",
options: null, options: null,
created_at: 0,
modified_at: 0,
}, },
]); ]);

View File

@@ -63,46 +63,47 @@
align-items: center; align-items: center;
} }
#ha-launch-screen svg { #ha-launch-screen svg {
width: 170px; width: 112px;
flex-shrink: 0; flex-shrink: 0;
} }
#ha-launch-screen .ha-launch-screen-spacer { #ha-launch-screen .ha-launch-screen-spacer-top {
flex: 1; flex: 1;
margin-top: calc( 2 * max(env(safe-area-inset-bottom), 48px) + 46px );
padding-top: 48px;
}
#ha-launch-screen .ha-launch-screen-spacer-bottom {
flex: 1;
padding-top: 48px;
}
.ohf-logo {
margin: max(env(safe-area-inset-bottom), 48px) 0;
display: flex;
flex-direction: column;
align-items: center;
opacity: .66;
}
@media (prefers-color-scheme: dark) {
.ohf-logo {
filter: invert(1);
}
} }
</style> </style>
</head> </head>
<body> <body>
<div id="ha-launch-screen"> <div id="ha-launch-screen">
<div class="ha-launch-screen-spacer"></div> <div class="ha-launch-screen-spacer-top"></div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
<path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/> <path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/>
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/> <path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
</svg> </svg>
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div> <div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer-bottom"></div>
<div class="ohf-logo">
<img src="/static/images/ohf-badge.svg" alt="Home Assistant is a project by the Open Home Foundation" height="46">
</div>
</div> </div>
<ha-demo></ha-demo> <ha-demo></ha-demo>
<%= renderTemplate("../../../src/html/_js_base.html.template") %> <%= renderTemplate("../../../src/html/_js_base.html.template") %>
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %> <%= renderTemplate("../../../src/html/_preload_roboto.html.template") %>
<script> <%= renderTemplate("../../../src/html/_script_loader.html.template") %>
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
if (!isS11_12) {
<% for (const entry of latestEntryJS) { %>
import("<%= entry %>");
<% } %>
window.latestJS = true;
}
</script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script>
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
(function (d, t) {
var g = d.createElement(t),
s = d.getElementsByTagName(t)[0];
g.src =
("https:" == location.protocol ? "//ssl" : "//www") +
".google-analytics.com/ga.js";
s.parentNode.insertBefore(g, s);
})(document, "script");
</script>
</body> </body>
</html> </html>

View File

@@ -1,5 +1,55 @@
import { convertEntities } from "../../../src/fake_data/entity"; import { convertEntities } from "../../../src/fake_data/entity";
export const mapEntities = () =>
convertEntities({
"zone.home": {
entity_id: "zone.home",
state: "zoning",
attributes: {
hidden: true,
latitude: 52.3631339,
longitude: 4.8903147,
radius: 200,
friendly_name: "Home",
icon: "hademo:home",
},
},
"zone.uva": {
entity_id: "zone.buckhead",
state: "zoning",
attributes: {
hidden: true,
radius: 400,
friendly_name: "UvA",
icon: "hademo:school",
latitude: 52.3558182,
longitude: 4.9535376,
},
},
"person.arsaboo": {
entity_id: "person.arsaboo",
state: "not_home",
attributes: {
radius: 50,
friendly_name: "Arsaboo",
latitude: 52.3579946,
longitude: 4.8664597,
entity_picture: "/assets/arsaboo/images/arsaboo.jpg",
},
},
"person.melody": {
entity_id: "person.melody",
state: "not_home",
attributes: {
radius: 50,
friendly_name: "Melody",
latitude: 52.3408927,
longitude: 4.8711073,
entity_picture: "/assets/arsaboo/images/melody.jpg",
},
},
});
export const energyEntities = () => export const energyEntities = () =>
convertEntities({ convertEntities({
"sensor.grid_fossil_fuel_percentage": { "sensor.grid_fossil_fuel_percentage": {

View File

@@ -1,35 +1,52 @@
import type { LocalizeFunc } from "../../../src/common/translations/localize"; import type { LocalizeFunc } from "../../../src/common/translations/localize";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { selectedDemoConfig } from "../configs/demo-configs"; import {
selectedDemoConfig,
selectedDemoConfigIndex,
setDemoConfig,
} from "../configs/demo-configs";
import "../custom-cards/cast-demo-row"; import "../custom-cards/cast-demo-row";
import "../custom-cards/ha-demo-card"; import "../custom-cards/ha-demo-card";
import type { HADemoCard } from "../custom-cards/ha-demo-card"; import { mapEntities } from "./entities";
export const mockLovelace = ( export const mockLovelace = (
hass: MockHomeAssistant, hass: MockHomeAssistant,
localizePromise: Promise<LocalizeFunc> localizePromise: Promise<LocalizeFunc>
) => { ) => {
hass.mockWS("lovelace/config", () => hass.mockWS("lovelace/config", ({ url_path }) => {
Promise.all([selectedDemoConfig, localizePromise]).then( if (url_path === "map") {
hass.addEntities(mapEntities());
return {
strategy: {
type: "map",
},
};
}
return Promise.all([selectedDemoConfig, localizePromise]).then(
([config, localize]) => config.lovelace(localize) ([config, localize]) => config.lovelace(localize)
) );
); });
hass.mockWS("lovelace/config/save", () => Promise.resolve()); hass.mockWS("lovelace/config/save", () => Promise.resolve());
hass.mockWS("lovelace/resources", () => Promise.resolve([])); hass.mockWS("lovelace/resources", () => Promise.resolve([]));
}; };
customElements.whenDefined("hui-card").then(() => { customElements.whenDefined("hui-root").then(() => {
// eslint-disable-next-line // eslint-disable-next-line
const HUIView = customElements.get("hui-card"); const HUIRoot = customElements.get("hui-root")!;
// Patch HUI-VIEW to make the lovelace object available to the demo card
const oldCreateCard = HUIView!.prototype.createElement;
HUIView!.prototype.createElement = function (config) { const oldFirstUpdated = HUIRoot.prototype.firstUpdated;
const el = oldCreateCard.call(this, config);
if (config.type === "custom:ha-demo-card") { HUIRoot.prototype.firstUpdated = function (changedProperties) {
(el as HADemoCard).lovelace = this.lovelace; oldFirstUpdated.call(this, changedProperties);
} this.addEventListener("set-demo-config", async (ev) => {
return el; const index = (ev as CustomEvent).detail.index;
try {
await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err: any) {
setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex);
alert("Failed to switch config :-(");
}
});
}; };
}); });

View File

@@ -0,0 +1 @@
export const isFrontpageEmbed = document.location.search === "?frontpage";

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

View File

@@ -1,7 +1,9 @@
import { load } from "js-yaml"; import { load } from "js-yaml";
import { html, css, LitElement, PropertyValues } from "lit"; import { LitElement, PropertyValueMap, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element"; import memoizeOne from "memoize-one";
import "../../../src/panels/lovelace/cards/hui-card";
import type { HuiCard } from "../../../src/panels/lovelace/cards/hui-card";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
export interface DemoCardConfig { export interface DemoCardConfig {
@@ -19,7 +21,12 @@ class DemoCard extends LitElement {
@state() private _size?: number; @state() private _size?: number;
@query("#card") private _card!: HTMLElement; @query("hui-card", false) private _card?: HuiCard;
private _config = memoizeOne((config: string) => {
const c = (load(config) as any)[0];
return c;
});
render() { render() {
return html` return html`
@@ -30,63 +37,32 @@ class DemoCard extends LitElement {
: ""} : ""}
</h2> </h2>
<div class="root"> <div class="root">
<div id="card"></div> <hui-card
${this.showConfig ? html`<pre>${this.config.config.trim()}</pre>` : ""} .config=${this._config(this.config.config)}
.hass=${this.hass}
@card-updated=${this._cardUpdated}
></hui-card>
${this.showConfig
? html`<pre>${this.config.config.trim()}</pre>`
: nothing}
</div> </div>
`; `;
} }
updated(changedProps: PropertyValues) { private async _cardUpdated(ev) {
super.updated(changedProps); ev.stopPropagation();
this._updateSize();
if (changedProps.has("config")) {
const card = this._card;
while (card.lastChild) {
card.removeChild(card.lastChild);
}
const el = this._createCardElement((load(this.config.config) as any)[0]);
card.appendChild(el);
this._getSize(el);
}
if (changedProps.has("hass")) {
const card = this._card.lastChild;
if (card) {
(card as any).hass = this.hass;
}
}
} }
async _getSize(el) { private async _updateSize() {
await customElements.whenDefined(el.localName); this._size = await this._card?.getCardSize();
if (!("getCardSize" in el)) {
this._size = undefined;
return;
}
this._size = await el.getCardSize();
} }
_createCardElement(cardConfig) { protected update(
const element = createCardElement(cardConfig); _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
if (this.hass) { ): void {
element.hass = this.hass; super.update(_changedProperties);
} this._updateSize();
element.addEventListener(
"ll-rebuild",
(ev) => {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
},
{ once: true }
);
return element;
}
_rebuildCard(cardElToReplace, config) {
const newCardEl = this._createCardElement(config);
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
} }
static styles = css` static styles = css`
@@ -101,7 +77,7 @@ class DemoCard extends LitElement {
font-size: 0.5em; font-size: 0.5em;
color: var(--primary-text-color); color: var(--primary-text-color);
} }
#card { hui-card {
max-width: 400px; max-width: 400px;
width: 100vw; width: 100vw;
} }

View File

@@ -532,15 +532,6 @@ export default {
last_changed: "2018-07-19T10:44:46.200946+00:00", last_changed: "2018-07-19T10:44:46.200946+00:00",
last_updated: "2018-07-19T10:44:46.200946+00:00", last_updated: "2018-07-19T10:44:46.200946+00:00",
}, },
"mailbox.demomailbox": {
entity_id: "mailbox.demomailbox",
state: "10",
attributes: {
friendly_name: "DemoMailbox",
},
last_changed: "2018-07-19T10:45:16.555210+00:00",
last_updated: "2018-07-19T10:45:16.555210+00:00",
},
"input_select.living_room_preset": { "input_select.living_room_preset": {
entity_id: "input_select.living_room_preset", entity_id: "input_select.living_room_preset",
state: "Visitors", state: "Visitors",

View File

@@ -3,13 +3,16 @@ title: When to use remove, delete, add and create
subtitle: The difference between remove/delete and add/create. subtitle: The difference between remove/delete and add/create.
--- ---
# Remove vs Delete # Removing or deleting content
Remove and Delete are quite similar, but can be frustrating if used inconsistently. _Remove_ and _Delete_ are quite similar, but can be frustrating if used inconsistently.
- Remove refers to an action that can be restored or reapplied.
- Delete refers to a permanent, non-recoverable action.
## Remove ## Remove
Take away and set aside, but kept in existence. The term _Remove_ should always be used when an item/setting or content is to be removed or disassociated, but the action can be reversed or reapplied.
For example: For example:
@@ -22,7 +25,7 @@ For example:
## Delete ## Delete
Erase, rendered nonexistent or nonrecoverable. The term _Delete_ should always be used to refer to any action that will cause the permanent deletion of an item/setting or content.
For example: For example:

View File

@@ -15,6 +15,7 @@ import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import "../../components/demo-black-white-row"; import "../../components/demo-black-white-row";
import { DeviceRegistryEntry } from "../../../../src/data/device_registry";
const ENTITIES = [ const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", { getEntity("alarm_control_panel", "alarm", "disarmed", {
@@ -41,7 +42,7 @@ const ENTITIES = [
}), }),
]; ];
const DEVICES = [ const DEVICES: DeviceRegistryEntry[] = [
{ {
area_id: "bedroom", area_id: "bedroom",
configuration_url: null, configuration_url: null,
@@ -53,6 +54,7 @@ const DEVICES = [
identifiers: [["demo", "volume1"] as [string, string]], identifiers: [["demo", "volume1"] as [string, string]],
manufacturer: null, manufacturer: null,
model: null, model: null,
model_id: null,
name_by_user: null, name_by_user: null,
name: "Dishwasher", name: "Dishwasher",
sw_version: null, sw_version: null,
@@ -60,6 +62,8 @@ const DEVICES = [
via_device_id: null, via_device_id: null,
serial_number: null, serial_number: null,
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
{ {
area_id: "backyard", area_id: "backyard",
@@ -72,6 +76,7 @@ const DEVICES = [
identifiers: [["demo", "pwm1"] as [string, string]], identifiers: [["demo", "pwm1"] as [string, string]],
manufacturer: null, manufacturer: null,
model: null, model: null,
model_id: null,
name_by_user: null, name_by_user: null,
name: "Lamp", name: "Lamp",
sw_version: null, sw_version: null,
@@ -79,6 +84,8 @@ const DEVICES = [
via_device_id: null, via_device_id: null,
serial_number: null, serial_number: null,
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
{ {
area_id: null, area_id: null,
@@ -91,6 +98,7 @@ const DEVICES = [
identifiers: [["demo", "pwm1"] as [string, string]], identifiers: [["demo", "pwm1"] as [string, string]],
manufacturer: null, manufacturer: null,
model: null, model: null,
model_id: null,
name_by_user: "User name", name_by_user: "User name",
name: "Technical name", name: "Technical name",
sw_version: null, sw_version: null,
@@ -98,6 +106,8 @@ const DEVICES = [
via_device_id: null, via_device_id: null,
serial_number: null, serial_number: null,
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
]; ];
@@ -110,6 +120,8 @@ const AREAS: AreaRegistryEntry[] = [
picture: null, picture: null,
aliases: [], aliases: [],
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
{ {
area_id: "bedroom", area_id: "bedroom",
@@ -119,6 +131,8 @@ const AREAS: AreaRegistryEntry[] = [
picture: null, picture: null,
aliases: [], aliases: [],
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
{ {
area_id: "livingroom", area_id: "livingroom",
@@ -128,6 +142,8 @@ const AREAS: AreaRegistryEntry[] = [
picture: null, picture: null,
aliases: [], aliases: [],
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
]; ];

View File

@@ -21,6 +21,7 @@ import { FloorRegistryEntry } from "../../../../src/data/floor_registry";
import { LabelRegistryEntry } from "../../../../src/data/label_registry"; import { LabelRegistryEntry } from "../../../../src/data/label_registry";
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry"; import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry"; import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
import { DeviceRegistryEntry } from "../../../../src/data/device_registry";
const ENTITIES = [ const ENTITIES = [
getEntity("alarm_control_panel", "alarm", "disarmed", { getEntity("alarm_control_panel", "alarm", "disarmed", {
@@ -41,7 +42,7 @@ const ENTITIES = [
}), }),
]; ];
const DEVICES = [ const DEVICES: DeviceRegistryEntry[] = [
{ {
area_id: "bedroom", area_id: "bedroom",
configuration_url: null, configuration_url: null,
@@ -53,6 +54,7 @@ const DEVICES = [
identifiers: [["demo", "volume1"] as [string, string]], identifiers: [["demo", "volume1"] as [string, string]],
manufacturer: null, manufacturer: null,
model: null, model: null,
model_id: null,
name_by_user: null, name_by_user: null,
name: "Dishwasher", name: "Dishwasher",
sw_version: null, sw_version: null,
@@ -60,6 +62,8 @@ const DEVICES = [
via_device_id: null, via_device_id: null,
serial_number: null, serial_number: null,
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
{ {
area_id: "backyard", area_id: "backyard",
@@ -72,6 +76,7 @@ const DEVICES = [
identifiers: [["demo", "pwm1"] as [string, string]], identifiers: [["demo", "pwm1"] as [string, string]],
manufacturer: null, manufacturer: null,
model: null, model: null,
model_id: null,
name_by_user: null, name_by_user: null,
name: "Lamp", name: "Lamp",
sw_version: null, sw_version: null,
@@ -79,6 +84,8 @@ const DEVICES = [
via_device_id: null, via_device_id: null,
serial_number: null, serial_number: null,
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
{ {
area_id: null, area_id: null,
@@ -91,6 +98,7 @@ const DEVICES = [
identifiers: [["demo", "pwm1"] as [string, string]], identifiers: [["demo", "pwm1"] as [string, string]],
manufacturer: null, manufacturer: null,
model: null, model: null,
model_id: null,
name_by_user: "User name", name_by_user: "User name",
name: "Technical name", name: "Technical name",
sw_version: null, sw_version: null,
@@ -98,6 +106,8 @@ const DEVICES = [
via_device_id: null, via_device_id: null,
serial_number: null, serial_number: null,
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
]; ];
@@ -110,6 +120,8 @@ const AREAS: AreaRegistryEntry[] = [
picture: null, picture: null,
aliases: [], aliases: [],
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
{ {
area_id: "bedroom", area_id: "bedroom",
@@ -119,6 +131,8 @@ const AREAS: AreaRegistryEntry[] = [
picture: null, picture: null,
aliases: [], aliases: [],
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
{ {
area_id: "livingroom", area_id: "livingroom",
@@ -128,6 +142,8 @@ const AREAS: AreaRegistryEntry[] = [
picture: null, picture: null,
aliases: [], aliases: [],
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
]; ];
@@ -138,6 +154,8 @@ const FLOORS: FloorRegistryEntry[] = [
level: 0, level: 0,
icon: null, icon: null,
aliases: [], aliases: [],
created_at: 0,
modified_at: 0,
}, },
{ {
floor_id: "first", floor_id: "first",
@@ -145,6 +163,8 @@ const FLOORS: FloorRegistryEntry[] = [
level: 1, level: 1,
icon: "mdi:numeric-1", icon: "mdi:numeric-1",
aliases: [], aliases: [],
created_at: 0,
modified_at: 0,
}, },
{ {
floor_id: "second", floor_id: "second",
@@ -152,6 +172,8 @@ const FLOORS: FloorRegistryEntry[] = [
level: 2, level: 2,
icon: "mdi:numeric-2", icon: "mdi:numeric-2",
aliases: [], aliases: [],
created_at: 0,
modified_at: 0,
}, },
]; ];
@@ -162,6 +184,8 @@ const LABELS: LabelRegistryEntry[] = [
icon: null, icon: null,
color: "yellow", color: "yellow",
description: null, description: null,
created_at: 0,
modified_at: 0,
}, },
{ {
label_id: "entertainment", label_id: "entertainment",
@@ -169,6 +193,8 @@ const LABELS: LabelRegistryEntry[] = [
icon: "mdi:popcorn", icon: "mdi:popcorn",
color: "blue", color: "blue",
description: null, description: null,
created_at: 0,
modified_at: 0,
}, },
]; ];

View File

@@ -287,11 +287,11 @@ const CONFIGS = [
config: ` config: `
- type: entities - type: entities
entities: entities:
- type: call-service - type: perform-action
icon: mdi:power icon: mdi:power
name: Bed light name: Bed light
action_name: Toggle light action_name: Toggle light
service: light.toggle action: light.toggle
data: data:
entity_id: light.bed_light entity_id: light.bed_light
- type: section - type: section

View File

@@ -0,0 +1,3 @@
---
title: Picture Card
---

View File

@@ -0,0 +1,61 @@
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, query } from "lit/decorators";
import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons";
const ENTITIES = [
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
];
const CONFIGS = [
{
heading: "Image URL",
config: `
- type: picture
image: /images/living_room.png
`,
},
{
heading: "Person entity",
config: `
- type: picture
image_entity: person.paulus
`,
},
{
heading: "Error: Image required",
config: `
- type: picture
entity: person.paulus
`,
},
];
@customElement("demo-lovelace-picture-card")
class DemoPicture extends LitElement {
@query("#demos") private _demoRoot!: HTMLElement;
protected render(): TemplateResult {
return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`;
}
protected firstUpdated(changedProperties: PropertyValues) {
super.firstUpdated(changedProperties);
const hass = provideHass(this._demoRoot);
hass.updateTranslations(null, "en");
hass.updateTranslations("lovelace", "en");
hass.addEntities(ENTITIES);
mockIcons(hass);
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-lovelace-picture-card": DemoPicture;
}
}

View File

@@ -25,6 +25,15 @@ const ENTITIES = [
friendly_name: "Movement Backyard", friendly_name: "Movement Backyard",
device_class: "motion", device_class: "motion",
}), }),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
getEntity("sensor", "battery", 35, {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
}),
]; ];
const CONFIGS = [ const CONFIGS = [
@@ -123,6 +132,19 @@ const CONFIGS = [
left: 35% left: 35%
`, `,
}, },
{
heading: "Person entity",
config: `
- type: picture-elements
image_entity: person.paulus
elements:
- type: state-icon
entity: sensor.battery
style:
top: 8%
left: 8%
`,
},
]; ];
@customElement("demo-lovelace-picture-elements-card") @customElement("demo-lovelace-picture-elements-card")

View File

@@ -12,6 +12,10 @@ const ENTITIES = [
getEntity("light", "bed_light", "off", { getEntity("light", "bed_light", "off", {
friendly_name: "Bed Light", friendly_name: "Bed Light",
}), }),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
]; ];
const CONFIGS = [ const CONFIGS = [
@@ -50,6 +54,13 @@ const CONFIGS = [
entity: camera.demo_camera entity: camera.demo_camera
`, `,
}, },
{
heading: "Person entity",
config: `
- type: picture-entity
entity: person.paulus
`,
},
{ {
heading: "Hidden name", heading: "Hidden name",
config: ` config: `

View File

@@ -20,6 +20,15 @@ const ENTITIES = [
friendly_name: "Basement Floor Wet", friendly_name: "Basement Floor Wet",
device_class: "moisture", device_class: "moisture",
}), }),
getEntity("person", "paulus", "home", {
friendly_name: "Paulus",
entity_picture: "/images/paulus.jpg",
}),
getEntity("sensor", "battery", 35, {
device_class: "battery",
friendly_name: "Battery",
unit_of_measurement: "%",
}),
]; ];
const CONFIGS = [ const CONFIGS = [
@@ -90,6 +99,15 @@ const CONFIGS = [
- light.ceiling_lights - light.ceiling_lights
`, `,
}, },
{
heading: "Person entity",
config: `
- type: picture-glance
image_entity: person.paulus
entities:
- sensor.battery
`,
},
{ {
heading: "Custom icon", heading: "Custom icon",
config: ` config: `

View File

@@ -8,6 +8,7 @@ import { getEntity } from "../../../../src/fake_data/entity";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import "../../components/demo-cards"; import "../../components/demo-cards";
import { mockIcons } from "../../../../demo/src/stubs/icons"; import { mockIcons } from "../../../../demo/src/stubs/icons";
import { ClimateEntityFeature } from "../../../../src/data/climate";
const ENTITIES = [ const ENTITIES = [
getEntity("switch", "tv_outlet", "on", { getEntity("switch", "tv_outlet", "on", {
@@ -60,6 +61,36 @@ const ENTITIES = [
CoverEntityFeature.OPEN_TILT + CoverEntityFeature.OPEN_TILT +
CoverEntityFeature.STOP_TILT, CoverEntityFeature.STOP_TILT,
}), }),
getEntity("input_number", "counter", "1.0", {
friendly_name: "Counter",
initial: 0,
min: 0,
max: 100,
step: 1,
mode: "slider",
}),
getEntity("climate", "dual_thermostat", "heat/cool", {
friendly_name: "Dual thermostat",
hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"],
min_temp: 7,
max_temp: 35,
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
preset_modes: ["home", "eco", "away"],
swing_modes: ["auto", "1", "2", "3", "off"],
current_temperature: 23,
target_temp_high: 24,
target_temp_low: 21,
fan_mode: "auto_low",
preset_mode: "home",
swing_mode: "auto",
supported_features:
ClimateEntityFeature.TURN_ON +
ClimateEntityFeature.TURN_OFF +
ClimateEntityFeature.SWING_MODE +
ClimateEntityFeature.PRESET_MODE +
ClimateEntityFeature.FAN_MODE +
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
}),
]; ];
const CONFIGS = [ const CONFIGS = [
@@ -193,6 +224,25 @@ const CONFIGS = [
- type: "cover-tilt" - type: "cover-tilt"
`, `,
}, },
{
heading: "Number buttons feature",
config: `
- type: tile
entity: input_number.counter
features:
- type: numeric-input
style: buttons
`,
},
{
heading: "Dual thermostat feature",
config: `
- type: tile
entity: climate.dual_thermostat
features:
- type: target-temperature
`,
},
]; ];
@customElement("demo-lovelace-tile-card") @customElement("demo-lovelace-tile-card")

View File

@@ -140,6 +140,9 @@ const ENTITIES: HassEntity[] = [
createEntity("climate.auto_preheating", "auto", undefined, { createEntity("climate.auto_preheating", "auto", undefined, {
hvac_action: "preheating", hvac_action: "preheating",
}), }),
createEntity("climate.auto_defrosting", "auto", undefined, {
hvac_action: "defrosting",
}),
createEntity("climate.auto_heating", "auto", undefined, { createEntity("climate.auto_heating", "auto", undefined, {
hvac_action: "heating", hvac_action: "heating",
}), }),
@@ -355,13 +358,11 @@ export class DemoEntityState extends LitElement {
}, },
entity_id: { entity_id: {
title: "Entity ID", title: "Entity ID",
width: "30%",
filterable: true, filterable: true,
sortable: true, sortable: true,
}, },
state: { state: {
title: "State", title: "State",
width: "20%",
sortable: true, sortable: true,
template: (entry) => template: (entry) =>
html`${computeStateDisplay( html`${computeStateDisplay(
@@ -376,14 +377,12 @@ export class DemoEntityState extends LitElement {
device_class: { device_class: {
title: "Device class", title: "Device class",
template: (entry) => html`${entry.device_class ?? "-"}`, template: (entry) => html`${entry.device_class ?? "-"}`,
width: "20%",
filterable: true, filterable: true,
sortable: true, sortable: true,
}, },
domain: { domain: {
title: "Domain", title: "Domain",
template: (entry) => html`${computeDomain(entry.entity_id)}`, template: (entry) => html`${computeDomain(entry.entity_id)}`,
width: "20%",
filterable: true, filterable: true,
sortable: true, sortable: true,
}, },

View File

@@ -203,6 +203,8 @@ const createEntityRegistryEntries = (
options: null, options: null,
labels: [], labels: [],
categories: {}, categories: {},
created_at: 0,
modified_at: 0,
}, },
]; ];
@@ -215,6 +217,7 @@ const createDeviceRegistryEntries = (
connections: [], connections: [],
manufacturer: "ESPHome", manufacturer: "ESPHome",
model: "Mock Device", model: "Mock Device",
model_id: "ABC-001",
name: "Tag Reader", name: "Tag Reader",
sw_version: null, sw_version: null,
hw_version: "1.0.0", hw_version: "1.0.0",
@@ -227,6 +230,8 @@ const createDeviceRegistryEntries = (
disabled_by: null, disabled_by: null,
configuration_url: null, configuration_url: null,
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
]; ];

View File

@@ -127,14 +127,13 @@ export class HassioBackups extends LitElement {
main: true, main: true,
sortable: true, sortable: true,
filterable: true, filterable: true,
grows: true, flex: 2,
template: (backup) => template: (backup) =>
html`${backup.name || backup.slug} html`${backup.name || backup.slug}
<div class="secondary">${backup.secondary}</div>`, <div class="secondary">${backup.secondary}</div>`,
}, },
size: { size: {
title: this.supervisor.localize("backup.size"), title: this.supervisor.localize("backup.size"),
width: "15%",
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
@@ -142,7 +141,6 @@ export class HassioBackups extends LitElement {
}, },
location: { location: {
title: this.supervisor.localize("backup.location"), title: this.supervisor.localize("backup.location"),
width: "15%",
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,
sortable: true, sortable: true,
@@ -151,7 +149,6 @@ export class HassioBackups extends LitElement {
}, },
date: { date: {
title: this.supervisor.localize("backup.created"), title: this.supervisor.localize("backup.created"),
width: "15%",
direction: "desc", direction: "desc",
hidden: narrow, hidden: narrow,
filterable: true, filterable: true,

View File

@@ -1,6 +1,8 @@
import Fuse from "fuse.js";
import type { IFuseOptions } from "fuse.js"; import type { IFuseOptions } from "fuse.js";
import Fuse from "fuse.js";
import { stripDiacritics } from "../../../src/common/string/strip-diacritics";
import { StoreAddon } from "../../../src/data/supervisor/store"; import { StoreAddon } from "../../../src/data/supervisor/store";
import { getStripDiacriticsFn } from "../../../src/util/fuse";
export function filterAndSort(addons: StoreAddon[], filter: string) { export function filterAndSort(addons: StoreAddon[], filter: string) {
const options: IFuseOptions<StoreAddon> = { const options: IFuseOptions<StoreAddon> = {
@@ -8,7 +10,8 @@ export function filterAndSort(addons: StoreAddon[], filter: string) {
isCaseSensitive: false, isCaseSensitive: false,
minMatchCharLength: Math.min(filter.length, 2), minMatchCharLength: Math.min(filter.length, 2),
threshold: 0.2, threshold: 0.2,
getFn: getStripDiacriticsFn,
}; };
const fuse = new Fuse(addons, options); const fuse = new Fuse(addons, options);
return fuse.search(filter).map((result) => result.item); return fuse.search(stripDiacritics(filter)).map((result) => result.item);
} }

View File

@@ -66,7 +66,8 @@ class HassioRepositoriesDialog extends LitElement {
repo.slug !== "core" && // The core add-ons repository repo.slug !== "core" && // The core add-ons repository
repo.slug !== "local" && // Locally managed add-ons repo.slug !== "local" && // Locally managed add-ons
repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons
repo.slug !== "5c53de3b" // The ESPHome repository repo.slug !== "5c53de3b" && // The ESPHome repository
repo.slug !== "d5369777" // Music Assistant repository
) )
.sort((a, b) => .sort((a, b) =>
caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language) caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language)

View File

@@ -4,11 +4,7 @@
el.src = src; el.src = src;
document.body.appendChild(el); document.body.appendChild(el);
} }
if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) { if (<%= modernRegex %>.test(navigator.userAgent)) {
<% for (const entry of es5EntryJS) { %>
loadES5("<%= entry %>");
<% } %>
} else {
try { try {
<% for (const entry of latestEntryJS) { %> <% for (const entry of latestEntryJS) { %>
new Function("import('<%= entry %>')")(); new Function("import('<%= entry %>')")();
@@ -17,6 +13,10 @@
<% for (const entry of es5EntryJS) { %> <% for (const entry of es5EntryJS) { %>
loadES5("<%= entry %>"); loadES5("<%= entry %>");
<% } %> <% } %>
} else {
<% for (const entry of es5EntryJS) { %>
loadES5("<%= entry %>");
<% } %>
} }
} }
})(); })();

View File

@@ -1,6 +1,5 @@
// Compat needs to be first import // Compat needs to be first import
import "../../src/resources/compatibility"; import "../../src/resources/compatibility";
import "../../src/resources/safari-14-attachshadow-patch";
import "./hassio-main"; import "./hassio-main";
import("../../src/resources/ha-style"); import("../../src/resources/ha-style");

View File

@@ -16,7 +16,7 @@
"lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"", "lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"",
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types && yarn run lint:lit", "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types && yarn run lint:lit",
"format": "yarn run format:eslint && yarn run format:prettier", "format": "yarn run format:eslint && yarn run format:prettier",
"postinstall": "husky install", "postinstall": "husky",
"prepack": "pinst --disable", "prepack": "pinst --disable",
"postpack": "pinst --enable", "postpack": "pinst --enable",
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\"" "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\""
@@ -25,15 +25,15 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@babel/runtime": "7.24.6", "@babel/runtime": "7.25.4",
"@braintree/sanitize-url": "7.0.2", "@braintree/sanitize-url": "7.1.0",
"@codemirror/autocomplete": "6.16.2", "@codemirror/autocomplete": "6.18.0",
"@codemirror/commands": "6.5.0", "@codemirror/commands": "6.6.0",
"@codemirror/language": "6.10.1", "@codemirror/language": "6.10.2",
"@codemirror/legacy-modes": "6.4.0", "@codemirror/legacy-modes": "6.4.1",
"@codemirror/search": "6.5.6", "@codemirror/search": "6.5.6",
"@codemirror/state": "6.4.1", "@codemirror/state": "6.4.1",
"@codemirror/view": "6.26.3", "@codemirror/view": "6.32.0",
"@egjs/hammerjs": "2.0.17", "@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.12.5", "@formatjs/intl-datetimeformat": "6.12.5",
"@formatjs/intl-displaynames": "6.6.8", "@formatjs/intl-displaynames": "6.6.8",
@@ -43,17 +43,17 @@
"@formatjs/intl-numberformat": "8.10.3", "@formatjs/intl-numberformat": "8.10.3",
"@formatjs/intl-pluralrules": "5.2.14", "@formatjs/intl-pluralrules": "5.2.14",
"@formatjs/intl-relativetimeformat": "11.2.14", "@formatjs/intl-relativetimeformat": "11.2.14",
"@fullcalendar/core": "6.1.13", "@fullcalendar/core": "6.1.15",
"@fullcalendar/daygrid": "6.1.13", "@fullcalendar/daygrid": "6.1.15",
"@fullcalendar/interaction": "6.1.13", "@fullcalendar/interaction": "6.1.15",
"@fullcalendar/list": "6.1.13", "@fullcalendar/list": "6.1.15",
"@fullcalendar/luxon3": "6.1.13", "@fullcalendar/luxon3": "6.1.15",
"@fullcalendar/timegrid": "6.1.13", "@fullcalendar/timegrid": "6.1.15",
"@lezer/highlight": "1.2.0", "@lezer/highlight": "1.2.1",
"@lit-labs/context": "0.4.1", "@lit-labs/context": "0.4.1",
"@lit-labs/motion": "1.0.7", "@lit-labs/motion": "1.0.7",
"@lit-labs/observers": "2.0.2", "@lit-labs/observers": "2.0.2",
"@lit-labs/virtualizer": "2.0.12", "@lit-labs/virtualizer": "2.0.14",
"@lrnwebcomponents/simple-tooltip": "8.0.2", "@lrnwebcomponents/simple-tooltip": "8.0.2",
"@material/chips": "=14.0.0-canary.53b3cad2f.0", "@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0", "@material/data-table": "=14.0.0-canary.53b3cad2f.0",
@@ -80,7 +80,7 @@
"@material/mwc-top-app-bar": "0.27.0", "@material/mwc-top-app-bar": "0.27.0",
"@material/mwc-top-app-bar-fixed": "0.27.0", "@material/mwc-top-app-bar-fixed": "0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0", "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
"@material/web": "1.5.0", "@material/web": "2.1.0",
"@mdi/js": "7.4.47", "@mdi/js": "7.4.47",
"@mdi/svg": "7.4.47", "@mdi/svg": "7.4.47",
"@polymer/paper-item": "3.0.1", "@polymer/paper-item": "3.0.1",
@@ -88,8 +88,8 @@
"@polymer/paper-tabs": "3.1.0", "@polymer/paper-tabs": "3.1.0",
"@polymer/polymer": "3.5.1", "@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0", "@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.3.13", "@vaadin/combo-box": "24.4.6",
"@vaadin/vaadin-themable-mixin": "24.3.13", "@vaadin/vaadin-themable-mixin": "24.4.6",
"@vibrant/color": "3.2.1-alpha.1", "@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1", "@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
@@ -97,10 +97,10 @@
"@webcomponents/scoped-custom-element-registry": "0.0.9", "@webcomponents/scoped-custom-element-registry": "0.0.9",
"@webcomponents/webcomponentsjs": "2.8.0", "@webcomponents/webcomponentsjs": "2.8.0",
"app-datepicker": "5.1.1", "app-datepicker": "5.1.1",
"chart.js": "4.4.3", "chart.js": "4.4.4",
"color-name": "2.0.0", "color-name": "2.0.0",
"comlink": "4.4.1", "comlink": "4.4.1",
"core-js": "3.37.1", "core-js": "3.38.1",
"cropperjs": "1.6.2", "cropperjs": "1.6.2",
"date-fns": "3.6.0", "date-fns": "3.6.0",
"date-fns-tz": "3.1.3", "date-fns-tz": "3.1.3",
@@ -110,27 +110,27 @@
"fuse.js": "7.0.0", "fuse.js": "7.0.0",
"google-timezones-json": "1.2.0", "google-timezones-json": "1.2.0",
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch", "hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
"home-assistant-js-websocket": "9.3.0", "home-assistant-js-websocket": "9.4.0",
"idb-keyval": "6.2.1", "idb-keyval": "6.2.1",
"intl-messageformat": "10.5.14", "intl-messageformat": "10.5.14",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"leaflet": "1.9.4", "leaflet": "1.9.4",
"leaflet-draw": "1.0.4", "leaflet-draw": "1.0.4",
"lit": "2.8.0", "lit": "2.8.0",
"luxon": "3.4.4", "luxon": "3.5.0",
"marked": "12.0.2", "marked": "14.0.0",
"memoize-one": "6.0.0", "memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1", "node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2", "proxy-polyfill": "0.3.2",
"punycode": "2.3.1", "punycode": "2.3.1",
"qr-scanner": "1.4.2", "qr-scanner": "1.4.2",
"qrcode": "1.5.3", "qrcode": "1.5.4",
"roboto-fontface": "0.10.0", "roboto-fontface": "0.10.0",
"rrule": "2.8.1", "rrule": "2.8.1",
"sortablejs": "1.15.2", "sortablejs": "1.15.2",
"stacktrace-js": "2.0.2", "stacktrace-js": "2.0.2",
"superstruct": "1.0.4", "superstruct": "2.0.2",
"tinykeys": "2.1.0", "tinykeys": "3.0.0",
"tsparticles-engine": "2.12.0", "tsparticles-engine": "2.12.0",
"tsparticles-preset-links": "2.12.0", "tsparticles-preset-links": "2.12.0",
"ua-parser-js": "1.0.38", "ua-parser-js": "1.0.38",
@@ -149,26 +149,26 @@
"xss": "1.0.15" "xss": "1.0.15"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.24.6", "@babel/core": "7.25.2",
"@babel/helper-define-polyfill-provider": "0.6.2", "@babel/helper-define-polyfill-provider": "0.6.2",
"@babel/plugin-proposal-decorators": "7.24.6", "@babel/plugin-proposal-decorators": "7.24.7",
"@babel/plugin-transform-runtime": "7.24.6", "@babel/plugin-transform-runtime": "7.25.4",
"@babel/preset-env": "7.24.6", "@babel/preset-env": "7.25.4",
"@babel/preset-typescript": "7.24.6", "@babel/preset-typescript": "7.24.7",
"@bundle-stats/plugin-webpack-filter": "4.13.2", "@bundle-stats/plugin-webpack-filter": "4.14.2",
"@koa/cors": "5.0.0", "@koa/cors": "5.0.0",
"@lokalise/node-api": "12.5.0", "@lokalise/node-api": "12.7.0",
"@octokit/auth-oauth-device": "7.1.1", "@octokit/auth-oauth-device": "7.1.1",
"@octokit/plugin-retry": "7.1.1", "@octokit/plugin-retry": "7.1.1",
"@octokit/rest": "20.1.1", "@octokit/rest": "21.0.2",
"@open-wc/dev-server-hmr": "0.1.4", "@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.4", "@rollup/plugin-babel": "6.0.4",
"@rollup/plugin-commonjs": "25.0.8", "@rollup/plugin-commonjs": "26.0.1",
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-node-resolve": "15.2.3", "@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-replace": "5.0.5", "@rollup/plugin-replace": "5.0.7",
"@types/babel__plugin-transform-runtime": "7.9.5", "@types/babel__plugin-transform-runtime": "7.9.5",
"@types/chromecast-caf-receiver": "6.0.14", "@types/chromecast-caf-receiver": "6.0.17",
"@types/chromecast-caf-sender": "1.0.10", "@types/chromecast-caf-sender": "1.0.10",
"@types/color-name": "1.1.4", "@types/color-name": "1.1.4",
"@types/glob": "8.1.0", "@types/glob": "8.1.0",
@@ -178,19 +178,20 @@
"@types/leaflet-draw": "1.0.11", "@types/leaflet-draw": "1.0.11",
"@types/lodash.merge": "4.6.9", "@types/lodash.merge": "4.6.9",
"@types/luxon": "3.4.2", "@types/luxon": "3.4.2",
"@types/mocha": "10.0.6", "@types/mocha": "10.0.7",
"@types/qrcode": "1.5.5", "@types/qrcode": "1.5.5",
"@types/serve-handler": "6.1.4", "@types/serve-handler": "6.1.4",
"@types/sortablejs": "1.15.8", "@types/sortablejs": "1.15.8",
"@types/tar": "6.1.13", "@types/tar": "6.1.13",
"@types/ua-parser-js": "0.7.39", "@types/ua-parser-js": "0.7.39",
"@types/webspeechapi": "0.0.29", "@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "7.11.0", "@typescript-eslint/eslint-plugin": "7.18.0",
"@typescript-eslint/parser": "7.11.0", "@typescript-eslint/parser": "7.18.0",
"@web/dev-server": "0.1.38", "@web/dev-server": "0.1.38",
"@web/dev-server-rollup": "0.4.1", "@web/dev-server-rollup": "0.4.1",
"babel-loader": "9.1.3", "babel-loader": "9.1.3",
"babel-plugin-template-html-minifier": "4.1.0", "babel-plugin-template-html-minifier": "4.1.0",
"browserslist-useragent-regexp": "4.1.3",
"chai": "5.1.1", "chai": "5.1.1",
"del": "7.1.0", "del": "7.1.0",
"eslint": "8.57.0", "eslint": "8.57.0",
@@ -200,51 +201,51 @@
"eslint-import-resolver-webpack": "0.13.8", "eslint-import-resolver-webpack": "0.13.8",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-lit": "1.14.0", "eslint-plugin-lit": "1.14.0",
"eslint-plugin-lit-a11y": "4.1.2", "eslint-plugin-lit-a11y": "4.1.4",
"eslint-plugin-unused-imports": "4.0.0", "eslint-plugin-unused-imports": "4.1.3",
"eslint-plugin-wc": "2.1.0", "eslint-plugin-wc": "2.1.1",
"fancy-log": "2.0.0", "fancy-log": "2.0.0",
"fs-extra": "11.2.0", "fs-extra": "11.2.0",
"glob": "10.4.1", "glob": "11.0.0",
"gulp": "5.0.0", "gulp": "5.0.0",
"gulp-brotli": "3.0.0",
"gulp-json-transform": "0.5.0", "gulp-json-transform": "0.5.0",
"gulp-rename": "2.0.0", "gulp-rename": "2.0.0",
"gulp-zopfli-green": "6.0.1", "gulp-zopfli-green": "6.0.2",
"html-minifier-terser": "7.2.0", "html-minifier-terser": "7.2.0",
"husky": "9.0.11", "husky": "9.1.5",
"instant-mocha": "1.5.2", "instant-mocha": "1.5.2",
"jszip": "3.10.1", "jszip": "3.10.1",
"lint-staged": "15.2.5", "lint-staged": "15.2.9",
"lit-analyzer": "2.0.3", "lit-analyzer": "2.0.3",
"lodash.merge": "4.6.2", "lodash.merge": "4.6.2",
"lodash.template": "4.5.0", "lodash.template": "4.5.0",
"magic-string": "0.30.10", "magic-string": "0.30.11",
"map-stream": "0.0.7", "map-stream": "0.0.7",
"mocha": "10.4.0", "mocha": "10.5.0",
"object-hash": "3.0.0", "object-hash": "3.0.0",
"open": "10.1.0", "open": "10.1.0",
"pinst": "3.0.0", "pinst": "3.0.0",
"prettier": "3.2.5", "prettier": "3.3.3",
"rollup": "2.79.1", "rollup": "2.79.1",
"rollup-plugin-string": "3.0.0", "rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2", "rollup-plugin-terser": "7.0.2",
"rollup-plugin-visualizer": "5.12.0", "rollup-plugin-visualizer": "5.12.0",
"serve-handler": "6.1.5", "serve-handler": "6.1.5",
"sinon": "18.0.0", "sinon": "18.0.0",
"source-map-url": "0.4.1",
"systemjs": "6.15.1", "systemjs": "6.15.1",
"tar": "7.2.0", "tar": "7.4.3",
"terser-webpack-plugin": "5.3.10", "terser-webpack-plugin": "5.3.10",
"transform-async-modules-webpack-plugin": "1.1.1", "transform-async-modules-webpack-plugin": "1.1.1",
"ts-lit-plugin": "2.0.2", "ts-lit-plugin": "2.0.2",
"typescript": "5.4.5", "typescript": "5.5.4",
"webpack": "5.91.0", "webpack": "5.94.0",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"webpack-dev-server": "5.0.4", "webpack-dev-server": "5.0.4",
"webpack-manifest-plugin": "5.0.0", "webpack-manifest-plugin": "5.0.0",
"webpack-stats-plugin": "1.1.3", "webpack-stats-plugin": "1.1.3",
"webpackbar": "6.0.1", "webpackbar": "6.0.1",
"workbox-build": "7.1.1" "workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
}, },
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch", "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": { "resolutions": {
@@ -253,8 +254,9 @@
"lit": "2.8.0", "lit": "2.8.0",
"clean-css": "5.3.3", "clean-css": "5.3.3",
"@lit/reactive-element": "1.6.3", "@lit/reactive-element": "1.6.3",
"@fullcalendar/daygrid": "6.1.15",
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch", "sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch" "leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
}, },
"packageManager": "yarn@4.2.2" "packageManager": "yarn@4.4.0"
} }

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 430 B

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="b" data-name="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760.69 138.69">
<g id="c" data-name="Layer_1">
<g>
<g>
<path d="M136.22,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM136.27,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
<path d="M184.16,80.53c0,3.47-1.06,6.27-3.18,8.41s-4.98,3.21-8.59,3.21h-7.45v12h-6.56v-35.18h14.06c3.64,0,6.5,1.04,8.59,3.11s3.13,4.89,3.13,8.45ZM177.25,80.39c0-1.64-.52-2.98-1.56-4.03s-2.52-1.57-4.44-1.57h-6.3v11.65h6.26c1.95,0,3.45-.55,4.49-1.65s1.56-2.57,1.56-4.39Z"/>
<path d="M210.82,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/>
<path d="M246.95,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
<path d="M266.45,68.98h6.56v14.44l14.7.05v-14.48h6.63v35.18h-6.63v-14.84l-14.7-.09v14.93h-6.56v-35.18Z"/>
<path d="M316.41,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM316.46,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
<path d="M373.66,68.98v35.18h-6.45v-20.55l-8.11,20.55h-6.23l-8.02-20.39v20.39h-6.28v-35.18h6.28l11.13,27.54,11.23-27.54h6.45Z"/>
<path d="M402.87,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/>
<path d="M427.83,75.12v8.93h13.01l-.05,5.91h-12.96v14.2h-6.52v-35.18h21.98l-.05,6.14h-15.42Z"/>
<path d="M463.16,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM463.21,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
<path d="M485,68.98h6.56v22.12c0,2.31.72,4.12,2.16,5.43s3.3,1.96,5.58,1.96,4.08-.67,5.58-2.02,2.25-3.13,2.25-5.37v-22.12h6.52v22.31c0,2.08-.38,3.98-1.14,5.7s-1.79,3.14-3.09,4.25-2.82,1.98-4.56,2.59-3.59.91-5.55.91c-2.59,0-4.96-.52-7.1-1.55s-3.88-2.58-5.2-4.65-1.99-4.49-1.99-7.25v-22.31Z"/>
<path d="M549.63,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
<path d="M586.9,86.58c.05,3.34-.71,6.37-2.27,9.08s-3.7,4.82-6.42,6.32-5.73,2.23-9.02,2.18h-12.42v-35.18h12.42c2.45-.03,4.78.39,6.98,1.28s4.1,2.1,5.68,3.66,2.84,3.43,3.75,5.64,1.35,4.55,1.3,7.03ZM579.99,86.58c0-3.39-1-6.16-3.01-8.3s-4.62-3.21-7.84-3.21h-5.81v23.04h5.81c3.27,0,5.89-1.06,7.88-3.19s2.98-4.91,2.98-8.34Z"/>
<path d="M609.16,96.19h-12.73l-2.79,7.97h-6.82l12.68-35.18h6.63l12.66,35.18h-6.96l-2.67-7.97ZM607.24,90.73l-4.43-12.87-4.45,12.87h8.88Z"/>
<path d="M642.87,75.17h-9.89v28.99h-6.56v-28.99h-9.94v-6.19h26.39v6.19Z"/>
<path d="M647.06,104.16v-35.18h6.56v35.18h-6.56Z"/>
<path d="M675.71,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM675.76,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
<path d="M726.96,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
</g>
<g>
<path d="M94.34,79.34c0,2.75-2.25,5-5,5h-50c-2.75,0-5-2.25-5-5v-20c0-2.75,1.59-6.59,3.54-8.54l22.93-22.93c1.94-1.94,5.13-1.94,7.07,0l22.93,22.93c1.94,1.94,3.54,5.79,3.54,8.54v20Z"/>
<g>
<rect x="34.34" y="94.34" width="60" height="10" rx="2.5" ry="2.5"/>
<rect x="34.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/>
<rect x="84.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/>
</g>
</g>
<path d="M735.34,3c12.32,0,22.34,10.02,22.34,22.34v88c0,12.32-10.02,22.34-22.34,22.34H25.34c-12.32,0-22.34-10.02-22.34-22.34V25.34C3,13.02,13.02,3,25.34,3h710M735.34,0H25.34C11.37,0,0,11.37,0,25.34v88c0,13.98,11.37,25.34,25.34,25.34h710c13.97,0,25.34-11.37,25.34-25.34V25.34c0-13.98-11.37-25.34-25.34-25.34h0Z"/>
<g>
<path d="M120.98,36.79h2.95v7.26l7.66.02v-7.29h2.97v17.37h-2.97v-7.47l-7.66-.02v7.49h-2.95v-17.37Z"/>
<path d="M146.97,36.47c1.63,0,3.09.39,4.37,1.16s2.28,1.84,2.99,3.2,1.06,2.9,1.06,4.61c.02,1.7-.32,3.24-1.04,4.62s-1.72,2.47-3.02,3.25-2.75,1.16-4.36,1.14c-1.62.02-3.08-.36-4.37-1.14s-2.29-1.86-3-3.24-1.05-2.91-1.03-4.61c0-1.27.2-2.47.61-3.58s.99-2.08,1.72-2.88,1.63-1.42,2.68-1.88,2.18-.67,3.39-.66ZM146.99,51.57c1.6,0,2.89-.56,3.85-1.67s1.45-2.6,1.45-4.45-.48-3.32-1.45-4.43-2.25-1.66-3.85-1.66-2.89.55-3.86,1.66-1.45,2.58-1.45,4.43.48,3.34,1.44,4.46,2.25,1.67,3.88,1.67Z"/>
<path d="M176.51,36.79v17.37h-2.89v-10.78l-4.29,10.78h-2.81l-4.25-10.71v10.71h-2.84v-17.37h2.84l5.66,13.92,5.69-13.92h2.89Z"/>
<path d="M192.41,51.37v2.79h-10.78v-17.37h10.78v2.81h-7.83v4.5h7v2.61h-7v4.66h7.83Z"/>
<path d="M213.93,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM213.07,47.62l-2.43-6.95-2.45,6.95h4.88Z"/>
<path d="M226.96,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
<path d="M242.38,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
<path d="M252.68,54.16v-17.37h2.95v17.37h-2.95Z"/>
<path d="M265.82,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
<path d="M287.47,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/>
<path d="M298.87,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM298.01,47.62l-2.43-6.95-2.45,6.95h4.88Z"/>
<path d="M320.89,36.79v17.37h-2.93l-8.25-12.67v12.67h-2.93v-17.37h2.93l8.25,12.65v-12.65h2.93Z"/>
<path d="M337.31,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/>
<path d="M348.75,54.16v-17.14h2.05v17.14h-2.05Z"/>
<path d="M360.95,36.72c1.55,0,2.82.38,3.81,1.14,1,.74,1.61,1.72,1.82,2.95l-1.95.52c-.16-.87-.56-1.54-1.23-2.02s-1.5-.71-2.5-.71c-1.08,0-1.95.27-2.6.8s-.97,1.24-.97,2.13c0,1.36.84,2.26,2.52,2.71l2.9.73c1.37.34,2.41.9,3.11,1.68s1.05,1.74,1.05,2.9c0,1.46-.53,2.64-1.6,3.54s-2.49,1.36-4.28,1.36c-1.61,0-2.95-.37-4.03-1.12s-1.72-1.76-1.95-3.06l1.98-.55c.13.88.55,1.56,1.25,2.06s1.63.75,2.77.75,2.12-.26,2.79-.77,1.02-1.22,1.02-2.14c0-1.44-.84-2.36-2.52-2.77l-2.88-.71c-1.38-.34-2.42-.9-3.12-1.69s-1.05-1.75-1.05-2.9c0-1.44.52-2.61,1.56-3.51s2.4-1.35,4.09-1.35Z"/>
<path d="M388.35,49.75h-7.54l-1.59,4.4h-2.07l6.25-17.14h2.36l6.31,17.14h-2.15l-1.57-4.4ZM387.73,48.05l-3.09-8.71-3.2,8.71h6.29Z"/>
<path d="M415.46,42.47c0,1.6-.5,2.91-1.5,3.95s-2.32,1.56-3.97,1.56h-4.53v6.18h-2.05v-17.14h6.6c1.67,0,3,.49,3.98,1.47s1.47,2.31,1.47,3.98ZM413.31,42.42c0-1.07-.32-1.92-.95-2.56s-1.51-.96-2.64-.96h-4.26v7.24h4.17c1.15,0,2.06-.34,2.71-1.02s.98-1.58.98-2.7Z"/>
<path d="M428.37,46.9l3.43,7.26h-2.31l-3.18-6.95h-4.76v6.95h-2.05v-17.14h6.54c1.81,0,3.22.45,4.24,1.35s1.53,2.14,1.53,3.72c0,1.22-.3,2.26-.9,3.1s-1.44,1.41-2.53,1.7ZM429.64,42.12c0-1.01-.32-1.81-.95-2.38s-1.52-.86-2.66-.86h-4.5v6.47h4.53c1.15,0,2.03-.28,2.64-.85s.92-1.36.92-2.38Z"/>
<path d="M443.34,36.74c1.18-.02,2.28.2,3.31.65s1.9,1.07,2.62,1.85,1.28,1.73,1.69,2.83.6,2.27.59,3.52c.02,1.67-.33,3.19-1.03,4.54s-1.68,2.42-2.95,3.19-2.68,1.14-4.25,1.12c-1.59,0-3-.38-4.25-1.13s-2.21-1.81-2.89-3.15-1.03-2.87-1.03-4.57c-.02-1.67.32-3.18,1.02-4.53s1.67-2.42,2.93-3.2,2.68-1.15,4.24-1.13ZM443.34,52.45c1.8,0,3.26-.64,4.38-1.91s1.68-2.92,1.68-4.95-.56-3.71-1.68-4.98-2.58-1.9-4.38-1.9-3.28.63-4.4,1.9-1.68,2.93-1.68,4.98.56,3.69,1.68,4.96,2.59,1.9,4.39,1.9Z"/>
<path d="M464.3,37.02v12.42c0,1.49-.47,2.71-1.41,3.64s-2.17,1.39-3.71,1.39c-1.56,0-2.76-.46-3.61-1.37s-1.27-2.13-1.27-3.69v-.55h1.98v.55c0,1.18.29,1.99.86,2.45.59.45,1.26.67,2.02.67.93,0,1.68-.26,2.24-.78.57-.52.85-1.32.85-2.39v-12.35h2.05Z"/>
<path d="M479.86,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/>
<path d="M496.97,42.42c-.36-1.15-1.01-2.06-1.93-2.71s-2.02-.98-3.3-.98c-1.79,0-3.24.63-4.35,1.89s-1.66,2.92-1.66,4.96.55,3.72,1.66,4.96,2.55,1.86,4.33,1.86c1.27,0,2.39-.31,3.35-.94.95-.63,1.61-1.44,1.98-2.45l1.93.83c-.55,1.41-1.48,2.53-2.78,3.36-1.31.81-2.81,1.22-4.51,1.22-2.4,0-4.35-.81-5.84-2.43s-2.24-3.75-2.24-6.4c0-1.74.34-3.28,1.02-4.62s1.64-2.39,2.88-3.13,2.65-1.11,4.25-1.11c1.8,0,3.33.46,4.59,1.38,1.26.91,2.12,2.1,2.57,3.59l-1.93.71Z"/>
<path d="M512.92,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/>
<path d="M536.4,49.66c0,1.39-.49,2.48-1.46,3.29s-2.27,1.21-3.9,1.21h-6.72v-17.12h6.58c1.65,0,2.95.41,3.89,1.24s1.41,1.93,1.41,3.32c0,.92-.21,1.72-.64,2.4s-1.02,1.2-1.79,1.57c.81.37,1.45.91,1.92,1.62s.7,1.53.7,2.47ZM526.3,38.81v5.97h4.54c1.03,0,1.85-.27,2.46-.81s.92-1.24.92-2.09c0-.91-.31-1.65-.93-2.22s-1.45-.85-2.5-.85h-4.5ZM534.45,49.39c0-.91-.32-1.64-.95-2.17s-1.44-.8-2.46-.8h-4.74v5.95h4.74c1.04,0,1.86-.27,2.48-.81s.92-1.26.92-2.16Z"/>
<path d="M541.07,37.02l4.54,8.1,4.54-8.1h2.24l-5.76,10.05v7.09h-2.05v-7.09l-5.83-10.05h2.31Z"/>
<path d="M574.27,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/>
<path d="M577.95,37.02h2.05v7.55l8.74.02v-7.58h2.05v17.14h-2.05v-7.76l-8.74-.02v7.79h-2.05v-17.14Z"/>
<path d="M606.55,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20240604.0" version = "20240809.0"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "The Home Assistant frontend" description = "The Home Assistant frontend"
readme = "README.md" readme = "README.md"

View File

@@ -37,8 +37,7 @@
{ {
"description": "Group tsparticles engine and presets", "description": "Group tsparticles engine and presets",
"groupName": "tsparticles", "groupName": "tsparticles",
"matchPackageNames": ["tsparticles-engine"], "matchPackageNames": ["tsparticles-engine", "tsparticles-preset-{/,}**"]
"matchPackagePrefixes": ["tsparticles-preset-"]
}, },
{ {
"description": "Group date-fns with dependent timezone package", "description": "Group date-fns with dependent timezone package",
@@ -48,8 +47,8 @@
{ {
"description": "Group and temporarily disable WDS packages", "description": "Group and temporarily disable WDS packages",
"groupName": "Web Dev Server", "groupName": "Web Dev Server",
"matchPackagePrefixes": ["@web/dev-server"], "enabled": false,
"enabled": false "matchPackageNames": ["@web/dev-server{/,}**"]
} }
] ]
} }

View File

@@ -31,6 +31,7 @@ import {
mdiFormatListBulleted, mdiFormatListBulleted,
mdiFormatListCheckbox, mdiFormatListCheckbox,
mdiFormTextbox, mdiFormTextbox,
mdiForumOutline,
mdiGauge, mdiGauge,
mdiGoogleAssistant, mdiGoogleAssistant,
mdiGoogleCirclesCommunities, mdiGoogleCirclesCommunities,
@@ -39,7 +40,6 @@ import {
mdiImageFilterFrames, mdiImageFilterFrames,
mdiLightbulb, mdiLightbulb,
mdiLightningBolt, mdiLightningBolt,
mdiMailbox,
mdiMapMarkerRadius, mdiMapMarkerRadius,
mdiMeterGas, mdiMeterGas,
mdiMicrophoneMessage, mdiMicrophoneMessage,
@@ -98,7 +98,7 @@ export const FIXED_DOMAIN_ICONS = {
calendar: mdiCalendar, calendar: mdiCalendar,
climate: mdiThermostat, climate: mdiThermostat,
configurator: mdiCog, configurator: mdiCog,
conversation: mdiMicrophoneMessage, conversation: mdiForumOutline,
counter: mdiCounter, counter: mdiCounter,
date: mdiCalendar, date: mdiCalendar,
datetime: mdiCalendarClock, datetime: mdiCalendarClock,
@@ -118,7 +118,6 @@ export const FIXED_DOMAIN_ICONS = {
input_text: mdiFormTextbox, input_text: mdiFormTextbox,
lawn_mower: mdiRobotMower, lawn_mower: mdiRobotMower,
light: mdiLightbulb, light: mdiLightbulb,
mailbox: mdiMailbox,
notify: mdiCommentAlert, notify: mdiCommentAlert,
number: mdiRayVertex, number: mdiRayVertex,
persistent_notification: mdiBell, persistent_notification: mdiBell,
@@ -235,6 +234,8 @@ export const SENSOR_ENTITIES = [
"weather", "weather",
]; ];
export const ASSIST_ENTITIES = ["conversation", "stt", "tts"];
/** Domains that render an input element instead of a text value when displayed in a row. /** Domains that render an input element instead of a text value when displayed in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally * Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements * be the default) unless the element itself enforces it (e.g. a button). Also those elements

View File

@@ -0,0 +1 @@
export const preventDefault = (ev) => ev.preventDefault();

View File

@@ -26,7 +26,7 @@ export const FIXED_DOMAIN_STATES = {
humidifier: ["on", "off"], humidifier: ["on", "off"],
input_boolean: ["on", "off"], input_boolean: ["on", "off"],
input_button: [], input_button: [],
lawn_mower: ["error", "paused", "mowing", "docked"], lawn_mower: ["error", "paused", "mowing", "returning", "docked"],
light: ["on", "off"], light: ["on", "off"],
lock: [ lock: [
"jammed", "jammed",
@@ -125,6 +125,7 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = {
"off", "off",
"idle", "idle",
"preheating", "preheating",
"defrosting",
"heating", "heating",
"cooling", "cooling",
"drying", "drying",

View File

@@ -25,7 +25,9 @@ export const navigate = (path: string, options?: NavigateOptions) => {
if (__DEMO__) { if (__DEMO__) {
if (replace) { if (replace) {
mainWindow.history.replaceState( mainWindow.history.replaceState(
mainWindow.history.state?.root ? { root: true } : options?.data ?? null, mainWindow.history.state?.root
? { root: true }
: (options?.data ?? null),
"", "",
`${mainWindow.location.pathname}#${path}` `${mainWindow.location.pathname}#${path}`
); );
@@ -34,7 +36,7 @@ export const navigate = (path: string, options?: NavigateOptions) => {
} }
} else if (replace) { } else if (replace) {
mainWindow.history.replaceState( mainWindow.history.replaceState(
mainWindow.history.state?.root ? { root: true } : options?.data ?? null, mainWindow.history.state?.root ? { root: true } : (options?.data ?? null),
"", "",
path path
); );

View File

@@ -1,3 +1,4 @@
import { stripDiacritics } from "../strip-diacritics";
import { fuzzyScore } from "./filter"; import { fuzzyScore } from "./filter";
/** /**
@@ -19,10 +20,10 @@ export const fuzzySequentialMatch = (
for (const word of item.strings) { for (const word of item.strings) {
const scores = fuzzyScore( const scores = fuzzyScore(
filter, filter,
filter.toLowerCase(), stripDiacritics(filter.toLowerCase()),
0, 0,
word, word,
word.toLowerCase(), stripDiacritics(word.toLowerCase()),
0, 0,
true true
); );

View File

@@ -0,0 +1,2 @@
export const stripDiacritics = (str: string) =>
str.normalize("NFD").replace(/[\u0300-\u036F]/g, "");

View File

@@ -1,5 +1,6 @@
import { LitElement, TemplateResult, html } from "lit"; import { LitElement, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { HassServiceTarget } from "home-assistant-js-websocket";
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
import "./ha-progress-button"; import "./ha-progress-button";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
@@ -17,7 +18,9 @@ class HaCallServiceButton extends LitElement {
@property() public service!: string; @property() public service!: string;
@property({ type: Object }) public serviceData = {}; @property({ type: Object }) public target!: HassServiceTarget;
@property({ type: Object }) public data = {};
@property() public confirmation?; @property() public confirmation?;
@@ -39,7 +42,8 @@ class HaCallServiceButton extends LitElement {
const eventData = { const eventData = {
domain: this.domain, domain: this.domain,
service: this.service, service: this.service,
serviceData: this.serviceData, data: this.data,
target: this.target,
success: false, success: false,
}; };
@@ -47,7 +51,12 @@ class HaCallServiceButton extends LitElement {
this.shadowRoot!.querySelector("ha-progress-button")!; this.shadowRoot!.querySelector("ha-progress-button")!;
try { try {
await this.hass.callService(this.domain, this.service, this.serviceData); await this.hass.callService(
this.domain,
this.service,
this.data,
this.target
);
this.progress = false; this.progress = false;
progressElement.actionSuccess(); progressElement.actionSuccess();
eventData.success = true; eventData.success = true;
@@ -85,7 +94,8 @@ declare global {
"hass-service-called": { "hass-service-called": {
domain: string; domain: string;
service: string; service: string;
serviceData: object; target: HassServiceTarget;
data: object;
success: boolean; success: boolean;
}; };
} }

View File

@@ -159,10 +159,10 @@ export class StateHistoryChartTimeline extends LitElement {
}, },
afterUpdate: (y) => { afterUpdate: (y) => {
const yWidth = this.showNames const yWidth = this.showNames
? y.width ?? 0 ? (y.width ?? 0)
: computeRTL(this.hass) : computeRTL(this.hass)
? 0 ? 0
: y.left ?? 0; : (y.left ?? 0);
if ( if (
this._yWidth !== Math.floor(yWidth) && this._yWidth !== Math.floor(yWidth) &&
y.ticks.length === this.data.length y.ticks.length === this.data.length

View File

@@ -0,0 +1,319 @@
import "@material/mwc-list";
import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js";
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { createCloseHeading } from "../ha-dialog";
import "../ha-list-item";
import "../ha-sortable";
import "../ha-button";
import { DataTableColumnContainer, DataTableColumnData } from "./ha-data-table";
import { DataTableSettingsDialogParams } from "./show-dialog-data-table-settings";
import { fireEvent } from "../../common/dom/fire_event";
@customElement("dialog-data-table-settings")
export class DialogDataTableSettings extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: DataTableSettingsDialogParams;
@state() private _columnOrder?: string[];
@state() private _hiddenColumns?: string[];
public showDialog(params: DataTableSettingsDialogParams) {
this._params = params;
this._columnOrder = params.columnOrder;
this._hiddenColumns = params.hiddenColumns;
}
public closeDialog() {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
private _sortedColumns = memoizeOne(
(
columns: DataTableColumnContainer,
columnOrder: string[] | undefined,
hiddenColumns: string[] | undefined
) =>
Object.keys(columns)
.filter((col) => !columns[col].hidden)
.sort((a, b) => {
const orderA = columnOrder?.indexOf(a) ?? -1;
const orderB = columnOrder?.indexOf(b) ?? -1;
const hiddenA =
hiddenColumns?.includes(a) ?? Boolean(columns[a].defaultHidden);
const hiddenB =
hiddenColumns?.includes(b) ?? Boolean(columns[b].defaultHidden);
if (hiddenA !== hiddenB) {
return hiddenA ? 1 : -1;
}
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
}
if (orderB === -1) {
return -1;
}
}
return orderA - orderB;
})
.reduce(
(arr, key) => {
arr.push({ key, ...columns[key] });
return arr;
},
[] as (DataTableColumnData & { key: string })[]
)
);
protected render() {
if (!this._params) {
return nothing;
}
const localize = this._params.localizeFunc || this.hass.localize;
const columns = this._sortedColumns(
this._params.columns,
this._columnOrder,
this._hiddenColumns
);
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
localize("ui.components.data-table.settings.header")
)}
>
<ha-sortable
@item-moved=${this._columnMoved}
draggable-selector=".draggable"
handle-selector=".handle"
>
<mwc-list>
${repeat(
columns,
(col) => col.key,
(col, _idx) => {
const canMove = !col.main && col.moveable !== false;
const canHide = !col.main && col.hideable !== false;
const isVisible = !(this._columnOrder &&
this._columnOrder.includes(col.key)
? (this._hiddenColumns?.includes(col.key) ??
col.defaultHidden)
: col.defaultHidden);
return html`<ha-list-item
hasMeta
class=${classMap({
hidden: !isVisible,
draggable: canMove && isVisible,
})}
graphic="icon"
noninteractive
>${col.title || col.label || col.key}
${canMove && isVisible
? html`<ha-svg-icon
class="handle"
.path=${mdiDrag}
slot="graphic"
></ha-svg-icon>`
: nothing}
<ha-icon-button
tabindex="0"
class="action"
.disabled=${!canHide}
.hidden=${!isVisible}
.path=${isVisible ? mdiEye : mdiEyeOff}
slot="meta"
.label=${this.hass!.localize(
`ui.components.data-table.settings.${isVisible ? "hide" : "show"}`,
{ title: typeof col.title === "string" ? col.title : "" }
)}
.column=${col.key}
@click=${this._toggle}
></ha-icon-button>
</ha-list-item>`;
}
)}
</mwc-list>
</ha-sortable>
<ha-button slot="secondaryAction" @click=${this._reset}
>${localize("ui.components.data-table.settings.restore")}</ha-button
>
<ha-button slot="primaryAction" @click=${this.closeDialog}>
${localize("ui.components.data-table.settings.done")}
</ha-button>
</ha-dialog>
`;
}
private _columnMoved(ev: CustomEvent): void {
ev.stopPropagation();
if (!this._params) {
return;
}
const { oldIndex, newIndex } = ev.detail;
const columns = this._sortedColumns(
this._params.columns,
this._columnOrder,
this._hiddenColumns
);
const columnOrder = columns.map((column) => column.key);
const option = columnOrder.splice(oldIndex, 1)[0];
columnOrder.splice(newIndex, 0, option);
this._columnOrder = columnOrder;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
}
_toggle(ev) {
if (!this._params) {
return;
}
const column = ev.target.column;
const wasHidden = ev.target.hidden;
const hidden = [
...(this._hiddenColumns ??
Object.entries(this._params.columns)
.filter(([_key, col]) => col.defaultHidden)
.map(([key]) => key)),
];
if (wasHidden && hidden.includes(column)) {
hidden.splice(hidden.indexOf(column), 1);
} else if (!wasHidden) {
hidden.push(column);
}
const columns = this._sortedColumns(
this._params.columns,
this._columnOrder,
hidden
);
if (!this._columnOrder) {
this._columnOrder = columns.map((col) => col.key);
} else {
const newOrder = this._columnOrder.filter((col) => col !== column);
// Array.findLastIndex when supported or core-js polyfill
const findLastIndex = (
arr: Array<any>,
fn: (item: any, index: number, arr: Array<any>) => boolean
) => {
for (let i = arr.length - 1; i >= 0; i--) {
if (fn(arr[i], i, arr)) return i;
}
return -1;
};
let lastMoveable = findLastIndex(
newOrder,
(col) =>
col !== column &&
!hidden.includes(col) &&
!this._params!.columns[col].main &&
this._params!.columns[col].moveable !== false
);
if (lastMoveable === -1) {
lastMoveable = newOrder.length - 1;
}
columns.forEach((col) => {
if (!newOrder.includes(col.key)) {
if (col.moveable === false) {
newOrder.unshift(col.key);
} else {
newOrder.splice(lastMoveable + 1, 0, col.key);
}
if (
col.key !== column &&
col.defaultHidden &&
!hidden.includes(col.key)
) {
hidden.push(col.key);
}
}
});
this._columnOrder = newOrder;
}
this._hiddenColumns = hidden;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
}
_reset() {
this._columnOrder = undefined;
this._hiddenColumns = undefined;
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
this.closeDialog();
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
ha-dialog {
--mdc-dialog-max-width: 500px;
--dialog-z-index: 10;
--dialog-content-padding: 0 8px;
}
@media all and (max-width: 451px) {
ha-dialog {
--vertical-align-dialog: flex-start;
--dialog-surface-margin-top: 250px;
--ha-dialog-border-radius: 28px 28px 0 0;
--mdc-dialog-min-height: calc(100% - 250px);
--mdc-dialog-max-height: calc(100% - 250px);
}
}
ha-list-item {
--mdc-list-side-padding: 12px;
overflow: visible;
}
.hidden {
color: var(--disabled-text-color);
}
.handle {
cursor: move; /* fallback if grab cursor is unsupported */
cursor: grab;
}
.actions {
display: flex;
flex-direction: row;
}
ha-icon-button {
display: block;
margin: -12px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-data-table-settings": DialogDataTableSettings;
}
}

View File

@@ -34,6 +34,7 @@ import type { HaCheckbox } from "../ha-checkbox";
import "../ha-svg-icon"; import "../ha-svg-icon";
import "../search-input"; import "../search-input";
import { filterData, sortData } from "./sort-filter"; import { filterData, sortData } from "./sort-filter";
import { LocalizeFunc } from "../../common/translations/localize";
export interface RowClickedEvent { export interface RowClickedEvent {
id: string; id: string;
@@ -65,6 +66,10 @@ export interface DataTableSortColumnData {
valueColumn?: string; valueColumn?: string;
direction?: SortingDirection; direction?: SortingDirection;
groupable?: boolean; groupable?: boolean;
moveable?: boolean;
hideable?: boolean;
defaultHidden?: boolean;
showNarrow?: boolean;
} }
export interface DataTableColumnData<T = any> extends DataTableSortColumnData { export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
@@ -79,9 +84,10 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
| "overflow-menu" | "overflow-menu"
| "flex"; | "flex";
template?: (row: T) => TemplateResult | string | typeof nothing; template?: (row: T) => TemplateResult | string | typeof nothing;
width?: string; extraTemplate?: (row: T) => TemplateResult | string | typeof nothing;
minWidth?: string;
maxWidth?: string; maxWidth?: string;
grows?: boolean; flex?: number;
forceLTR?: boolean; forceLTR?: boolean;
hidden?: boolean; hidden?: boolean;
} }
@@ -105,6 +111,10 @@ const UNDEFINED_GROUP_KEY = "zzzzz_undefined";
export class HaDataTable extends LitElement { export class HaDataTable extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public localizeFunc?: LocalizeFunc;
@property({ type: Boolean }) public narrow = false;
@property({ type: Object }) public columns: DataTableColumnContainer = {}; @property({ type: Object }) public columns: DataTableColumnContainer = {};
@property({ type: Array }) public data: DataTableRowData[] = []; @property({ type: Array }) public data: DataTableRowData[] = [];
@@ -145,6 +155,10 @@ export class HaDataTable extends LitElement {
@property({ attribute: false }) public initialCollapsedGroups?: string[]; @property({ attribute: false }) public initialCollapsedGroups?: string[];
@property({ attribute: false }) public hiddenColumns?: string[];
@property({ attribute: false }) public columnOrder?: string[];
@state() private _filterable = false; @state() private _filterable = false;
@state() private _filter = ""; @state() private _filter = "";
@@ -202,6 +216,18 @@ export class HaDataTable extends LitElement {
this.updateComplete.then(() => this._calcTableHeight()); this.updateComplete.then(() => this._calcTableHeight());
} }
protected updated() {
const header = this.renderRoot.querySelector(".mdc-data-table__header-row");
if (!header) {
return;
}
if (header.scrollWidth > header.clientWidth) {
this.style.setProperty("--table-row-width", `${header.scrollWidth}px`);
} else {
this.style.removeProperty("--table-row-width");
}
}
public willUpdate(properties: PropertyValues) { public willUpdate(properties: PropertyValues) {
super.willUpdate(properties); super.willUpdate(properties);
@@ -235,6 +261,7 @@ export class HaDataTable extends LitElement {
(column: ClonedDataTableColumnData) => { (column: ClonedDataTableColumnData) => {
delete column.title; delete column.title;
delete column.template; delete column.template;
delete column.extraTemplate;
} }
); );
@@ -272,12 +299,46 @@ export class HaDataTable extends LitElement {
this._sortFilterData(); this._sortFilterData();
} }
if (properties.has("selectable")) { if (properties.has("selectable") || properties.has("hiddenColumns")) {
this._items = [...this._items]; this._items = [...this._items];
} }
} }
private _sortedColumns = memoizeOne(
(columns: DataTableColumnContainer, columnOrder?: string[]) => {
if (!columnOrder || !columnOrder.length) {
return columns;
}
return Object.keys(columns)
.sort((a, b) => {
const orderA = columnOrder!.indexOf(a);
const orderB = columnOrder!.indexOf(b);
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
}
if (orderB === -1) {
return -1;
}
}
return orderA - orderB;
})
.reduce((obj, key) => {
obj[key] = columns[key];
return obj;
}, {}) as DataTableColumnContainer;
}
);
protected render() { protected render() {
const localize = this.localizeFunc || this.hass.localize;
const columns = this._sortedColumns(this.columns, this.columnOrder);
const renderRow = (row: DataTableRowData, index: number) =>
this._renderRow(columns, this.narrow, row, index);
return html` return html`
<div class="mdc-data-table"> <div class="mdc-data-table">
<slot name="header" @slotchange=${this._calcTableHeight}> <slot name="header" @slotchange=${this._calcTableHeight}>
@@ -306,7 +367,12 @@ export class HaDataTable extends LitElement {
: `calc(100% - ${this._headerHeight}px)`, : `calc(100% - ${this._headerHeight}px)`,
})} })}
> >
<div class="mdc-data-table__header-row" role="row" aria-rowindex="1"> <div
class="mdc-data-table__header-row"
role="row"
aria-rowindex="1"
@scroll=${this._scrollContent}
>
<slot name="header-row"> <slot name="header-row">
${this.selectable ${this.selectable
? html` ? html`
@@ -326,9 +392,15 @@ export class HaDataTable extends LitElement {
</div> </div>
` `
: ""} : ""}
${Object.entries(this.columns).map(([key, column]) => { ${Object.entries(columns).map(([key, column]) => {
if (column.hidden) { if (
return ""; column.hidden ||
(this.columnOrder && this.columnOrder.includes(key)
? (this.hiddenColumns?.includes(key) ??
column.defaultHidden)
: column.defaultHidden)
) {
return nothing;
} }
const sorted = key === this.sortColumn; const sorted = key === this.sortColumn;
const classes = { const classes = {
@@ -343,18 +415,16 @@ export class HaDataTable extends LitElement {
column.type === "overflow", column.type === "overflow",
sortable: Boolean(column.sortable), sortable: Boolean(column.sortable),
"not-sorted": Boolean(column.sortable && !sorted), "not-sorted": Boolean(column.sortable && !sorted),
grows: Boolean(column.grows),
}; };
return html` return html`
<div <div
aria-label=${ifDefined(column.label)} aria-label=${ifDefined(column.label)}
class="mdc-data-table__header-cell ${classMap(classes)}" class="mdc-data-table__header-cell ${classMap(classes)}"
style=${column.width style=${styleMap({
? styleMap({ minWidth: column.minWidth,
[column.grows ? "minWidth" : "width"]: column.width, maxWidth: column.maxWidth,
maxWidth: column.maxWidth || "", flex: column.flex || 1,
}) })}
: ""}
role="columnheader" role="columnheader"
aria-sort=${ifDefined( aria-sort=${ifDefined(
sorted sorted
@@ -387,7 +457,7 @@ export class HaDataTable extends LitElement {
<div class="mdc-data-table__row" role="row"> <div class="mdc-data-table__row" role="row">
<div class="mdc-data-table__cell grows center" role="cell"> <div class="mdc-data-table__cell grows center" role="cell">
${this.noDataText || ${this.noDataText ||
this.hass.localize("ui.components.data-table.no-data")} localize("ui.components.data-table.no-data")}
</div> </div>
</div> </div>
</div> </div>
@@ -399,7 +469,7 @@ export class HaDataTable extends LitElement {
@scroll=${this._saveScrollPos} @scroll=${this._saveScrollPos}
.items=${this._items} .items=${this._items}
.keyFunction=${this._keyFunction} .keyFunction=${this._keyFunction}
.renderItem=${this._renderRow} .renderItem=${renderRow}
></lit-virtualizer> ></lit-virtualizer>
`} `}
</div> </div>
@@ -409,7 +479,12 @@ export class HaDataTable extends LitElement {
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row; private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
private _renderRow = (row: DataTableRowData, index: number) => { private _renderRow = (
columns: DataTableColumnContainer,
narrow: boolean,
row: DataTableRowData,
index: number
) => {
// not sure how this happens... // not sure how this happens...
if (!row) { if (!row) {
return nothing; return nothing;
@@ -454,8 +529,14 @@ export class HaDataTable extends LitElement {
</div> </div>
` `
: ""} : ""}
${Object.entries(this.columns).map(([key, column]) => { ${Object.entries(columns).map(([key, column]) => {
if (column.hidden) { if (
(narrow && !column.main && !column.showNarrow) ||
column.hidden ||
(this.columnOrder && this.columnOrder.includes(key)
? (this.hiddenColumns?.includes(key) ?? column.defaultHidden)
: column.defaultHidden)
) {
return nothing; return nothing;
} }
return html` return html`
@@ -472,17 +553,46 @@ export class HaDataTable extends LitElement {
"mdc-data-table__cell--overflow-menu": "mdc-data-table__cell--overflow-menu":
column.type === "overflow-menu", column.type === "overflow-menu",
"mdc-data-table__cell--overflow": column.type === "overflow", "mdc-data-table__cell--overflow": column.type === "overflow",
grows: Boolean(column.grows),
forceLTR: Boolean(column.forceLTR), forceLTR: Boolean(column.forceLTR),
})}" })}"
style=${column.width style=${styleMap({
? styleMap({ minWidth: column.minWidth,
[column.grows ? "minWidth" : "width"]: column.width, maxWidth: column.maxWidth,
maxWidth: column.maxWidth ? column.maxWidth : "", flex: column.flex || 1,
}) })}
: ""}
> >
${column.template ? column.template(row) : row[key]} ${column.template
? column.template(row)
: narrow && column.main
? html`<div class="primary">${row[key]}</div>
<div class="secondary">
${Object.entries(columns)
.filter(
([key2, column2]) =>
!column2.hidden &&
!column2.main &&
!column2.showNarrow &&
!(this.columnOrder &&
this.columnOrder.includes(key2)
? (this.hiddenColumns?.includes(key2) ??
column2.defaultHidden)
: column2.defaultHidden)
)
.map(
([key2, column2], i) =>
html`${i !== 0
? " ⸱ "
: nothing}${column2.template
? column2.template(row)
: row[key2]}`
)}
</div>
${column.extraTemplate
? column.extraTemplate(row)
: nothing}`
: html`${row[key]}${column.extraTemplate
? column.extraTemplate(row)
: nothing}`}
</div> </div>
`; `;
})} })}
@@ -500,7 +610,7 @@ export class HaDataTable extends LitElement {
filteredData = await this._memFilterData( filteredData = await this._memFilterData(
this.data, this.data,
this._sortColumns, this._sortColumns,
this._filter this._filter.trim()
); );
} }
@@ -528,6 +638,8 @@ export class HaDataTable extends LitElement {
return; return;
} }
const localize = this.localizeFunc || this.hass.localize;
if (this.appendRow || this.hasFab || this.groupColumn) { if (this.appendRow || this.hasFab || this.groupColumn) {
let items = [...data]; let items = [...data];
@@ -581,7 +693,7 @@ export class HaDataTable extends LitElement {
> >
</ha-icon-button> </ha-icon-button>
${groupName === UNDEFINED_GROUP_KEY ${groupName === UNDEFINED_GROUP_KEY
? this.hass.localize("ui.components.data-table.ungrouped") ? localize("ui.components.data-table.ungrouped")
: groupName || ""} : groupName || ""}
</div>`, </div>`,
}); });
@@ -716,6 +828,17 @@ export class HaDataTable extends LitElement {
@eventOptions({ passive: true }) @eventOptions({ passive: true })
private _saveScrollPos(e: Event) { private _saveScrollPos(e: Event) {
this._savedScrollPos = (e.target as HTMLDivElement).scrollTop; this._savedScrollPos = (e.target as HTMLDivElement).scrollTop;
this.renderRoot.querySelector(".mdc-data-table__header-row")!.scrollLeft = (
e.target as HTMLDivElement
).scrollLeft;
}
@eventOptions({ passive: true })
private _scrollContent(e: Event) {
this.renderRoot.querySelector("lit-virtualizer")!.scrollLeft = (
e.target as HTMLDivElement
).scrollLeft;
} }
private _collapseGroup = (ev: Event) => { private _collapseGroup = (ev: Event) => {
@@ -790,8 +913,8 @@ export class HaDataTable extends LitElement {
.mdc-data-table__row { .mdc-data-table__row {
display: flex; display: flex;
width: 100%;
height: var(--data-table-row-height, 52px); height: var(--data-table-row-height, 52px);
width: var(--table-row-width, 100%);
} }
.mdc-data-table__row ~ .mdc-data-table__row { .mdc-data-table__row ~ .mdc-data-table__row {
@@ -815,18 +938,26 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-row { .mdc-data-table__header-row {
height: 56px; height: 56px;
display: flex; display: flex;
width: 100%;
border-bottom: 1px solid var(--divider-color); border-bottom: 1px solid var(--divider-color);
overflow: auto;
} }
/* Hide scrollbar for Chrome, Safari and Opera */
.mdc-data-table__header-row::-webkit-scrollbar { .mdc-data-table__header-row::-webkit-scrollbar {
display: none; display: none;
} }
/* Hide scrollbar for IE, Edge and Firefox */
.mdc-data-table__header-row {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.mdc-data-table__cell, .mdc-data-table__cell,
.mdc-data-table__header-cell { .mdc-data-table__header-cell {
padding-right: 16px; padding-right: 16px;
padding-left: 16px; padding-left: 16px;
min-width: 150px;
align-self: center; align-self: center;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -861,6 +992,7 @@ export class HaDataTable extends LitElement {
width: 100%; width: 100%;
border: 0; border: 0;
white-space: nowrap; white-space: nowrap;
position: relative;
} }
.mdc-data-table__cell { .mdc-data-table__cell {
@@ -873,6 +1005,8 @@ export class HaDataTable extends LitElement {
letter-spacing: 0.0178571429em; letter-spacing: 0.0178571429em;
text-decoration: inherit; text-decoration: inherit;
text-transform: inherit; text-transform: inherit;
flex-grow: 0;
flex-shrink: 0;
} }
.mdc-data-table__cell a { .mdc-data-table__cell a {
@@ -891,7 +1025,8 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell--icon, .mdc-data-table__header-cell--icon,
.mdc-data-table__cell--icon { .mdc-data-table__cell--icon {
width: 54px; min-width: 64px;
flex: 0 0 64px !important;
} }
.mdc-data-table__cell--icon img { .mdc-data-table__cell--icon img {
@@ -931,11 +1066,14 @@ export class HaDataTable extends LitElement {
.mdc-data-table__header-cell--overflow-menu, .mdc-data-table__header-cell--overflow-menu,
.mdc-data-table__header-cell--icon-button, .mdc-data-table__header-cell--icon-button,
.mdc-data-table__cell--icon-button { .mdc-data-table__cell--icon-button {
min-width: 64px;
flex: 0 0 64px !important;
padding: 8px; padding: 8px;
} }
.mdc-data-table__header-cell--icon-button, .mdc-data-table__header-cell--icon-button,
.mdc-data-table__cell--icon-button { .mdc-data-table__cell--icon-button {
min-width: 56px;
width: 56px; width: 56px;
} }

View File

@@ -0,0 +1,28 @@
import { fireEvent } from "../../common/dom/fire_event";
import { LocalizeFunc } from "../../common/translations/localize";
import { DataTableColumnContainer } from "./ha-data-table";
export interface DataTableSettingsDialogParams {
columns: DataTableColumnContainer;
onUpdate: (
columnOrder: string[] | undefined,
hiddenColumns: string[] | undefined
) => void;
hiddenColumns?: string[];
columnOrder?: string[];
localizeFunc?: LocalizeFunc;
}
export const loadDataTableSettingsDialog = () =>
import("./dialog-data-table-settings");
export const showDataTableSettingsDialog = (
element: HTMLElement,
dialogParams: DataTableSettingsDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-data-table-settings",
dialogImport: loadDataTableSettingsDialog,
dialogParams,
});
};

View File

@@ -1,5 +1,6 @@
import { expose } from "comlink"; import { expose } from "comlink";
import { stringCompare } from "../../common/string/compare"; import { stringCompare } from "../../common/string/compare";
import { stripDiacritics } from "../../common/string/strip-diacritics";
import type { import type {
ClonedDataTableColumnData, ClonedDataTableColumnData,
DataTableRowData, DataTableRowData,
@@ -12,20 +13,18 @@ const filterData = (
columns: SortableColumnContainer, columns: SortableColumnContainer,
filter: string filter: string
) => { ) => {
filter = filter.toUpperCase(); filter = stripDiacritics(filter.toLowerCase());
return data.filter((row) => return data.filter((row) =>
Object.entries(columns).some((columnEntry) => { Object.entries(columns).some((columnEntry) => {
const [key, column] = columnEntry; const [key, column] = columnEntry;
if (column.filterable) { if (column.filterable) {
if ( const value = String(
String( column.filterKey
column.filterKey ? row[column.valueColumn || key][column.filterKey]
? row[column.valueColumn || key][column.filterKey] : row[column.valueColumn || key]
: row[column.valueColumn || key] );
)
.toUpperCase() if (stripDiacritics(value).toLowerCase().includes(filter)) {
.includes(filter)
) {
return true; return true;
} }
} }

View File

@@ -0,0 +1,330 @@
import { mdiDrag } from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
import { LitElement, PropertyValues, css, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { repeat } from "lit/directives/repeat";
import memoizeOne from "memoize-one";
import { ensureArray } from "../../common/array/ensure-array";
import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import {
STATE_DISPLAY_SPECIAL_CONTENT,
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS,
} from "../../state-display/state-display";
import { HomeAssistant, ValueChangedEvent } from "../../types";
import "../ha-combo-box";
import type { HaComboBox } from "../ha-combo-box";
const HIDDEN_ATTRIBUTES = [
"access_token",
"available_modes",
"battery_icon",
"battery_level",
"code_arm_required",
"code_format",
"color_modes",
"device_class",
"editable",
"effect_list",
"entity_id",
"entity_picture",
"event_types",
"fan_modes",
"fan_speed_list",
"friendly_name",
"frontend_stream_type",
"has_date",
"has_time",
"hvac_modes",
"icon",
"id",
"max_color_temp_kelvin",
"max_mireds",
"max_temp",
"max",
"min_color_temp_kelvin",
"min_mireds",
"min_temp",
"min",
"mode",
"operation_list",
"options",
"percentage_step",
"precipitation_unit",
"preset_modes",
"pressure_unit",
"remaining",
"sound_mode_list",
"source_list",
"state_class",
"step",
"supported_color_modes",
"supported_features",
"swing_modes",
"target_temp_step",
"temperature_unit",
"token",
"unit_of_measurement",
"visibility_unit",
"wind_speed_unit",
];
@customElement("ha-entity-state-content-picker")
class HaEntityStatePicker extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public entityId?: string;
@property({ type: Boolean }) public autofocus = false;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = false;
@property({ type: Boolean, attribute: "allow-name" }) public allowName =
false;
@property() public label?: string;
@property() public value?: string[] | string;
@property() public helper?: string;
@state() private _opened = false;
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
protected shouldUpdate(changedProps: PropertyValues) {
return !(!changedProps.has("_opened") && this._opened);
}
private options = memoizeOne(
(entityId?: string, stateObj?: HassEntity, allowName?: boolean) => {
const domain = entityId ? computeDomain(entityId) : undefined;
return [
{
label: this.hass.localize("ui.components.state-content-picker.state"),
value: "state",
},
...(allowName
? [
{
label: this.hass.localize(
"ui.components.state-content-picker.name"
),
value: "name",
},
]
: []),
{
label: this.hass.localize(
"ui.components.state-content-picker.last_changed"
),
value: "last_changed",
},
{
label: this.hass.localize(
"ui.components.state-content-picker.last_updated"
),
value: "last_updated",
},
...(domain
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
).map((content) => ({
label: this.hass.localize(
`ui.components.state-content-picker.${content}`
),
value: content,
}))
: []),
...Object.keys(stateObj?.attributes ?? {})
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
.map((attribute) => ({
value: attribute,
label: this.hass.formatEntityAttributeName(stateObj!, attribute),
})),
];
}
);
private _filter = "";
protected render() {
if (!this.hass) {
return nothing;
}
const value = this._value;
const stateObj = this.entityId
? this.hass.states[this.entityId]
: undefined;
const options = this.options(this.entityId, stateObj, this.allowName);
const optionItems = options.filter(
(option) => !this._value.includes(option.value)
);
return html`
${value?.length
? html`
<ha-sortable
no-style
@item-moved=${this._moveItem}
.disabled=${this.disabled}
>
<ha-chip-set>
${repeat(
this._value,
(item) => item,
(item, idx) => {
const label =
options.find((option) => option.value === item)?.label ||
item;
return html`
<ha-input-chip
.idx=${idx}
@remove=${this._removeItem}
.label=${label}
selected
>
<ha-svg-icon
slot="icon"
.path=${mdiDrag}
data-handle
></ha-svg-icon>
${label}
</ha-input-chip>
`;
}
)}
</ha-chip-set>
</ha-sortable>
`
: nothing}
<ha-combo-box
item-value-path="value"
item-label-path="label"
.hass=${this.hass}
.label=${this.label}
.helper=${this.helper}
.disabled=${this.disabled}
.required=${this.required && !value.length}
.value=${""}
.items=${optionItems}
allow-custom-value
@filter-changed=${this._filterChanged}
@value-changed=${this._comboBoxValueChanged}
@opened-changed=${this._openedChanged}
></ha-combo-box>
`;
}
private get _value() {
return !this.value ? [] : ensureArray(this.value);
}
private _openedChanged(ev: ValueChangedEvent<boolean>) {
this._opened = ev.detail.value;
this._comboBox.filteredItems = this._comboBox.items;
}
private _filterChanged(ev?: CustomEvent): void {
this._filter = ev?.detail.value || "";
const filteredItems = this._comboBox.items?.filter((item) => {
const label = item.label || item.value;
return label.toLowerCase().includes(this._filter?.toLowerCase());
});
if (this._filter) {
filteredItems?.unshift({ label: this._filter, value: this._filter });
}
this._comboBox.filteredItems = filteredItems;
}
private async _moveItem(ev: CustomEvent) {
ev.stopPropagation();
const { oldIndex, newIndex } = ev.detail;
const value = this._value;
const newValue = value.concat();
const element = newValue.splice(oldIndex, 1)[0];
newValue.splice(newIndex, 0, element);
this._setValue(newValue);
await this.updateComplete;
this._filterChanged();
}
private async _removeItem(ev) {
ev.stopPropagation();
const value: string[] = [...this._value];
value.splice(ev.target.idx, 1);
this._setValue(value);
await this.updateComplete;
this._filterChanged();
}
private _comboBoxValueChanged(ev: CustomEvent): void {
ev.stopPropagation();
const newValue = ev.detail.value;
if (this.disabled || newValue === "") {
return;
}
const currentValue = this._value;
if (currentValue.includes(newValue)) {
return;
}
setTimeout(() => {
this._filterChanged();
this._comboBox.setInputValue("");
}, 0);
this._setValue([...currentValue, newValue]);
}
private _setValue(value: string[]) {
const newValue =
value.length === 0 ? undefined : value.length === 1 ? value[0] : value;
this.value = newValue;
fireEvent(this, "value-changed", {
value: newValue,
});
}
static styles = css`
:host {
position: relative;
}
ha-chip-set {
padding: 8px 0;
}
.sortable-fallback {
display: none;
opacity: 0;
}
.sortable-ghost {
opacity: 0.4;
}
.sortable-drag {
cursor: grabbing;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"ha-entity-state-content-picker": HaEntityStatePicker;
}
}

View File

@@ -134,7 +134,7 @@ export class HaStateLabelBadge extends LitElement {
this._timerTimeRemaining this._timerTimeRemaining
)} )}
.description=${this.showName .description=${this.showName
? this.name ?? computeStateName(entityState) ? (this.name ?? computeStateName(entityState))
: undefined} : undefined}
> >
${!image && showIcon ${!image && showIcon

View File

@@ -90,7 +90,8 @@ class HaAnsiToHtml extends LitElement {
private _parseTextToColoredPre(text) { private _parseTextToColoredPre(text) {
const pre = document.createElement("pre"); const pre = document.createElement("pre");
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g; // eslint-disable-next-line no-control-regex
const re = /\x1b(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1b\\))/g;
let i = 0; let i = 0;
const state: State = { const state: State = {

View File

@@ -279,6 +279,8 @@ export class HaAreaPicker extends LitElement {
icon: null, icon: null,
aliases: [], aliases: [],
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
]; ];
} }
@@ -295,6 +297,8 @@ export class HaAreaPicker extends LitElement {
icon: "mdi:plus", icon: "mdi:plus",
aliases: [], aliases: [],
labels: [], labels: [],
created_at: 0,
modified_at: 0,
}, },
]; ];
} }
@@ -377,6 +381,8 @@ export class HaAreaPicker extends LitElement {
picture: null, picture: null,
labels: [], labels: [],
aliases: [], aliases: [],
created_at: 0,
modified_at: 0,
}, },
] as AreaRegistryEntry[]; ] as AreaRegistryEntry[];
} else { } else {
@@ -393,6 +399,8 @@ export class HaAreaPicker extends LitElement {
picture: null, picture: null,
labels: [], labels: [],
aliases: [], aliases: [],
created_at: 0,
modified_at: 0,
}, },
] as AreaRegistryEntry[]; ] as AreaRegistryEntry[];
} }

View File

@@ -1,10 +1,12 @@
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { css, html, LitElement, TemplateResult } from "lit"; import { css, html, LitElement, TemplateResult, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { mdiClose } from "@mdi/js";
import { ifDefined } from "lit/directives/if-defined"; import { ifDefined } from "lit/directives/if-defined";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation"; import { stopPropagation } from "../common/dom/stop_propagation";
import "./ha-select"; import "./ha-select";
import "./ha-icon-button";
import { HaTextField } from "./ha-textfield"; import { HaTextField } from "./ha-textfield";
import "./ha-input-helper-text"; import "./ha-input-helper-text";
@@ -124,116 +126,128 @@ export class HaBaseTimeInput extends LitElement {
*/ */
@property() amPm: "AM" | "PM" = "AM"; @property() amPm: "AM" | "PM" = "AM";
@property({ type: Boolean, reflect: true }) public clearable?: boolean;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
${this.label ${this.label
? html`<label>${this.label}${this.required ? " *" : ""}</label>` ? html`<label>${this.label}${this.required ? " *" : ""}</label>`
: ""} : ""}
<div class="time-input-wrap"> <div class="time-input-wrap-wrap">
${this.enableDay <div class="time-input-wrap">
? html` ${this.enableDay
<ha-textfield ? html`
id="day" <ha-textfield
id="day"
type="number"
inputmode="numeric"
.value=${this.days.toFixed()}
.label=${this.dayLabel}
name="days"
@change=${this._valueChanged}
@focusin=${this._onFocus}
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
min="0"
.disabled=${this.disabled}
suffix=":"
class="hasSuffix"
>
</ha-textfield>
`
: ""}
<ha-textfield
id="hour"
type="number"
inputmode="numeric"
.value=${this.hours.toFixed()}
.label=${this.hourLabel}
name="hours"
@change=${this._valueChanged}
@focusin=${this._onFocus}
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="2"
max=${ifDefined(this._hourMax)}
min="0"
.disabled=${this.disabled}
suffix=":"
class="hasSuffix"
>
</ha-textfield>
<ha-textfield
id="min"
type="number"
inputmode="numeric"
.value=${this._formatValue(this.minutes)}
.label=${this.minLabel}
@change=${this._valueChanged}
@focusin=${this._onFocus}
name="minutes"
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="2"
max="59"
min="0"
.disabled=${this.disabled}
.suffix=${this.enableSecond ? ":" : ""}
class=${this.enableSecond ? "has-suffix" : ""}
>
</ha-textfield>
${this.enableSecond
? html`<ha-textfield
id="sec"
type="number" type="number"
inputmode="numeric" inputmode="numeric"
.value=${this.days.toFixed()} .value=${this._formatValue(this.seconds)}
.label=${this.dayLabel} .label=${this.secLabel}
name="days"
@change=${this._valueChanged} @change=${this._valueChanged}
@focusin=${this._onFocus} @focusin=${this._onFocus}
name="seconds"
no-spinner no-spinner
.required=${this.required} .required=${this.required}
.autoValidate=${this.autoValidate} .autoValidate=${this.autoValidate}
maxlength="2"
max="59"
min="0" min="0"
.disabled=${this.disabled} .disabled=${this.disabled}
suffix=":" .suffix=${this.enableMillisecond ? ":" : ""}
class="hasSuffix" class=${this.enableMillisecond ? "has-suffix" : ""}
> >
</ha-textfield> </ha-textfield>`
` : ""}
: ""} ${this.enableMillisecond
? html`<ha-textfield
id="millisec"
type="number"
.value=${this._formatValue(this.milliseconds, 3)}
.label=${this.millisecLabel}
@change=${this._valueChanged}
@focusin=${this._onFocus}
name="milliseconds"
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="3"
max="999"
min="0"
.disabled=${this.disabled}
>
</ha-textfield>`
: ""}
${this.clearable && !this.required && !this.disabled
? html`<ha-icon-button
label="clear"
@click=${this._clearValue}
.path=${mdiClose}
></ha-icon-button>`
: nothing}
</div>
<ha-textfield
id="hour"
type="number"
inputmode="numeric"
.value=${this.hours.toFixed()}
.label=${this.hourLabel}
name="hours"
@change=${this._valueChanged}
@focusin=${this._onFocus}
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="2"
max=${ifDefined(this._hourMax)}
min="0"
.disabled=${this.disabled}
suffix=":"
class="hasSuffix"
>
</ha-textfield>
<ha-textfield
id="min"
type="number"
inputmode="numeric"
.value=${this._formatValue(this.minutes)}
.label=${this.minLabel}
@change=${this._valueChanged}
@focusin=${this._onFocus}
name="minutes"
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="2"
max="59"
min="0"
.disabled=${this.disabled}
.suffix=${this.enableSecond ? ":" : ""}
class=${this.enableSecond ? "has-suffix" : ""}
>
</ha-textfield>
${this.enableSecond
? html`<ha-textfield
id="sec"
type="number"
inputmode="numeric"
.value=${this._formatValue(this.seconds)}
.label=${this.secLabel}
@change=${this._valueChanged}
@focusin=${this._onFocus}
name="seconds"
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="2"
max="59"
min="0"
.disabled=${this.disabled}
.suffix=${this.enableMillisecond ? ":" : ""}
class=${this.enableMillisecond ? "has-suffix" : ""}
>
</ha-textfield>`
: ""}
${this.enableMillisecond
? html`<ha-textfield
id="millisec"
type="number"
.value=${this._formatValue(this.milliseconds, 3)}
.label=${this.millisecLabel}
@change=${this._valueChanged}
@focusin=${this._onFocus}
name="milliseconds"
no-spinner
.required=${this.required}
.autoValidate=${this.autoValidate}
maxlength="3"
max="999"
min="0"
.disabled=${this.disabled}
>
</ha-textfield>`
: ""}
${this.format === 24 ${this.format === 24
? "" ? ""
: html`<ha-select : html`<ha-select
@@ -249,13 +263,17 @@ export class HaBaseTimeInput extends LitElement {
<mwc-list-item value="AM">AM</mwc-list-item> <mwc-list-item value="AM">AM</mwc-list-item>
<mwc-list-item value="PM">PM</mwc-list-item> <mwc-list-item value="PM">PM</mwc-list-item>
</ha-select>`} </ha-select>`}
${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
: ""}
</div> </div>
${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
: ""}
`; `;
} }
private _clearValue(): void {
fireEvent(this, "value-changed");
}
private _valueChanged(ev: InputEvent) { private _valueChanged(ev: InputEvent) {
const textField = ev.currentTarget as HaTextField; const textField = ev.currentTarget as HaTextField;
this[textField.name] = this[textField.name] =
@@ -302,18 +320,25 @@ export class HaBaseTimeInput extends LitElement {
} }
static styles = css` static styles = css`
:host([clearable]) {
position: relative;
}
:host { :host {
display: block; display: block;
} }
.time-input-wrap-wrap {
display: flex;
}
.time-input-wrap { .time-input-wrap {
display: flex; display: flex;
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0; border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
direction: ltr; direction: ltr;
padding-right: 3px;
} }
ha-textfield { ha-textfield {
width: 40px; width: 55px;
text-align: center; text-align: center;
--mdc-shape-small: 0; --mdc-shape-small: 0;
--text-field-appearance: none; --text-field-appearance: none;
@@ -335,6 +360,21 @@ export class HaBaseTimeInput extends LitElement {
--mdc-shape-small: 0; --mdc-shape-small: 0;
width: 85px; width: 85px;
} }
:host([clearable]) .mdc-select__anchor {
padding-inline-end: var(--select-selected-text-padding-end, 12px);
}
ha-icon-button {
position: relative
--mdc-icon-button-size: 36px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
direction: var(--direction);
display: flex;
align-items: center;
background-color:var(--mdc-text-field-fill-color, whitesmoke);
border-bottom-style: solid;
border-bottom-width: 1px;
}
label { label {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;

View File

@@ -47,6 +47,8 @@ export class HaCodeEditor extends ReactiveElement {
@property({ type: Boolean }) public readOnly = false; @property({ type: Boolean }) public readOnly = false;
@property({ type: Boolean }) public linewrap = false;
@property({ type: Boolean, attribute: "autocomplete-entities" }) @property({ type: Boolean, attribute: "autocomplete-entities" })
public autocompleteEntities = false; public autocompleteEntities = false;
@@ -134,6 +136,13 @@ export class HaCodeEditor extends ReactiveElement {
), ),
}); });
} }
if (changedProps.has("linewrap")) {
transactions.push({
effects: this._loadedCodeMirror!.linewrapCompartment!.reconfigure(
this.linewrap ? this._loadedCodeMirror!.EditorView.lineWrapping : []
),
});
}
if (changedProps.has("_value") && this._value !== this.value) { if (changedProps.has("_value") && this._value !== this.value) {
transactions.push({ transactions.push({
changes: { changes: {
@@ -181,6 +190,9 @@ export class HaCodeEditor extends ReactiveElement {
this._loadedCodeMirror.readonlyCompartment.of( this._loadedCodeMirror.readonlyCompartment.of(
this._loadedCodeMirror.EditorView.editable.of(!this.readOnly) this._loadedCodeMirror.EditorView.editable.of(!this.readOnly)
), ),
this._loadedCodeMirror.linewrapCompartment.of(
this.linewrap ? this._loadedCodeMirror.EditorView.lineWrapping : []
),
this._loadedCodeMirror.EditorView.updateListener.of(this._onUpdate), this._loadedCodeMirror.EditorView.updateListener.of(this._onUpdate),
]; ];

View File

@@ -196,8 +196,8 @@ export class HaControlNumberButton extends LitElement {
--control-number-buttons-background-opacity: 0.2; --control-number-buttons-background-opacity: 0.2;
--control-number-buttons-border-radius: 10px; --control-number-buttons-border-radius: 10px;
--mdc-icon-size: 16px; --mdc-icon-size: 16px;
height: 40px; height: var(--feature-height);
width: 200px; width: 100%;
color: var(--primary-text-color); color: var(--primary-text-color);
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
font-style: normal; font-style: normal;

View File

@@ -89,13 +89,18 @@ export class HaFilterDomains extends LitElement {
}); });
return Array.from(domains.values()) return Array.from(domains.values())
.map((domain) => ({
domain,
name: domainToName(this.hass.localize, domain),
}))
.filter( .filter(
(entry) => (entry) =>
!filter || !filter ||
entry.toLowerCase().includes(filter) || entry.domain.toLowerCase().includes(filter) ||
domainToName(this.hass.localize, entry).toLowerCase().includes(filter) entry.name.toLowerCase().includes(filter)
) )
.sort((a, b) => stringCompare(a, b, this.hass.locale.language)); .sort((a, b) => stringCompare(a.name, b.name, this.hass.locale.language))
.map((entry) => entry.domain);
}); });
protected updated(changed) { protected updated(changed) {

View File

@@ -295,6 +295,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
icon: null, icon: null,
level: null, level: null,
aliases: [], aliases: [],
created_at: 0,
modified_at: 0,
}, },
]; ];
} }
@@ -309,6 +311,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
icon: "mdi:plus", icon: "mdi:plus",
level: null, level: null,
aliases: [], aliases: [],
created_at: 0,
modified_at: 0,
}, },
]; ];
} }
@@ -391,6 +395,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
icon: null, icon: null,
level: null, level: null,
aliases: [], aliases: [],
created_at: 0,
modified_at: 0,
}, },
] as FloorRegistryEntry[]; ] as FloorRegistryEntry[];
} else { } else {
@@ -405,6 +411,8 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) {
icon: "mdi:plus", icon: "mdi:plus",
level: null, level: null,
aliases: [], aliases: [],
created_at: 0,
modified_at: 0,
}, },
] as FloorRegistryEntry[]; ] as FloorRegistryEntry[];
} }

View File

@@ -2,7 +2,7 @@ import type { Selector } from "../../data/selector";
import type { HaFormSchema } from "./types"; import type { HaFormSchema } from "./types";
export const computeInitialHaFormData = ( export const computeInitialHaFormData = (
schema: HaFormSchema[] schema: HaFormSchema[] | readonly HaFormSchema[]
): Record<string, any> => { ): Record<string, any> => {
const data = {}; const data = {};
schema.forEach((field) => { schema.forEach((field) => {
@@ -36,6 +36,8 @@ export const computeInitialHaFormData = (
minutes: 0, minutes: 0,
seconds: 0, seconds: 0,
}; };
} else if (field.type === "expandable") {
data[field.name] = computeInitialHaFormData(field.schema);
} else if ("selector" in field) { } else if ("selector" in field) {
const selector: Selector = field.selector; const selector: Selector = field.selector;
@@ -92,6 +94,8 @@ export const computeInitialHaFormData = (
data[field.name] = selector.color_temp?.min_mireds ?? 153; data[field.name] = selector.color_temp?.min_mireds ?? 153;
} else if ( } else if (
"action" in selector || "action" in selector ||
"trigger" in selector ||
"condition" in selector ||
"media" in selector || "media" in selector ||
"target" in selector "target" in selector
) { ) {

View File

@@ -0,0 +1,246 @@
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import "./ha-icon-button";
import "../panels/lovelace/editor/card-editor/ha-grid-layout-slider";
import { mdiRestore } from "@mdi/js";
import { styleMap } from "lit/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import { HomeAssistant } from "../types";
import { conditionalClamp } from "../common/number/clamp";
type GridSizeValue = {
rows?: number | "auto";
columns?: number;
};
@customElement("ha-grid-size-picker")
export class HaGridSizeEditor extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public value?: GridSizeValue;
@property({ attribute: false }) public rows = 8;
@property({ attribute: false }) public columns = 4;
@property({ attribute: false }) public rowMin?: number;
@property({ attribute: false }) public rowMax?: number;
@property({ attribute: false }) public columnMin?: number;
@property({ attribute: false }) public columnMax?: number;
@property({ attribute: false }) public isDefault?: boolean;
@state() public _localValue?: GridSizeValue = undefined;
protected willUpdate(changedProperties) {
if (changedProperties.has("value")) {
this._localValue = this.value;
}
}
protected render() {
const disabledColumns =
this.columnMin !== undefined && this.columnMin === this.columnMax;
const disabledRows =
this.rowMin !== undefined && this.rowMin === this.rowMax;
const autoHeight = this._localValue?.rows === "auto";
const rowMin = this.rowMin ?? 1;
const rowMax = this.rowMax ?? this.rows;
const columnMin = this.columnMin ?? 1;
const columnMax = this.columnMax ?? this.columns;
const rowValue = autoHeight ? rowMin : this._localValue?.rows;
const columnValue = this._localValue?.columns;
return html`
<div class="grid">
<ha-grid-layout-slider
aria-label=${this.hass.localize(
"ui.components.grid-size-picker.columns"
)}
id="columns"
.min=${columnMin}
.max=${columnMax}
.range=${this.columns}
.value=${columnValue}
@value-changed=${this._valueChanged}
@slider-moved=${this._sliderMoved}
.disabled=${disabledColumns}
></ha-grid-layout-slider>
<ha-grid-layout-slider
aria-label=${this.hass.localize(
"ui.components.grid-size-picker.rows"
)}
id="rows"
.min=${rowMin}
.max=${rowMax}
.range=${this.rows}
vertical
.value=${rowValue}
@value-changed=${this._valueChanged}
@slider-moved=${this._sliderMoved}
.disabled=${disabledRows}
></ha-grid-layout-slider>
${!this.isDefault
? html`
<ha-icon-button
@click=${this._reset}
class="reset"
.path=${mdiRestore}
label=${this.hass.localize(
"ui.components.grid-size-picker.reset_default"
)}
title=${this.hass.localize(
"ui.components.grid-size-picker.reset_default"
)}
>
</ha-icon-button>
`
: nothing}
<div
class="preview"
style=${styleMap({
"--total-rows": this.rows,
"--total-columns": this.columns,
"--rows": rowValue,
"--columns": columnValue,
})}
>
<div>
${Array(this.rows * this.columns)
.fill(0)
.map((_, index) => {
const row = Math.floor(index / this.columns) + 1;
const column = (index % this.columns) + 1;
return html`
<div
class="cell"
data-row=${row}
data-column=${column}
@click=${this._cellClick}
></div>
`;
})}
</div>
<div class="selected">
<div class="cell"></div>
</div>
</div>
</div>
`;
}
_cellClick(ev) {
const cell = ev.currentTarget as HTMLElement;
const rows = Number(cell.getAttribute("data-row"));
const columns = Number(cell.getAttribute("data-column"));
const clampedRow = conditionalClamp(rows, this.rowMin, this.rowMax);
const clampedColumn = conditionalClamp(
columns,
this.columnMin,
this.columnMax
);
fireEvent(this, "value-changed", {
value: { rows: clampedRow, columns: clampedColumn },
});
}
private _valueChanged(ev) {
ev.stopPropagation();
const key = ev.currentTarget.id;
const newValue = {
...this.value,
[key]: ev.detail.value,
};
fireEvent(this, "value-changed", { value: newValue });
}
private _reset(ev) {
ev.stopPropagation();
fireEvent(this, "value-changed", {
value: {
rows: undefined,
columns: undefined,
},
});
}
private _sliderMoved(ev) {
ev.stopPropagation();
const key = ev.currentTarget.id;
const value = ev.detail.value;
if (value === undefined) return;
this._localValue = {
...this.value,
[key]: ev.detail.value,
};
}
static styles = [
css`
.grid {
display: grid;
grid-template-areas:
"reset column-slider"
"row-slider preview";
grid-template-rows: auto 1fr;
grid-template-columns: auto 1fr;
gap: 8px;
}
#columns {
grid-area: column-slider;
}
#rows {
grid-area: row-slider;
}
.reset {
grid-area: reset;
}
.preview {
position: relative;
grid-area: preview;
aspect-ratio: 1 / 1.2;
}
.preview > div {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: grid;
grid-template-columns: repeat(var(--total-columns), 1fr);
grid-template-rows: repeat(var(--total-rows), 1fr);
gap: 4px;
}
.preview .cell {
background-color: var(--disabled-color);
grid-column: span 1;
grid-row: span 1;
border-radius: 4px;
opacity: 0.2;
cursor: pointer;
}
.selected {
pointer-events: none;
}
.selected .cell {
background-color: var(--primary-color);
grid-column: 1 / span var(--columns, 0);
grid-row: 1 / span var(--rows, 0);
opacity: 0.5;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"ha-grid-size-picker": HaGridSizeEditor;
}
}

View File

@@ -303,6 +303,8 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
icon: null, icon: null,
color: null, color: null,
description: null, description: null,
created_at: 0,
modified_at: 0,
}, },
]; ];
} }
@@ -317,6 +319,8 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
icon: "mdi:plus", icon: "mdi:plus",
color: null, color: null,
description: null, description: null,
created_at: 0,
modified_at: 0,
}, },
]; ];
} }

View File

@@ -28,6 +28,11 @@ const LAWN_MOWER_ACTIONS: Partial<
service: "start_mowing", service: "start_mowing",
feature: LawnMowerEntityFeature.START_MOWING, feature: LawnMowerEntityFeature.START_MOWING,
}, },
returning: {
action: "pause",
service: "pause",
feature: LawnMowerEntityFeature.PAUSE,
},
paused: { paused: {
action: "resume_mowing", action: "resume_mowing",
service: "start_mowing", service: "start_mowing",

View File

@@ -1,9 +1,11 @@
import { MdMenuItem } from "@material/web/menu/menu-item"; import { MdMenuItem } from "@material/web/menu/menu-item";
import { css } from "lit"; import { css } from "lit";
import { customElement } from "lit/decorators"; import { customElement, property } from "lit/decorators";
@customElement("ha-menu-item") @customElement("ha-menu-item")
export class HaMenuItem extends MdMenuItem { export class HaMenuItem extends MdMenuItem {
@property({ attribute: false }) clickAction?: (item?: HTMLElement) => void;
static override styles = [ static override styles = [
...super.styles, ...super.styles,
css` css`

View File

@@ -1,9 +1,30 @@
import { MdMenu } from "@material/web/menu/menu"; import { MdMenu } from "@material/web/menu/menu";
import type { CloseMenuEvent } from "@material/web/menu/menu";
import {
CloseReason,
KeydownCloseKey,
} from "@material/web/menu/internal/controllers/shared";
import { css } from "lit"; import { css } from "lit";
import { customElement } from "lit/decorators"; import { customElement } from "lit/decorators";
import type { HaMenuItem } from "./ha-menu-item";
@customElement("ha-menu") @customElement("ha-menu")
export class HaMenu extends MdMenu { export class HaMenu extends MdMenu {
connectedCallback(): void {
super.connectedCallback();
this.addEventListener("close-menu", this._handleCloseMenu);
}
private _handleCloseMenu(ev: CloseMenuEvent) {
if (
ev.detail.reason.kind === CloseReason.KEYDOWN &&
ev.detail.reason.key === KeydownCloseKey.ESCAPE
) {
return;
}
(ev.detail.initiator as HaMenuItem).clickAction?.(ev.detail.initiator);
}
static override styles = [ static override styles = [
...super.styles, ...super.styles,
css` css`
@@ -18,4 +39,8 @@ declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"ha-menu": HaMenu; "ha-menu": HaMenu;
} }
interface HTMLElementEventMap {
"close-menu": CloseMenuEvent;
}
} }

View File

@@ -1,72 +1,92 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item";
import { mdiCamera } from "@mdi/js"; import { mdiCamera } from "@mdi/js";
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, html, LitElement, nothing, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import type QrScanner from "qr-scanner"; import type QrScanner from "qr-scanner";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { stopPropagation } from "../common/dom/stop_propagation"; import { stopPropagation } from "../common/dom/stop_propagation";
import { LocalizeFunc } from "../common/translations/localize"; import { LocalizeFunc } from "../common/translations/localize";
import { addExternalBarCodeListener } from "../external_app/external_app_entrypoint";
import { HomeAssistant } from "../types";
import "./ha-alert"; import "./ha-alert";
import "./ha-button-menu"; import "./ha-button-menu";
import "./ha-list-item";
import "./ha-textfield"; import "./ha-textfield";
import type { HaTextField } from "./ha-textfield"; import type { HaTextField } from "./ha-textfield";
@customElement("ha-qr-scanner") @customElement("ha-qr-scanner")
class HaQrScanner extends LitElement { class HaQrScanner extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public localize!: LocalizeFunc; @property({ attribute: false }) public localize!: LocalizeFunc;
@property() public description?: string;
@property({ attribute: "alternative_option_label" })
public alternativeOptionLabel?: string;
@property() public error?: string;
@state() private _cameras?: QrScanner.Camera[]; @state() private _cameras?: QrScanner.Camera[];
@state() private _error?: string; @state() private _manual = false;
private _qrScanner?: QrScanner; private _qrScanner?: QrScanner;
private _qrNotFoundCount = 0; private _qrNotFoundCount = 0;
@query("video", true) private _video!: HTMLVideoElement; private _removeListener?: UnsubscribeFunc;
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement; @query("video", true) private _video?: HTMLVideoElement;
@query("#canvas-container", true) private _canvasContainer?: HTMLDivElement;
@query("ha-textfield") private _manualInput?: HaTextField; @query("ha-textfield") private _manualInput?: HaTextField;
public disconnectedCallback(): void { public disconnectedCallback(): void {
super.disconnectedCallback(); super.disconnectedCallback();
this._qrNotFoundCount = 0; this._qrNotFoundCount = 0;
if (this._nativeBarcodeScanner) {
this._closeExternalScanner();
}
if (this._qrScanner) { if (this._qrScanner) {
this._qrScanner.stop(); this._qrScanner.stop();
this._qrScanner.destroy(); this._qrScanner.destroy();
this._qrScanner = undefined; this._qrScanner = undefined;
} }
while (this._canvasContainer.lastChild) { while (this._canvasContainer?.lastChild) {
this._canvasContainer.removeChild(this._canvasContainer.lastChild); this._canvasContainer.removeChild(this._canvasContainer.lastChild);
} }
} }
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
if (this.hasUpdated && navigator.mediaDevices) { if (this.hasUpdated) {
this._loadQrScanner(); this._loadQrScanner();
} }
} }
protected firstUpdated() { protected firstUpdated() {
if (navigator.mediaDevices) { this._loadQrScanner();
this._loadQrScanner();
}
} }
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
if (changedProps.has("_error") && this._error) { if (changedProps.has("error") && this.error) {
fireEvent(this, "qr-code-error", { message: this._error }); alert(`error: ${this.error}`);
this._notifyExternalScanner(this.error);
} }
} }
protected render(): TemplateResult { protected render() {
return html`${this._error if (this._nativeBarcodeScanner && !this._manual) {
? html`<ha-alert alert-type="error">${this._error}</ha-alert>` return nothing;
}
return html`${this.error
? html`<ha-alert alert-type="error">${this.error}</ha-alert>`
: ""} : ""}
${navigator.mediaDevices ${navigator.mediaDevices && !this._manual
? html`<video></video> ? html`<video></video>
<div id="canvas-container"> <div id="canvas-container">
${this._cameras && this._cameras.length > 1 ${this._cameras && this._cameras.length > 1
@@ -80,21 +100,26 @@ class HaQrScanner extends LitElement {
></ha-icon-button> ></ha-icon-button>
${this._cameras!.map( ${this._cameras!.map(
(camera) => html` (camera) => html`
<mwc-list-item <ha-list-item
.value=${camera.id} .value=${camera.id}
@click=${this._cameraChanged} @click=${this._cameraChanged}
>${camera.label}</mwc-list-item
> >
${camera.label}
</ha-list-item>
` `
)} )}
</ha-button-menu>` </ha-button-menu>`
: ""} : nothing}
</div>` </div>`
: html`<ha-alert alert-type="warning"> : html`${this._manual
${!window.isSecureContext ? nothing
? this.localize("ui.components.qr-scanner.only_https_supported") : html`<ha-alert alert-type="warning">
: this.localize("ui.components.qr-scanner.not_supported")} ${!window.isSecureContext
</ha-alert> ? this.localize(
"ui.components.qr-scanner.only_https_supported"
)
: this.localize("ui.components.qr-scanner.not_supported")}
</ha-alert>`}
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p> <p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
<div class="row"> <div class="row">
<ha-textfield <ha-textfield
@@ -102,33 +127,44 @@ class HaQrScanner extends LitElement {
@keyup=${this._manualKeyup} @keyup=${this._manualKeyup}
@paste=${this._manualPaste} @paste=${this._manualPaste}
></ha-textfield> ></ha-textfield>
<mwc-button @click=${this._manualSubmit} <mwc-button @click=${this._manualSubmit}>
>${this.localize("ui.common.submit")}</mwc-button ${this.localize("ui.common.submit")}
> </mwc-button>
</div>`}`; </div>`}`;
} }
private get _nativeBarcodeScanner(): boolean {
return Boolean(this.hass.auth.external?.config.hasBarCodeScanner);
}
private async _loadQrScanner() { private async _loadQrScanner() {
if (this._nativeBarcodeScanner) {
this._openExternalScanner();
return;
}
if (!navigator.mediaDevices) {
return;
}
const QrScanner = (await import("qr-scanner")).default; const QrScanner = (await import("qr-scanner")).default;
if (!(await QrScanner.hasCamera())) { if (!(await QrScanner.hasCamera())) {
this._error = "No camera found"; this._reportError("No camera found");
return; return;
} }
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js"; QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
this._listCameras(QrScanner); this._listCameras(QrScanner);
this._qrScanner = new QrScanner( this._qrScanner = new QrScanner(
this._video, this._video!,
this._qrCodeScanned, this._qrCodeScanned,
this._qrCodeError this._qrCodeError
); );
// @ts-ignore // @ts-ignore
const canvas = this._qrScanner.$canvas; const canvas = this._qrScanner.$canvas;
this._canvasContainer.appendChild(canvas); this._canvasContainer!.appendChild(canvas);
canvas.style.display = "block"; canvas.style.display = "block";
try { try {
await this._qrScanner.start(); await this._qrScanner.start();
} catch (err: any) { } catch (err: any) {
this._error = err; this._reportError(err);
} }
} }
@@ -140,16 +176,16 @@ class HaQrScanner extends LitElement {
if (err === "No QR code found") { if (err === "No QR code found") {
this._qrNotFoundCount++; this._qrNotFoundCount++;
if (this._qrNotFoundCount === 250) { if (this._qrNotFoundCount === 250) {
this._error = err; this._reportError(err);
} }
return; return;
} }
this._error = err.message || err; this._reportError(err.message || err);
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(err); console.log(err);
}; };
private _qrCodeScanned = async (qrCodeString: string): Promise<void> => { private _qrCodeScanned = (qrCodeString: string): void => {
this._qrNotFoundCount = 0; this._qrNotFoundCount = 0;
fireEvent(this, "qr-code-scanned", { value: qrCodeString }); fireEvent(this, "qr-code-scanned", { value: qrCodeString });
}; };
@@ -175,6 +211,62 @@ class HaQrScanner extends LitElement {
this._qrScanner?.setCamera((ev.target as any).value); this._qrScanner?.setCamera((ev.target as any).value);
} }
private _openExternalScanner() {
this._removeListener = addExternalBarCodeListener((msg) => {
if (msg.command === "bar_code/scan_result") {
if (msg.payload.format !== "qr_code") {
this._notifyExternalScanner(
`Wrong barcode scanned! ${msg.payload.format}: ${msg.payload.rawValue}, we need a QR code.`
);
} else {
this._qrCodeScanned(msg.payload.rawValue);
}
} else if (msg.command === "bar_code/aborted") {
this._closeExternalScanner();
if (msg.payload.reason === "canceled") {
fireEvent(this, "qr-code-closed");
} else {
this._manual = true;
}
}
return true;
});
this.hass.auth.external!.fireMessage({
type: "bar_code/scan",
payload: {
title: this.title || "Scan QR code",
description: this.description || "Scan a barcode.",
alternative_option_label:
this.alternativeOptionLabel || "Click to manually enter the barcode",
},
});
}
private _closeExternalScanner() {
this._removeListener?.();
this._removeListener = undefined;
this.hass.auth.external!.fireMessage({
type: "bar_code/close",
});
}
private _notifyExternalScanner(message: string) {
if (!this.hass.auth.external) {
return;
}
this.hass.auth.external.fireMessage({
type: "bar_code/notify",
payload: {
message,
},
});
this.error = undefined;
}
private _reportError(message: string) {
fireEvent(this, "qr-code-error", { message });
}
static styles = css` static styles = css`
canvas { canvas {
width: 100%; width: 100%;
@@ -210,6 +302,7 @@ declare global {
interface HASSDomEvents { interface HASSDomEvents {
"qr-code-scanned": { value: string }; "qr-code-scanned": { value: string };
"qr-code-error": { message: string }; "qr-code-error": { message: string };
"qr-code-closed": undefined;
} }
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {

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