mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-25 10:47:19 +00:00
Compare commits
92 Commits
Fix-Hardwa
...
fix-breaki
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43dcd75be6 | ||
|
|
38f19b6180 | ||
|
|
c99f00ba50 | ||
|
|
ce5776f59d | ||
|
|
12ff70020a | ||
|
|
5d605447a5 | ||
|
|
6d88d46ce4 | ||
|
|
cbe2643146 | ||
|
|
d332b8ab14 | ||
|
|
adfef05110 | ||
|
|
ca6a7bfbe2 | ||
|
|
a22f96a481 | ||
|
|
688109524d | ||
|
|
62dd7111ce | ||
|
|
1267575f62 | ||
|
|
b7da4dc68f | ||
|
|
826474518f | ||
|
|
a4b92fef3a | ||
|
|
d41159591c | ||
|
|
cb256bc386 | ||
|
|
bd50d6a6a3 | ||
|
|
05418fc83b | ||
|
|
8b675cdbba | ||
|
|
36b4909950 | ||
|
|
157b3ba5f2 | ||
|
|
72443b4f24 | ||
|
|
1c7d3fe610 | ||
|
|
6ac4560b36 | ||
|
|
9309a4c7bc | ||
|
|
b582a4d014 | ||
|
|
e4d233afa8 | ||
|
|
b131b255ec | ||
|
|
20bdb9ff35 | ||
|
|
b611a58fce | ||
|
|
24e54554ad | ||
|
|
d23fca4dd1 | ||
|
|
729e2f5248 | ||
|
|
437723c6a6 | ||
|
|
a30c8205b1 | ||
|
|
c50cf78bb4 | ||
|
|
be52ba0ea9 | ||
|
|
55e9ebc4d2 | ||
|
|
29c3fb0f92 | ||
|
|
7c3cd9d88d | ||
|
|
4881d699e3 | ||
|
|
414db83359 | ||
|
|
cd4f6e19f4 | ||
|
|
da709cbbd1 | ||
|
|
399efca411 | ||
|
|
f8bccf9e79 | ||
|
|
87aab72b63 | ||
|
|
24688ba18e | ||
|
|
e8086b6a6f | ||
|
|
e0a9c57a54 | ||
|
|
e63953ecbc | ||
|
|
72af200190 | ||
|
|
2094ae534b | ||
|
|
153ebb2a20 | ||
|
|
5d58e52eea | ||
|
|
5038f9c3c6 | ||
|
|
b285fda61b | ||
|
|
6cd38472cd | ||
|
|
8fd5f53f96 | ||
|
|
b70eee77ef | ||
|
|
4148b8c7aa | ||
|
|
e22dd0c49d | ||
|
|
30a254f98f | ||
|
|
8fcb3a017b | ||
|
|
5e29c7efa9 | ||
|
|
b6cc3e3ef0 | ||
|
|
184bdc0c85 | ||
|
|
f7fb731dc8 | ||
|
|
77977f64a3 | ||
|
|
e8da573ba2 | ||
|
|
07332bf155 | ||
|
|
f3c7583bf7 | ||
|
|
1cc02415d3 | ||
|
|
6ca3f06ea0 | ||
|
|
198e2b7bdf | ||
|
|
5a68e2c977 | ||
|
|
d9d29db560 | ||
|
|
124c6dc2b8 | ||
|
|
0f3886e053 | ||
|
|
b8bd15aa33 | ||
|
|
ed39aa6a7c | ||
|
|
405cae9b5f | ||
|
|
3ca2cbb3f9 | ||
|
|
c295ae56ab | ||
|
|
830364721b | ||
|
|
19089213e3 | ||
|
|
b633067e5c | ||
|
|
1b8874cbd4 |
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: "06:00"
|
||||
open-pull-requests-limit: 10
|
||||
18
.github/workflows/ci.yaml
vendored
18
.github/workflows/ci.yaml
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -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
|
||||
|
||||
6
.github/workflows/demo.yaml
vendored
6
.github/workflows/demo.yaml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -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
63
.github/workflows/nightly.yaml
vendored
Normal 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
|
||||
12
.github/workflows/release.yaml
vendored
12
.github/workflows/release.yaml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -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
|
||||
|
||||
4
.github/workflows/translations.yaml
vendored
4
.github/workflows/translations.yaml
vendored
@@ -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: |
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -277,7 +277,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
||||
],
|
||||
show_header_toggle: false,
|
||||
type: "entities",
|
||||
title: "Bandbredd",
|
||||
title: "Bandwidth",
|
||||
},
|
||||
// {
|
||||
// title: "Updater",
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
]);
|
||||
};
|
||||
20
demo/src/stubs/config_entries.ts
Normal file
20
demo/src/stubs/config_entries.ts
Normal 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,
|
||||
},
|
||||
]);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -194,6 +194,7 @@ const createEntityRegistryEntries = (
|
||||
name: null,
|
||||
icon: null,
|
||||
platform: "updater",
|
||||
has_entity_name: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, © <a href="https://carto.com/attributions">CARTO</a>',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -186,6 +186,7 @@ class StateHistoryCharts extends LitElement {
|
||||
line-height: 60px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-height: var(--history-max-height);
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,6 +76,7 @@ class HaAttributes extends LitElement {
|
||||
css`
|
||||
.attribute-container {
|
||||
margin-bottom: 8px;
|
||||
direction: ltr;
|
||||
}
|
||||
.data-entry {
|
||||
display: flex;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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*$/,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
61
src/data/repairs.ts
Normal 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}`);
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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>(
|
||||
|
||||
8
src/data/supported_brands.ts
Normal file
8
src/data/supported_brands.ts
Normal 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",
|
||||
});
|
||||
@@ -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");
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -378,6 +378,10 @@ class OnboardingCoreConfig extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-locations-editor {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -178,9 +178,6 @@ class DialogAreaDetail extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,6 +185,10 @@ export class CloudTTSPref extends LitElement {
|
||||
right: auto;
|
||||
left: 24px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -169,9 +169,6 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -736,6 +736,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
readonly: true,
|
||||
selectable: false,
|
||||
entity_category: null,
|
||||
has_entity_name: false,
|
||||
});
|
||||
}
|
||||
if (changed) {
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
: ``}
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
: ``}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user