Compare commits

...

92 Commits

Author SHA1 Message Date
Zack
43dcd75be6 Fix Breaking Icon with new MDI Icons 2022-07-21 15:00:07 -05:00
Bram Kragten
38f19b6180 Repair: load translations for all integrations with issues (#13252) 2022-07-21 09:52:44 -05:00
Steve Repsher
c99f00ba50 Setup stronger type for localize key (#13244) 2022-07-21 13:13:11 +00:00
Zack Barett
ce5776f59d Add Repairs to Settings (#13249)
Co-authored-by: Zack Barett <zackbarett@hey.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-21 14:48:37 +02:00
Steve Repsher
12ff70020a Fix more bad localize keys (#13250) 2022-07-21 12:17:29 +02:00
Felipe Santos
5d605447a5 Support more icons for media players (#12997)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-21 12:16:00 +02:00
Michael Irigoyen
6d88d46ce4 Update Material Design Icons to v7.0.96 (#13175)
* Update Material Design Icons to v7.0.96

* Fetch updated MDI packages
2022-07-21 11:43:48 +02:00
Bram Kragten
cbe2643146 Use translation_key for repairs (#13246) 2022-07-20 11:52:55 -05:00
Steve Repsher
d332b8ab14 Fix bad localize keys (#13245)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-20 18:34:09 +02:00
Bram Kragten
adfef05110 Bump contrast and brightness of dark mode map (#13243) 2022-07-20 14:15:34 +00:00
Yosi Levy
ca6a7bfbe2 Additional RTL energy fixes (#13182) 2022-07-20 14:37:48 +02:00
Franck Nijhof
a22f96a481 Migrate repairs to repairs API (#13242) 2022-07-20 14:34:57 +02:00
Yosi Levy
688109524d RTL fixes - media, attributes (#13241) 2022-07-20 11:14:50 +02:00
Sven Serlier
62dd7111ce Update design.home-assistant.io (#13240)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-20 08:24:52 +00:00
Zack Barett
1267575f62 Merge pull request #13235 from home-assistant/fix-builds
Stringify Python version to use 3.10 vs 3.1
2022-07-19 23:25:39 -05:00
Paulus Schoutsen
b7da4dc68f Stringify Python version to use 3.10 vs 3.1 2022-07-19 21:11:21 -07:00
Zack Barett
826474518f Merge pull request #13228 from voydz/lovelace-map-autofit 2022-07-19 16:44:48 -05:00
Maximilian Ertl
a4b92fef3a Correctly set "allow-downloads" flag on iframes (#13218) 2022-07-19 20:59:45 +02:00
Bram Kragten
d41159591c Change map styles to "Voyager" (#13227) 2022-07-19 20:56:50 +02:00
Felix Rudat
cb256bc386 Add auto_fit config option to lovelace map card 2022-07-19 17:45:48 +02:00
Zack Barett
bd50d6a6a3 Start the Repairs dashboard (#13192)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-19 14:25:47 +00:00
Bram Kragten
05418fc83b Fix dev tools event (#13225) 2022-07-19 11:17:13 +00:00
Franck Nijhof
8b675cdbba Remove unused mypy config from pyproject (#13224) 2022-07-19 13:14:20 +02:00
Franck Nijhof
36b4909950 Bump Python to 3.10 (#13223) 2022-07-19 12:28:57 +02:00
Raman Gupta
157b3ba5f2 Clean up zwave_js device actions logic (#13185) 2022-07-19 12:11:05 +02:00
Yosi Levy
72443b4f24 RTL card fixes (#13207) 2022-07-19 12:09:50 +02:00
J. Nick Koston
1c7d3fe610 Sync frontend recoverable states with core (#13197) 2022-07-19 12:09:20 +02:00
Franck Nijhof
6ac4560b36 Use YAML in developer tools events (#13222) 2022-07-19 12:07:51 +02:00
Zack Barett
9309a4c7bc Update language when ZHA or Zwave arent installed (re: supported brands) (#13191) 2022-07-19 11:44:02 +02:00
Franck Nijhof
b582a4d014 Bump node to 16 (#13221) 2022-07-19 11:39:08 +02:00
Zack Barett
e4d233afa8 Filter Integration in Target and Area selectors + clean up some code (#13202) 2022-07-18 22:07:55 +02:00
dependabot[bot]
b131b255ec Bump actions/stale from 3.0.13 to 5.1.0 (#13212)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-18 17:09:37 +02:00
Kendell R
20bdb9ff35 Use theme default font for charts (#13210)
* Use theme default font for charts

* Prettier
2022-07-18 15:35:51 +02:00
Zack Barett
b611a58fce Add support for Supported Brands (#13184) 2022-07-13 17:51:17 +02:00
Zack Barett
24e54554ad Fix History Graph Name not being friendly (#13179) 2022-07-13 11:48:52 +02:00
Erik Montnemery
d23fca4dd1 Correct display of barometric pressure and rain (#13183) 2022-07-12 16:13:29 -05:00
Paulus Schoutsen
729e2f5248 Use entity name in device info page (#13165)
* Use entity name in device info page

* Adjust to new format

* Use latest API

* Fix types

* Fix CI?

Co-authored-by: Zack <zackbarett@hey.com>
2022-07-11 19:07:07 -07:00
Zack Barett
437723c6a6 Fix Number Selector Label (#13178) 2022-07-12 01:05:47 +00:00
Bram Kragten
a30c8205b1 Update dialog styles (#13132) 2022-07-11 15:07:10 -05:00
puddly
c50cf78bb4 Allow stale ZHA coordinator entries to be deleted (#13154)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-11 13:14:37 -05:00
Zack Barett
be52ba0ea9 Fix Suggested Value in HA-Form (#13173) 2022-07-11 09:46:32 -05:00
Yosi Levy
55e9ebc4d2 Energy panel/cards - RTL fixes (#13171) 2022-07-11 15:47:22 +02:00
Bram Kragten
29c3fb0f92 Fix energy demo (#13172) 2022-07-11 08:44:34 -05:00
Erik Montnemery
7c3cd9d88d Merge pull request #13170 from home-assistant/number_customize_units
Allow customizing number unit of measurement
2022-07-11 14:49:47 +02:00
Erik
4881d699e3 Allow customizing number unit of measurement 2022-07-11 14:05:23 +02:00
Joakim Sørensen
414db83359 Hide homeassistant from partial restore if no version (#13168) 2022-07-11 12:55:43 +02:00
Joakim Sørensen
cd4f6e19f4 Await backup restore (#13167) 2022-07-11 12:50:37 +02:00
Joakim Sørensen
da709cbbd1 Add hacs_repository my redirect (#13153) 2022-07-11 09:06:45 +02:00
Raman Gupta
399efca411 Only show firmware update warning if no firmware update is in progress (#13068) 2022-07-07 15:03:11 +02:00
Bram Kragten
f8bccf9e79 Bumped version to 20220707.0 2022-07-07 15:02:08 +02:00
Bram Kragten
87aab72b63 opti search params history 2022-07-07 15:01:29 +02:00
Bram Kragten
24688ba18e fix reload history when no selection made (#13137) 2022-07-07 14:58:51 +02:00
Zack Barett
e8086b6a6f Refactor History Panel Code a bit (#13129)
Co-authored-by: D3v01dZA <caltona1@gmail.com>
2022-07-07 14:27:28 +02:00
Zack Barett
e0a9c57a54 Bumped version to 20220706.0 (#13125) 2022-07-06 18:55:52 +02:00
Bram Kragten
e63953ecbc Fix scene editor (#13123) 2022-07-06 13:50:37 +00:00
Bram Kragten
72af200190 Remove localstorage from history, use url (#13122) 2022-07-06 08:44:28 -05:00
Zack Barett
2094ae534b Fix History Panel when no entities are found (#13103) 2022-07-06 01:05:40 +02:00
Zack Barett
153ebb2a20 Bumped version to 20220705.0 (#13098) 2022-07-05 18:40:26 +02:00
Michael Irigoyen
5d58e52eea Update MDI to v6.9.96 (#13096) 2022-07-05 18:38:24 +02:00
Zack Barett
5038f9c3c6 Some Updates to the History Panel (#13095) 2022-07-05 15:31:17 +00:00
Zack Barett
b285fda61b Move Sign out back to Account Card for Cloud (#13094) 2022-07-05 17:15:09 +02:00
D3v01dZA
6cd38472cd Multiple entities on history panel bugfix and additional improvements (#13045)
Co-authored-by: Zack Barett <zackbarett@hey.com>
2022-07-05 09:23:38 -05:00
Erik Montnemery
8fd5f53f96 Exclude config and diagnostic entities from scenes (#13072)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-05 08:49:05 -05:00
Zack Barett
b70eee77ef Remove config Path from about (#13049)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2022-07-05 09:41:40 +00:00
Raman Gupta
4148b8c7aa Get rid of dupe HTML in zwave_js firmware upload dialog (#13067) 2022-07-05 11:26:38 +02:00
Zack Barett
e22dd0c49d Fix Energy Compare Translations (#13055)
* Fix Energy Compare Translations

* Fix alert
2022-07-05 11:23:46 +02:00
dependabot[bot]
30a254f98f Bump actions/checkout from 2 to 3 (#13075)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:45:41 +02:00
dependabot[bot]
8fcb3a017b Bump actions/setup-python from 2 to 4 (#13078)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:28:53 +02:00
dependabot[bot]
5e29c7efa9 Bump dessant/lock-threads from 2.0.1 to 3.0.0 (#13077)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:26:46 +02:00
dependabot[bot]
b6cc3e3ef0 Bump actions/setup-node from 2 to 3 (#13080)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:04:22 +02:00
dependabot[bot]
184bdc0c85 Bump github/codeql-action from 1 to 2 (#13081)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-05 10:04:02 +02:00
Sven Serlier
f7fb731dc8 Add dependabot (#13073)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2022-07-05 09:40:57 +02:00
Raman Gupta
77977f64a3 Don't check zwave_js node firmware update capabilities (#13066) 2022-07-03 13:45:19 -07:00
Franck Nijhof
e8da573ba2 Only upload nighlty wheel build artifact (#13064) 2022-07-02 08:49:27 -05:00
Franck Nijhof
07332bf155 Fix version detection in build env to allow for nightly builds (#13062) 2022-07-02 00:22:54 +02:00
Franck Nijhof
f3c7583bf7 Add nightly frontend builds (#13061) 2022-07-01 14:30:40 -07:00
Paulus Schoutsen
1cc02415d3 Fix rendering config entry titles (#13060)
* Fix rendering config entry titles

* Fix location height

* Only fetch installation type when user not created yet
2022-07-01 13:52:56 -05:00
Paulus Schoutsen
6ca3f06ea0 Merge pull request #13057 from home-assistant/hide-sun-onboarding
Do not show the sun during onboarding
2022-07-01 11:05:45 -07:00
Paulus Schoutsen
198e2b7bdf Do not show the sun during onboarding 2022-07-01 10:23:23 -07:00
Zack Barett
5a68e2c977 Merge pull request #13053 from home-assistant/improve-autocompletion
improve autocompletion
2022-06-30 14:57:42 -05:00
Sven Serlier
d9d29db560 Fix demo labels (#13033) 2022-06-30 21:51:23 +02:00
Zack Barett
124c6dc2b8 Merge pull request #13048 from emufan/patch-1 2022-06-30 14:49:52 -05:00
Bram Kragten
0f3886e053 improve autocompletion 2022-06-30 21:41:58 +02:00
Zack Barett
b8bd15aa33 Bumped version to 20220630.0 (#13050) 2022-06-30 10:17:43 -07:00
Zack Barett
ed39aa6a7c Merge pull request #13046 from Nardol/no_fix_em_dash 2022-06-30 08:20:41 -05:00
Zack Barett
405cae9b5f Merge pull request #13047 from piitaya/feat/ha-code-editor-autocomplete-icon-option 2022-06-30 08:19:54 -05:00
emufan
3ca2cbb3f9 Update ha-config-integrations.ts 2022-06-30 15:03:34 +02:00
piitaya
c295ae56ab separate autocomplete options in ha-code-editor 2022-06-30 11:27:04 +02:00
piitaya
830364721b separate autocomplete options in ha-code-editor 2022-06-30 11:22:30 +02:00
Patrick ZAJDA
19089213e3 Add em dash instead of blank when no fix is needed in statistics
Signed-off-by: Patrick ZAJDA <patrick@zajda.fr>
2022-06-30 11:12:35 +02:00
Zack Barett
b633067e5c Merge pull request #13041 from home-assistant/Fix-About-Page 2022-06-29 17:50:43 -05:00
Zack
1b8874cbd4 Fix Translation on About Page 2022-06-29 15:27:54 -05:00
137 changed files with 2536 additions and 1139 deletions

8
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
time: "06:00"
open-pull-requests-limit: 10

View File

@@ -11,7 +11,7 @@ on:
- master
env:
NODE_VERSION: 14
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
jobs:
@@ -19,9 +19,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
@@ -43,9 +43,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
@@ -62,9 +62,9 @@ jobs:
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
@@ -81,9 +81,9 @@ jobs:
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -6,7 +6,7 @@ on:
- dev
env:
NODE_VERSION: 14
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
jobs:
@@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn

View File

@@ -9,7 +9,7 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2.0.1
- uses: dessant/lock-threads@v3.0.0
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: "30"

63
.github/workflows/nightly.yaml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Nightly
on:
workflow_dispatch:
schedule:
- cron: "0 1 * * *"
env:
PYTHON_VERSION: "3.10"
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
permissions:
actions: none
jobs:
nightly:
name: Nightly
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
- name: Download translations
run: ./script/translations_download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Bump version
run: script/version_bump.js nightly
- name: Build nightly Python wheels
run: |
pip install build
yarn install
script/build_frontend
rm -rf dist home_assistant_frontend.egg-info
python3 -m build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error

View File

@@ -6,8 +6,8 @@ on:
- published
env:
PYTHON_VERSION: 3.8
NODE_VERSION: 14
PYTHON_VERSION: "3.10"
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions
@@ -21,21 +21,21 @@ jobs:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # Required to upload release assets
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 90 days stale policy
uses: actions/stale@v3.0.13
uses: actions/stale@v5.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90

View File

@@ -8,7 +8,7 @@ on:
- src/translations/en.json
env:
NODE_VERSION: 14
NODE_VERSION: 16
jobs:
upload:
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Upload Translations
run: |

2
.nvmrc
View File

@@ -1 +1 @@
14
16

View File

@@ -27,7 +27,7 @@ module.exports = {
version() {
const version = fs
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
.match(/version\W+=\W"(\d{8}\.\d)"/);
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
if (!version) {
throw Error("Version not found");
}

View File

@@ -1 +1,30 @@
[]
[
{
"path": "M20,20H7A2,2 0 0,1 5,18V8.94L2.23,5.64C2.09,5.47 2,5.24 2,5A1,1 0 0,1 3,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20M8.5,7A0.5,0.5 0 0,0 8,7.5V8.5A0.5,0.5 0 0,0 8.5,9H18.5A0.5,0.5 0 0,0 19,8.5V7.5A0.5,0.5 0 0,0 18.5,7H8.5M8.5,11A0.5,0.5 0 0,0 8,11.5V12.5A0.5,0.5 0 0,0 8.5,13H18.5A0.5,0.5 0 0,0 19,12.5V11.5A0.5,0.5 0 0,0 18.5,11H8.5M8.5,15A0.5,0.5 0 0,0 8,15.5V16.5A0.5,0.5 0 0,0 8.5,17H13.5A0.5,0.5 0 0,0 14,16.5V15.5A0.5,0.5 0 0,0 13.5,15H8.5Z",
"name": "android-messages"
},
{
"path": "M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,12L17.5,10.5L15,12V4H20V12Z",
"name": "book-variant-multiple"
},
{
"path": "M21,14H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10L8,21V22H16V21L14,18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z",
"name": "desktop-mac"
},
{
"path": "M21,14V4H3V14H21M21,2A2,2 0 0,1 23,4V16A2,2 0 0,1 21,18H14L16,21V22H8V21L10,18H3C1.89,18 1,17.1 1,16V4C1,2.89 1.89,2 3,2H21M4,5H15V10H4V5M16,5H20V7H16V5M20,8V13H16V8H20M4,11H9V13H4V11M10,11H15V13H10V11Z",
"name": "desktop-mac-dashboard"
},
{
"path": "M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z",
"name": "discord"
},
{
"path": "M8.06,7.78C7.5,7.78 7.17,7.73 7.08,7.64L6.66,13.73C7.19,14.05 7.88,14.3 8.72,14.5C9.56,14.71 10.78,14.77 12.38,14.67C13.97,14.58 15.63,14.23 17.34,13.64L16.55,4.22C15.67,5.09 14.38,5.91 12.66,6.66C11.13,7.31 9.81,7.69 8.72,7.78H8.06M7.97,5.34C7.28,5.94 7,6.34 7.13,6.56C7.22,6.78 7.7,6.84 8.58,6.75C9.67,6.66 10.91,6.31 12.28,5.72C13.22,5.31 14.03,4.88 14.72,4.41C15.41,3.94 15.88,3.55 16.13,3.23C16.38,2.92 16.47,2.7 16.41,2.58C16.34,2.42 16.03,2.34 15.47,2.34C14.34,2.34 12.94,2.7 11.25,3.42C9.81,4.05 8.72,4.69 7.97,5.34M17.34,2.2C17.41,2.33 17.44,2.47 17.44,2.63L18.61,17C18.61,18.73 18,20.09 16.83,21.07C15.64,22.05 14.03,22.55 12,22.55C10,22.55 8.4,22.04 7.2,21C6,20 5.39,18.64 5.39,16.92L6.09,6.47C6.09,6.22 6.2,5.94 6.42,5.63C6.64,5.31 6.84,5.06 7.03,4.88L7.36,4.59C8.33,3.78 9.5,3.08 10.88,2.5C11.81,2.08 12.73,1.77 13.62,1.57C14.5,1.37 15.3,1.3 16,1.38C16.71,1.46 17.16,1.73 17.34,2.2Z",
"name": "google-home"
},
{
"path": "M19.25,19H4.75V3H19.25M14,22H10V21H14M18,0H6A3,3 0 0,0 3,3V21A3,3 0 0,0 6,24H18A3,3 0 0,0 21,21V3A3,3 0 0,0 18,0Z",
"name": "tablet-android"
}
]

View File

@@ -797,7 +797,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 34,
on: true,
friendly_name: "altan_motion_sensor",
friendly_name: "Porch motion sensor",
device_class: "motion",
},
},
@@ -818,7 +818,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 74,
on: true,
friendly_name: "badrumssensor",
friendly_name: "Bathroom motion sensor",
device_class: "motion",
},
},
@@ -829,7 +829,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47,
on: true,
dark: true,
friendly_name: "R\u00f6relsesensor k\u00e4llaren 1",
friendly_name: "Basement motion sensor",
device_class: "motion",
icon: "mdi:walk",
},
@@ -863,7 +863,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 60,
on: true,
friendly_name: "R\u00f6relsesensor skafferiet",
friendly_name: "Pantry motion sensor",
device_class: "motion",
icon: "mdi:walk",
},
@@ -875,7 +875,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60,
on: true,
dark: true,
friendly_name: "R\u00f6relsesensor k\u00e4llaren 2",
friendly_name: "Stair motion sensor",
device_class: "motion",
icon: "mdi:walk",
},
@@ -887,7 +887,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47,
on: true,
dark: true,
friendly_name: "B\u00e4nksensor",
friendly_name: "Bench sensor",
device_class: "motion",
},
},

View File

@@ -277,7 +277,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
],
show_header_toggle: false,
type: "entities",
title: "Bandbredd",
title: "Bandwidth",
},
// {
// title: "Updater",

View File

@@ -1,5 +1,4 @@
// Compat needs to be first import
import "../../src/resources/compatibility";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate";
import {
@@ -7,9 +6,14 @@ import {
provideHass,
} from "../../src/fake_data/provide_hass";
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
import "../../src/resources/compatibility";
import { HomeAssistant } from "../../src/types";
import { selectedDemoConfig } from "./configs/demo-configs";
import { mockAuth } from "./stubs/auth";
import { mockConfigEntries } from "./stubs/config_entries";
import { mockEnergy } from "./stubs/energy";
import { energyEntities } from "./stubs/entities";
import { mockEntityRegistry } from "./stubs/entity_registry";
import { mockEvents } from "./stubs/events";
import { mockFrontend } from "./stubs/frontend";
import { mockHistory } from "./stubs/history";
@@ -20,9 +24,6 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
@@ -51,8 +52,36 @@ class HaDemo extends HomeAssistantAppEl {
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass);
mockConfigEntries(hass);
mockEntityRegistry(hass, [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
},
]);
hass.addEntities(energyEntities());

View File

@@ -1,41 +0,0 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry?domain=co2signal", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
hass.mockWS("config/entity_registry/list", () => [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
},
]);
};

View File

@@ -0,0 +1,20 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfigEntries = (hass: MockHomeAssistant) => {
hass.mockWS("config_entries/get", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_remove_device: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
};

View File

@@ -4,4 +4,6 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEntityRegistry = (
hass: MockHomeAssistant,
data: EntityRegistryEntry[] = []
) => hass.mockWS("config/entity_registry/list", () => data);
) => {
hass.mockWS("config/entity_registry/list", () => data);
};

View File

@@ -8,7 +8,7 @@ module.exports = [
{
category: "lovelace",
// Label for in the sidebar
header: "Lovelace",
header: "Dashboards",
// Specify order of pages. Any pages in the category folder but not listed here will
// automatically be added after the pages listed here.
pages: ["introduction"],
@@ -34,7 +34,7 @@ module.exports = [
},
{
category: "misc",
header: "Miscelaneous",
header: "Miscellaneous",
},
{
category: "brand",

View File

@@ -31,7 +31,7 @@ const ENTITIES = [
friendly_name: "Office Light",
}),
getEntity("fan", "kitchen", "on", {
friendly_name: "Second Office Fan",
friendly_name: "Kitchen Fan",
}),
getEntity("binary_sensor", "kitchen_door", "on", {
friendly_name: "Office Door",
@@ -102,7 +102,7 @@ class DemoArea extends LitElement {
picture: "/images/office.jpg",
},
{
name: "Second Office",
name: "Kitchen",
area_id: "kitchen",
picture: "/images/kitchen.png",
},

View File

@@ -1,11 +1,11 @@
---
title: Introduction
---
Lovelace has many different cards. Each card allows the user to tell
Dashboards have many different cards. Each card allows the user to tell
a different story about what is going on in their house. These cards
are very customizable, as no household is the same.
This gallery helps our developers and designers to see all the
different states that each card can be in.
Check [the Lovelace documentation](https://www.home-assistant.io/lovelace) for instructions on how to get started with Lovelace.
Check [the Dashboards documentation](https://www.home-assistant.io/dashboards/) for instructions on how to get started with Dashboards.

View File

@@ -194,6 +194,7 @@ const createEntityRegistryEntries = (
name: null,
icon: null,
platform: "updater",
has_entity_name: false,
},
];

View File

@@ -69,7 +69,7 @@ const ENTITIES = [
effect_list: ["random", "colorloop"],
}),
getEntity("light", "color_RGB_light", "on", {
friendly_name: "Color Effets Light",
friendly_name: "Color Effects Light",
brightness: 255,
rgb_color: [30, 100, 255],
supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,

View File

@@ -81,10 +81,10 @@ class HassioAddonRepositoryEl extends LitElement {
? this.supervisor.localize(
"common.new_version_available"
)
: this.supervisor.localize("addon.installed")
: this.supervisor.localize("addon.state.installed")
: addon.available
? this.supervisor.localize("addon.not_installed")
: this.supervisor.localize("addon.not_available")}
? this.supervisor.localize("addon.state.not_installed")
: this.supervisor.localize("addon.state.not_available")}
.iconClass=${addon.installed
? addon.update_available
? "update"

View File

@@ -336,7 +336,7 @@ class HassioAddonConfig extends LitElement {
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.common.update_available",
"addon.failed_to_reset",
"error",
extractApiErrorMessage(err)
);

View File

@@ -81,7 +81,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
);
} catch (err: any) {
this._error = this.supervisor.localize(
"addon.documentation.get_logs",
"addon.documentation.get_documentation",
"error",
extractApiErrorMessage(err)
);

View File

@@ -168,23 +168,24 @@ export class SupervisorBackupContent extends LitElement {
: ""}
${this.backupType === "partial"
? html`<div class="partial-picker">
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup
? this.backup.homeassistant
: this.hass.config.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@change=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>
${this.backup?.homeassistant
? html`<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup
? this.backup.homeassistant
: this.hass.config.version}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@change=${this.toggleHomeAssistant}
>
</ha-checkbox>
</ha-formfield>`
: ""}
${foldersSection?.templates.length
? html`
<ha-formfield

View File

@@ -201,26 +201,24 @@ class HassioBackupDialog
}
if (!this._dialogParams?.onboarding) {
this.hass!.callApi(
"POST",
try {
await this.hass!.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
).then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
`hassio/${
atLeastVersion(this.hass!.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
);
this.closeDialog();
} catch (error: any) {
this._error = error.body.message;
}
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
await fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(backupDetails),
});

View File

@@ -25,7 +25,7 @@ import {
} from "../../src/data/supervisor/supervisor";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
import { HomeAssistant, Route } from "../../src/types";
import { HomeAssistant, Route, TranslationDict } from "../../src/types";
import { getTranslation } from "../../src/util/common-translation";
declare global {
@@ -124,9 +124,13 @@ export class SupervisorBaseElement extends urlSyncMixin(
this.supervisor = {
...this.supervisor,
localize: await computeLocalize(this.constructor.prototype, language, {
[language]: data,
}),
localize: await computeLocalize<TranslationDict["supervisor"]>(
this.constructor.prototype,
language,
{
[language]: data,
}
),
};
}

View File

@@ -72,8 +72,8 @@
"@material/mwc-textfield": "0.25.3",
"@material/mwc-top-app-bar-fixed": "^0.25.3",
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
"@mdi/js": "6.7.96",
"@mdi/svg": "6.7.96",
"@mdi/js": "7.0.96",
"@mdi/svg": "7.0.96",
"@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20220629.0"
version = "20220707.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
@@ -23,8 +23,3 @@ include-package-data = true
[tool.setuptools.packages.find]
include = ["hass_frontend*"]
[tool.mypy]
python_version = 3.4
show_error_codes = true
strict = true

View File

@@ -24,10 +24,15 @@ function auto(version) {
return patch(version);
}
function nightly() {
return `${today()}.dev`;
}
const methods = {
patch,
today,
auto,
nightly,
};
async function main(args) {
@@ -57,7 +62,11 @@ async function main(args) {
console.log("Current version:", version);
console.log("New version:", newVersion);
fs.writeFileSync("pyproject.toml", setup.replace(version, newVersion), "utf-8");
fs.writeFileSync(
"pyproject.toml",
setup.replace(version, newVersion),
"utf-8"
);
if (!commit) {
return;

View File

@@ -37,6 +37,7 @@ import {
mdiLightningBolt,
mdiMailbox,
mdiMapMarkerRadius,
mdiMicrophone,
mdiMolecule,
mdiMoleculeCo,
mdiMoleculeCo2,
@@ -47,7 +48,6 @@ import {
mdiRobotVacuum,
mdiScriptText,
mdiSineWave,
mdiTextToSpeech,
mdiThermometer,
mdiThermostat,
mdiTimerOutline,
@@ -74,8 +74,9 @@ export const FIXED_DOMAIN_ICONS = {
camera: mdiVideo,
climate: mdiThermostat,
configurator: mdiCog,
conversation: mdiTextToSpeech,
conversation: mdiMicrophone,
counter: mdiCounter,
demo: mdiHomeAssistant,
fan: mdiFan,
google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities,

View File

@@ -76,7 +76,11 @@ class Storage {
public setValue(storageKey: string, value: any): any {
this._storage[storageKey] = value;
try {
window.localStorage.setItem(storageKey, JSON.stringify(value));
if (value === undefined) {
window.localStorage.removeItem(storageKey);
} else {
window.localStorage.setItem(storageKey, JSON.stringify(value));
}
} catch (err: any) {
// Safari in private mode doesn't allow localstorage
}

View File

@@ -5,8 +5,7 @@ export type LeafletModuleType = typeof import("leaflet");
export type LeafletDrawModuleType = typeof import("leaflet-draw");
export const setupLeafletMap = async (
mapElement: HTMLElement,
darkMode?: boolean
mapElement: HTMLElement
): Promise<[Map, LeafletModuleType, TileLayer]> => {
if (!mapElement.parentNode) {
throw new Error("Cannot setup Leaflet map on disconnected element");
@@ -23,7 +22,7 @@ export const setupLeafletMap = async (
mapElement.parentNode.appendChild(style);
map.setView([52.3731339, 4.8903147], 13);
const tileLayer = createTileLayer(Leaflet, Boolean(darkMode)).addTo(map);
const tileLayer = createTileLayer(Leaflet).addTo(map);
return [map, Leaflet, tileLayer];
};
@@ -31,23 +30,19 @@ export const setupLeafletMap = async (
export const replaceTileLayer = (
leaflet: LeafletModuleType,
map: Map,
tileLayer: TileLayer,
darkMode: boolean
tileLayer: TileLayer
): TileLayer => {
map.removeLayer(tileLayer);
tileLayer = createTileLayer(leaflet, darkMode);
tileLayer = createTileLayer(leaflet);
tileLayer.addTo(map);
return tileLayer;
};
const createTileLayer = (
leaflet: LeafletModuleType,
darkMode: boolean
): TileLayer =>
const createTileLayer = (leaflet: LeafletModuleType): TileLayer =>
leaflet.tileLayer(
`https://{s}.basemaps.cartocdn.com/${
darkMode ? "dark_all" : "light_all"
}/{z}/{x}/{y}${leaflet.Browser.retina ? "@2x.png" : ".png"}`,
`https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}${
leaflet.Browser.retina ? "@2x.png" : ".png"
}`,
{
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attributions">CARTO</a>',

View File

@@ -8,6 +8,7 @@ import {
mdiCalendar,
mdiCast,
mdiCastConnected,
mdiCastOff,
mdiChartSankey,
mdiCheckCircleOutline,
mdiClock,
@@ -25,7 +26,15 @@ import {
mdiPowerPlug,
mdiPowerPlugOff,
mdiRestart,
mdiSpeaker,
mdiSpeakerOff,
mdiSpeakerPause,
mdiSpeakerPlay,
mdiSwapHorizontal,
mdiTelevision,
mdiTelevisionOff,
mdiTelevisionPause,
mdiTelevisionPlay,
mdiToggleSwitchVariant,
mdiToggleSwitchVariantOff,
mdiWeatherNight,
@@ -127,7 +136,40 @@ export const domainIconWithoutDefault = (
}
case "media_player":
return compareState === "playing" ? mdiCastConnected : mdiCast;
switch (stateObj?.attributes.device_class) {
case "speaker":
switch (compareState) {
case "playing":
return mdiSpeakerPlay;
case "paused":
return mdiSpeakerPause;
case "off":
return mdiSpeakerOff;
default:
return mdiSpeaker;
}
case "tv":
switch (compareState) {
case "playing":
return mdiTelevisionPlay;
case "paused":
return mdiTelevisionPause;
case "off":
return mdiTelevisionOff;
default:
return mdiTelevision;
}
default:
switch (compareState) {
case "playing":
case "paused":
return mdiCastConnected;
case "off":
return mdiCastOff;
default:
return mdiCast;
}
}
case "switch":
switch (stateObj?.attributes.device_class) {

View File

@@ -3,10 +3,41 @@ import { shouldPolyfill as shouldPolyfillPluralRules } from "@formatjs/intl-plur
import { shouldPolyfill as shouldPolyfillRelativeTime } from "@formatjs/intl-relativetimeformat/lib/should-polyfill";
import { shouldPolyfill as shouldPolyfillDateTime } from "@formatjs/intl-datetimeformat/lib/should-polyfill";
import IntlMessageFormat from "intl-messageformat";
import { Resources } from "../../types";
import { Resources, TranslationDict } from "../../types";
import { getLocalLanguage } from "../../util/common-translation";
export type LocalizeFunc = (key: string, ...args: any[]) => string;
// Exclude some patterns from key type checking for now
// These are intended to be removed as errors are fixed
// Fixing component category will require tighter definition of types from backend and/or web socket
type LocalizeKeyExceptions =
| `${string}`
| `panel.${string}`
| `state.${string}`
| `state_attributes.${string}`
| `state_badge.${string}`
| `groups.${string}`
| `config_entry.${string}`
| `ui.${string}`
| `${keyof TranslationDict["supervisor"]}.${string}`
| `component.${string}`;
// Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types
type FlattenObjectKeys<
T extends Record<string, any>,
Key extends keyof T = keyof T
> = Key extends string
? T[Key] extends Record<string, unknown>
? `${Key}.${FlattenObjectKeys<T[Key]>}`
: `${Key}`
: never;
export type LocalizeFunc<
Dict extends Record<string, unknown> = TranslationDict
> = (
key: FlattenObjectKeys<Dict> | LocalizeKeyExceptions,
...args: any[]
) => string;
interface FormatType {
[format: string]: any;
}
@@ -65,12 +96,14 @@ export const polyfillsLoaded =
* }
*/
export const computeLocalize = async (
export const computeLocalize = async <
Dict extends Record<string, unknown> = TranslationDict
>(
cache: any,
language: string,
resources: Resources,
formats?: FormatsType
): Promise<LocalizeFunc> => {
): Promise<LocalizeFunc<Dict>> => {
if (polyfillsLoaded) {
await polyfillsLoaded;
}

View File

@@ -188,6 +188,10 @@ export default class HaChartBase extends LitElement {
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
"--secondary-text-color"
);
ChartConstructor.defaults.font.family =
computedStyles.getPropertyValue("--mdc-typography-body1-font-family") ||
computedStyles.getPropertyValue("--mdc-typography-font-family") ||
"Roboto, Noto, sans-serif";
this.chart = new ChartConstructor(ctx, {
type: this.chartType,
@@ -376,6 +380,7 @@ export default class HaChartBase extends LitElement {
.chartTooltip .title {
text-align: center;
font-weight: 500;
direction: ltr;
}
.chartTooltip .footer {
font-weight: 500;

View File

@@ -186,6 +186,7 @@ class StateHistoryCharts extends LitElement {
line-height: 60px;
color: var(--secondary-text-color);
}
.container {
max-height: var(--history-max-height);
}

View File

@@ -84,20 +84,20 @@ class HaAddonPicker extends LitElement {
} else {
showAlertDialog(this, {
title: this.hass.localize(
"ui.componencts.addon-picker.error.no_supervisor.title"
"ui.components.addon-picker.error.no_supervisor.title"
),
text: this.hass.localize(
"ui.componencts.addon-picker.error.no_supervisor.description"
"ui.components.addon-picker.error.no_supervisor.description"
),
});
}
} catch (err: any) {
showAlertDialog(this, {
title: this.hass.localize(
"ui.componencts.addon-picker.error.fetch_addons.title"
"ui.components.addon-picker.error.fetch_addons.title"
),
text: this.hass.localize(
"ui.componencts.addon-picker.error.fetch_addons.description"
"ui.components.addon-picker.error.fetch_addons.description"
),
});
}

View File

@@ -76,6 +76,7 @@ class HaAttributes extends LitElement {
css`
.attribute-container {
margin-bottom: 8px;
direction: ltr;
}
.data-entry {
display: flex;

View File

@@ -51,7 +51,7 @@ class HaBluePrintPicker extends LitElement {
return html`
<ha-select
.label=${this.label ||
this.hass.localize("ui.components.blueprint-picker.label")}
this.hass.localize("ui.components.blueprint-picker.select_blueprint")}
fixedMenuPosition
naturalMenuWidth
.value=${this.value}

View File

@@ -2,6 +2,7 @@ import type {
Completion,
CompletionContext,
CompletionResult,
CompletionSource,
} from "@codemirror/autocomplete";
import type { EditorView, KeyBinding, ViewUpdate } from "@codemirror/view";
import { HassEntities } from "home-assistant-js-websocket";
@@ -48,6 +49,9 @@ export class HaCodeEditor extends ReactiveElement {
@property({ type: Boolean, attribute: "autocomplete-entities" })
public autocompleteEntities = false;
@property({ type: Boolean, attribute: "autocomplete-icons" })
public autocompleteIcons = false;
@property() public error = false;
@state() private _value = "";
@@ -160,16 +164,22 @@ export class HaCodeEditor extends ReactiveElement {
),
];
if (!this.readOnly && this.autocompleteEntities && this.hass) {
extensions.push(
this._loadedCodeMirror.autocompletion({
override: [
this._entityCompletions.bind(this),
this._mdiCompletions.bind(this),
],
maxRenderedOptions: 10,
})
);
if (!this.readOnly) {
const completionSources: CompletionSource[] = [];
if (this.autocompleteEntities && this.hass) {
completionSources.push(this._entityCompletions.bind(this));
}
if (this.autocompleteIcons) {
completionSources.push(this._mdiCompletions.bind(this));
}
if (completionSources.length > 0) {
extensions.push(
this._loadedCodeMirror.autocompletion({
override: completionSources,
maxRenderedOptions: 10,
})
);
}
}
this.codemirror = new this._loadedCodeMirror.EditorView({
@@ -199,7 +209,7 @@ export class HaCodeEditor extends ReactiveElement {
private _entityCompletions(
context: CompletionContext
): CompletionResult | null | Promise<CompletionResult | null> {
const entityWord = context.matchBefore(/[a-z_]{3,}\./);
const entityWord = context.matchBefore(/[a-z_]{3,}\.\w*/);
if (
!entityWord ||
@@ -217,7 +227,7 @@ export class HaCodeEditor extends ReactiveElement {
return {
from: Number(entityWord.from),
options: states,
span: /^\w*.\w*$/,
span: /^[a-z_]{3,}\.\w*$/,
};
}
@@ -247,7 +257,7 @@ export class HaCodeEditor extends ReactiveElement {
private async _mdiCompletions(
context: CompletionContext
): Promise<CompletionResult | null> {
const match = context.matchBefore(/mdi:/);
const match = context.matchBefore(/mdi:\S*/);
if (!match || (match.from === match.to && !context.explicit)) {
return null;
@@ -258,7 +268,7 @@ export class HaCodeEditor extends ReactiveElement {
return {
from: Number(match.from),
options: iconItems,
span: /^\w*.\w*$/,
span: /^mdi:\S*$/,
};
}

View File

@@ -11,7 +11,7 @@ export const createCloseHeading = (
hass: HomeAssistant,
title: string | TemplateResult
) => html`
<span class="header_title">${title}</span>
<div class="header_title">${title}</div>
<ha-icon-button
.label=${hass.localize("ui.dialogs.generic.close")}
.path=${mdiClose}
@@ -40,10 +40,13 @@ export class HaDialog extends DialogBase {
z-index: var(--dialog-z-index, 7);
-webkit-backdrop-filter: var(--dialog-backdrop-filter, none);
backdrop-filter: var(--dialog-backdrop-filter, none);
--mdc-dialog-box-shadow: var(--dialog-box-shadow, none);
--mdc-typography-headline6-font-weight: 400;
--mdc-typography-headline6-font-size: 1.574rem;
}
.mdc-dialog__actions {
justify-content: var(--justify-action-buttons, flex-end);
padding-bottom: max(env(safe-area-inset-bottom), 8px);
padding-bottom: max(env(safe-area-inset-bottom), 24px);
}
.mdc-dialog__actions span:nth-child(1) {
flex: var(--secondary-action-button-flex, unset);
@@ -54,17 +57,23 @@ export class HaDialog extends DialogBase {
.mdc-dialog__container {
align-items: var(--vertial-align-dialog, center);
}
.mdc-dialog__title {
padding: 24px 24px 0 24px;
}
.mdc-dialog__actions {
padding: 0 24px 24px 24px;
}
.mdc-dialog__title::before {
display: block;
height: 20px;
height: 0px;
}
.mdc-dialog .mdc-dialog__content {
position: var(--dialog-content-position, relative);
padding: var(--dialog-content-padding, 20px 24px);
padding: var(--dialog-content-padding, 24px);
}
:host([hideactions]) .mdc-dialog .mdc-dialog__content {
padding-bottom: max(
var(--dialog-content-padding, 20px),
var(--dialog-content-padding, 24px),
env(safe-area-inset-bottom)
);
}
@@ -72,10 +81,7 @@ export class HaDialog extends DialogBase {
position: var(--dialog-surface-position, relative);
top: var(--dialog-surface-top);
min-height: var(--mdc-dialog-min-height, auto);
border-radius: var(
--ha-dialog-border-radius,
var(--ha-card-border-radius, 4px)
);
border-radius: var(--ha-dialog-border-radius, 28px);
}
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
display: flex;
@@ -89,8 +95,8 @@ export class HaDialog extends DialogBase {
color: inherit;
}
.header_title {
margin-right: 40px;
margin-inline-end: 40px;
margin-right: 32px;
margin-inline-end: 32px;
margin-inline-start: initial;
direction: var(--direction);
}

View File

@@ -6,7 +6,7 @@ export const computeInitialHaFormData = (
): Record<string, any> => {
const data = {};
schema.forEach((field) => {
if (field.description?.suggested_value) {
if (field.description?.suggested_value !== undefined) {
data[field.name] = field.description.suggested_value;
} else if ("default" in field) {
data[field.name] = field.default;

View File

@@ -3,15 +3,15 @@ import {
CSSResultGroup,
html,
LitElement,
TemplateResult,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, query } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { HaCheckbox } from "../ha-checkbox";
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
import "../ha-slider";
import { HaTextField } from "../ha-textfield";
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
@customElement("ha-form-integer")
export class HaFormInteger extends LitElement implements HaFormElement {
@@ -105,7 +105,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
}
return (
this.schema.description?.suggested_value ||
this.schema.description?.suggested_value !== undefined ||
this.schema.default ||
this.schema.valueMin ||
0

View File

@@ -29,7 +29,102 @@ interface DeprecatedIcon {
};
}
const mdiDeprecatedIcons: DeprecatedIcon = {};
const mdiDeprecatedIcons: DeprecatedIcon = {
"android-messages": {
newName: "message-text",
removeIn: "2022.10",
},
"book-variant-multiple": {
newName: "bookmark-box-multiple",
removeIn: "2022.10",
},
"desktop-mac": {
newName: "monitor",
removeIn: "2022.10",
},
"desktop-mac-dashboard": {
newName: "monitor-dashboard",
removeIn: "2022.10",
},
discord: {
removeIn: "2022.10",
},
"diving-scuba": {
newName: "diving-scuba-mask",
removeIn: "2022.10",
},
"email-send": {
newName: "email-arrow-right",
removeIn: "2022.10",
},
"email-send-outline": {
newName: "email-arrow-right-outline",
removeIn: "2022.10",
},
"email-receive": {
newName: "email-arrow-left",
removeIn: "2022.10",
},
"email-receive-outline": {
newName: "email-arrow-left-outline",
removeIn: "2022.10",
},
"format-textdirection-r-to-l": {
newName: "format-pilcrow-arrow-left",
removeIn: "2022.10",
},
"format-textdirection-l-to-r": {
newName: "format-pilcrow-arrow-right",
removeIn: "2022.10",
},
"google-controller": {
newName: "controller",
removeIn: "2022.10",
},
"google-controller-off": {
newName: "controller-off",
removeIn: "2022.10",
},
"google-home": {
removeIn: "2022.10",
},
lecturn: {
newName: "lectern",
removeIn: "2022.10",
},
receipt: {
newName: "receipt-text",
removeIn: "2022.10",
},
"receipt-outline": {
newName: "receipt-text-outline",
removeIn: "2022.10",
},
"tablet-android": {
newName: "tablet",
removeIn: "2022.10",
},
"text-to-speech": {
newName: "microphone-message",
removeIn: "2022.10",
},
"text-to-speech-off": {
newName: "microphone-message-off",
removeIn: "2022.10",
},
"timeline-help": {
newName: "timeline-question",
removeIn: "2022.10",
},
"timeline-help-outline": {
newName: "timeline-question-outline",
removeIn: "2022.10",
},
"vector-point": {
newName: "vector-point-select",
removeIn: "2022.10",
},
};
const chunks: Chunks = {};

View File

@@ -1,8 +1,9 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit";
import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { DeviceRegistryEntry } from "../../data/device_registry";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
@@ -11,7 +12,11 @@ import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import { AreaSelector } from "../../data/selector";
import type { AreaSelector } from "../../data/selector";
import {
filterSelectorDevices,
filterSelectorEntities,
} from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import "../ha-area-picker";
@@ -29,13 +34,15 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
@state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
@@ -45,7 +52,7 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
];
}
protected updated(changedProperties) {
protected updated(changedProperties: PropertyValues): void {
if (
changedProperties.has("selector") &&
(this.selector.area.device?.integration ||
@@ -58,7 +65,7 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
}
}
protected render() {
protected render(): TemplateResult {
if (
(this.selector.area.device?.integration ||
this.selector.area.entity?.integration) &&
@@ -77,12 +84,6 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
no-add
.deviceFilter=${this._filterDevices}
.entityFilter=${this._filterEntities}
.includeDeviceClasses=${this.selector.area.entity?.device_class
? [this.selector.area.entity.device_class]
: undefined}
.includeDomains=${this.selector.area.entity?.domain
? [this.selector.area.entity.domain]
: undefined}
.disabled=${this.disabled}
.required=${this.required}
></ha-area-picker>
@@ -98,27 +99,22 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
no-add
.deviceFilter=${this._filterDevices}
.entityFilter=${this._filterEntities}
.includeDeviceClasses=${this.selector.area.entity?.device_class
? [this.selector.area.entity.device_class]
: undefined}
.includeDomains=${this.selector.area.entity?.domain
? [this.selector.area.entity.domain]
: undefined}
.disabled=${this.disabled}
.required=${this.required}
></ha-areas-picker>
`;
}
private _filterEntities = (entity: EntityRegistryEntry): boolean => {
const filterIntegration = this.selector.area.entity?.integration;
if (
filterIntegration &&
this._entitySources?.[entity.entity_id]?.domain !== filterIntegration
) {
return false;
private _filterEntities = (entity: HassEntity): boolean => {
if (!this.selector.area.entity) {
return true;
}
return true;
return filterSelectorEntities(
this.selector.area.entity,
entity,
this._entitySources
);
};
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
@@ -126,47 +122,17 @@ export class HaAreaSelector extends SubscribeMixin(LitElement) {
return true;
}
const {
manufacturer: filterManufacturer,
model: filterModel,
integration: filterIntegration,
} = this.selector.area.device;
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
return false;
}
if (filterModel && device.model !== filterModel) {
return false;
}
if (filterIntegration && this._entitySources && this._entities) {
const deviceIntegrations = this._deviceIntegrations(
this._entitySources,
this._entities
);
if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
return false;
}
}
return true;
return filterSelectorDevices(
this.selector.area.device,
device,
deviceIntegrations
);
};
private _deviceIntegrations = memoizeOne(
(entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
const deviceIntegrations: Record<string, string[]> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
if (!source?.domain) {
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
}
);
}
declare global {

View File

@@ -2,8 +2,8 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { ConfigEntry } from "../../data/config_entries";
import type { DeviceRegistryEntry } from "../../data/device_registry";
import { getDeviceIntegrationLookup } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
@@ -13,6 +13,7 @@ import {
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import type { DeviceSelector } from "../../data/selector";
import { filterSelectorDevices } from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../types";
import "../device/ha-device-picker";
@@ -34,12 +35,12 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@state() public _configEntries?: ConfigEntry[];
@property({ type: Boolean }) public disabled = false;
@property({ type: Boolean }) public required = true;
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
@@ -107,48 +108,17 @@ export class HaDeviceSelector extends SubscribeMixin(LitElement) {
}
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
const {
manufacturer: filterManufacturer,
model: filterModel,
integration: filterIntegration,
} = this.selector.device;
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
return false;
}
if (filterModel && device.model !== filterModel) {
return false;
}
if (filterIntegration && this._entitySources && this._entities) {
const deviceIntegrations = this._deviceIntegrations(
this._entitySources,
this._entities
);
if (!deviceIntegrations?.[device.id]?.includes(filterIntegration)) {
return false;
}
}
return true;
return filterSelectorDevices(
this.selector.device,
device,
deviceIntegrations
);
};
private _deviceIntegrations = memoizeOne(
(entitySources: EntitySources, entities: EntityRegistryEntry[]) => {
const deviceIntegrations: Record<string, string[]> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
if (!source?.domain) {
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
}
);
}
declare global {

View File

@@ -1,12 +1,12 @@
import { HassEntity } from "home-assistant-js-websocket";
import { html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import { EntitySelector } from "../../data/selector";
import type { EntitySelector } from "../../data/selector";
import { filterSelectorEntities } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../entity/ha-entities-picker";
import "../entity/ha-entity-picker";
@@ -73,37 +73,8 @@ export class HaEntitySelector extends LitElement {
}
}
private _filterEntities = (entity: HassEntity): boolean => {
const {
domain: filterDomain,
device_class: filterDeviceClass,
integration: filterIntegration,
} = this.selector.entity;
if (filterDomain) {
const entityDomain = computeStateDomain(entity);
if (
Array.isArray(filterDomain)
? !filterDomain.includes(entityDomain)
: entityDomain !== filterDomain
) {
return false;
}
}
if (
filterDeviceClass &&
entity.attributes.device_class !== filterDeviceClass
) {
return false;
}
if (
filterIntegration &&
this._entitySources?.[entity.entity_id]?.domain !== filterIntegration
) {
return false;
}
return true;
};
private _filterEntities = (entity: HassEntity): boolean =>
filterSelectorEntities(this.selector.entity, entity, this._entitySources);
}
declare global {

View File

@@ -4,9 +4,9 @@ import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../../common/dom/fire_event";
import { NumberSelector } from "../../data/selector";
import { HomeAssistant } from "../../types";
import "../ha-input-helper-text";
import "../ha-slider";
import "../ha-textfield";
import "../ha-input-helper-text";
@customElement("ha-selector-number")
export class HaNumberSelector extends LitElement {
@@ -30,21 +30,25 @@ export class HaNumberSelector extends LitElement {
const isBox = this.selector.number.mode === "box";
return html`
${this.label ? html`${this.label}${this.required ? " *" : ""}` : ""}
<div class="input">
${!isBox
? html`<ha-slider
.min=${this.selector.number.min}
.max=${this.selector.number.max}
.value=${this._value}
.step=${this.selector.number.step ?? 1}
.disabled=${this.disabled}
.required=${this.required}
pin
ignore-bar-touch
@change=${this._handleSliderChange}
>
</ha-slider>`
? html`
${this.label
? html`${this.label}${this.required ? " *" : ""}`
: ""}
<ha-slider
.min=${this.selector.number.min}
.max=${this.selector.number.max}
.value=${this._value}
.step=${this.selector.number.step ?? 1}
.disabled=${this.disabled}
.required=${this.required}
pin
ignore-bar-touch
@change=${this._handleSliderChange}
>
</ha-slider>
`
: ""}
<ha-textfield
inputMode="numeric"

View File

@@ -3,17 +3,33 @@ import {
HassServiceTarget,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
import { DeviceRegistryEntry } from "../../data/device_registry";
import {
EntityRegistryEntry,
subscribeEntityRegistry,
} from "../../data/entity_registry";
import { TargetSelector } from "../../data/selector";
css,
CSSResultGroup,
html,
LitElement,
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import {
DeviceRegistryEntry,
getDeviceIntegrationLookup,
} from "../../data/device_registry";
import type { EntityRegistryEntry } from "../../data/entity_registry";
import { subscribeEntityRegistry } from "../../data/entity_registry";
import {
EntitySources,
fetchEntitySourcesWithCache,
} from "../../data/entity_sources";
import {
filterSelectorDevices,
filterSelectorEntities,
TargetSelector,
} from "../../data/selector";
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../types";
import type { HomeAssistant } from "../../types";
import "../ha-target-picker";
@customElement("ha-selector-target")
@@ -28,119 +44,82 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
@property() public helper?: string;
@state() private _entityPlaformLookup?: Record<string, string>;
@state() private _configEntries?: ConfigEntry[];
@property({ type: Boolean }) public disabled = false;
@state() private _entitySources?: EntitySources;
@state() private _entities?: EntityRegistryEntry[];
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
public hassSubscribe(): UnsubscribeFunc[] {
return [
subscribeEntityRegistry(this.hass.connection!, (entities) => {
const entityLookup = {};
for (const confEnt of entities) {
if (!confEnt.platform) {
continue;
}
entityLookup[confEnt.entity_id] = confEnt.platform;
}
this._entityPlaformLookup = entityLookup;
this._entities = entities.filter((entity) => entity.device_id !== null);
}),
];
}
protected updated(changedProperties) {
if (changedProperties.has("selector")) {
const oldSelector = changedProperties.get("selector");
if (
oldSelector !== this.selector &&
(this.selector.target.device?.integration ||
this.selector.target.entity?.integration)
) {
this._loadConfigEntries();
}
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
if (
changedProperties.has("selector") &&
this.selector.target.device?.integration &&
!this._entitySources
) {
fetchEntitySourcesWithCache(this.hass).then((sources) => {
this._entitySources = sources;
});
}
}
protected render() {
protected render(): TemplateResult {
if (
(this.selector.target.device?.integration ||
this.selector.target.entity?.integration) &&
!this._entitySources
) {
return html``;
}
return html`<ha-target-picker
.hass=${this.hass}
.value=${this.value}
.helper=${this.helper}
.deviceFilter=${this._filterDevices}
.entityRegFilter=${this._filterRegEntities}
.entityFilter=${this._filterEntities}
.includeDeviceClasses=${this.selector.target.entity?.device_class
? [this.selector.target.entity.device_class]
: undefined}
.includeDomains=${this.selector.target.entity?.domain
? [this.selector.target.entity.domain]
: undefined}
.disabled=${this.disabled}
></ha-target-picker>`;
}
private _filterEntities = (entity: HassEntity): boolean => {
if (
this.selector.target.entity?.integration ||
this.selector.target.device?.integration
) {
if (
!this._entityPlaformLookup ||
this._entityPlaformLookup[entity.entity_id] !==
(this.selector.target.entity?.integration ||
this.selector.target.device?.integration)
) {
return false;
}
if (!this.selector.target.entity) {
return true;
}
return true;
};
private _filterRegEntities = (entity: EntityRegistryEntry): boolean => {
if (this.selector.target.entity?.integration) {
if (entity.platform !== this.selector.target.entity.integration) {
return false;
}
}
return true;
return filterSelectorEntities(
this.selector.target.entity,
entity,
this._entitySources
);
};
private _filterDevices = (device: DeviceRegistryEntry): boolean => {
if (
this.selector.target.device?.manufacturer &&
device.manufacturer !== this.selector.target.device.manufacturer
) {
return false;
if (!this.selector.target.device) {
return true;
}
if (
this.selector.target.device?.model &&
device.model !== this.selector.target.device.model
) {
return false;
}
if (
this.selector.target.device?.integration ||
this.selector.target.entity?.integration
) {
if (
!this._configEntries?.some((entry) =>
device.config_entries.includes(entry.entry_id)
)
) {
return false;
}
}
return true;
};
private async _loadConfigEntries() {
this._configEntries = (await getConfigEntries(this.hass)).filter(
(entry) =>
entry.domain === this.selector.target.device?.integration ||
entry.domain === this.selector.target.entity?.integration
const deviceIntegrations =
this._entitySources && this._entities
? this._deviceIntegrationLookup(this._entitySources, this._entities)
: undefined;
return filterSelectorDevices(
this.selector.target.device,
device,
deviceIntegrations
);
}
};
static get styles(): CSSResultGroup {
return css`

View File

@@ -31,6 +31,7 @@ export class HaTemplateSelector extends LitElement {
.readOnly=${this.disabled}
autofocus
autocomplete-entities
autocomplete-icons
@value-changed=${this._handleChange}
dir="ltr"
></ha-code-editor>

View File

@@ -42,8 +42,8 @@ import "./entity/ha-entity-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "./ha-area-picker";
import "./ha-icon-button";
import "./ha-svg-icon";
import "./ha-input-helper-text";
import "./ha-svg-icon";
@customElement("ha-target-picker")
export class HaTargetPicker extends SubscribeMixin(LitElement) {
@@ -119,55 +119,68 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
if (!this._areas || !this._devices || !this._entities) {
return html``;
}
return html`<div class=${this.horizontal ? "horizontal-container" : ""}>
${this.horizontal ? this._renderChips() : this._renderItems()}
${this._renderPicker()}
${this.horizontal ? this._renderItems() : this._renderChips()}
</div>`;
return html`
${this.horizontal
? html`
<div class="horizontal-container">
${this._renderChips()} ${this._renderPicker()}
</div>
${this._renderItems()}
`
: html`
<div>
${this._renderItems()} ${this._renderPicker()}
${this._renderChips()}
</div>
`}
`;
}
private _renderItems() {
return html`<div class="mdc-chip-set items">
${this.value?.area_id
? ensureArray(this.value.area_id).map((area_id) => {
const area = this._areas![area_id];
return this._renderChip(
"area_id",
area_id,
area?.name || area_id,
undefined,
mdiSofa
);
})
: ""}
${this.value?.device_id
? ensureArray(this.value.device_id).map((device_id) => {
const device = this._devices![device_id];
return this._renderChip(
"device_id",
device_id,
device ? computeDeviceName(device, this.hass) : device_id,
undefined,
mdiDevices
);
})
: ""}
${this.value?.entity_id
? ensureArray(this.value.entity_id).map((entity_id) => {
const entity = this.hass.states[entity_id];
return this._renderChip(
"entity_id",
entity_id,
entity ? computeStateName(entity) : entity_id,
entity
);
})
: ""}
</div>`;
return html`
<div class="mdc-chip-set items">
${this.value?.area_id
? ensureArray(this.value.area_id).map((area_id) => {
const area = this._areas![area_id];
return this._renderChip(
"area_id",
area_id,
area?.name || area_id,
undefined,
mdiSofa
);
})
: ""}
${this.value?.device_id
? ensureArray(this.value.device_id).map((device_id) => {
const device = this._devices![device_id];
return this._renderChip(
"device_id",
device_id,
device ? computeDeviceName(device, this.hass) : device_id,
undefined,
mdiDevices
);
})
: ""}
${this.value?.entity_id
? ensureArray(this.value.entity_id).map((entity_id) => {
const entity = this.hass.states[entity_id];
return this._renderChip(
"entity_id",
entity_id,
entity ? computeStateName(entity) : entity_id,
entity
);
})
: ""}
</div>
`;
}
private _renderChips() {
return html`<div class="mdc-chip-set">
return html`
<div class="mdc-chip-set">
<div
class="mdc-chip area_id add"
.type=${"area_id"}
@@ -231,7 +244,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
</div>
${this.helper
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
: ""} `;
: ""}
`;
}
private async _showPicker(ev) {
@@ -300,7 +314,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
class="mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${this.hass.localize("ui.components.target-picker.expand")}
.label=${this.hass.localize("ui.components.target-picker.remove")}
.path=${mdiClose}
hideTooltip
.id=${id}
@@ -320,51 +334,54 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
private _renderPicker() {
switch (this._addMode) {
case "area_id":
return html`<ha-area-picker
.hass=${this.hass}
id="input"
.type=${"area_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}
no-add
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
class=${this.horizontal ? "hidden-picker" : ""}
@value-changed=${this._targetPicked}
></ha-area-picker>`;
return html`
<ha-area-picker
.hass=${this.hass}
id="input"
.type=${"area_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}
no-add
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-area-picker>
`;
case "device_id":
return html`<ha-device-picker
.hass=${this.hass}
id="input"
.type=${"device_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
class=${this.horizontal ? "hidden-picker" : ""}
@value-changed=${this._targetPicked}
></ha-device-picker>`;
return html`
<ha-device-picker
.hass=${this.hass}
id="input"
.type=${"device_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}
.deviceFilter=${this.deviceFilter}
.entityFilter=${this.entityRegFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
></ha-device-picker>
`;
case "entity_id":
return html`<ha-entity-picker
.hass=${this.hass}
id="input"
.type=${"entity_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
class=${this.horizontal ? "hidden-picker" : ""}
@value-changed=${this._targetPicked}
allow-custom-entity
></ha-entity-picker>`;
return html`
<ha-entity-picker
.hass=${this.hass}
id="input"
.type=${"entity_id"}
.label=${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}
.entityFilter=${this.entityFilter}
.includeDeviceClasses=${this.includeDeviceClasses}
.includeDomains=${this.includeDomains}
@value-changed=${this._targetPicked}
allow-custom-entity
></ha-entity-picker>
`;
}
return html``;
}
@@ -553,15 +570,11 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
.hidden-picker {
height: 0px;
display: inline-block;
overflow: hidden;
position: absolute;
}
.horizontal-container {
display: flex;
flex-wrap: wrap;
min-height: 56px;
align-items: center;
}
.mdc-chip {
color: var(--primary-text-color);

View File

@@ -23,7 +23,7 @@ export class HaThemePicker extends LitElement {
return html`
<ha-select
.label=${this.label ||
this.hass!.localize("ui.components.theme_picker.theme")}
this.hass!.localize("ui.components.theme-picker.theme")}
.value=${this.value}
.required=${this.required}
.disabled=${this.disabled}
@@ -34,7 +34,7 @@ export class HaThemePicker extends LitElement {
>
<mwc-list-item value="remove"
>${this.hass!.localize(
"ui.components.theme_picker.no_theme"
"ui.components.theme-picker.no_theme"
)}</mwc-list-item
>
${Object.keys(this.hass!.themes.themes)

View File

@@ -41,7 +41,7 @@ export class HaYamlEditor extends LitElement {
try {
this._yaml =
value && !isEmpty(value)
? dump(value, { schema: this.yamlSchema })
? dump(value, { schema: this.yamlSchema, quotingType: '"' })
: "";
} catch (err: any) {
// eslint-disable-next-line no-console
@@ -70,6 +70,7 @@ export class HaYamlEditor extends LitElement {
.readOnly=${this.readOnly}
mode="yaml"
autocomplete-entities
autocomplete-icons
.error=${this.isValid === false}
@value-changed=${this._onChange}
dir="ltr"

View File

@@ -6,21 +6,19 @@ import {
Map,
Marker,
Polyline,
TileLayer,
} from "leaflet";
import { css, CSSResultGroup, PropertyValues, ReactiveElement } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
LeafletModuleType,
replaceTileLayer,
setupLeafletMap,
} from "../../common/dom/setup-leaflet-map";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { computeStateName } from "../../common/entity/compute_state_name";
import "./ha-entity-marker";
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
import { HomeAssistant } from "../../types";
import "../ha-icon-button";
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
import "./ha-entity-marker";
const getEntityId = (entity: string | HaMapEntity): string =>
typeof entity === "string" ? entity : entity.entity_id;
@@ -60,8 +58,6 @@ export class HaMap extends ReactiveElement {
private Leaflet?: LeafletModuleType;
private _tileLayer?: TileLayer;
private _resizeObserver?: ResizeObserver;
private _mapItems: Array<Marker | Circle> = [];
@@ -142,12 +138,6 @@ export class HaMap extends ReactiveElement {
return;
}
const darkMode = this.darkMode ?? this.hass.themes.darkMode;
this._tileLayer = replaceTileLayer(
this.Leaflet!,
this.leafletMap!,
this._tileLayer!,
darkMode
);
this.shadowRoot!.getElementById("map")!.classList.toggle("dark", darkMode);
}
@@ -159,10 +149,7 @@ export class HaMap extends ReactiveElement {
this.shadowRoot!.append(map);
}
const darkMode = this.darkMode ?? this.hass.themes.darkMode;
[this.leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
map,
darkMode
);
[this.leafletMap, this.Leaflet] = await setupLeafletMap(map);
this.shadowRoot!.getElementById("map")!.classList.toggle("dark", darkMode);
this._loaded = true;
}
@@ -473,6 +460,13 @@ export class HaMap extends ReactiveElement {
.dark {
color: #ffffff;
}
.leaflet-tile-pane {
filter: var(--map-filter);
}
.dark .leaflet-bar a {
background: var(--card-background-color);
color: #ffffff;
}
.leaflet-marker-draggable {
cursor: move !important;
}

View File

@@ -3,6 +3,8 @@ import { Store } from "home-assistant-js-websocket/dist/store";
import { stringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types";
import { DeviceRegistryEntry } from "./device_registry";
import { EntityRegistryEntry } from "./entity_registry";
export interface AreaRegistryEntry {
area_id: string;
@@ -10,6 +12,14 @@ export interface AreaRegistryEntry {
picture: string | null;
}
export interface AreaEntityLookup {
[areaId: string]: EntityRegistryEntry[];
}
export interface AreaDeviceLookup {
[areaId: string]: DeviceRegistryEntry[];
}
export interface AreaRegistryEntryMutableParams {
name: string;
picture?: string | null;
@@ -79,3 +89,35 @@ export const subscribeAreaRegistry = (
conn,
onChange
);
export const getAreaEntityLookup = (
entities: EntityRegistryEntry[]
): AreaEntityLookup => {
const areaEntityLookup: AreaEntityLookup = {};
for (const entity of entities) {
if (!entity.area_id) {
continue;
}
if (!(entity.area_id in areaEntityLookup)) {
areaEntityLookup[entity.area_id] = [];
}
areaEntityLookup[entity.area_id].push(entity);
}
return areaEntityLookup;
};
export const getAreaDeviceLookup = (
devices: DeviceRegistryEntry[]
): AreaDeviceLookup => {
const areaDeviceLookup: AreaDeviceLookup = {};
for (const device of devices) {
if (!device.area_id) {
continue;
}
if (!(device.area_id in areaDeviceLookup)) {
areaDeviceLookup[device.area_id] = [];
}
areaDeviceLookup[device.area_id].push(device);
}
return areaDeviceLookup;
};

View File

@@ -11,7 +11,8 @@ export interface ConfigEntry {
| "migration_error"
| "setup_retry"
| "not_loaded"
| "failed_unload";
| "failed_unload"
| "setup_in_progress";
supports_options: boolean;
supports_remove_device: boolean;
supports_unload: boolean;
@@ -28,12 +29,21 @@ export type ConfigEntryMutableParams = Partial<
>
>;
// https://github.com/home-assistant/core/blob/2286dea636fda001f03433ba14d7adbda43979e5/homeassistant/config_entries.py#L81
export const ERROR_STATES: ConfigEntry["state"][] = [
"migration_error",
"setup_error",
"setup_retry",
];
// https://github.com/home-assistant/core/blob/2286dea636fda001f03433ba14d7adbda43979e5/homeassistant/config_entries.py#L81
export const RECOVERABLE_STATES: ConfigEntry["state"][] = [
"not_loaded",
"loaded",
"setup_error",
"setup_retry",
];
export const getConfigEntries = (
hass: HomeAssistant,
filters?: { type?: "helper" | "integration"; domain?: string }

View File

@@ -1,10 +1,11 @@
import { Connection, createCollection } from "home-assistant-js-websocket";
import { Store } from "home-assistant-js-websocket/dist/store";
import type { Store } from "home-assistant-js-websocket/dist/store";
import { computeStateName } from "../common/entity/compute_state_name";
import { caseInsensitiveStringCompare } from "../common/string/compare";
import { debounce } from "../common/util/debounce";
import { HomeAssistant } from "../types";
import { EntityRegistryEntry } from "./entity_registry";
import type { HomeAssistant } from "../types";
import type { EntityRegistryEntry } from "./entity_registry";
import type { EntitySources } from "./entity_sources";
export interface DeviceRegistryEntry {
id: string;
@@ -20,7 +21,7 @@ export interface DeviceRegistryEntry {
area_id: string | null;
name_by_user: string | null;
entry_type: "service" | null;
disabled_by: string | null;
disabled_by: "user" | "integration" | "config_entry" | null;
configuration_url: string | null;
}
@@ -126,3 +127,39 @@ export const sortDeviceRegistryByName = (entries: DeviceRegistryEntry[]) =>
entries.sort((entry1, entry2) =>
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "")
);
export const getDeviceEntityLookup = (
entities: EntityRegistryEntry[]
): DeviceEntityLookup => {
const deviceEntityLookup: DeviceEntityLookup = {};
for (const entity of entities) {
if (!entity.device_id) {
continue;
}
if (!(entity.device_id in deviceEntityLookup)) {
deviceEntityLookup[entity.device_id] = [];
}
deviceEntityLookup[entity.device_id].push(entity);
}
return deviceEntityLookup;
};
export const getDeviceIntegrationLookup = (
entitySources: EntitySources,
entities: EntityRegistryEntry[]
): Record<string, string[]> => {
const deviceIntegrations: Record<string, string[]> = {};
for (const entity of entities) {
const source = entitySources[entity.entity_id];
if (!source?.domain || entity.device_id === null) {
continue;
}
if (!deviceIntegrations[entity.device_id!]) {
deviceIntegrations[entity.device_id!] = [];
}
deviceIntegrations[entity.device_id!].push(source.domain);
}
return deviceIntegrations;
};

View File

@@ -16,12 +16,13 @@ export interface EntityRegistryEntry {
disabled_by: string | null;
hidden_by: string | null;
entity_category: "config" | "diagnostic" | null;
has_entity_name: boolean;
original_name?: string;
}
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
unique_id: string;
capabilities: Record<string, unknown>;
original_name?: string;
original_icon?: string;
device_class?: string;
original_device_class?: string;
@@ -37,6 +38,10 @@ export interface SensorEntityOptions {
unit_of_measurement?: string | null;
}
export interface NumberEntityOptions {
unit_of_measurement?: string | null;
}
export interface WeatherEntityOptions {
precipitation_unit?: string | null;
pressure_unit?: string | null;
@@ -155,3 +160,16 @@ export const sortEntityRegistryByName = (entries: EntityRegistryEntry[]) =>
entries.sort((entry1, entry2) =>
caseInsensitiveStringCompare(entry1.name || "", entry2.name || "")
);
export const getEntityPlatformLookup = (
entities: EntityRegistryEntry[]
): Record<string, string> => {
const entityLookup = {};
for (const confEnt of entities) {
if (!confEnt.platform) {
continue;
}
entityLookup[confEnt.entity_id] = confEnt.platform;
}
return entityLookup;
};

View File

@@ -1,4 +1,4 @@
import { HassEntity } from "home-assistant-js-websocket";
import { HassEntities, HassEntity } from "home-assistant-js-websocket";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateDisplayFromEntityAttributes } from "../common/entity/compute_state_display";
import {
@@ -268,7 +268,8 @@ const processTimelineEntity = (
localize: LocalizeFunc,
language: FrontendLocaleData,
entityId: string,
states: EntityHistoryState[]
states: EntityHistoryState[],
current_state: HassEntity | undefined
): TimelineEntity => {
const data: TimelineState[] = [];
const first: EntityHistoryState = states[0];
@@ -292,7 +293,10 @@ const processTimelineEntity = (
}
return {
name: computeStateNameFromEntityAttributes(entityId, states[0].a),
name: computeStateNameFromEntityAttributes(
entityId,
current_state?.attributes || first.a
),
entity_id: entityId,
data,
};
@@ -300,7 +304,8 @@ const processTimelineEntity = (
const processLineChartEntities = (
unit,
entities: HistoryStates
entities: HistoryStates,
hassEntities: HassEntities
): LineChartUnit => {
const data: LineChartEntity[] = [];
@@ -349,9 +354,16 @@ const processLineChartEntities = (
processedStates.push(processedState);
}
const attributes =
entityId in hassEntities
? hassEntities[entityId].attributes
: "friendly_name" in first.a
? first.a
: undefined;
data.push({
domain,
name: computeStateNameFromEntityAttributes(entityId, first.a),
name: computeStateNameFromEntityAttributes(entityId, attributes || {}),
entity_id: entityId,
states: processedStates,
});
@@ -411,7 +423,13 @@ export const computeHistory = (
if (!unit) {
timelineDevices.push(
processTimelineEntity(localize, hass.locale, entityId, stateInfo)
processTimelineEntity(
localize,
hass.locale,
entityId,
stateInfo,
currentState
)
);
} else if (unit in lineChartDevices && entityId in lineChartDevices[unit]) {
lineChartDevices[unit][entityId].push(...stateInfo);
@@ -424,7 +442,7 @@ export const computeHistory = (
});
const unitStates = Object.keys(lineChartDevices).map((unit) =>
processLineChartEntities(unit, lineChartDevices[unit])
processLineChartEntities(unit, lineChartDevices[unit], hass.states)
);
return { line: unitStates, timeline: timelineDevices };

61
src/data/repairs.ts Normal file
View File

@@ -0,0 +1,61 @@
import type { HomeAssistant } from "../types";
import { DataEntryFlowStep } from "./data_entry_flow";
export interface RepairsIssue {
domain: string;
issue_id: string;
active: boolean;
is_fixable: boolean;
severity: "error" | "warning" | "critical";
breaks_in_ha_version?: string;
ignored: boolean;
created: string;
dismissed_version?: string;
learn_more_url?: string;
translation_key?: string;
translation_placeholders?: Record<string, string>;
}
export const severitySort = {
critical: 1,
error: 2,
warning: 3,
};
export const fetchRepairsIssues = async (hass: HomeAssistant) =>
hass.callWS<{ issues: RepairsIssue[] }>({
type: "repairs/list_issues",
});
export const dismissRepairsIssue = async (
hass: HomeAssistant,
issue: RepairsIssue
) =>
hass.callWS<string>({
type: "repairs/dismiss_issue",
issue_id: issue.issue_id,
domain: issue.domain,
});
export const createRepairsFlow = (
hass: HomeAssistant,
handler: string,
issue_id: string
) =>
hass.callApi<DataEntryFlowStep>("POST", "repairs/issues/fix", {
handler,
issue_id,
});
export const fetchRepairsFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi<DataEntryFlowStep>("GET", `repairs/issues/fix/${flowId}`);
export const handleRepairsFlowStep = (
hass: HomeAssistant,
flowId: string,
data: Record<string, any>
) =>
hass.callApi<DataEntryFlowStep>("POST", `repairs/issues/fix/${flowId}`, data);
export const deleteRepairsFlow = (hass: HomeAssistant, flowId: string) =>
hass.callApi("DELETE", `repairs/issues/fix/${flowId}`);

View File

@@ -1,3 +1,8 @@
import type { HassEntity } from "home-assistant-js-websocket";
import { computeStateDomain } from "../common/entity/compute_state_domain";
import type { DeviceRegistryEntry } from "./device_registry";
import type { EntitySources } from "./entity_sources";
export type Selector =
| ActionSelector
| AddonSelector
@@ -35,18 +40,22 @@ export interface AddonSelector {
};
}
export interface SelectorDevice {
integration?: DeviceSelector["device"]["integration"];
manufacturer?: DeviceSelector["device"]["manufacturer"];
model?: DeviceSelector["device"]["model"];
}
export interface SelectorEntity {
integration?: EntitySelector["entity"]["integration"];
domain?: EntitySelector["entity"]["domain"];
device_class?: EntitySelector["entity"]["device_class"];
}
export interface AreaSelector {
area: {
entity?: {
integration?: EntitySelector["entity"]["integration"];
domain?: EntitySelector["entity"]["domain"];
device_class?: EntitySelector["entity"]["device_class"];
};
device?: {
integration?: DeviceSelector["device"]["integration"];
manufacturer?: DeviceSelector["device"]["manufacturer"];
model?: DeviceSelector["device"]["model"];
};
entity?: SelectorEntity;
device?: SelectorDevice;
multiple?: boolean;
};
}
@@ -89,10 +98,7 @@ export interface DeviceSelector {
integration?: string;
manufacturer?: string;
model?: string;
entity?: {
domain?: EntitySelector["entity"]["domain"];
device_class?: EntitySelector["entity"]["device_class"];
};
entity?: SelectorEntity;
multiple?: boolean;
};
}
@@ -201,16 +207,8 @@ export interface StringSelector {
export interface TargetSelector {
target: {
entity?: {
integration?: EntitySelector["entity"]["integration"];
domain?: EntitySelector["entity"]["domain"];
device_class?: EntitySelector["entity"]["device_class"];
};
device?: {
integration?: DeviceSelector["device"]["integration"];
manufacturer?: DeviceSelector["device"]["manufacturer"];
model?: DeviceSelector["device"]["model"];
};
entity?: SelectorEntity;
device?: SelectorDevice;
};
}
@@ -227,3 +225,69 @@ export interface TimeSelector {
// eslint-disable-next-line @typescript-eslint/ban-types
time: {};
}
export const filterSelectorDevices = (
filterDevice: SelectorDevice,
device: DeviceRegistryEntry,
deviceIntegrationLookup: Record<string, string[]> | undefined
): boolean => {
const {
manufacturer: filterManufacturer,
model: filterModel,
integration: filterIntegration,
} = filterDevice;
if (filterManufacturer && device.manufacturer !== filterManufacturer) {
return false;
}
if (filterModel && device.model !== filterModel) {
return false;
}
if (filterIntegration && deviceIntegrationLookup) {
if (!deviceIntegrationLookup?.[device.id]?.includes(filterIntegration)) {
return false;
}
}
return true;
};
export const filterSelectorEntities = (
filterEntity: SelectorEntity,
entity: HassEntity,
entitySources?: EntitySources
): boolean => {
const {
domain: filterDomain,
device_class: filterDeviceClass,
integration: filterIntegration,
} = filterEntity;
if (filterDomain) {
const entityDomain = computeStateDomain(entity);
if (
Array.isArray(filterDomain)
? !filterDomain.includes(entityDomain)
: entityDomain !== filterDomain
) {
return false;
}
}
if (
filterDeviceClass &&
entity.attributes.device_class !== filterDeviceClass
) {
return false;
}
if (
filterIntegration &&
entitySources?.[entity.entity_id]?.domain !== filterIntegration
) {
return false;
}
return true;
};

View File

@@ -1,7 +1,7 @@
import { Connection, getCollection } from "home-assistant-js-websocket";
import { Store } from "home-assistant-js-websocket/dist/store";
import { LocalizeFunc } from "../../common/translations/localize";
import { HomeAssistant } from "../../types";
import { HomeAssistant, TranslationDict } from "../../types";
import { HassioAddonsInfo } from "../hassio/addon";
import { HassioHassOSInfo, HassioHostInfo } from "../hassio/host";
import { NetworkInfo } from "../hassio/network";
@@ -67,7 +67,7 @@ export interface Supervisor {
os: HassioHassOSInfo;
addon: HassioAddonsInfo;
store: SupervisorStore;
localize: LocalizeFunc;
localize: LocalizeFunc<TranslationDict["supervisor"]>;
}
export const supervisorApiWsRequest = <T>(

View File

@@ -0,0 +1,8 @@
import type { HomeAssistant } from "../types";
export type SupportedBrandHandler = Record<string, string>;
export const getSupportedBrands = (hass: HomeAssistant) =>
hass.callWS<Record<string, SupportedBrandHandler>>({
type: "supported_brands",
});

View File

@@ -39,7 +39,8 @@ export type TranslationCategory =
| "mfa_setup"
| "system_health"
| "device_class"
| "application_credentials";
| "application_credentials"
| "issues";
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
fetchFrontendUserData(hass.connection, "language");

View File

@@ -199,13 +199,15 @@ export const getWeatherUnit = (
case "visibility":
return stateObj.attributes.visibility_unit || lengthUnit;
case "precipitation":
return stateObj.attributes.precipitation_unit || lengthUnit === "km"
? "mm"
: "in";
return (
stateObj.attributes.precipitation_unit ||
(lengthUnit === "km" ? "mm" : "in")
);
case "pressure":
return stateObj.attributes.pressure_unit || lengthUnit === "km"
? "hPa"
: "inHg";
return (
stateObj.attributes.pressure_unit ||
(lengthUnit === "km" ? "hPa" : "inHg")
);
case "temperature":
return (
stateObj.attributes.temperature_unit ||

View File

@@ -26,6 +26,7 @@ export interface ZHADevice {
power_source?: string;
area_id?: string;
device_type: string;
active_coordinator: boolean;
signature: any;
neighbors: Neighbor[];
pairing_status?: string;

View File

@@ -7,6 +7,7 @@ import {
handleConfigFlowStep,
} from "../../data/config_flow";
import { domainToName } from "../../data/integration";
import { getSupportedBrands } from "../../data/supported_brands";
import {
DataEntryFlowDialogParams,
loadDataEntryFlowDialog,
@@ -22,12 +23,14 @@ export const showConfigFlowDialog = (
showFlowDialog(element, dialogParams, {
loadDevicesAndAreas: true,
getFlowHandlers: async (hass) => {
const [integrations, helpers] = await Promise.all([
const [integrations, helpers, supportedBrands] = await Promise.all([
getConfigFlowHandlers(hass, "integration"),
getConfigFlowHandlers(hass, "helper"),
getSupportedBrands(hass),
hass.loadBackendTranslation("title", undefined, true),
]);
return { integrations, helpers };
return { integrations, helpers, supportedBrands };
},
createFlow: async (hass, handler) => {
const [step] = await Promise.all([

View File

@@ -10,12 +10,14 @@ import {
DataEntryFlowStepMenu,
DataEntryFlowStepProgress,
} from "../../data/data_entry_flow";
import { IntegrationManifest } from "../../data/integration";
import { HomeAssistant } from "../../types";
import type { IntegrationManifest } from "../../data/integration";
import type { SupportedBrandHandler } from "../../data/supported_brands";
import type { HomeAssistant } from "../../types";
export interface FlowHandlers {
integrations: string[];
helpers: string[];
supportedBrands: Record<string, SupportedBrandHandler>;
}
export interface FlowConfig {
loadDevicesAndAreas: boolean;

View File

@@ -1,5 +1,6 @@
import "@polymer/paper-item";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";

View File

@@ -15,18 +15,19 @@ import memoizeOne from "memoize-one";
import { isComponentLoaded } from "../../common/config/is_component_loaded";
import { fireEvent } from "../../common/dom/fire_event";
import { navigate } from "../../common/navigate";
import "../../components/search-input";
import { caseInsensitiveStringCompare } from "../../common/string/compare";
import { LocalizeFunc } from "../../common/translations/localize";
import "../../components/ha-icon-next";
import "../../components/search-input";
import { getConfigEntries } from "../../data/config_entries";
import { domainToName } from "../../data/integration";
import { showZWaveJSAddNodeDialog } from "../../panels/config/integrations/integration-panels/zwave_js/show-dialog-zwave_js-add-node";
import { HomeAssistant } from "../../types";
import { brandsUrl } from "../../util/brands-url";
import { documentationUrl } from "../../util/documentation-url";
import { configFlowContentStyles } from "./styles";
import { showConfirmationDialog } from "../generic/show-dialog-box";
import { FlowHandlers } from "./show-dialog-data-entry-flow";
import { configFlowContentStyles } from "./styles";
interface HandlerObj {
name: string;
@@ -35,6 +36,10 @@ interface HandlerObj {
is_helper?: boolean;
}
interface SupportedBrandObj extends HandlerObj {
supported_flows: string[];
}
declare global {
// for fire event
interface HASSDomEvents {
@@ -63,11 +68,22 @@ class StepFlowPickHandler extends LitElement {
h: FlowHandlers,
filter?: string,
_localize?: LocalizeFunc
): [HandlerObj[], HandlerObj[]] => {
const integrations: HandlerObj[] = h.integrations.map((handler) => ({
name: domainToName(this.hass.localize, handler),
slug: handler,
}));
): [(HandlerObj | SupportedBrandObj)[], HandlerObj[]] => {
const integrations: (HandlerObj | SupportedBrandObj)[] =
h.integrations.map((handler) => ({
name: domainToName(this.hass.localize, handler),
slug: handler,
}));
for (const [domain, domainBrands] of Object.entries(h.supportedBrands)) {
for (const [slug, name] of Object.entries(domainBrands)) {
integrations.push({
slug,
name,
supported_flows: [domain],
});
}
}
if (filter) {
const options: Fuse.IFuseOptions<HandlerObj> = {
@@ -238,27 +254,10 @@ class StepFlowPickHandler extends LitElement {
}
private async _handlerPicked(ev) {
const handler: HandlerObj = ev.currentTarget.handler;
const handler: HandlerObj | SupportedBrandObj = ev.currentTarget.handler;
if (handler.is_add) {
if (handler.slug === "zwave_js") {
const entries = await getConfigEntries(this.hass, {
domain: "zwave_js",
});
if (!entries.length) {
return;
}
showZWaveJSAddNodeDialog(this, {
entry_id: entries[0].entry_id,
});
} else if (handler.slug === "zha") {
navigate("/config/zha/add");
}
// This closes dialog.
fireEvent(this, "flow-update");
this._handleAddPicked(handler.slug);
return;
}
@@ -269,11 +268,115 @@ class StepFlowPickHandler extends LitElement {
return;
}
if ("supported_flows" in handler) {
const slug = handler.supported_flows[0];
showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.config.integrations.config_flow.supported_brand_flow",
{
supported_brand: handler.name,
flow_domain_name: domainToName(this.hass.localize, slug),
}
),
confirm: () => {
if (["zha", "zwave_js"].includes(slug)) {
this._handleAddPicked(slug);
return;
}
fireEvent(this, "handler-picked", {
handler: slug,
});
},
});
return;
}
fireEvent(this, "handler-picked", {
handler: handler.slug,
});
}
private async _handleAddPicked(slug: string): Promise<void> {
if (slug === "zwave_js") {
const entries = await getConfigEntries(this.hass, {
domain: "zwave_js",
});
if (!entries.length) {
// If the component isn't loaded, ask them to load the integration first
showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
{
integration: "Z-Wave",
supported_hardware_link: html`<a
href=${documentationUrl(this.hass, "/docs/z-wave/controllers")}
target="_blank"
rel="noreferrer"
>${this.hass.localize(
"ui.panel.config.integrations.config_flow.supported_hardware"
)}</a
>`,
}
),
confirmText: this.hass.localize(
"ui.panel.config.integrations.config_flow.proceed"
),
confirm: () => {
fireEvent(this, "handler-picked", {
handler: "zwave_js",
});
},
});
return;
}
showZWaveJSAddNodeDialog(this, {
entry_id: entries[0].entry_id,
});
} else if (slug === "zha") {
// If the component isn't loaded, ask them to load the integration first
if (!isComponentLoaded(this.hass, "zha")) {
showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
{
integration: "Zigbee",
supported_hardware_link: html`<a
href=${documentationUrl(
this.hass,
"/integrations/zha/#known-working-zigbee-radio-modules"
)}
target="_blank"
rel="noreferrer"
>${this.hass.localize(
"ui.panel.config.integrations.config_flow.supported_hardware"
)}</a
>`,
}
),
confirmText: this.hass.localize(
"ui.panel.config.integrations.config_flow.proceed"
),
confirm: () => {
fireEvent(this, "handler-picked", {
handler: "zha",
});
},
});
return;
}
navigate("/config/zha/add");
}
// This closes dialog.
fireEvent(this, "flow-update");
}
private _maybeSubmit(ev: KeyboardEvent) {
if (ev.key !== "Enter") {
return;

View File

@@ -144,8 +144,6 @@ class DialogBox extends LitElement {
}
p {
margin: 0;
padding-top: 6px;
padding-bottom: 24px;
color: var(--primary-text-color);
}
.no-bottom-padding {
@@ -157,7 +155,6 @@ class DialogBox extends LitElement {
ha-dialog {
--mdc-dialog-heading-ink-color: var(--primary-text-color);
--mdc-dialog-content-ink-color: var(--primary-text-color);
--justify-action-buttons: space-between;
/* Place above other dialogs */
--dialog-z-index: 104;
}

View File

@@ -124,7 +124,6 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this._fetchOnboardingSteps();
this._fetchInstallationType();
import("./onboarding-integrations");
import("./onboarding-core-config");
registerServiceWorker(this, false);
@@ -215,6 +214,9 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
});
history.replaceState(null, "", location.pathname);
await this._connectHass(auth);
} else {
// User creating screen needs to know the installation type.
this._fetchInstallationType();
}
this._steps = steps;

View File

@@ -378,6 +378,10 @@ class OnboardingCoreConfig extends LitElement {
color: var(--secondary-text-color);
}
ha-locations-editor {
height: 200px;
}
.flex {
flex: 1;
}

View File

@@ -30,7 +30,13 @@ import { HomeAssistant } from "../types";
import "./action-badge";
import "./integration-badge";
const HIDDEN_DOMAINS = new Set(["hassio", "met", "radio_browser", "rpi_power"]);
const HIDDEN_DOMAINS = new Set([
"hassio",
"met",
"radio_browser",
"rpi_power",
"sun",
]);
@customElement("onboarding-integrations")
class OnboardingIntegrations extends LitElement {
@@ -75,7 +81,10 @@ class OnboardingIntegrations extends LitElement {
// Render discovered and existing entries together sorted by localized title.
const entries: Array<[string, TemplateResult]> = this._entries.map(
(entry) => {
const title = domainToName(this.hass.localize, entry.domain);
const title =
entry.title ||
domainToName(this.hass.localize, entry.domain) ||
entry.domain;
return [
title,
html`

View File

@@ -178,9 +178,6 @@ class DialogAreaDetail extends LitElement {
return [
haStyleDialog,
css`
.form {
padding-bottom: 24px;
}
ha-textfield {
display: block;
margin-bottom: 16px;

View File

@@ -1,7 +1,5 @@
import "@material/mwc-button";
import type { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/paper-item/paper-item-body";
import { css, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators";
@@ -11,9 +9,7 @@ import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { debounce } from "../../../../common/util/debounce";
import "../../../../components/buttons/ha-call-api-button";
import "../../../../components/ha-alert";
import "../../../../components/ha-button-menu";
import "../../../../components/ha-card";
import "../../../../components/ha-icon-button";
import {
cloudLogout,
CloudStatusLoggedIn,
@@ -23,6 +19,7 @@ import {
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
import "../../../../layouts/hass-subpage";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { haStyle } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import "../../ha-config-section";
import "./cloud-alexa-pref";
@@ -52,23 +49,6 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
.narrow=${this.narrow}
header="Home Assistant Cloud"
>
<ha-button-menu
slot="toolbar-icon"
corner="BOTTOM_START"
@action=${this._handleMenuAction}
activatable
>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<mwc-list-item>
${this.hass.localize("ui.panel.config.cloud.account.sign_out")}
</mwc-list-item>
</ha-button-menu>
<div class="content">
<ha-config-section .isWide=${this.isWide}>
<span slot="header">Home Assistant Cloud</span>
@@ -156,6 +136,11 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
)}
</mwc-button>
</a>
<mwc-button @click=${this._signOut} class="warning">
${this.hass.localize(
"ui.panel.config.cloud.account.sign_out"
)}
</mwc-button>
</div>
</ha-card>
</ha-config-section>
@@ -279,18 +264,15 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
}
}
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.config.cloud.account.sign_out_confirm"
),
confirmText: this.hass!.localize("ui.common.yes"),
dismissText: this.hass!.localize("ui.common.no"),
confirm: () => this._logoutFromCloud(),
});
}
private async _signOut() {
showConfirmationDialog(this, {
text: this.hass.localize(
"ui.panel.config.cloud.account.sign_out_confirm"
),
confirmText: this.hass!.localize("ui.common.yes"),
dismissText: this.hass!.localize("ui.common.no"),
confirm: () => this._logoutFromCloud(),
});
}
private async _logoutFromCloud() {
@@ -303,41 +285,39 @@ export class CloudAccount extends SubscribeMixin(LitElement) {
}
static get styles() {
return css`
[slot="introduction"] {
margin: -1em 0;
}
[slot="introduction"] a {
color: var(--primary-color);
}
.content {
padding-bottom: 24px;
}
.account-row {
display: flex;
padding: 0 16px;
}
.card-actions {
display: flex;
flex-direction: row-reverse;
}
.card-actions a {
text-decoration: none;
}
mwc-button {
align-self: center;
}
.wrap {
white-space: normal;
}
.status {
text-transform: capitalize;
padding: 16px;
}
a {
color: var(--primary-color);
}
`;
return [
haStyle,
css`
[slot="introduction"] {
margin: -1em 0;
}
[slot="introduction"] a {
color: var(--primary-color);
}
.content {
padding-bottom: 24px;
}
.account-row {
display: flex;
padding: 0 16px;
}
.card-actions {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
}
mwc-button {
align-self: center;
}
.wrap {
white-space: normal;
}
.status {
text-transform: capitalize;
padding: 16px;
}
`,
];
}
}

View File

@@ -185,6 +185,10 @@ export class CloudTTSPref extends LitElement {
right: auto;
left: 24px;
}
.card-actions {
display: flex;
flex-direction: row-reverse;
}
`;
}
}

View File

@@ -23,6 +23,11 @@ import "../../../components/ha-menu-button";
import "../../../components/ha-svg-icon";
import "../../../components/ha-tip";
import { CloudStatus } from "../../../data/cloud";
import {
fetchRepairsIssues,
RepairsIssue,
severitySort,
} from "../../../data/repairs";
import {
checkForEntityUpdates,
filterUpdateEntitiesWithInstall,
@@ -36,6 +41,7 @@ import { HomeAssistant } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import "../ha-config-section";
import { configSections } from "../ha-panel-config";
import "../repairs/ha-config-repairs";
import "./ha-config-navigation";
import "./ha-config-updates";
@@ -118,6 +124,11 @@ class HaConfigDashboard extends LitElement {
@state() private _tip?: string;
@state() private _repairsIssues: { issues: RepairsIssue[]; total: number } = {
issues: [],
total: 0,
};
private _pages = memoizeOne((clouStatus, isLoaded) => {
const pages: PageNavigation[] = [];
if (clouStatus && isLoaded) {
@@ -134,9 +145,12 @@ class HaConfigDashboard extends LitElement {
});
protected render(): TemplateResult {
const [canInstallUpdates, totalUpdates] =
const { updates: canInstallUpdates, total: totalUpdates } =
this._filterUpdateEntitiesWithInstall(this.hass.states);
const { issues: repairsIssues, total: totalRepairIssues } =
this._repairsIssues;
return html`
<ha-app-layout>
<app-header fixed slot="header">
@@ -174,26 +188,55 @@ class HaConfigDashboard extends LitElement {
.isWide=${this.isWide}
full-width
>
${canInstallUpdates.length
? html`<ha-card outlined>
<ha-config-updates
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalUpdates}
.updateEntities=${canInstallUpdates}
></ha-config-updates>
${totalUpdates > canInstallUpdates.length
? html`<a class="button" href="/config/updates">
${this.hass.localize(
"ui.panel.config.updates.more_updates",
{
count: totalUpdates - canInstallUpdates.length,
}
)}
</a>`
: ""}
</ha-card>`
${repairsIssues.length
? html`
<ha-card outlined>
<ha-config-repairs
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalRepairIssues}
.repairsIssues=${repairsIssues}
></ha-config-repairs>
${totalRepairIssues > repairsIssues.length
? html`
<a class="button" href="/config/repairs">
${this.hass.localize(
"ui.panel.config.repairs.more_repairs",
{
count: totalRepairIssues - repairsIssues.length,
}
)}
</a>
`
: ""}
</ha-card>
`
: ""}
${canInstallUpdates.length
? html`
<ha-card outlined>
<ha-config-updates
.hass=${this.hass}
.narrow=${this.narrow}
.total=${totalUpdates}
.updateEntities=${canInstallUpdates}
></ha-config-updates>
${totalUpdates > canInstallUpdates.length
? html`
<a class="button" href="/config/updates">
${this.hass.localize(
"ui.panel.config.updates.more_updates",
{
count: totalUpdates - canInstallUpdates.length,
}
)}
</a>
`
: ""}
</ha-card>
`
: ""}
<ha-card outlined>
<ha-config-navigation
.hass=${this.hass}
@@ -211,6 +254,11 @@ class HaConfigDashboard extends LitElement {
`;
}
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
this._fetchIssues();
}
protected override updated(changedProps: PropertyValues): void {
super.updated(changedProps);
@@ -220,16 +268,33 @@ class HaConfigDashboard extends LitElement {
}
private _filterUpdateEntitiesWithInstall = memoizeOne(
(entities: HassEntities): [UpdateEntity[], number] => {
(entities: HassEntities): { updates: UpdateEntity[]; total: number } => {
const updates = filterUpdateEntitiesWithInstall(entities);
return [
updates.slice(0, updates.length === 3 ? updates.length : 2),
updates.length,
];
return {
updates: updates.slice(0, updates.length === 3 ? updates.length : 2),
total: updates.length,
};
}
);
private async _fetchIssues(): Promise<void> {
const repairsIssues = (await fetchRepairsIssues(this.hass)).issues;
this._repairsIssues = {
issues: repairsIssues
.sort((a, b) => severitySort[a.severity] - severitySort[b.severity])
.slice(0, repairsIssues.length === 3 ? repairsIssues.length : 2),
total: repairsIssues.length,
};
const integrations: Set<string> = new Set();
for (const issue of this._repairsIssues.issues) {
integrations.add(issue.domain);
}
this.hass.loadBackendTranslation("issues", [...integrations]);
}
private _showQuickBar(): void {
showQuickBar(this, {
commandMode: true,

View File

@@ -163,17 +163,27 @@ export class HaDeviceEntitiesCard extends LitElement {
if (this.hass) {
element.hass = this.hass;
const stateObj = this.hass.states[entry.entity_id];
const name = stripPrefixFromEntityName(
computeStateName(stateObj),
this.deviceName.toLowerCase()
);
if (entry.hidden_by) {
config.name = `${
name || computeStateName(stateObj)
} (${this.hass.localize("ui.panel.config.devices.entities.hidden")})`;
} else if (name) {
config.name = name;
let name = entry.name
? entry.name
: entry.has_entity_name
? entry.original_name || this.deviceName
: stripPrefixFromEntityName(
computeStateName(stateObj),
this.deviceName.toLowerCase()
);
if (!name) {
name = computeStateName(stateObj);
}
if (entry.hidden_by) {
name += ` (${this.hass.localize(
"ui.panel.config.devices.entities.hidden"
)})`;
}
config.name = name;
}
// @ts-ignore
element.entry = entry;

View File

@@ -30,7 +30,7 @@ export const getZHADeviceActions = async (
const actions: DeviceAction[] = [];
if (zhaDevice.device_type !== "Coordinator") {
if (!zhaDevice.active_coordinator) {
actions.push({
label: hass.localize("ui.dialogs.zha_device_info.buttons.reconfigure"),
action: () => showZHAReconfigureDeviceDialog(el, { device: zhaDevice }),
@@ -58,50 +58,50 @@ export const getZHADeviceActions = async (
);
}
if (zhaDevice.device_type !== "Coordinator") {
actions.push(
...[
{
label: hass.localize(
"ui.dialogs.zha_device_info.buttons.zigbee_information"
actions.push(
...[
{
label: hass.localize(
"ui.dialogs.zha_device_info.buttons.zigbee_information"
),
action: () => showZHADeviceZigbeeInfoDialog(el, { device: zhaDevice }),
},
{
label: hass.localize("ui.dialogs.zha_device_info.buttons.clusters"),
action: () => showZHAClusterDialog(el, { device: zhaDevice }),
},
{
label: hass.localize(
"ui.dialogs.zha_device_info.buttons.view_in_visualization"
),
action: () =>
navigate(`/config/zha/visualization/${zhaDevice!.device_reg_id}`),
},
]
);
if (!zhaDevice.active_coordinator) {
actions.push({
label: hass.localize("ui.dialogs.zha_device_info.buttons.remove"),
classes: "warning",
action: async () => {
const confirmed = await showConfirmationDialog(el, {
text: hass.localize(
"ui.dialogs.zha_device_info.confirmations.remove"
),
action: () =>
showZHADeviceZigbeeInfoDialog(el, { device: zhaDevice }),
},
{
label: hass.localize("ui.dialogs.zha_device_info.buttons.clusters"),
action: () => showZHAClusterDialog(el, { device: zhaDevice }),
},
{
label: hass.localize(
"ui.dialogs.zha_device_info.buttons.view_in_visualization"
),
action: () =>
navigate(`/config/zha/visualization/${zhaDevice!.device_reg_id}`),
},
{
label: hass.localize("ui.dialogs.zha_device_info.buttons.remove"),
classes: "warning",
action: async () => {
const confirmed = await showConfirmationDialog(el, {
text: hass.localize(
"ui.dialogs.zha_device_info.confirmations.remove"
),
});
});
if (!confirmed) {
return;
}
if (!confirmed) {
return;
}
await hass.callService("zha", "remove", {
ieee: zhaDevice.ieee,
});
await hass.callService("zha", "remove", {
ieee: zhaDevice.ieee,
});
history.back();
},
},
]
);
history.back();
},
});
}
return actions;

View File

@@ -2,7 +2,6 @@ import { getConfigEntries } from "../../../../../../data/config_entries";
import { DeviceRegistryEntry } from "../../../../../../data/device_registry";
import {
fetchZwaveIsAnyFirmwareUpdateInProgress,
fetchZwaveNodeFirmwareUpdateCapabilities,
fetchZwaveNodeIsFirmwareUpdateInProgress,
fetchZwaveNodeStatus,
} from "../../../../../../data/zwave_js";
@@ -87,37 +86,31 @@ export const getZwaveDeviceActions = async (
return actions;
}
const [
firmwareUpdateCapabilities,
isAnyFirmwareUpdateInProgress,
isNodeFirmwareUpdateInProgress,
] = await Promise.all([
fetchZwaveNodeFirmwareUpdateCapabilities(hass, device.id),
fetchZwaveIsAnyFirmwareUpdateInProgress(hass, entryId),
fetchZwaveNodeIsFirmwareUpdateInProgress(hass, device.id),
]);
const [isAnyFirmwareUpdateInProgress, isNodeFirmwareUpdateInProgress] =
await Promise.all([
fetchZwaveIsAnyFirmwareUpdateInProgress(hass, entryId),
fetchZwaveNodeIsFirmwareUpdateInProgress(hass, device.id),
]);
if (
firmwareUpdateCapabilities.firmware_upgradable &&
(!isAnyFirmwareUpdateInProgress || isNodeFirmwareUpdateInProgress)
) {
if (!isAnyFirmwareUpdateInProgress || isNodeFirmwareUpdateInProgress) {
actions.push({
label: hass.localize(
"ui.panel.config.zwave_js.device_info.update_firmware"
),
action: async () => {
if (
await showConfirmationDialog(el, {
isNodeFirmwareUpdateInProgress ||
(await fetchZwaveNodeIsFirmwareUpdateInProgress(hass, device.id)) ||
(await showConfirmationDialog(el, {
text: hass.localize(
"ui.panel.config.zwave_js.update_firmware.warning"
),
dismissText: hass.localize("ui.common.no"),
confirmText: hass.localize("ui.common.yes"),
})
}))
) {
showZWaveJUpdateFirmwareNodeDialog(el, {
device,
firmwareUpdateCapabilities,
});
}
},

View File

@@ -169,9 +169,6 @@ class DialogDeviceRegistryDetail extends LitElement {
haStyle,
haStyleDialog,
css`
.form {
padding-bottom: 24px;
}
mwc-button.warning {
margin-right: auto;
}

View File

@@ -344,7 +344,7 @@ export class HaConfigDevicePage extends LitElement {
.disabled=${device.disabled_by}
.label=${device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.automation.create_disabled",
"ui.panel.config.devices.automation.create_disable",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
@@ -437,7 +437,7 @@ export class HaConfigDevicePage extends LitElement {
.disabled=${device.disabled_by}
.label=${device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.scene.create_disabled",
"ui.panel.config.devices.scene.create_disable",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
@@ -530,7 +530,7 @@ export class HaConfigDevicePage extends LitElement {
.disabled=${device.disabled_by}
.label=${device.disabled_by
? this.hass.localize(
"ui.panel.config.devices.script.create_disabled",
"ui.panel.config.devices.script.create_disable",
"type",
this.hass.localize(
`ui.panel.config.devices.type.${
@@ -768,26 +768,27 @@ export class HaConfigDevicePage extends LitElement {
: ""
}
</ha-device-info-card>
${!this.narrow ? [automationCard, sceneCard, scriptCard] : ""}
${!this.narrow ? [automationCard, sceneCard, scriptCard] : ""}
</div>
<div class="column">
${["control", "sensor", "config", "diagnostic"].map((category) =>
// Make sure we render controls if no other cards will be rendered
entitiesByCategory[category].length > 0 ||
(entities.length === 0 && category === "control")
? html`
<ha-device-entities-card
.hass=${this.hass}
.header=${this.hass.localize(
`ui.panel.config.devices.entities.${category}`
)}
.deviceName=${deviceName}
.entities=${entitiesByCategory[category]}
.showHidden=${device.disabled_by !== null}
>
</ha-device-entities-card>
`
: ""
${(["control", "sensor", "config", "diagnostic"] as const).map(
(category) =>
// Make sure we render controls if no other cards will be rendered
entitiesByCategory[category].length > 0 ||
(entities.length === 0 && category === "control")
? html`
<ha-device-entities-card
.hass=${this.hass}
.header=${this.hass.localize(
`ui.panel.config.devices.entities.${category}`
)}
.deviceName=${deviceName}
.entities=${entitiesByCategory[category]}
.showHidden=${device.disabled_by !== null}
>
</ha-device-entities-card>
`
: ""
)}
</div>
<div class="column">

View File

@@ -8,6 +8,9 @@ export const energyCardStyles = css`
height: 32px;
width: 32px;
margin-right: 8px;
margin-inline-end: 8px;
margin-inline-start: initial;
direction: var(--direction);
}
h3 {
margin-top: 24px;
@@ -24,6 +27,9 @@ export const energyCardStyles = css`
.row ha-icon,
.row img {
margin-right: 16px;
margin-inline-end: 16px;
margin-inline-start: initial;
direction: var(--direction);
}
.row img {
height: 24px;

View File

@@ -105,6 +105,10 @@ const OVERRIDE_DEVICE_CLASSES = {
],
};
const OVERRIDE_NUMBER_UNITS = {
temperature: ["°C", "°F", "K"],
};
const OVERRIDE_SENSOR_UNITS = {
temperature: ["°C", "°F", "K"],
pressure: ["hPa", "Pa", "kPa", "bar", "cbar", "mbar", "mmHg", "inHg", "psi"],
@@ -235,7 +239,7 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
}
}
if (domain === "sensor") {
if (domain === "number" || domain === "sensor") {
const stateObj: HassEntity | undefined =
this.hass.states[this.entry.entity_id];
this._unit_of_measurement = stateObj?.attributes?.unit_of_measurement;
@@ -361,6 +365,31 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
</ha-select>
`
: ""}
${domain === "number" &&
this._deviceClass &&
stateObj?.attributes.unit_of_measurement &&
OVERRIDE_NUMBER_UNITS[this._deviceClass]?.includes(
stateObj?.attributes.unit_of_measurement
)
? html`
<ha-select
.label=${this.hass.localize(
"ui.dialogs.entity_registry.editor.unit_of_measurement"
)}
.value=${stateObj.attributes.unit_of_measurement}
naturalMenuWidth
fixedMenuPosition
@selected=${this._unitChanged}
@closed=${stopPropagation}
>
${OVERRIDE_NUMBER_UNITS[this._deviceClass].map(
(unit: string) => html`
<mwc-list-item .value=${unit}>${unit}</mwc-list-item>
`
)}
</ha-select>
`
: ""}
${domain === "sensor" &&
this._deviceClass &&
stateObj?.attributes.unit_of_measurement &&
@@ -861,10 +890,10 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
params.hidden_by = this._hiddenBy;
}
if (
domain === "sensor" &&
(domain === "number" || domain === "number") &&
stateObj?.attributes?.unit_of_measurement !== this._unit_of_measurement
) {
params.options_domain = "sensor";
params.options_domain = domain;
params.options = { unit_of_measurement: this._unit_of_measurement };
}
if (
@@ -1023,12 +1052,10 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
bottom: 0;
width: 100%;
box-sizing: border-box;
border-top: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
display: flex;
justify-content: space-between;
padding: 8px;
padding-bottom: max(env(safe-area-inset-bottom), 8px);
padding: 0 24px 24px 24px;
justify-content: flex-end;
padding-bottom: max(env(safe-area-inset-bottom), 24px);
background-color: var(--mdc-theme-surface, #fff);
}
ha-select {

View File

@@ -736,6 +736,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
readonly: true,
selectable: false,
entity_category: null,
has_entity_name: false,
});
}
if (changed) {

View File

@@ -9,6 +9,7 @@ import {
mdiHeart,
mdiInformation,
mdiInformationOutline,
mdiLifebuoy,
mdiLightningBolt,
mdiMapMarkerRadius,
mdiMathLog,
@@ -267,6 +268,12 @@ export const configSections: { [name: string]: PageNavigation[] } = {
iconPath: mdiUpdate,
iconColor: "#3B808E",
},
{
path: "/config/repairs",
translationKey: "repairs",
iconPath: mdiLifebuoy,
iconColor: "#5c995c",
},
{
component: "logs",
path: "/config/logs",
@@ -448,6 +455,10 @@ class HaPanelConfig extends HassRouterPage {
tag: "ha-config-section-updates",
load: () => import("./core/ha-config-section-updates"),
},
repairs: {
tag: "ha-config-repairs-dashboard",
load: () => import("./repairs/ha-config-repairs-dashboard"),
},
users: {
tag: "ha-config-users",
load: () => import("./users/ha-config-users"),

View File

@@ -89,7 +89,7 @@ class DialogHardwareAvailable extends LitElement implements HassDialog {
)}
</h2>
<ha-icon-button
.label=${this.hass.localize("common.close")}
.label=${this.hass.localize("ui.common.close")}
.path=${mdiClose}
dialogAction="close"
></ha-icon-button>
@@ -97,7 +97,9 @@ class DialogHardwareAvailable extends LitElement implements HassDialog {
.hass=${this.hass}
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
.label=${this.hass.localize("common.search")}
.label=${this.hass.localize(
"ui.panel.config.hardware.available_hardware.search"
)}
>
</search-input>
</div>

View File

@@ -202,7 +202,7 @@ class HaConfigHardware extends LitElement {
title: this.hass.localize("ui.panel.config.hardware.reboot_host"),
text: this.hass.localize("ui.panel.config.hardware.reboot_host_confirm"),
confirmText: this.hass.localize("ui.panel.config.hardware.reboot_host"),
dismissText: this.hass.localize("common.cancel"),
dismissText: this.hass.localize("ui.common.cancel"),
});
if (!confirmed) {
@@ -236,7 +236,7 @@ class HaConfigHardware extends LitElement {
"ui.panel.config.hardware.shutdown_host_confirm"
),
confirmText: this.hass.localize("ui.panel.config.hardware.shutdown_host"),
dismissText: this.hass.localize("common.cancel"),
dismissText: this.hass.localize("ui.common.cancel"),
});
if (!confirmed) {

View File

@@ -24,6 +24,7 @@ import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
const JS_TYPE = __BUILD__;
const JS_VERSION = __VERSION__;
const PAGES: Array<{
@@ -132,7 +133,9 @@ class HaConfigInfo extends LitElement {
${this.hass.localize(
"ui.panel.config.info.frontend_version",
"version",
JS_VERSION
JS_VERSION,
"type",
JS_TYPE
)}
</span>
</div>
@@ -162,13 +165,6 @@ class HaConfigInfo extends LitElement {
`
)}
</mwc-list>
<p class="config-path">
${this.hass.localize(
"ui.panel.config.info.path_configuration",
"path",
hass.config.config_dir
)}
</p>
${!customUiList.length
? ""
: html`
@@ -199,7 +195,7 @@ class HaConfigInfo extends LitElement {
if (((window as any).CUSTOM_UI_LIST || []).length !== customUI.length) {
this.requestUpdate();
}
}, 1000);
}, 2000);
if (isComponentLoaded(this.hass, "hassio")) {
this._loadSupervisorInfo();
@@ -288,12 +284,6 @@ class HaConfigInfo extends LitElement {
}
}
.config-path {
color: var(--secondary-text-color);
text-align: center;
font-style: italic;
}
.custom-ui {
color: var(--secondary-text-color);
text-align: center;

View File

@@ -766,7 +766,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
display: flex;
justify-content: flex-end;
width: 100%;
margin-right: 8px;
align-items: center;
height: 56px;
position: sticky;

View File

@@ -31,6 +31,7 @@ import {
reloadConfigEntry,
updateConfigEntry,
ERROR_STATES,
RECOVERABLE_STATES,
} from "../../../data/config_entries";
import type { DeviceRegistryEntry } from "../../../data/device_registry";
import { getConfigEntryDiagnosticsDownloadUrl } from "../../../data/diagnostics";
@@ -366,7 +367,7 @@ export class HaIntegrationCard extends LitElement {
</a>`
: ""}
${!item.disabled_by &&
(item.state === "loaded" || item.state === "setup_retry") &&
RECOVERABLE_STATES.includes(item.state) &&
item.supports_unload &&
item.source !== "system"
? html`<mwc-list-item @request-selected=${this._handleReload}>

View File

@@ -60,6 +60,7 @@ class HaPanelDevMqtt extends LitElement {
<ha-code-editor
mode="jinja2"
autocomplete-entities
autocomplete-icons
.hass=${this.hass}
.value=${this.payload}
@value-changed=${this._handlePayload}

View File

@@ -401,7 +401,7 @@ class DialogZWaveJSAddNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: this._status === "finished"
@@ -451,7 +451,7 @@ class DialogZWaveJSAddNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: this._status === "provisioned"
@@ -469,7 +469,7 @@ class DialogZWaveJSAddNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>`
: ""}
</ha-dialog>

View File

@@ -120,7 +120,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
)}
</mwc-button>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: ``}
@@ -140,7 +140,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: ``}
@@ -160,7 +160,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: ``}
@@ -180,7 +180,7 @@ class DialogZWaveJSHealNetwork extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: ``}

View File

@@ -166,7 +166,7 @@ class DialogZWaveJSHealNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: ``}
@@ -186,7 +186,7 @@ class DialogZWaveJSHealNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: ``}

View File

@@ -85,7 +85,7 @@ class DialogZWaveJSReinterviewNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: ``}
@@ -105,7 +105,7 @@ class DialogZWaveJSReinterviewNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: ``}
@@ -125,7 +125,7 @@ class DialogZWaveJSReinterviewNode extends LitElement {
</div>
</div>
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
${this.hass.localize("ui.common.close")}
</mwc-button>
`
: ``}

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