mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-09 17:47:22 +00:00
Compare commits
91 Commits
fix-adapti
...
rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce8cabbad9 | ||
|
|
0802841606 | ||
|
|
cb93e1b741 | ||
|
|
30c383a2fc | ||
|
|
73ee235fef | ||
|
|
17c1043cfc | ||
|
|
da255dce40 | ||
|
|
0c68072f8f | ||
|
|
d197fd8f76 | ||
|
|
a961a87872 | ||
|
|
cc96c707b9 | ||
|
|
4b73713f2a | ||
|
|
c001102f15 | ||
|
|
c1e5e0bfcb | ||
|
|
a1412e90fd | ||
|
|
f6f40c1679 | ||
|
|
d77bebe96b | ||
|
|
1260af0b45 | ||
|
|
1d37eec411 | ||
|
|
5a52f83358 | ||
|
|
60724eb952 | ||
|
|
de5778079e | ||
|
|
f3710650f2 | ||
|
|
feb35dbc4f | ||
|
|
ee9e101fa6 | ||
|
|
24b16360a6 | ||
|
|
109c81a00d | ||
|
|
eaa1ddbf61 | ||
|
|
b11cb57a1e | ||
|
|
87b5f58779 | ||
|
|
8dac53c672 | ||
|
|
d0966bf35a | ||
|
|
6ba4fc0808 | ||
|
|
bd582ff816 | ||
|
|
d34bf83da0 | ||
|
|
b0cfb31bf3 | ||
|
|
6c39e5d2c5 | ||
|
|
7b51e71092 | ||
|
|
8a82483685 | ||
|
|
bb691fa7a2 | ||
|
|
2232db9c0f | ||
|
|
5375665dc6 | ||
|
|
480122f40a | ||
|
|
ee5c54030a | ||
|
|
b73f50e864 | ||
|
|
b9836073b7 | ||
|
|
a40512e0b5 | ||
|
|
b2122570fb | ||
|
|
885f9333d2 | ||
|
|
f812e7e9fb | ||
|
|
64dad39f6e | ||
|
|
df0fb423ed | ||
|
|
4c3156f290 | ||
|
|
ecdf374902 | ||
|
|
3e924e0cde | ||
|
|
6fb71e12c8 | ||
|
|
6138aa5489 | ||
|
|
61e865d3a6 | ||
|
|
febcbf6242 | ||
|
|
6a2fac6a9e | ||
|
|
b60c5467fc | ||
|
|
ecd563406e | ||
|
|
d5b66315e2 | ||
|
|
5b1719fc6e | ||
|
|
add22cf2e9 | ||
|
|
21509191fa | ||
|
|
1a73cccf0d | ||
|
|
407d68250a | ||
|
|
38b7bd18bb | ||
|
|
a00e944a35 | ||
|
|
481569804e | ||
|
|
a1d7e270ff | ||
|
|
225ccf1d2f | ||
|
|
4a5e1f9f3f | ||
|
|
b27b7210fd | ||
|
|
acd5181449 | ||
|
|
b6b2d03a80 | ||
|
|
7aee2b7cb7 | ||
|
|
df1914cb7a | ||
|
|
6706d5904d | ||
|
|
221aefd764 | ||
|
|
670057e8e6 | ||
|
|
427e46201c | ||
|
|
fd1240f335 | ||
|
|
aa7670cb59 | ||
|
|
468139229c | ||
|
|
39752f0e3f | ||
|
|
4d850d067f | ||
|
|
bcae64df88 | ||
|
|
690fd5a061 | ||
|
|
ac56c6df9a |
8
.github/workflows/cast_deployment.yaml
vendored
8
.github/workflows/cast_deployment.yaml
vendored
@@ -21,12 +21,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -56,12 +56,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
16
.github/workflows/ci.yaml
vendored
16
.github/workflows/ci.yaml
vendored
@@ -24,9 +24,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -58,9 +58,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -76,9 +76,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -100,9 +100,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
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@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
|
||||
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
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@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
|
||||
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
|
||||
# ℹ️ 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@cf1bb45a277cb3c205638b2cd5c984db1c46a412 # v4.31.7
|
||||
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
|
||||
8
.github/workflows/demo_deployment.yaml
vendored
8
.github/workflows/demo_deployment.yaml
vendored
@@ -22,12 +22,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -57,12 +57,12 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
4
.github/workflows/design_deployment.yaml
vendored
4
.github/workflows/design_deployment.yaml
vendored
@@ -16,10 +16,10 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
4
.github/workflows/design_preview.yaml
vendored
4
.github/workflows/design_preview.yaml
vendored
@@ -21,10 +21,10 @@ jobs:
|
||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
6
.github/workflows/nightly.yaml
vendored
6
.github/workflows/nightly.yaml
vendored
@@ -20,15 +20,15 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
|
||||
2
.github/workflows/relative-ci.yaml
vendored
2
.github/workflows/relative-ci.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send bundle stats and build information to RelativeCI
|
||||
uses: relative-ci/agent-action@c45aaa919ef85620af54242a241ac17a8fa35983 # v3.2.1
|
||||
uses: relative-ci/agent-action@feb19ddc698445db27401f1490f6ac182da0816f # v3.2.0
|
||||
with:
|
||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||
token: ${{ github.token }}
|
||||
|
||||
22
.github/workflows/release.yaml
vendored
22
.github/workflows/release.yaml
vendored
@@ -23,10 +23,10 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
script/release
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
# home-assistant/wheels doesn't support SHA pinning
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2025.12.0
|
||||
uses: home-assistant/wheels@2025.10.0
|
||||
with:
|
||||
abi: cp313
|
||||
tag: musllinux_1_2
|
||||
@@ -91,9 +91,9 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
- name: Tar folder
|
||||
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
||||
- name: Upload release asset
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
with:
|
||||
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
||||
@@ -120,9 +120,9 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
cache: yarn
|
||||
@@ -137,6 +137,6 @@ jobs:
|
||||
- name: Tar folder
|
||||
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
||||
- name: Upload release asset
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
with:
|
||||
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
||||
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@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 90
|
||||
|
||||
2
.github/workflows/translations.yaml
vendored
2
.github/workflows/translations.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
|
||||
@@ -305,8 +305,9 @@ export class HcMain extends HassElement {
|
||||
await llColl.refresh();
|
||||
this._unsubLovelace = llColl.subscribe(async (rawConfig) => {
|
||||
if (isStrategyDashboard(rawConfig)) {
|
||||
const { generateLovelaceDashboardStrategy } =
|
||||
await import("../../../../src/panels/lovelace/strategies/get-strategy");
|
||||
const { generateLovelaceDashboardStrategy } = await import(
|
||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
rawConfig,
|
||||
this.hass!
|
||||
@@ -346,8 +347,9 @@ export class HcMain extends HassElement {
|
||||
}
|
||||
|
||||
private async _generateDefaultLovelaceConfig() {
|
||||
const { generateLovelaceDashboardStrategy } =
|
||||
await import("../../../../src/panels/lovelace/strategies/get-strategy");
|
||||
const { generateLovelaceDashboardStrategy } = await import(
|
||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!)
|
||||
);
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Adaptive dialog (ha-adaptive-dialog)
|
||||
---
|
||||
@@ -1,732 +0,0 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { mdiCog, mdiHelp } from "@mdi/js";
|
||||
import "../../../../src/components/ha-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-dialog-footer";
|
||||
import "../../../../src/components/ha-adaptive-dialog";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
const SCHEMA: HaFormSchema[] = [
|
||||
{ type: "string", name: "Name", default: "", autofocus: true },
|
||||
{ type: "string", name: "Email", default: "" },
|
||||
];
|
||||
|
||||
type DialogType =
|
||||
| false
|
||||
| "basic"
|
||||
| "basic-subtitle-below"
|
||||
| "basic-subtitle-above"
|
||||
| "form"
|
||||
| "form-block-mode"
|
||||
| "actions"
|
||||
| "large"
|
||||
| "small";
|
||||
|
||||
@customElement("demo-components-ha-adaptive-dialog")
|
||||
export class DemoHaAdaptiveDialog extends LitElement {
|
||||
@state() private _openDialog: DialogType = false;
|
||||
|
||||
@state() private _hass?: HomeAssistant;
|
||||
|
||||
protected firstUpdated() {
|
||||
const hass = provideHass(this);
|
||||
this._hass = hass;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="content">
|
||||
<h1>Adaptive dialog <code><ha-adaptive-dialog></code></h1>
|
||||
|
||||
<p class="subtitle">
|
||||
Responsive dialog component that automatically switches between a full
|
||||
dialog and bottom sheet based on screen size.
|
||||
</p>
|
||||
|
||||
<h2>Demos</h2>
|
||||
|
||||
<div class="buttons">
|
||||
<ha-button @click=${this._handleOpenDialog("basic")}
|
||||
>Basic adaptive dialog</ha-button
|
||||
>
|
||||
<ha-button @click=${this._handleOpenDialog("basic-subtitle-below")}
|
||||
>Adaptive dialog with subtitle below</ha-button
|
||||
>
|
||||
<ha-button @click=${this._handleOpenDialog("basic-subtitle-above")}
|
||||
>Adaptive dialog with subtitle above</ha-button
|
||||
>
|
||||
<ha-button @click=${this._handleOpenDialog("small")}
|
||||
>Small width adaptive dialog</ha-button
|
||||
>
|
||||
<ha-button @click=${this._handleOpenDialog("large")}
|
||||
>Large width adaptive dialog</ha-button
|
||||
>
|
||||
<ha-button @click=${this._handleOpenDialog("form")}
|
||||
>Adaptive dialog with form</ha-button
|
||||
>
|
||||
<ha-button @click=${this._handleOpenDialog("form-block-mode")}
|
||||
>Adaptive dialog with form (block mode change)</ha-button
|
||||
>
|
||||
<ha-button @click=${this._handleOpenDialog("actions")}
|
||||
>Adaptive dialog with actions</ha-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
<strong>Tip:</strong> Resize your browser window to see the
|
||||
responsive behavior. The dialog automatically switches to a bottom
|
||||
sheet on narrow screens (<870px width) or short screens
|
||||
(<500px height).
|
||||
</p>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this._hass}
|
||||
.open=${this._openDialog === "basic"}
|
||||
header-title="Basic adaptive dialog"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<div>Adaptive dialog content</div>
|
||||
</ha-adaptive-dialog>
|
||||
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this._hass}
|
||||
.open=${this._openDialog === "basic-subtitle-below"}
|
||||
header-title="Adaptive dialog with subtitle"
|
||||
header-subtitle="This is an adaptive dialog with a subtitle below"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<div>Adaptive dialog content</div>
|
||||
</ha-adaptive-dialog>
|
||||
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this._hass}
|
||||
.open=${this._openDialog === "basic-subtitle-above"}
|
||||
header-title="Adaptive dialog with subtitle above"
|
||||
header-subtitle="This is an adaptive dialog with a subtitle above"
|
||||
header-subtitle-position="above"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<div>Adaptive dialog content</div>
|
||||
</ha-adaptive-dialog>
|
||||
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this._hass}
|
||||
.open=${this._openDialog === "small"}
|
||||
width="small"
|
||||
header-title="Small adaptive dialog"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<div>This dialog uses the small width preset (320px).</div>
|
||||
</ha-adaptive-dialog>
|
||||
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this._hass}
|
||||
.open=${this._openDialog === "large"}
|
||||
width="large"
|
||||
header-title="Large adaptive dialog"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<div>This dialog uses the large width preset (1024px).</div>
|
||||
</ha-adaptive-dialog>
|
||||
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this._hass}
|
||||
.open=${this._openDialog === "form"}
|
||||
header-title="Adaptive dialog with form"
|
||||
header-subtitle="This is an adaptive dialog with a form"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<ha-form autofocus .schema=${SCHEMA}></ha-form>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
@click=${this._handleClosed}
|
||||
slot="secondaryAction"
|
||||
variant="plain"
|
||||
>Cancel</ha-button
|
||||
>
|
||||
<ha-button
|
||||
@click=${this._handleClosed}
|
||||
slot="primaryAction"
|
||||
variant="accent"
|
||||
>Submit</ha-button
|
||||
>
|
||||
</ha-dialog-footer>
|
||||
</ha-adaptive-dialog>
|
||||
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this._hass}
|
||||
.open=${this._openDialog === "form-block-mode"}
|
||||
header-title="Adaptive dialog with form (block mode change)"
|
||||
header-subtitle="This form will not reset when the viewport size changes"
|
||||
block-mode-change
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<ha-form autofocus .schema=${SCHEMA}></ha-form>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
@click=${this._handleClosed}
|
||||
slot="secondaryAction"
|
||||
variant="plain"
|
||||
>Cancel</ha-button
|
||||
>
|
||||
<ha-button
|
||||
@click=${this._handleClosed}
|
||||
slot="primaryAction"
|
||||
variant="accent"
|
||||
>Submit</ha-button
|
||||
>
|
||||
</ha-dialog-footer>
|
||||
</ha-adaptive-dialog>
|
||||
|
||||
<ha-adaptive-dialog
|
||||
.hass=${this._hass}
|
||||
.open=${this._openDialog === "actions"}
|
||||
header-title="Adaptive dialog with actions"
|
||||
header-subtitle="This is an adaptive dialog with header actions"
|
||||
@closed=${this._handleClosed}
|
||||
>
|
||||
<div slot="headerActionItems">
|
||||
<ha-icon-button label="Settings" path=${mdiCog}></ha-icon-button>
|
||||
<ha-icon-button label="Help" path=${mdiHelp}></ha-icon-button>
|
||||
</div>
|
||||
|
||||
<div>Adaptive dialog content</div>
|
||||
</ha-adaptive-dialog>
|
||||
|
||||
<h2>Design</h2>
|
||||
|
||||
<h3>Responsive behavior</h3>
|
||||
|
||||
<p>
|
||||
The <code>ha-adaptive-dialog</code> component automatically switches
|
||||
between two modes based on screen size:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Dialog mode:</strong> Used on larger screens (width >
|
||||
870px and height > 500px). Renders as a centered dialog using
|
||||
<code>ha-wa-dialog</code>.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Bottom sheet mode:</strong> Used on mobile devices and
|
||||
smaller screens (width ≤ 870px or height ≤ 500px). Renders as a
|
||||
drawer from the bottom using <code>ha-bottom-sheet</code>.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
The mode is determined automatically and updates when the window is
|
||||
resized. To prevent mode changes after the initial mount (useful for
|
||||
preventing form resets), use the <code>block-mode-change</code>
|
||||
attribute.
|
||||
</p>
|
||||
|
||||
<h3>Width</h3>
|
||||
|
||||
<p>
|
||||
In dialog mode, there are multiple width presets available. These are
|
||||
ignored in bottom sheet mode.
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>small</code></td>
|
||||
<td><code>min(320px, var(--full-width))</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>medium</code></td>
|
||||
<td><code>min(580px, var(--full-width))</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>large</code></td>
|
||||
<td><code>min(1024px, var(--full-width))</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>full</code></td>
|
||||
<td><code>var(--full-width)</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Adaptive dialogs have a default width of <code>medium</code>.</p>
|
||||
|
||||
<h3>Header</h3>
|
||||
|
||||
<p>
|
||||
The header contains a navigation icon, title, subtitle, and action
|
||||
items.
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Slot</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>headerNavigationIcon</code></td>
|
||||
<td>
|
||||
Leading header action (e.g., close/back button). In bottom sheet
|
||||
mode, defaults to a close button if not provided.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>headerTitle</code></td>
|
||||
<td>The header title content.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>headerSubtitle</code></td>
|
||||
<td>The header subtitle content.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>headerActionItems</code></td>
|
||||
<td>Trailing header actions (e.g., icon buttons, menus).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>Header title</h4>
|
||||
|
||||
<p>
|
||||
The header title can be set using the <code>header-title</code>
|
||||
attribute or by providing custom content in the
|
||||
<code>headerTitle</code> slot.
|
||||
</p>
|
||||
|
||||
<h4>Header subtitle</h4>
|
||||
|
||||
<p>
|
||||
The header subtitle can be set using the
|
||||
<code>header-subtitle</code> attribute or by providing custom content
|
||||
in the <code>headerSubtitle</code> slot. The subtitle position
|
||||
relative to the title can be controlled with the
|
||||
<code>header-subtitle-position</code> attribute.
|
||||
</p>
|
||||
|
||||
<h4>Header navigation icon</h4>
|
||||
|
||||
<p>
|
||||
In bottom sheet mode, a close button is automatically provided if no
|
||||
custom navigation icon is specified. In dialog mode, the dialog can be
|
||||
closed via the standard dialog close button.
|
||||
</p>
|
||||
|
||||
<h4>Header action items</h4>
|
||||
|
||||
<p>
|
||||
The header action items usually contain icon buttons and/or menu
|
||||
buttons.
|
||||
</p>
|
||||
|
||||
<h3>Body</h3>
|
||||
|
||||
<p>The body is the content of the adaptive dialog.</p>
|
||||
|
||||
<h3>Footer</h3>
|
||||
|
||||
<p>The footer is the footer of the adaptive dialog.</p>
|
||||
|
||||
<p>
|
||||
It is recommended to use the <code>ha-dialog-footer</code> component
|
||||
for the footer and to style the buttons inside the footer as follows:
|
||||
</p>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Slot</th>
|
||||
<th>Description</th>
|
||||
<th>Variant to use</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>secondaryAction</code></td>
|
||||
<td>The secondary action button(s).</td>
|
||||
<td><code>plain</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>primaryAction</code></td>
|
||||
<td>The primary action button(s).</td>
|
||||
<td><code>accent</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Implementation</h2>
|
||||
|
||||
<h3>When to use</h3>
|
||||
|
||||
<p>
|
||||
Use <code>ha-adaptive-dialog</code> when you need a dialog that should
|
||||
adapt to different screen sizes automatically. This is particularly
|
||||
useful for:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Forms and data entry that need to work well on mobile devices</li>
|
||||
<li>
|
||||
Content that benefits from full-screen presentation on small devices
|
||||
</li>
|
||||
<li>
|
||||
Interfaces that need consistent behavior across desktop and mobile
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
If you don't need responsive behavior, use
|
||||
<code>ha-wa-dialog</code> directly for desktop-only dialogs or
|
||||
<code>ha-bottom-sheet</code> for mobile-only sheets.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Use the <code>block-mode-change</code> attribute when you want to
|
||||
prevent the dialog from switching modes after it's opened. This is
|
||||
especially useful for forms, as it prevents form data from being lost
|
||||
when users resize their browser window.
|
||||
</p>
|
||||
|
||||
<h3>Example usage</h3>
|
||||
|
||||
<pre><code><ha-adaptive-dialog
|
||||
.hass=\${this.hass}
|
||||
open
|
||||
width="medium"
|
||||
header-title="Dialog title"
|
||||
header-subtitle="Dialog subtitle"
|
||||
>
|
||||
<div slot="headerActionItems">
|
||||
<ha-icon-button label="Settings" path="mdiCog"></ha-icon-button>
|
||||
<ha-icon-button label="Help" path="mdiHelp"></ha-icon-button>
|
||||
</div>
|
||||
<div>Dialog content</div>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button slot="secondaryAction" variant="plain"
|
||||
>Cancel</ha-button
|
||||
>
|
||||
<ha-button slot="primaryAction" variant="accent">Submit</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-adaptive-dialog></code></pre>
|
||||
|
||||
<p>Example with <code>block-mode-change</code> for forms:</p>
|
||||
|
||||
<pre><code><ha-adaptive-dialog
|
||||
.hass=\${this.hass}
|
||||
open
|
||||
header-title="Edit configuration"
|
||||
block-mode-change
|
||||
>
|
||||
<ha-form .schema=\${schema} .data=\${data}></ha-form>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button slot="secondaryAction" variant="plain"
|
||||
>Cancel</ha-button
|
||||
>
|
||||
<ha-button slot="primaryAction" variant="accent">Save</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-adaptive-dialog></code></pre>
|
||||
|
||||
<h3>API</h3>
|
||||
|
||||
<p>
|
||||
This component combines <code>ha-wa-dialog</code> and
|
||||
<code>ha-bottom-sheet</code> with automatic mode switching based on
|
||||
screen size.
|
||||
</p>
|
||||
|
||||
<h4>Attributes</h4>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Attribute</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
<th>Options</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>open</code></td>
|
||||
<td>Controls the adaptive dialog open state.</td>
|
||||
<td><code>false</code></td>
|
||||
<td><code>false</code>, <code>true</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>width</code></td>
|
||||
<td>
|
||||
Preferred dialog width preset (dialog mode only, ignored in
|
||||
bottom sheet mode).
|
||||
</td>
|
||||
<td><code>medium</code></td>
|
||||
<td>
|
||||
<code>small</code>, <code>medium</code>, <code>large</code>,
|
||||
<code>full</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>header-title</code></td>
|
||||
<td>Header title text when no custom title slot is provided.</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>header-subtitle</code></td>
|
||||
<td>
|
||||
Header subtitle text when no custom subtitle slot is provided.
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>header-subtitle-position</code></td>
|
||||
<td>Position of the subtitle relative to the title.</td>
|
||||
<td><code>below</code></td>
|
||||
<td><code>above</code>, <code>below</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>aria-labelledby</code></td>
|
||||
<td>
|
||||
The ID of the element that labels the dialog (for
|
||||
accessibility).
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>aria-describedby</code></td>
|
||||
<td>
|
||||
The ID of the element that describes the dialog (for
|
||||
accessibility).
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>block-mode-change</code></td>
|
||||
<td>
|
||||
When set, the mode is determined at mount time based on the
|
||||
current screen size, but subsequent mode changes are blocked.
|
||||
Useful for preventing forms from resetting when the viewport
|
||||
size changes.
|
||||
</td>
|
||||
<td><code>false</code></td>
|
||||
<td><code>false</code>, <code>true</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>CSS custom properties</h4>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>CSS Property</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>--ha-dialog-surface-background</code></td>
|
||||
<td>Dialog/sheet background color.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-dialog-border-radius</code></td>
|
||||
<td>Border radius of the dialog surface (dialog mode only).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-dialog-show-duration</code></td>
|
||||
<td>Show animation duration (dialog mode only).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--ha-dialog-hide-duration</code></td>
|
||||
<td>Hide animation duration (dialog mode only).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>Events</h4>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>opened</code></td>
|
||||
<td>
|
||||
Fired when the adaptive dialog is shown (dialog mode only).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>closed</code></td>
|
||||
<td>
|
||||
Fired after the adaptive dialog is hidden (dialog mode only).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>after-show</code></td>
|
||||
<td>Fired after show animation completes (dialog mode only).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Focus management</h3>
|
||||
|
||||
<p>
|
||||
To automatically focus an element when the adaptive dialog opens, add
|
||||
the
|
||||
<code>autofocus</code> attribute to it. Components with
|
||||
<code>delegatesFocus: true</code> (like <code>ha-form</code>) will
|
||||
forward focus to their first focusable child.
|
||||
</p>
|
||||
|
||||
<p>Example:</p>
|
||||
|
||||
<pre><code><ha-adaptive-dialog .hass=\${this.hass} open>
|
||||
<ha-form autofocus .schema=\${schema}></ha-form>
|
||||
</ha-adaptive-dialog></code></pre>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleOpenDialog = (dialog: DialogType) => () => {
|
||||
this._openDialog = dialog;
|
||||
};
|
||||
|
||||
private _handleClosed = () => {
|
||||
this._openDialog = false;
|
||||
};
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: var(--ha-space-4);
|
||||
}
|
||||
|
||||
.content {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--ha-space-2);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: var(--ha-space-6);
|
||||
margin-bottom: var(--ha-space-3);
|
||||
}
|
||||
|
||||
h3,
|
||||
h4 {
|
||||
margin-top: var(--ha-space-4);
|
||||
margin-bottom: var(--ha-space-2);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: var(--ha-space-2) 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: var(--ha-space-2) 0;
|
||||
padding-left: var(--ha-space-5);
|
||||
}
|
||||
|
||||
li {
|
||||
margin: var(--ha-space-1) 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 1.1em;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: var(--ha-space-3) 0;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
text-align: left;
|
||||
padding: var(--ha-space-2);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--secondary-background-color);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--secondary-background-color);
|
||||
padding: var(--ha-space-3);
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin: var(--ha-space-3) 0;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--ha-space-2);
|
||||
margin: var(--ha-space-4) 0;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: var(--ha-space-3);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-adaptive-dialog": DemoHaAdaptiveDialog;
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ export class DemoHaWaDialog extends LitElement {
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>large</code></td>
|
||||
<td><code>min(1024px, var(--full-width))</code></td>
|
||||
<td><code>min(720px, var(--full-width))</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>full</code></td>
|
||||
|
||||
@@ -88,8 +88,8 @@ class HassioRegistriesDialog extends LitElement {
|
||||
<ha-button
|
||||
?disabled=${Boolean(
|
||||
!this._input.registry ||
|
||||
!this._input.username ||
|
||||
!this._input.password
|
||||
!this._input.username ||
|
||||
!this._input.password
|
||||
)}
|
||||
@click=${this._addNewRegistry}
|
||||
appearance="filled"
|
||||
|
||||
24
package.json
24
package.json
@@ -52,7 +52,7 @@
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/luxon3": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.1",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.0",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lit-labs/motion": "1.0.9",
|
||||
"@lit-labs/observers": "2.0.6",
|
||||
@@ -89,8 +89,8 @@
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "3.9.1",
|
||||
"@tsparticles/preset-links": "3.2.0",
|
||||
"@vaadin/combo-box": "24.9.6",
|
||||
"@vaadin/vaadin-themable-mixin": "24.9.6",
|
||||
"@vaadin/combo-box": "24.9.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.9.5",
|
||||
"@vibrant/color": "4.0.0",
|
||||
"@vue/web-component-wrapper": "1.3.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
@@ -152,13 +152,13 @@
|
||||
"@babel/helper-define-polyfill-provider": "0.6.5",
|
||||
"@babel/plugin-transform-runtime": "7.28.5",
|
||||
"@babel/preset-env": "7.28.5",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.7",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.6",
|
||||
"@lokalise/node-api": "15.4.0",
|
||||
"@octokit/auth-oauth-device": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.0.3",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.3.12",
|
||||
"@rspack/core": "1.6.6",
|
||||
"@rsdoctor/rspack-plugin": "1.3.11",
|
||||
"@rspack/core": "1.6.4",
|
||||
"@rspack/dev-server": "1.1.4",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.22",
|
||||
@@ -178,7 +178,7 @@
|
||||
"@types/tar": "6.1.13",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@vitest/coverage-v8": "4.0.15",
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"babel-loader": "10.0.0",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.3",
|
||||
@@ -194,7 +194,7 @@
|
||||
"eslint-plugin-wc": "3.0.2",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.2",
|
||||
"glob": "13.0.0",
|
||||
"glob": "12.0.0",
|
||||
"gulp": "5.0.1",
|
||||
"gulp-brotli": "3.0.0",
|
||||
"gulp-json-transform": "0.5.0",
|
||||
@@ -209,17 +209,17 @@
|
||||
"lodash.template": "4.5.0",
|
||||
"map-stream": "0.0.7",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.7.4",
|
||||
"prettier": "3.6.2",
|
||||
"rspack-manifest-plugin": "5.2.0",
|
||||
"serve": "14.2.5",
|
||||
"sinon": "21.0.0",
|
||||
"tar": "7.5.2",
|
||||
"terser-webpack-plugin": "5.3.15",
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.48.1",
|
||||
"typescript-eslint": "8.47.0",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "4.0.15",
|
||||
"vitest": "4.0.13",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20251203.0"
|
||||
version = "20251203.2"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
|
||||
@@ -45,8 +45,9 @@ export const computeFormatFunctions = async (
|
||||
formatEntityAttributeName: FormatEntityAttributeNameFunc;
|
||||
formatEntityName: FormatEntityNameFunc;
|
||||
}> => {
|
||||
const { computeStateDisplay } =
|
||||
await import("../entity/compute_state_display");
|
||||
const { computeStateDisplay } = await import(
|
||||
"../entity/compute_state_display"
|
||||
);
|
||||
const { computeAttributeValueDisplay, computeAttributeNameDisplay } =
|
||||
await import("../entity/compute_attribute_display");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import type { VisualMapComponentOption } from "echarts/components";
|
||||
import type { LineSeriesOption } from "echarts/charts";
|
||||
import type { YAXisOption } from "echarts/types/dist/shared";
|
||||
@@ -27,7 +27,6 @@ const safeParseFloat = (value) => {
|
||||
return isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
@customElement("state-history-chart-line")
|
||||
export class StateHistoryChartLine extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -796,6 +795,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
return Math.abs(value) < 1 ? value : roundingFn(value);
|
||||
}
|
||||
}
|
||||
customElements.define("state-history-chart-line", StateHistoryChartLine);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
||||
@@ -2,17 +2,15 @@ import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
||||
import { computeCssColor } from "../../common/color/compute-color";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import type { LabelRegistryEntry } from "../../data/label_registry";
|
||||
import "../chips/ha-chip-set";
|
||||
import "../ha-dropdown";
|
||||
import "../ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../ha-dropdown-item";
|
||||
import "../ha-icon";
|
||||
import "../ha-label";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import "../chips/ha-chip-set";
|
||||
import "../ha-button-menu";
|
||||
import "../ha-icon";
|
||||
import "../ha-list-item";
|
||||
|
||||
@customElement("ha-data-table-labels")
|
||||
class HaDataTableLabels extends LitElement {
|
||||
@@ -28,11 +26,12 @@ class HaDataTableLabels extends LitElement {
|
||||
(label) => this._renderLabel(label, true)
|
||||
)}
|
||||
${labels.length > 2
|
||||
? html`<ha-dropdown
|
||||
? html`<ha-button-menu
|
||||
absolute
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click=${stopPropagation}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
@click=${this._handleIconOverflowMenuOpened}
|
||||
@closed=${this._handleIconOverflowMenuClosed}
|
||||
>
|
||||
<ha-label slot="trigger" class="plus" dense>
|
||||
+${labels.length - 2}
|
||||
@@ -41,12 +40,12 @@ class HaDataTableLabels extends LitElement {
|
||||
labels.slice(2),
|
||||
(label) => label.label_id,
|
||||
(label) => html`
|
||||
<ha-dropdown-item .value=${label.label_id} .item=${label}>
|
||||
<ha-list-item @click=${this._labelClicked} .item=${label}>
|
||||
${this._renderLabel(label, false)}
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-dropdown>`
|
||||
</ha-button-menu>`
|
||||
: nothing}
|
||||
</ha-chip-set>
|
||||
`;
|
||||
@@ -82,12 +81,21 @@ class HaDataTableLabels extends LitElement {
|
||||
fireEvent(this, "label-clicked", { label });
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(
|
||||
ev: CustomEvent<{ item: HaDropdownItem & { item?: LabelRegistryEntry } }>
|
||||
) {
|
||||
const label = ev.detail?.item?.item;
|
||||
if (label) {
|
||||
fireEvent(this, "label-clicked", { label });
|
||||
protected _handleIconOverflowMenuOpened(e) {
|
||||
e.stopPropagation();
|
||||
// If this component is used inside a data table, the z-index of the row
|
||||
// needs to be increased. Otherwise the ha-button-menu would be displayed
|
||||
// underneath the next row in the table.
|
||||
const row = this.closest(".mdc-data-table__row") as HTMLDivElement | null;
|
||||
if (row) {
|
||||
row.style.zIndex = "1";
|
||||
}
|
||||
}
|
||||
|
||||
protected _handleIconOverflowMenuClosed() {
|
||||
const row = this.closest(".mdc-data-table__row") as HTMLDivElement | null;
|
||||
if (row) {
|
||||
row.style.zIndex = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +114,9 @@ class HaDataTableLabels extends LitElement {
|
||||
--ha-label-background-color: var(--color, var(--grey-color));
|
||||
--ha-label-background-opacity: 0.5;
|
||||
}
|
||||
ha-button-menu {
|
||||
border-radius: 10px;
|
||||
}
|
||||
.plus {
|
||||
--ha-label-background-color: transparent;
|
||||
border: 1px solid var(--divider-color);
|
||||
|
||||
@@ -16,10 +16,8 @@ import memoizeOne from "memoize-one";
|
||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { groupBy } from "../../common/util/group-by";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
import { haStyleScrollbar } from "../../resources/styles";
|
||||
import { loadVirtualizer } from "../../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@@ -28,6 +26,8 @@ import type { HaCheckbox } from "../ha-checkbox";
|
||||
import "../ha-svg-icon";
|
||||
import "../search-input";
|
||||
import { filterData, sortData } from "./sort-filter";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
|
||||
export interface RowClickedEvent {
|
||||
id: string;
|
||||
@@ -838,10 +838,10 @@ export class HaDataTable extends LitElement {
|
||||
} else if (this.sortDirection === "asc") {
|
||||
this.sortDirection = "desc";
|
||||
} else {
|
||||
this.sortDirection = "asc";
|
||||
this.sortDirection = null;
|
||||
}
|
||||
|
||||
this.sortColumn = columnId;
|
||||
this.sortColumn = this.sortDirection === null ? undefined : columnId;
|
||||
|
||||
fireEvent(this, "sorting-changed", {
|
||||
column: columnId,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { expose } from "comlink";
|
||||
import Fuse, { type FuseOptionKey } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ipCompare, stringCompare } from "../../common/string/compare";
|
||||
import { stripDiacritics } from "../../common/string/strip-diacritics";
|
||||
import { multiTermSearch } from "../../resources/fuseMultiTerm";
|
||||
import { HaFuse } from "../../resources/fuse";
|
||||
import type {
|
||||
ClonedDataTableColumnData,
|
||||
DataTableRowData,
|
||||
@@ -11,10 +11,9 @@ import type {
|
||||
SortingDirection,
|
||||
} from "./ha-data-table";
|
||||
|
||||
const getSearchKeys = memoizeOne(
|
||||
(columns: SortableColumnContainer): FuseOptionKey<DataTableRowData>[] => {
|
||||
const fuseIndex = memoizeOne(
|
||||
(data: DataTableRowData[], columns: SortableColumnContainer) => {
|
||||
const searchKeys = new Set<string>();
|
||||
|
||||
Object.entries(columns).forEach(([key, column]) => {
|
||||
if (column.filterable) {
|
||||
searchKeys.add(
|
||||
@@ -24,15 +23,10 @@ const getSearchKeys = memoizeOne(
|
||||
);
|
||||
}
|
||||
});
|
||||
return Array.from(searchKeys);
|
||||
return Fuse.createIndex([...searchKeys], data);
|
||||
}
|
||||
);
|
||||
|
||||
const fuseIndex = memoizeOne(
|
||||
(data: DataTableRowData[], keys: FuseOptionKey<DataTableRowData>[]) =>
|
||||
Fuse.createIndex(keys, data)
|
||||
);
|
||||
|
||||
const filterData = (
|
||||
data: DataTableRowData[],
|
||||
columns: SortableColumnContainer,
|
||||
@@ -44,13 +38,21 @@ const filterData = (
|
||||
return data;
|
||||
}
|
||||
|
||||
const keys = getSearchKeys(columns);
|
||||
const index = fuseIndex(data, columns);
|
||||
|
||||
const index = fuseIndex(data, keys);
|
||||
const fuse = new HaFuse(
|
||||
data,
|
||||
{ shouldSort: false, minMatchCharLength: 1 },
|
||||
index
|
||||
);
|
||||
|
||||
return multiTermSearch<DataTableRowData>(data, filter, keys, index, {
|
||||
threshold: 0.2, // reduce fuzzy matches in data tables
|
||||
});
|
||||
const searchResults = fuse.multiTermsSearch(filter);
|
||||
|
||||
if (searchResults) {
|
||||
return searchResults.map((result) => result.item);
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const sortData = (
|
||||
|
||||
@@ -9,7 +9,6 @@ import { computeDeviceName } from "../../common/entity/compute_device_name";
|
||||
import { getDeviceContext } from "../../common/entity/context/get_device_context";
|
||||
import { getConfigEntries, type ConfigEntry } from "../../data/config_entries";
|
||||
import {
|
||||
deviceComboBoxKeys,
|
||||
getDevices,
|
||||
type DevicePickerItem,
|
||||
type DeviceRegistryEntry,
|
||||
@@ -217,10 +216,6 @@ export class HaDevicePicker extends LitElement {
|
||||
.getItems=${this._getItems}
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.searchKeys=${deviceComboBoxKeys}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.device-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { isValidEntityId } from "../../common/entity/valid_entity_id";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../../data/entity";
|
||||
import {
|
||||
entityComboBoxKeys,
|
||||
getEntities,
|
||||
type EntityComboBoxItem,
|
||||
} from "../../data/entity_registry";
|
||||
@@ -289,14 +288,10 @@ export class HaEntityPicker extends LitElement {
|
||||
.hideClearIcon=${this.hideClearIcon}
|
||||
.searchFn=${this._searchFn}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.searchKeys=${entityComboBoxKeys}
|
||||
@value-changed=${this._valueChanged}
|
||||
.addButtonLabel=${this.addButton
|
||||
? this.hass.localize("ui.components.entity.entity-picker.add")
|
||||
: undefined}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.entity.entity-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
|
||||
@@ -38,21 +38,9 @@ type StatisticItemType = "entity" | "external" | "no_state";
|
||||
interface StatisticComboBoxItem extends PickerComboBoxItem {
|
||||
statistic_id?: string;
|
||||
stateObj?: HassEntity;
|
||||
domainName?: string;
|
||||
type?: StatisticItemType;
|
||||
}
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "label", weight: 10 },
|
||||
{ name: "search_labels.entityName", weight: 10 },
|
||||
{ name: "search_labels.friendlyName", weight: 9 },
|
||||
{ name: "search_labels.deviceName", weight: 8 },
|
||||
{ name: "search_labels.areaName", weight: 6 },
|
||||
{ name: "search_labels.domainName", weight: 4 },
|
||||
{ name: "statisticId", weight: 3 },
|
||||
{ name: "id", weight: 2 },
|
||||
];
|
||||
|
||||
@customElement("ha-statistic-picker")
|
||||
export class HaStatisticPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -245,6 +233,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
),
|
||||
type,
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: [label, id],
|
||||
icon_path: mdiShape,
|
||||
});
|
||||
} else if (type === "external") {
|
||||
@@ -257,7 +246,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
secondary: domainName,
|
||||
type,
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: { label, domainName },
|
||||
search_labels: [label, domainName, id],
|
||||
icon_path: mdiChartLine,
|
||||
});
|
||||
}
|
||||
@@ -291,12 +280,13 @@ export class HaStatisticPicker extends LitElement {
|
||||
stateObj: stateObj,
|
||||
type: "entity",
|
||||
sorting_label: [sortingPrefix, deviceName, entityName].join("_"),
|
||||
search_labels: {
|
||||
entityName: entityName || null,
|
||||
deviceName: deviceName || null,
|
||||
areaName: areaName || null,
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
friendlyName,
|
||||
},
|
||||
id,
|
||||
].filter(Boolean) as string[],
|
||||
});
|
||||
});
|
||||
|
||||
@@ -371,13 +361,13 @@ export class HaStatisticPicker extends LitElement {
|
||||
stateObj: stateObj,
|
||||
type: "entity",
|
||||
sorting_label: [sortingPrefix, deviceName, entityName].join("_"),
|
||||
search_labels: {
|
||||
entityName: entityName || null,
|
||||
deviceName: deviceName || null,
|
||||
areaName: areaName || null,
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
friendlyName,
|
||||
statisticId,
|
||||
},
|
||||
].filter(Boolean) as string[],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -404,7 +394,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
secondary: domainName,
|
||||
type: "external",
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: { label, domainName, statisticId },
|
||||
search_labels: [label, domainName, statisticId],
|
||||
icon_path: mdiChartLine,
|
||||
};
|
||||
}
|
||||
@@ -419,7 +409,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
secondary: this.hass.localize("ui.components.statistic-picker.no_state"),
|
||||
type: "no_state",
|
||||
sorting_label: [sortingPrefix, label].join("_"),
|
||||
search_labels: { label, statisticId },
|
||||
search_labels: [label, statisticId],
|
||||
icon_path: mdiShape,
|
||||
};
|
||||
}
|
||||
@@ -485,10 +475,6 @@ export class HaStatisticPicker extends LitElement {
|
||||
.searchFn=${this._searchFn}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.helper=${this.helper}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.statistic-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mdiAlert } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
@@ -17,7 +17,6 @@ import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../data/climate";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-state-icon";
|
||||
|
||||
@customElement("state-badge")
|
||||
export class StateBadge extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@@ -266,3 +265,5 @@ declare global {
|
||||
"state-badge": StateBadge;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("state-badge", StateBadge);
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { listenMediaQuery } from "../common/dom/media_query";
|
||||
import "./ha-bottom-sheet";
|
||||
import "./ha-dialog-header";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-wa-dialog";
|
||||
import type { DialogWidth } from "./ha-wa-dialog";
|
||||
|
||||
type DialogSheetMode = "dialog" | "bottom-sheet";
|
||||
|
||||
/**
|
||||
* Home Assistant adaptive dialog component
|
||||
*
|
||||
* @element ha-adaptive-dialog
|
||||
* @extends {LitElement}
|
||||
*
|
||||
* @summary
|
||||
* A responsive dialog component that automatically switches between a full dialog (ha-wa-dialog)
|
||||
* and a bottom sheet (ha-bottom-sheet) based on screen size. Uses dialog mode on larger screens
|
||||
* (>870px width and >500px height) and bottom sheet mode on smaller screens or mobile devices.
|
||||
*
|
||||
* @slot headerNavigationIcon - Leading header action (e.g. close/back button).
|
||||
* @slot headerTitle - Custom title content (used when header-title is not set).
|
||||
* @slot headerSubtitle - Custom subtitle content (used when header-subtitle is not set).
|
||||
* @slot headerActionItems - Trailing header actions (e.g. buttons, menus).
|
||||
* @slot - Dialog/sheet content body.
|
||||
* @slot footer - Dialog/sheet footer content.
|
||||
*
|
||||
* @cssprop --ha-dialog-surface-background - Dialog/sheet background color.
|
||||
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface (dialog mode only).
|
||||
* @cssprop --ha-dialog-show-duration - Show animation duration (dialog mode only).
|
||||
* @cssprop --ha-dialog-hide-duration - Hide animation duration (dialog mode only).
|
||||
*
|
||||
* @attr {boolean} open - Controls the dialog/sheet open state.
|
||||
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset (dialog mode only). Defaults to "medium".
|
||||
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
|
||||
* @attr {string} header-subtitle - Header subtitle text. If not set, the headerSubtitle slot is used.
|
||||
* @attr {("above"|"below")} header-subtitle-position - Position of the subtitle relative to the title. Defaults to "below".
|
||||
* @attr {boolean} block-mode-change - When set, the mode is determined at mount time based on the current screen size, but subsequent mode changes are blocked. Useful for preventing forms from resetting when the viewport size changes.
|
||||
*
|
||||
* @event opened - Fired when the dialog/sheet is shown (dialog mode only).
|
||||
* @event closed - Fired after the dialog/sheet is hidden.
|
||||
* @event after-show - Fired after show animation completes (dialog mode only).
|
||||
*
|
||||
* @remarks
|
||||
* **Responsive Behavior:**
|
||||
* The component automatically switches between dialog and bottom sheet modes based on viewport size.
|
||||
* Dialog mode is used for screens wider than 870px and taller than 500px.
|
||||
* Bottom sheet mode is used for mobile devices and smaller screens.
|
||||
*
|
||||
* When `block-mode-change` is set, the mode is determined once at mount time based on the initial
|
||||
* screen size. Subsequent viewport size changes will not trigger mode switches, which is useful
|
||||
* for preventing form resets or other state loss when users resize their browser window.
|
||||
*
|
||||
* **Focus Management:**
|
||||
* To automatically focus an element when opened, add the `autofocus` attribute to it.
|
||||
* Components with `delegatesFocus: true` (like `ha-form`) will forward focus to their first focusable child.
|
||||
* Example: `<ha-form autofocus .schema=${schema}></ha-form>`
|
||||
*/
|
||||
@customElement("ha-adaptive-dialog")
|
||||
export class HaAdaptiveDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "aria-labelledby" })
|
||||
public ariaLabelledBy?: string;
|
||||
|
||||
@property({ attribute: "aria-describedby" })
|
||||
public ariaDescribedBy?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public open = false;
|
||||
|
||||
@property({ type: String, reflect: true, attribute: "width" })
|
||||
public width: DialogWidth = "medium";
|
||||
|
||||
@property({ attribute: "header-title" })
|
||||
public headerTitle?: string;
|
||||
|
||||
@property({ attribute: "header-subtitle" })
|
||||
public headerSubtitle?: string;
|
||||
|
||||
@property({ type: String, attribute: "header-subtitle-position" })
|
||||
public headerSubtitlePosition: "above" | "below" = "below";
|
||||
|
||||
@property({ type: Boolean, attribute: "block-mode-change" })
|
||||
public blockModeChange = false;
|
||||
|
||||
@state() private _mode: DialogSheetMode = "dialog";
|
||||
|
||||
private _unsubMediaQuery?: () => void;
|
||||
|
||||
private _modeSet = false;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMediaQuery = listenMediaQuery(
|
||||
"(max-width: 870px), (max-height: 500px)",
|
||||
(matches) => {
|
||||
if (!this._modeSet || !this.blockModeChange) {
|
||||
this._mode = matches ? "bottom-sheet" : "dialog";
|
||||
this._modeSet = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMediaQuery?.();
|
||||
this._unsubMediaQuery = undefined;
|
||||
this._modeSet = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this._mode === "bottom-sheet") {
|
||||
return html`
|
||||
<ha-bottom-sheet .open=${this.open} flexcontent>
|
||||
<ha-dialog-header
|
||||
slot="header"
|
||||
.subtitlePosition=${this.headerSubtitlePosition}
|
||||
>
|
||||
<slot name="headerNavigationIcon" slot="navigationIcon">
|
||||
<ha-icon-button
|
||||
data-drawer="close"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
</slot>
|
||||
${this.headerTitle !== undefined
|
||||
? html`<span slot="title" class="title" id="ha-wa-dialog-title">
|
||||
${this.headerTitle}
|
||||
</span>`
|
||||
: html`<slot name="headerTitle" slot="title"></slot>`}
|
||||
${this.headerSubtitle !== undefined
|
||||
? html`<span slot="subtitle">${this.headerSubtitle}</span>`
|
||||
: html`<slot name="headerSubtitle" slot="subtitle"></slot>`}
|
||||
<slot name="headerActionItems" slot="actionItems"></slot>
|
||||
</ha-dialog-header>
|
||||
<slot></slot>
|
||||
<slot name="footer" slot="footer"></slot>
|
||||
</ha-bottom-sheet>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this.open}
|
||||
.width=${this.width}
|
||||
.ariaLabelledBy=${this.ariaLabelledBy}
|
||||
.ariaDescribedBy=${this.ariaDescribedBy}
|
||||
.headerTitle=${this.headerTitle}
|
||||
.headerSubtitle=${this.headerSubtitle}
|
||||
.headerSubtitlePosition=${this.headerSubtitlePosition}
|
||||
flexcontent
|
||||
>
|
||||
<slot name="headerNavigationIcon" slot="headerNavigationIcon">
|
||||
<ha-icon-button
|
||||
data-dialog="close"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
</slot>
|
||||
<slot name="headerTitle" slot="headerTitle"></slot>
|
||||
<slot name="headerSubtitle" slot="headerSubtitle"></slot>
|
||||
<slot name="headerActionItems" slot="headerActionItems"></slot>
|
||||
<slot></slot>
|
||||
<slot name="footer" slot="footer"></slot>
|
||||
</ha-wa-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
css`
|
||||
ha-bottom-sheet {
|
||||
--ha-bottom-sheet-surface-background: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--card-background-color, var(--ha-color-surface-default))
|
||||
);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-adaptive-dialog": HaAdaptiveDialog;
|
||||
}
|
||||
}
|
||||
270
src/components/ha-area-floor-picker.ts
Normal file
270
src/components/ha-area-floor-picker.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { mdiTextureBox } from "@mdi/js";
|
||||
import type { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeAreaName } from "../common/entity/compute_area_name";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import {
|
||||
getAreasAndFloors,
|
||||
type AreaFloorValue,
|
||||
type FloorComboBoxItem,
|
||||
} from "../data/area_floor";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../types";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./ha-combo-box-item";
|
||||
import "./ha-floor-icon";
|
||||
import "./ha-generic-picker";
|
||||
import type { HaGenericPicker } from "./ha-generic-picker";
|
||||
import "./ha-icon-button";
|
||||
import type { PickerValueRenderer } from "./ha-picker-field";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-tree-indicator";
|
||||
|
||||
const SEPARATOR = "________";
|
||||
|
||||
@customElement("ha-area-floor-picker")
|
||||
export class HaAreaFloorPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ attribute: false }) public value?: AreaFloorValue;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: String, attribute: "search-label" })
|
||||
public searchLabel?: string;
|
||||
|
||||
/**
|
||||
* Show only areas with entities from specific domains.
|
||||
* @type {Array}
|
||||
* @attr include-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-domains" })
|
||||
public includeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show no areas with entities of these domains.
|
||||
* @type {Array}
|
||||
* @attr exclude-domains
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-domains" })
|
||||
public excludeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show only areas with entities of these device classes.
|
||||
* @type {Array}
|
||||
* @attr include-device-classes
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
/**
|
||||
* List of areas to be excluded.
|
||||
* @type {Array}
|
||||
* @attr exclude-areas
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-areas" })
|
||||
public excludeAreas?: string[];
|
||||
|
||||
/**
|
||||
* List of floors to be excluded.
|
||||
* @type {Array}
|
||||
* @attr exclude-floors
|
||||
*/
|
||||
@property({ type: Array, attribute: "exclude-floors" })
|
||||
public excludeFloors?: string[];
|
||||
|
||||
@property({ attribute: false })
|
||||
public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
|
||||
@property({ attribute: false })
|
||||
public entityFilter?: (entity: HassEntity) => boolean;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@query("ha-generic-picker") private _picker?: HaGenericPicker;
|
||||
|
||||
public async open() {
|
||||
await this.updateComplete;
|
||||
await this._picker?.open();
|
||||
}
|
||||
|
||||
private _valueRenderer: PickerValueRenderer = (value: string) => {
|
||||
const item = this._parseValue(value);
|
||||
|
||||
const area = item.type === "area" && this.hass.areas[value];
|
||||
|
||||
if (area) {
|
||||
const areaName = computeAreaName(area);
|
||||
return html`
|
||||
${area.icon
|
||||
? html`<ha-icon slot="start" .icon=${area.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiTextureBox}
|
||||
></ha-svg-icon>`}
|
||||
<slot name="headline">${areaName}</slot>
|
||||
`;
|
||||
}
|
||||
|
||||
const floor = item.type === "floor" && this.hass.floors[value];
|
||||
|
||||
if (floor) {
|
||||
const floorName = computeFloorName(floor);
|
||||
return html`
|
||||
<ha-floor-icon slot="start" .floor=${floor}></ha-floor-icon>
|
||||
<span slot="headline">${floorName}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-svg-icon slot="start" .path=${mdiTextureBox}></ha-svg-icon>
|
||||
<span slot="headline">${value}</span>
|
||||
`;
|
||||
};
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<FloorComboBoxItem> = (
|
||||
item,
|
||||
{ index },
|
||||
combobox
|
||||
) => {
|
||||
const nextItem = combobox.filteredItems?.[index + 1];
|
||||
const isLastArea =
|
||||
!nextItem ||
|
||||
nextItem.type === "floor" ||
|
||||
(nextItem.type === "area" && !nextItem.area?.floor_id);
|
||||
|
||||
const rtl = computeRTL(this.hass);
|
||||
|
||||
const hasFloor = item.type === "area" && item.area?.floor_id;
|
||||
|
||||
return html`
|
||||
<ha-combo-box-item
|
||||
type="button"
|
||||
style=${item.type === "area" && hasFloor
|
||||
? "--md-list-item-leading-space: 48px;"
|
||||
: ""}
|
||||
>
|
||||
${item.type === "area" && hasFloor
|
||||
? html`
|
||||
<ha-tree-indicator
|
||||
style=${styleMap({
|
||||
width: "48px",
|
||||
position: "absolute",
|
||||
top: "0px",
|
||||
left: rtl ? undefined : "4px",
|
||||
right: rtl ? "4px" : undefined,
|
||||
transform: rtl ? "scaleX(-1)" : "",
|
||||
})}
|
||||
.end=${isLastArea}
|
||||
slot="start"
|
||||
></ha-tree-indicator>
|
||||
`
|
||||
: nothing}
|
||||
${item.type === "floor" && item.floor
|
||||
? html`<ha-floor-icon
|
||||
slot="start"
|
||||
.floor=${item.floor}
|
||||
></ha-floor-icon>`
|
||||
: item.icon
|
||||
? html`<ha-icon slot="start" .icon=${item.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${item.icon_path || mdiTextureBox}
|
||||
></ha-svg-icon>`}
|
||||
${item.primary}
|
||||
</ha-combo-box-item>
|
||||
`;
|
||||
};
|
||||
|
||||
private _getAreasAndFloorsMemoized = memoizeOne(getAreasAndFloors);
|
||||
|
||||
private _getItems = () =>
|
||||
this._getAreasAndFloorsMemoized(
|
||||
this.hass.states,
|
||||
this.hass.floors,
|
||||
this.hass.areas,
|
||||
this.hass.devices,
|
||||
this.hass.entities,
|
||||
this._formatValue,
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.includeDeviceClasses,
|
||||
this.deviceFilter,
|
||||
this.entityFilter,
|
||||
this.excludeAreas,
|
||||
this.excludeFloors
|
||||
);
|
||||
|
||||
private _formatValue = memoizeOne((value: AreaFloorValue): string =>
|
||||
[value.type, value.id].join(SEPARATOR)
|
||||
);
|
||||
|
||||
private _parseValue = memoizeOne((value: string): AreaFloorValue => {
|
||||
const [type, id] = value.split(SEPARATOR);
|
||||
|
||||
return { id, type: type as "floor" | "area" };
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const placeholder =
|
||||
this.placeholder ?? this.hass.localize("ui.components.area-picker.area");
|
||||
|
||||
const value = this.value ? this._formatValue(this.value) : undefined;
|
||||
|
||||
return html`
|
||||
<ha-generic-picker
|
||||
.hass=${this.hass}
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label}
|
||||
.searchLabel=${this.searchLabel}
|
||||
.notFoundLabel=${this.hass.localize(
|
||||
"ui.components.area-picker.no_match"
|
||||
)}
|
||||
.placeholder=${placeholder}
|
||||
.value=${value}
|
||||
.getItems=${this._getItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: ValueChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value;
|
||||
|
||||
if (!value) {
|
||||
this._setValue(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const selected = this._parseValue(value);
|
||||
this._setValue(selected);
|
||||
}
|
||||
|
||||
private _setValue(value?: AreaFloorValue) {
|
||||
this.value = value;
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "change");
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-area-floor-picker": HaAreaFloorPicker;
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,6 @@ import "./ha-svg-icon";
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "areaName", weight: 10 },
|
||||
{ name: "aliases", weight: 8 },
|
||||
{ name: "floorName", weight: 6 },
|
||||
{ name: "id", weight: 3 },
|
||||
];
|
||||
@customElement("ha-area-picker")
|
||||
export class HaAreaPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -297,12 +291,12 @@ export class HaAreaPicker extends LitElement {
|
||||
icon: area.icon || undefined,
|
||||
icon_path: area.icon ? undefined : mdiTextureBox,
|
||||
sorting_label: areaName,
|
||||
search_labels: {
|
||||
areaName: areaName || null,
|
||||
floorName: floorName || null,
|
||||
id: area.area_id,
|
||||
aliases: area.aliases.join(" "),
|
||||
},
|
||||
search_labels: [
|
||||
areaName,
|
||||
floorName,
|
||||
area.area_id,
|
||||
...area.aliases,
|
||||
].filter((v): v is string => Boolean(v)),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -385,10 +379,6 @@ export class HaAreaPicker extends LitElement {
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.addButtonLabel=${this.addButtonLabel}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.area-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -134,7 +134,6 @@ export class HaAssistChat extends LitElement {
|
||||
})}"
|
||||
breaks
|
||||
cache
|
||||
assist
|
||||
.content=${message.text}
|
||||
>
|
||||
</ha-markdown>
|
||||
|
||||
@@ -2,13 +2,12 @@ import "@home-assistant/webawesome/dist/components/drawer/drawer";
|
||||
import { css, html, LitElement, type PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { SwipeGestureRecognizer } from "../common/util/swipe-gesture-recognizer";
|
||||
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
|
||||
export const BOTTOM_SHEET_ANIMATION_DURATION_MS = 300;
|
||||
|
||||
@customElement("ha-bottom-sheet")
|
||||
export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
export class HaBottomSheet extends LitElement {
|
||||
@property({ type: Boolean }) public open = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "flexcontent" })
|
||||
@@ -18,12 +17,6 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@query("#drawer") private _drawer!: HTMLElement;
|
||||
|
||||
@query("#body") private _bodyElement!: HTMLDivElement;
|
||||
|
||||
protected get scrollableElement(): HTMLElement | null {
|
||||
return this._bodyElement;
|
||||
}
|
||||
|
||||
private _gestureRecognizer = new SwipeGestureRecognizer();
|
||||
|
||||
private _isDragging = false;
|
||||
@@ -56,13 +49,9 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
@touchstart=${this._handleTouchStart}
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
<div class="content-wrapper">
|
||||
<div id="body" class="body ha-scrollbar">
|
||||
<slot></slot>
|
||||
</div>
|
||||
${this.renderScrollableFades()}
|
||||
<div id="body" class="body ha-scrollbar">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<slot name="footer"></slot>
|
||||
</wa-drawer>
|
||||
`;
|
||||
}
|
||||
@@ -178,87 +167,60 @@ export class HaBottomSheet extends ScrollableFadeMixin(LitElement) {
|
||||
this._isDragging = false;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...super.styles,
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
wa-drawer {
|
||||
--wa-color-surface-raised: transparent;
|
||||
--spacing: 0;
|
||||
--size: var(--ha-bottom-sheet-height, auto);
|
||||
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
}
|
||||
wa-drawer::part(dialog) {
|
||||
max-height: var(--ha-bottom-sheet-max-height, 90vh);
|
||||
align-items: center;
|
||||
transform: var(--dialog-transform);
|
||||
transition: var(--dialog-transition);
|
||||
}
|
||||
wa-drawer::part(body) {
|
||||
max-width: var(--ha-bottom-sheet-max-width);
|
||||
width: 100%;
|
||||
border-top-left-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
background-color: var(
|
||||
--ha-bottom-sheet-surface-background,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
:host([flexcontent]) wa-drawer::part(body) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
:host([flexcontent]) .body {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
slot[name="footer"] {
|
||||
display: block;
|
||||
padding: var(--ha-space-0);
|
||||
}
|
||||
::slotted([slot="footer"]) {
|
||||
display: flex;
|
||||
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
|
||||
var(--ha-space-4);
|
||||
gap: var(--ha-space-3);
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host([flexcontent]) slot[name="footer"] {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
static styles = [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
wa-drawer {
|
||||
--wa-color-surface-raised: transparent;
|
||||
--spacing: 0;
|
||||
--size: var(--ha-bottom-sheet-height, auto);
|
||||
--show-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
}
|
||||
wa-drawer::part(dialog) {
|
||||
max-height: var(--ha-bottom-sheet-max-height, 90vh);
|
||||
align-items: center;
|
||||
transform: var(--dialog-transform);
|
||||
transition: var(--dialog-transition);
|
||||
}
|
||||
wa-drawer::part(body) {
|
||||
max-width: var(--ha-bottom-sheet-max-width);
|
||||
width: 100%;
|
||||
border-top-left-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
background-color: var(
|
||||
--ha-bottom-sheet-surface-background,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
:host([flexcontent]) wa-drawer::part(body) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
:host([flexcontent]) .body {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(
|
||||
--ha-bottom-sheet-padding,
|
||||
0 var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -35,12 +35,6 @@ import "./ha-svg-icon";
|
||||
|
||||
const ADD_NEW_ID = "___ADD_NEW___";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "floorName", weight: 10 },
|
||||
{ name: "aliases", weight: 8 },
|
||||
{ name: "floor_id", weight: 3 },
|
||||
];
|
||||
|
||||
interface FloorComboBoxItem extends PickerComboBoxItem {
|
||||
floor?: FloorRegistryEntry;
|
||||
}
|
||||
@@ -292,11 +286,9 @@ export class HaFloorPicker extends LitElement {
|
||||
primary: floorName,
|
||||
floor: floor,
|
||||
sorting_label: floor.level?.toString() || "zzzzz",
|
||||
search_labels: {
|
||||
floorName,
|
||||
floor_id: floor.floor_id,
|
||||
aliases: floor.aliases.join(" "),
|
||||
},
|
||||
search_labels: [floorName, floor.floor_id, ...floor.aliases].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -401,10 +393,6 @@ export class HaFloorPicker extends LitElement {
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.floor-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -4,10 +4,8 @@ import { mdiPlaylistPlus } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing, type CSSResultGroup } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-bottom-sheet";
|
||||
import "./ha-button";
|
||||
@@ -48,9 +46,8 @@ export class HaGenericPicker extends LitElement {
|
||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||
public hideClearIcon = false;
|
||||
|
||||
/** To prevent lags, getItems needs to be memoized */
|
||||
@property({ attribute: false })
|
||||
public getItems!: (
|
||||
public getItems?: (
|
||||
searchString?: string,
|
||||
section?: string
|
||||
) => (PickerComboBoxItem | string)[];
|
||||
@@ -67,9 +64,6 @@ export class HaGenericPicker extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public searchFn?: PickerComboBoxSearchFn<PickerComboBoxItem>;
|
||||
|
||||
@property({ attribute: false })
|
||||
public searchKeys?: FuseWeightedKey[];
|
||||
|
||||
@property({ attribute: false })
|
||||
public notFoundLabel?: string | ((search: string) => string);
|
||||
|
||||
@@ -113,8 +107,6 @@ export class HaGenericPicker extends LitElement {
|
||||
|
||||
@property({ attribute: "selected-section" }) public selectedSection?: string;
|
||||
|
||||
@property({ attribute: "unknown-item-text" }) public unknownItemText?: string;
|
||||
|
||||
@query(".container") private _containerElement?: HTMLDivElement;
|
||||
|
||||
@query("ha-picker-combo-box") private _comboBox?: HaPickerComboBox;
|
||||
@@ -164,8 +156,6 @@ export class HaGenericPicker extends LitElement {
|
||||
type="button"
|
||||
class=${this._opened ? "opened" : ""}
|
||||
compact
|
||||
.unknown=${this._unknownValue(this.value, this.getItems())}
|
||||
.unknownItemText=${this.unknownItemText}
|
||||
aria-label=${ifDefined(this.label)}
|
||||
@click=${this.open}
|
||||
@clear=${this._clear}
|
||||
@@ -239,23 +229,10 @@ export class HaGenericPicker extends LitElement {
|
||||
.sections=${this.sections}
|
||||
.sectionTitleFunction=${this.sectionTitleFunction}
|
||||
.selectedSection=${this.selectedSection}
|
||||
.searchKeys=${this.searchKeys}
|
||||
></ha-picker-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private _unknownValue = memoizeOne(
|
||||
(value?: string, items?: (PickerComboBoxItem | string)[]) => {
|
||||
if (value === undefined || !items) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !items.some(
|
||||
(item) => typeof item !== "string" && item.id === value
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
private _renderHelper() {
|
||||
return this.helper
|
||||
? html`<ha-input-helper-text .disabled=${this.disabled}
|
||||
|
||||
@@ -32,12 +32,6 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public step = 1;
|
||||
|
||||
@property({ type: Boolean, attribute: "rows-disabled" })
|
||||
public rowsDisabled?: boolean;
|
||||
|
||||
@property({ type: Boolean, attribute: "columns-disabled" })
|
||||
public columnsDisabled?: boolean;
|
||||
|
||||
@state() public _localValue?: CardGridSize = { rows: 1, columns: 1 };
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
@@ -48,11 +42,9 @@ export class HaGridSizeEditor extends LitElement {
|
||||
|
||||
protected render() {
|
||||
const disabledColumns =
|
||||
this.columnsDisabled ||
|
||||
(this.columnMin !== undefined && this.columnMin === this.columnMax);
|
||||
this.columnMin !== undefined && this.columnMin === this.columnMax;
|
||||
const disabledRows =
|
||||
this.rowsDisabled ||
|
||||
(this.rowMin !== undefined && this.rowMin === this.rowMax);
|
||||
this.rowMin !== undefined && this.rowMin === this.rowMax;
|
||||
|
||||
const autoHeight = this._localValue?.rows === "auto";
|
||||
const fullWidth = this._localValue?.columns === "full";
|
||||
@@ -80,7 +72,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
.disabled=${disabledColumns}
|
||||
tooltip-mode=${disabledColumns ? "never" : "always"}
|
||||
tooltip-mode="always"
|
||||
></ha-grid-layout-slider>
|
||||
|
||||
<ha-grid-layout-slider
|
||||
@@ -96,7 +88,7 @@ export class HaGridSizeEditor extends LitElement {
|
||||
@value-changed=${this._valueChanged}
|
||||
@slider-moved=${this._sliderMoved}
|
||||
.disabled=${disabledRows}
|
||||
tooltip-mode=${disabledRows ? "never" : "always"}
|
||||
tooltip-mode="always"
|
||||
></ha-grid-layout-slider>
|
||||
${!this.isDefault
|
||||
? html`
|
||||
|
||||
@@ -15,7 +15,6 @@ import type { LabelRegistryEntry } from "../data/label_registry";
|
||||
import {
|
||||
createLabelRegistryEntry,
|
||||
getLabels,
|
||||
labelComboBoxKeys,
|
||||
subscribeLabelRegistry,
|
||||
} from "../data/label_registry";
|
||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
@@ -238,7 +237,6 @@ export class HaLabelPicker extends SubscribeMixin(LitElement) {
|
||||
.getItems=${this._getItems}
|
||||
.getAdditionalItems=${this._getAdditionalItems}
|
||||
.valueRenderer=${valueRenderer}
|
||||
.searchKeys=${labelComboBoxKeys}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<slot .slot=${this._slotNodes?.length ? "field" : undefined}></slot>
|
||||
|
||||
@@ -40,12 +40,14 @@ export const getLanguageOptions = (
|
||||
return {
|
||||
id: lang,
|
||||
primary,
|
||||
search_labels: [primary],
|
||||
};
|
||||
});
|
||||
} else if (locale) {
|
||||
options = languages.map((lang) => ({
|
||||
id: lang,
|
||||
primary: formatLanguageCode(lang, locale),
|
||||
search_labels: [formatLanguageCode(lang, locale)],
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -70,15 +70,13 @@ export class HaMarkdown extends LitElement {
|
||||
a {
|
||||
color: var(--markdown-link-color, var(--primary-color));
|
||||
}
|
||||
:host([assist]) img {
|
||||
height: auto;
|
||||
width: auto;
|
||||
transition: height 0.2s ease-in-out;
|
||||
}
|
||||
img {
|
||||
background-color: var(--markdown-image-background-color);
|
||||
border-radius: var(--markdown-image-border-radius);
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
width: auto;
|
||||
transition: height 0.2s ease-in-out;
|
||||
}
|
||||
p:first-child > img:first-child {
|
||||
vertical-align: top;
|
||||
|
||||
@@ -14,11 +14,7 @@ import memoizeOne from "memoize-one";
|
||||
import { tinykeys } from "tinykeys";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
|
||||
import {
|
||||
multiTermSortedSearch,
|
||||
type FuseWeightedKey,
|
||||
} from "../resources/fuseMultiTerm";
|
||||
import { HaFuse } from "../resources/fuse";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import { loadVirtualizer } from "../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../types";
|
||||
@@ -29,26 +25,11 @@ import "./ha-icon";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
|
||||
export const DEFAULT_SEARCH_KEYS: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "primary",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "secondary",
|
||||
weight: 7,
|
||||
},
|
||||
{
|
||||
name: "id",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export interface PickerComboBoxItem {
|
||||
id: string;
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
search_labels?: Record<string, string | null>;
|
||||
search_labels?: string[];
|
||||
sorting_label?: string;
|
||||
icon_path?: string;
|
||||
icon?: string;
|
||||
@@ -78,7 +59,7 @@ export type PickerComboBoxSearchFn<T extends PickerComboBoxItem> = (
|
||||
) => T[];
|
||||
|
||||
@customElement("ha-picker-combo-box")
|
||||
export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
export class HaPickerComboBox extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
// eslint-disable-next-line lit/no-native-attributes
|
||||
@@ -95,13 +76,10 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property({ attribute: false })
|
||||
public searchKeys?: FuseWeightedKey[];
|
||||
|
||||
@state() private _listScrolled = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
public getItems!: (
|
||||
public getItems?: (
|
||||
searchString?: string,
|
||||
section?: string
|
||||
) => (PickerComboBoxItem | string)[];
|
||||
@@ -148,14 +126,8 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@state() private _items: (PickerComboBoxItem | string)[] = [];
|
||||
|
||||
protected get scrollableElement(): HTMLElement | null {
|
||||
return this._virtualizerElement as HTMLElement | null;
|
||||
}
|
||||
|
||||
@state() private _sectionTitle?: string;
|
||||
|
||||
@state() private _valuePinned = true;
|
||||
|
||||
private _allItems: (PickerComboBoxItem | string)[] = [];
|
||||
|
||||
private _selectedItemIndex = -1;
|
||||
@@ -208,31 +180,19 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
<div class="virtualizer-wrapper">
|
||||
<lit-virtualizer
|
||||
.keyFunction=${this._keyFunction}
|
||||
tabindex="0"
|
||||
scroller
|
||||
.items=${this._items}
|
||||
.renderItem=${this._renderItem}
|
||||
style="min-height: 36px;"
|
||||
class=${this._listScrolled ? "scrolled" : ""}
|
||||
.layout=${this.value && this._valuePinned
|
||||
? {
|
||||
pin: {
|
||||
index: this._getInitialSelectedIndex(),
|
||||
block: "center",
|
||||
},
|
||||
}
|
||||
: undefined}
|
||||
@unpinned=${this._handleUnpinned}
|
||||
@scroll=${this._onScrollList}
|
||||
@focus=${this._focusList}
|
||||
@visibilityChanged=${this._visibilityChanged}
|
||||
>
|
||||
</lit-virtualizer>
|
||||
${this.renderScrollableFades()}
|
||||
</div>`;
|
||||
<lit-virtualizer
|
||||
.keyFunction=${this._keyFunction}
|
||||
tabindex="0"
|
||||
scroller
|
||||
.items=${this._items}
|
||||
.renderItem=${this._renderItem}
|
||||
style="min-height: 36px;"
|
||||
class=${this._listScrolled ? "scrolled" : ""}
|
||||
@scroll=${this._onScrollList}
|
||||
@focus=${this._focusList}
|
||||
@visibilityChanged=${this._visibilityChanged}
|
||||
>
|
||||
</lit-virtualizer>`;
|
||||
}
|
||||
|
||||
private _renderSectionButtons() {
|
||||
@@ -276,16 +236,15 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _handleUnpinned() {
|
||||
this._valuePinned = false;
|
||||
}
|
||||
|
||||
private _getAdditionalItems = (searchString?: string) =>
|
||||
this.getAdditionalItems?.(searchString) || [];
|
||||
|
||||
private _getItems = () => {
|
||||
let items = [...this.getItems(this._search, this.selectedSection)];
|
||||
let items = [
|
||||
...(this.getItems
|
||||
? this.getItems(this._search, this.selectedSection)
|
||||
: []),
|
||||
];
|
||||
|
||||
if (!this.sections?.length) {
|
||||
items = items.sort((entityA, entityB) =>
|
||||
@@ -312,9 +271,6 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
};
|
||||
|
||||
private _renderItem = (item: PickerComboBoxItem | string, index: number) => {
|
||||
if (!item) {
|
||||
return nothing;
|
||||
}
|
||||
if (item === "padding") {
|
||||
return html`<div class="bottom-padding"></div>`;
|
||||
}
|
||||
@@ -375,9 +331,8 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
};
|
||||
|
||||
private _fuseIndex = memoizeOne(
|
||||
(states: PickerComboBoxItem[], searchKeys?: FuseWeightedKey[]) =>
|
||||
Fuse.createIndex(searchKeys || DEFAULT_SEARCH_KEYS, states)
|
||||
private _fuseIndex = memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
Fuse.createIndex(["search_labels"], states)
|
||||
);
|
||||
|
||||
private _filterChanged = (ev: Event) => {
|
||||
@@ -393,26 +348,34 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = this._fuseIndex(
|
||||
const index = this._fuseIndex(this._allItems as PickerComboBoxItem[]);
|
||||
const fuse = new HaFuse(
|
||||
this._allItems as PickerComboBoxItem[],
|
||||
this.searchKeys
|
||||
{
|
||||
shouldSort: false,
|
||||
minMatchCharLength: Math.min(searchString.length, 2),
|
||||
},
|
||||
index
|
||||
);
|
||||
|
||||
let filteredItems = multiTermSortedSearch<PickerComboBoxItem>(
|
||||
this._allItems as PickerComboBoxItem[],
|
||||
searchString,
|
||||
this.searchKeys || DEFAULT_SEARCH_KEYS,
|
||||
(item) => item.id,
|
||||
index
|
||||
) as (PickerComboBoxItem | string)[];
|
||||
const results = fuse.multiTermsSearch(searchString);
|
||||
let filteredItems = [...this._allItems];
|
||||
|
||||
if (!filteredItems.length) {
|
||||
filteredItems.push(NO_ITEMS_AVAILABLE_ID);
|
||||
if (results) {
|
||||
const items: (PickerComboBoxItem | string)[] = results.map(
|
||||
(result) => result.item
|
||||
);
|
||||
|
||||
if (!items.length) {
|
||||
filteredItems.push(NO_ITEMS_AVAILABLE_ID);
|
||||
}
|
||||
|
||||
const additionalItems = this._getAdditionalItems();
|
||||
items.push(...additionalItems);
|
||||
|
||||
filteredItems = items;
|
||||
}
|
||||
|
||||
const additionalItems = this._getAdditionalItems();
|
||||
filteredItems.push(...additionalItems);
|
||||
|
||||
if (this.searchFn) {
|
||||
filteredItems = this.searchFn(
|
||||
searchString,
|
||||
@@ -619,187 +582,158 @@ export class HaPickerComboBox extends ScrollableFadeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _keyFunction = (item: PickerComboBoxItem | string) =>
|
||||
typeof item === "string" ? item : item?.id;
|
||||
typeof item === "string" ? item : item.id;
|
||||
|
||||
private _getInitialSelectedIndex() {
|
||||
if (!this._virtualizerElement || !this.value) {
|
||||
return 0;
|
||||
}
|
||||
static styles = [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: var(--ha-space-3);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
const index = this._virtualizerElement.items.findIndex(
|
||||
(item) =>
|
||||
typeof item !== "string" &&
|
||||
(item as PickerComboBoxItem).id === this.value
|
||||
);
|
||||
ha-textfield {
|
||||
padding: 0 var(--ha-space-3);
|
||||
margin-bottom: var(--ha-space-3);
|
||||
}
|
||||
|
||||
if (index === -1) {
|
||||
return 0;
|
||||
}
|
||||
:host([mode="dialog"]) ha-textfield {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
ha-combo-box-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...super.styles,
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: var(--ha-space-3);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
ha-textfield {
|
||||
padding: 0 var(--ha-space-3);
|
||||
margin-bottom: var(--ha-space-3);
|
||||
}
|
||||
|
||||
:host([mode="dialog"]) ha-textfield {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
ha-combo-box-item {
|
||||
width: 100%;
|
||||
}
|
||||
ha-combo-box-item.selected {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-hover);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
ha-combo-box-item.selected {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-hover);
|
||||
background-color: var(--ha-color-fill-neutral-normal-hover);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
ha-combo-box-item.selected {
|
||||
background-color: var(--ha-color-fill-neutral-normal-hover);
|
||||
}
|
||||
}
|
||||
lit-virtualizer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.virtualizer-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
lit-virtualizer:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
lit-virtualizer {
|
||||
flex: 1;
|
||||
}
|
||||
lit-virtualizer.scrolled {
|
||||
border-top: 1px solid var(--ha-color-border-neutral-quiet);
|
||||
}
|
||||
|
||||
lit-virtualizer:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
.bottom-padding {
|
||||
height: max(var(--safe-area-inset-bottom, 0px), var(--ha-space-8));
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
lit-virtualizer.scrolled {
|
||||
border-top: 1px solid var(--ha-color-border-neutral-quiet);
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bottom-padding {
|
||||
height: max(var(--safe-area-inset-bottom, 0px), var(--ha-space-8));
|
||||
width: 100%;
|
||||
}
|
||||
.combo-box-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
min-height: 36px;
|
||||
}
|
||||
.combo-box-row.current-value {
|
||||
background-color: var(--ha-color-fill-primary-quiet-resting);
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.combo-box-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
min-height: 36px;
|
||||
}
|
||||
.combo-box-row.current-value {
|
||||
background-color: var(--ha-color-fill-primary-quiet-resting);
|
||||
}
|
||||
.combo-box-row.selected {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-hover);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.combo-box-row.selected {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-hover);
|
||||
background-color: var(--ha-color-fill-neutral-normal-hover);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.combo-box-row.selected {
|
||||
background-color: var(--ha-color-fill-neutral-normal-hover);
|
||||
}
|
||||
}
|
||||
.sections {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--ha-space-2);
|
||||
padding: var(--ha-space-3) var(--ha-space-3);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.sections {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--ha-space-2);
|
||||
padding: var(--ha-space-3) var(--ha-space-3);
|
||||
overflow: auto;
|
||||
}
|
||||
:host([mode="dialog"]) .sections {
|
||||
padding: var(--ha-space-3) var(--ha-space-4);
|
||||
}
|
||||
|
||||
:host([mode="dialog"]) .sections {
|
||||
padding: var(--ha-space-3) var(--ha-space-4);
|
||||
}
|
||||
.sections ha-filter-chip {
|
||||
flex-shrink: 0;
|
||||
--md-filter-chip-selected-container-color: var(
|
||||
--ha-color-fill-primary-normal-hover
|
||||
);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.sections ha-filter-chip {
|
||||
flex-shrink: 0;
|
||||
--md-filter-chip-selected-container-color: var(
|
||||
--ha-color-fill-primary-normal-hover
|
||||
);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.sections .separator {
|
||||
height: var(--ha-space-8);
|
||||
width: 0;
|
||||
border: 1px solid var(--ha-color-border-neutral-quiet);
|
||||
}
|
||||
|
||||
.sections .separator {
|
||||
height: var(--ha-space-8);
|
||||
width: 0;
|
||||
border: 1px solid var(--ha-color-border-neutral-quiet);
|
||||
}
|
||||
.section-title,
|
||||
.title {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
padding: var(--ha-space-1) var(--ha-space-2);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--secondary-text-color);
|
||||
min-height: var(--ha-space-6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section-title,
|
||||
.title {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
padding: var(--ha-space-1) var(--ha-space-2);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--secondary-text-color);
|
||||
min-height: var(--ha-space-6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 100%;
|
||||
}
|
||||
:host([mode="dialog"]) .title {
|
||||
padding: var(--ha-space-1) var(--ha-space-4);
|
||||
}
|
||||
|
||||
:host([mode="dialog"]) .title {
|
||||
padding: var(--ha-space-1) var(--ha-space-4);
|
||||
}
|
||||
:host([mode="dialog"]) ha-textfield {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
:host([mode="dialog"]) ha-textfield {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
.section-title-wrapper {
|
||||
height: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section-title-wrapper {
|
||||
height: 0;
|
||||
position: relative;
|
||||
}
|
||||
.section-title {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
width: calc(100% - var(--ha-space-8));
|
||||
}
|
||||
|
||||
.section-title {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
width: calc(100% - var(--ha-space-8));
|
||||
}
|
||||
.section-title.show {
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.section-title.show {
|
||||
opacity: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.empty-search {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--ha-space-3);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
.empty-search {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--ha-space-3);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { consume } from "@lit/context";
|
||||
import { mdiClose, mdiMenuDown } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
@@ -8,10 +7,8 @@ import {
|
||||
type CSSResultGroup,
|
||||
type TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { localizeContext } from "../data/context";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-combo-box-item";
|
||||
import type { HaComboBoxItem } from "./ha-combo-box-item";
|
||||
import "./ha-icon-button";
|
||||
@@ -36,10 +33,6 @@ export class HaPickerField extends LitElement {
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public unknown = false;
|
||||
|
||||
@property({ attribute: "unknown-item-text" }) public unknownItemText?: string;
|
||||
|
||||
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||
public hideClearIcon = false;
|
||||
|
||||
@@ -48,10 +41,6 @@ export class HaPickerField extends LitElement {
|
||||
|
||||
@query("ha-combo-box-item", true) public item!: HaComboBoxItem;
|
||||
|
||||
@state()
|
||||
@consume({ context: localizeContext, subscribe: true })
|
||||
private localize!: HomeAssistant["localize"];
|
||||
|
||||
public async focus() {
|
||||
await this.updateComplete;
|
||||
await this.item?.focus();
|
||||
@@ -72,12 +61,6 @@ export class HaPickerField extends LitElement {
|
||||
${this.placeholder}
|
||||
</span>
|
||||
`}
|
||||
${this.unknown
|
||||
? html`<div slot="supporting-text" class="unknown">
|
||||
${this.unknownItemText ||
|
||||
this.localize("ui.components.combo-box.unknown_item")}
|
||||
</div>`
|
||||
: nothing}
|
||||
${showClearIcon
|
||||
? html`
|
||||
<ha-icon-button
|
||||
@@ -120,8 +103,8 @@ export class HaPickerField extends LitElement {
|
||||
--md-list-item-two-line-container-height: 56px;
|
||||
--md-list-item-top-space: 0px;
|
||||
--md-list-item-bottom-space: 0px;
|
||||
--md-list-item-leading-space: var(--ha-space-4);
|
||||
--md-list-item-trailing-space: var(--ha-space-2);
|
||||
--md-list-item-leading-space: 8px;
|
||||
--md-list-item-trailing-space: 8px;
|
||||
--ha-md-list-item-gap: var(--ha-space-2);
|
||||
/* Remove the default focus ring */
|
||||
--md-focus-ring-width: 0px;
|
||||
@@ -159,10 +142,6 @@ export class HaPickerField extends LitElement {
|
||||
background-color: var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
:host([unknown]) ha-combo-box-item {
|
||||
background-color: var(--ha-color-fill-warning-quiet-resting);
|
||||
}
|
||||
|
||||
.clear {
|
||||
margin: 0 -8px;
|
||||
--mdc-icon-button-size: 32px;
|
||||
@@ -177,10 +156,6 @@ export class HaPickerField extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.unknown {
|
||||
color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { prepareZXingModule } from "barcode-detector";
|
||||
import type QrScanner from "qr-scanner";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { addExternalBarCodeListener } from "../external_app/external_app_entrypoint";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-alert";
|
||||
import "./ha-button";
|
||||
import "./ha-dropdown";
|
||||
import "./ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "./ha-dropdown-item";
|
||||
import "./ha-button-menu";
|
||||
import "./ha-list-item";
|
||||
import "./ha-spinner";
|
||||
import "./ha-textfield";
|
||||
import type { HaTextField } from "./ha-textfield";
|
||||
@@ -52,8 +52,6 @@ class HaQrScanner extends LitElement {
|
||||
|
||||
@state() private _warning?: string;
|
||||
|
||||
@state() private _selectedCamera?: string;
|
||||
|
||||
private _qrScanner?: QrScanner;
|
||||
|
||||
private _qrNotFoundCount = 0;
|
||||
@@ -123,7 +121,7 @@ class HaQrScanner extends LitElement {
|
||||
!this._error &&
|
||||
this._cameras &&
|
||||
this._cameras.length > 1
|
||||
? html`<ha-dropdown @wa-select=${this._handleDropdownSelect}>
|
||||
? html`<ha-button-menu fixed @closed=${stopPropagation}>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize(
|
||||
@@ -133,17 +131,15 @@ class HaQrScanner extends LitElement {
|
||||
></ha-icon-button>
|
||||
${this._cameras!.map(
|
||||
(camera) => html`
|
||||
<ha-dropdown-item
|
||||
<ha-list-item
|
||||
.value=${camera.id}
|
||||
class=${this._selectedCamera === camera.id
|
||||
? "selected"
|
||||
: ""}
|
||||
@click=${this._cameraChanged}
|
||||
>
|
||||
${camera.label}
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-dropdown>`
|
||||
</ha-button-menu>`
|
||||
: nothing}
|
||||
</div>`
|
||||
: html`<ha-alert alert-type="warning">
|
||||
@@ -209,9 +205,6 @@ class HaQrScanner extends LitElement {
|
||||
|
||||
private async _listCameras(qrScanner: typeof QrScanner): Promise<void> {
|
||||
this._cameras = await qrScanner.listCameras(true);
|
||||
if (this._cameras.length > 0) {
|
||||
this._selectedCamera = this._cameras[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
private _qrCodeError = (err: any) => {
|
||||
@@ -259,12 +252,8 @@ class HaQrScanner extends LitElement {
|
||||
this._qrCodeScanned(this._manualInput!.value);
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const cameraId = ev.detail?.item?.value;
|
||||
if (cameraId) {
|
||||
this._selectedCamera = cameraId;
|
||||
this._qrScanner?.setCamera(cameraId);
|
||||
}
|
||||
private _cameraChanged(ev: CustomEvent): void {
|
||||
this._qrScanner?.setCamera((ev.target as any).value);
|
||||
}
|
||||
|
||||
private _openExternalScanner() {
|
||||
@@ -370,7 +359,7 @@ class HaQrScanner extends LitElement {
|
||||
#canvas-container {
|
||||
position: relative;
|
||||
}
|
||||
ha-icon-button {
|
||||
ha-button-menu {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
@@ -380,9 +369,6 @@ class HaQrScanner extends LitElement {
|
||||
color: white;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
}
|
||||
ha-dropdown-item.selected {
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -450,7 +450,7 @@ export class HaServiceControl extends LitElement {
|
||||
|
||||
const hasOptional = Boolean(
|
||||
!shouldRenderServiceDataYaml &&
|
||||
serviceData?.flatFields.some((field) => showOptionalToggle(field))
|
||||
serviceData?.flatFields.some((field) => showOptionalToggle(field))
|
||||
);
|
||||
|
||||
const targetEntities = this._getTargetedEntities(
|
||||
|
||||
@@ -21,13 +21,6 @@ interface ServiceComboBoxItem extends PickerComboBoxItem {
|
||||
service_id?: string;
|
||||
}
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "name", weight: 10 },
|
||||
{ name: "description", weight: 8 },
|
||||
{ name: "domainName", weight: 6 },
|
||||
{ name: "serviceId", weight: 3 },
|
||||
];
|
||||
|
||||
@customElement("ha-service-picker")
|
||||
class HaServicePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -148,10 +141,6 @@ class HaServicePicker extends LitElement {
|
||||
this.hass.localize,
|
||||
this.hass.services
|
||||
)}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.service-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
@@ -205,7 +194,9 @@ class HaServicePicker extends LitElement {
|
||||
secondary: description,
|
||||
domain_name: domainName,
|
||||
service_id: serviceId,
|
||||
search_labels: { serviceId, domainName, name, description },
|
||||
search_labels: [serviceId, domainName, name, description].filter(
|
||||
Boolean
|
||||
),
|
||||
sorting_label: serviceId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,30 +15,17 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { isValidEntityId } from "../common/entity/valid_entity_id";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import {
|
||||
areaFloorComboBoxKeys,
|
||||
getAreasAndFloors,
|
||||
type AreaFloorValue,
|
||||
type FloorComboBoxItem,
|
||||
} from "../data/area_floor";
|
||||
import { getConfigEntries, type ConfigEntry } from "../data/config_entries";
|
||||
import { labelsContext } from "../data/context";
|
||||
import {
|
||||
deviceComboBoxKeys,
|
||||
getDevices,
|
||||
type DevicePickerItem,
|
||||
} from "../data/device_registry";
|
||||
import { getDevices, type DevicePickerItem } from "../data/device_registry";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "../data/entity";
|
||||
import {
|
||||
entityComboBoxKeys,
|
||||
getEntities,
|
||||
type EntityComboBoxItem,
|
||||
} from "../data/entity_registry";
|
||||
import { getEntities, type EntityComboBoxItem } from "../data/entity_registry";
|
||||
import { domainToName } from "../data/integration";
|
||||
import {
|
||||
getLabels,
|
||||
labelComboBoxKeys,
|
||||
type LabelRegistryEntry,
|
||||
} from "../data/label_registry";
|
||||
import { getLabels, type LabelRegistryEntry } from "../data/label_registry";
|
||||
import {
|
||||
areaMeetsFilter,
|
||||
deviceMeetsFilter,
|
||||
@@ -50,11 +37,7 @@ import {
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import { isHelperDomain } from "../panels/config/helpers/const";
|
||||
import { showHelperDetailDialog } from "../panels/config/helpers/show-dialog-helper-detail";
|
||||
import {
|
||||
multiTermSearch,
|
||||
multiTermSortedSearch,
|
||||
type FuseWeightedKey,
|
||||
} from "../resources/fuseMultiTerm";
|
||||
import { HaFuse } from "../resources/fuse";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { brandsUrl } from "../util/brands-url";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
@@ -130,16 +113,16 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _fuseIndexes = {
|
||||
area: memoizeOne((states: FloorComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, areaFloorComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
entity: memoizeOne((states: EntityComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, entityComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
device: memoizeOne((states: DevicePickerItem[]) =>
|
||||
this._createFuseIndex(states, deviceComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
label: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, labelComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
};
|
||||
|
||||
@@ -151,8 +134,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _createFuseIndex = (states, keys: FuseWeightedKey[]) =>
|
||||
Fuse.createIndex(keys, states);
|
||||
private _createFuseIndex = (states) =>
|
||||
Fuse.createIndex(["search_labels"], states);
|
||||
|
||||
protected render() {
|
||||
if (this.addOnTop) {
|
||||
@@ -752,7 +735,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
"entity",
|
||||
entityItems,
|
||||
searchTerm,
|
||||
entityComboBoxKeys
|
||||
(item: EntityComboBoxItem) =>
|
||||
item.stateObj?.entity_id === searchTerm
|
||||
) as EntityComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -781,12 +765,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
deviceItems = this._filterGroup(
|
||||
"device",
|
||||
deviceItems,
|
||||
searchTerm,
|
||||
deviceComboBoxKeys
|
||||
);
|
||||
deviceItems = this._filterGroup("device", deviceItems, searchTerm);
|
||||
}
|
||||
|
||||
if (!filterType && deviceItems.length) {
|
||||
@@ -820,9 +799,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
areasAndFloors = this._filterGroup(
|
||||
"area",
|
||||
areasAndFloors,
|
||||
searchTerm,
|
||||
areaFloorComboBoxKeys,
|
||||
false
|
||||
searchTerm
|
||||
) as FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -867,12 +844,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
labels = this._filterGroup(
|
||||
"label",
|
||||
labels,
|
||||
searchTerm,
|
||||
labelComboBoxKeys
|
||||
);
|
||||
labels = this._filterGroup("label", labels, searchTerm);
|
||||
}
|
||||
|
||||
if (!filterType && labels.length) {
|
||||
@@ -891,24 +863,40 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
type: TargetType,
|
||||
items: (FloorComboBoxItem | PickerComboBoxItem | EntityComboBoxItem)[],
|
||||
searchTerm: string,
|
||||
weightedKeys: FuseWeightedKey[],
|
||||
sort = true
|
||||
checkExact?: (
|
||||
item: FloorComboBoxItem | PickerComboBoxItem | EntityComboBoxItem
|
||||
) => boolean
|
||||
) {
|
||||
const fuseIndex = this._fuseIndexes[type](items);
|
||||
const fuse = new HaFuse(
|
||||
items,
|
||||
{
|
||||
shouldSort: false,
|
||||
minMatchCharLength: Math.min(searchTerm.length, 2),
|
||||
},
|
||||
fuseIndex
|
||||
);
|
||||
|
||||
if (sort) {
|
||||
return multiTermSortedSearch(
|
||||
items,
|
||||
searchTerm,
|
||||
weightedKeys,
|
||||
(item) => item.id,
|
||||
fuseIndex
|
||||
);
|
||||
const results = fuse.multiTermsSearch(searchTerm);
|
||||
let filteredItems = items;
|
||||
if (results) {
|
||||
filteredItems = results.map((result) => result.item);
|
||||
}
|
||||
|
||||
return multiTermSearch(items, searchTerm, weightedKeys, fuseIndex, {
|
||||
ignoreLocation: true,
|
||||
});
|
||||
if (!checkExact) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
// If there is exact match for entity id, put it first
|
||||
const index = filteredItems.findIndex((item) => checkExact(item));
|
||||
if (index === -1) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
const [exactMatch] = filteredItems.splice(index, 1);
|
||||
filteredItems.unshift(exactMatch);
|
||||
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
private _getAdditionalItems = () => this._getCreateItems(this.createDomains);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { ScrollableFadeMixin } from "../mixins/scrollable-fade-mixin";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-dialog-header";
|
||||
@@ -73,7 +72,7 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
|
||||
* @see https://github.com/home-assistant/frontend/issues/27143
|
||||
*/
|
||||
@customElement("ha-wa-dialog")
|
||||
export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
export class HaWaDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: "aria-labelledby" })
|
||||
@@ -114,10 +113,6 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
@state()
|
||||
private _bodyScrolled = false;
|
||||
|
||||
protected get scrollableElement(): HTMLElement | null {
|
||||
return this.bodyContainer;
|
||||
}
|
||||
|
||||
protected updated(
|
||||
changedProperties: Map<string | number | symbol, unknown>
|
||||
): void {
|
||||
@@ -166,11 +161,8 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
<slot name="headerActionItems" slot="actionItems"></slot>
|
||||
</ha-dialog-header>
|
||||
</slot>
|
||||
<div class="content-wrapper">
|
||||
<div class="body ha-scrollbar" @scroll=${this._handleBodyScroll}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
${this.renderScrollableFades()}
|
||||
<div class="body ha-scrollbar" @scroll=${this._handleBodyScroll}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<slot name="footer" slot="footer"></slot>
|
||||
</wa-dialog>
|
||||
@@ -207,180 +199,165 @@ export class HaWaDialog extends ScrollableFadeMixin(LitElement) {
|
||||
this._bodyScrolled = (ev.target as HTMLDivElement).scrollTop > 0;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...super.styles,
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
wa-dialog {
|
||||
--full-width: var(
|
||||
--ha-dialog-width-full,
|
||||
min(95vw, var(--safe-width))
|
||||
);
|
||||
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
|
||||
--spacing: var(--dialog-content-padding, var(--ha-space-6));
|
||||
--show-duration: var(--ha-dialog-show-duration, 200ms);
|
||||
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
|
||||
--ha-dialog-surface-background: var(
|
||||
--card-background-color,
|
||||
var(--ha-color-surface-default)
|
||||
);
|
||||
--wa-color-surface-raised: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--card-background-color, var(--ha-color-surface-default))
|
||||
);
|
||||
--wa-panel-border-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-3xl)
|
||||
);
|
||||
max-width: var(--ha-dialog-max-width, var(--safe-width));
|
||||
}
|
||||
static styles = [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
wa-dialog {
|
||||
--full-width: var(--ha-dialog-width-full, min(95vw, var(--safe-width)));
|
||||
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
|
||||
--spacing: var(--dialog-content-padding, var(--ha-space-6));
|
||||
--show-duration: var(--ha-dialog-show-duration, 200ms);
|
||||
--hide-duration: var(--ha-dialog-hide-duration, 200ms);
|
||||
--ha-dialog-surface-background: var(
|
||||
--card-background-color,
|
||||
var(--ha-color-surface-default)
|
||||
);
|
||||
--wa-color-surface-raised: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--card-background-color, var(--ha-color-surface-default))
|
||||
);
|
||||
--wa-panel-border-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-3xl)
|
||||
);
|
||||
max-width: var(--ha-dialog-max-width, var(--safe-width));
|
||||
}
|
||||
|
||||
:host([width="small"]) wa-dialog {
|
||||
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
|
||||
}
|
||||
:host([width="small"]) wa-dialog {
|
||||
--width: min(var(--ha-dialog-width-sm, 320px), var(--full-width));
|
||||
}
|
||||
|
||||
:host([width="large"]) wa-dialog {
|
||||
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
|
||||
}
|
||||
:host([width="large"]) wa-dialog {
|
||||
--width: min(var(--ha-dialog-width-lg, 1024px), var(--full-width));
|
||||
}
|
||||
|
||||
:host([width="full"]) wa-dialog {
|
||||
--width: var(--full-width);
|
||||
}
|
||||
:host([width="full"]) wa-dialog {
|
||||
--width: var(--full-width);
|
||||
}
|
||||
|
||||
wa-dialog::part(dialog) {
|
||||
min-width: var(--width, var(--full-width));
|
||||
max-width: var(--width, var(--full-width));
|
||||
max-height: var(
|
||||
--ha-dialog-max-height,
|
||||
calc(var(--safe-height) - var(--ha-space-20))
|
||||
);
|
||||
min-height: var(--ha-dialog-min-height);
|
||||
margin-top: var(--dialog-surface-margin-top, auto);
|
||||
/* Used to offset the dialog from the safe areas when space is limited */
|
||||
transform: translate(
|
||||
calc(
|
||||
var(--safe-area-offset-left, var(--ha-space-0)) - var(
|
||||
--safe-area-offset-right,
|
||||
var(--ha-space-0)
|
||||
)
|
||||
),
|
||||
calc(
|
||||
var(--safe-area-offset-top, var(--ha-space-0)) - var(
|
||||
--safe-area-offset-bottom,
|
||||
var(--ha-space-0)
|
||||
)
|
||||
)
|
||||
);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
wa-dialog::part(dialog) {
|
||||
min-width: var(--width, var(--full-width));
|
||||
max-width: var(--width, var(--full-width));
|
||||
max-height: var(
|
||||
--ha-dialog-max-height,
|
||||
calc(var(--safe-height) - var(--ha-space-20))
|
||||
);
|
||||
min-height: var(--ha-dialog-min-height);
|
||||
margin-top: var(--dialog-surface-margin-top, auto);
|
||||
/* Used to offset the dialog from the safe areas when space is limited */
|
||||
transform: translate(
|
||||
calc(
|
||||
var(--safe-area-offset-left, var(--ha-space-0)) - var(
|
||||
--safe-area-offset-right,
|
||||
var(--ha-space-0)
|
||||
)
|
||||
),
|
||||
calc(
|
||||
var(--safe-area-offset-top, var(--ha-space-0)) - var(
|
||||
--safe-area-offset-bottom,
|
||||
var(--ha-space-0)
|
||||
)
|
||||
)
|
||||
);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
:host([type="standard"]) {
|
||||
--ha-dialog-border-radius: var(--ha-space-0);
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
:host([type="standard"]) {
|
||||
--ha-dialog-border-radius: var(--ha-space-0);
|
||||
|
||||
wa-dialog {
|
||||
/* Make the container fill the whole screen width and not the safe width */
|
||||
--full-width: var(--ha-dialog-width-full, 100vw);
|
||||
--width: var(--full-width);
|
||||
}
|
||||
wa-dialog {
|
||||
/* Make the container fill the whole screen width and not the safe width */
|
||||
--full-width: var(--ha-dialog-width-full, 100vw);
|
||||
--width: var(--full-width);
|
||||
}
|
||||
|
||||
wa-dialog::part(dialog) {
|
||||
/* Make the dialog fill the whole screen height and not the safe height */
|
||||
min-height: var(--ha-dialog-min-height, 100vh);
|
||||
min-height: var(--ha-dialog-min-height, 100dvh);
|
||||
max-height: var(--ha-dialog-max-height, 100vh);
|
||||
max-height: var(--ha-dialog-max-height, 100dvh);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
/* Use safe area as padding instead of the container size */
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
/* Reset the transform to center the dialog */
|
||||
transform: none;
|
||||
}
|
||||
wa-dialog::part(dialog) {
|
||||
/* Make the dialog fill the whole screen height and not the safe height */
|
||||
min-height: var(--ha-dialog-min-height, 100vh);
|
||||
min-height: var(--ha-dialog-min-height, 100dvh);
|
||||
max-height: var(--ha-dialog-max-height, 100vh);
|
||||
max-height: var(--ha-dialog-max-height, 100dvh);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
/* Use safe area as padding instead of the container size */
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
/* Reset the transform to center the dialog */
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.header-title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
margin: 0;
|
||||
margin-bottom: 0;
|
||||
color: var(--ha-dialog-header-title-color, var(--primary-text-color));
|
||||
font-size: var(
|
||||
--ha-dialog-header-title-font-size,
|
||||
var(--ha-font-size-2xl)
|
||||
);
|
||||
line-height: var(
|
||||
--ha-dialog-header-title-line-height,
|
||||
var(--ha-line-height-condensed)
|
||||
);
|
||||
font-weight: var(
|
||||
--ha-dialog-header-title-font-weight,
|
||||
var(--ha-font-weight-normal)
|
||||
);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: var(--ha-space-3);
|
||||
}
|
||||
.header-title {
|
||||
margin: 0;
|
||||
margin-bottom: 0;
|
||||
color: var(--ha-dialog-header-title-color, var(--primary-text-color));
|
||||
font-size: var(
|
||||
--ha-dialog-header-title-font-size,
|
||||
var(--ha-font-size-2xl)
|
||||
);
|
||||
line-height: var(
|
||||
--ha-dialog-header-title-line-height,
|
||||
var(--ha-line-height-condensed)
|
||||
);
|
||||
font-weight: var(
|
||||
--ha-dialog-header-title-font-weight,
|
||||
var(--ha-font-weight-normal)
|
||||
);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: var(--ha-space-3);
|
||||
}
|
||||
|
||||
wa-dialog::part(body) {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
wa-dialog::part(body) {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
.body {
|
||||
position: var(--dialog-content-position, relative);
|
||||
padding: 0 var(--dialog-content-padding, var(--ha-space-6))
|
||||
var(--dialog-content-padding, var(--ha-space-6))
|
||||
var(--dialog-content-padding, var(--ha-space-6));
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
:host([flexcontent]) .body {
|
||||
max-width: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.body {
|
||||
position: var(--dialog-content-position, relative);
|
||||
padding: var(
|
||||
--dialog-content-padding,
|
||||
0 var(--ha-space-6) var(--ha-space-6) var(--ha-space-6)
|
||||
);
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
:host([flexcontent]) .body {
|
||||
max-width: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
wa-dialog::part(footer) {
|
||||
padding: var(--ha-space-0);
|
||||
}
|
||||
|
||||
wa-dialog::part(footer) {
|
||||
padding: var(--ha-space-0);
|
||||
}
|
||||
|
||||
::slotted([slot="footer"]) {
|
||||
display: flex;
|
||||
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
|
||||
var(--ha-space-4);
|
||||
gap: var(--ha-space-3);
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
::slotted([slot="footer"]) {
|
||||
display: flex;
|
||||
padding: var(--ha-space-3) var(--ha-space-4) var(--ha-space-4)
|
||||
var(--ha-space-4);
|
||||
gap: var(--ha-space-3);
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../ha-state-icon";
|
||||
|
||||
@customElement("ha-entity-marker")
|
||||
class HaEntityMarker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -90,6 +89,8 @@ class HaEntityMarker extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
customElements.define("ha-entity-marker", HaEntityMarker);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-marker": HaEntityMarker;
|
||||
|
||||
@@ -17,12 +17,6 @@ interface UserComboBoxItem extends PickerComboBoxItem {
|
||||
user?: User;
|
||||
}
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "primary", weight: 10 },
|
||||
{ name: "search_labels.username", weight: 6 },
|
||||
{ name: "id", weight: 3 },
|
||||
];
|
||||
|
||||
@customElement("ha-user-picker")
|
||||
class HaUserPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -115,7 +109,9 @@ class HaUserPicker extends LitElement {
|
||||
id: user.id,
|
||||
primary: user.name,
|
||||
domain_name: user.name,
|
||||
search_labels: { username: user.username },
|
||||
search_labels: [user.name, user.id, user.username].filter(
|
||||
Boolean
|
||||
) as string[],
|
||||
sorting_label: user.name,
|
||||
user,
|
||||
}));
|
||||
@@ -138,10 +134,6 @@ class HaUserPicker extends LitElement {
|
||||
.getItems=${this._getItems}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.rowRenderer=${this._rowRenderer}
|
||||
.searchKeys=${SEARCH_KEYS}
|
||||
.unknownItemText=${this.hass.localize(
|
||||
"ui.components.user-picker.unknown"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-generic-picker>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { AreaRegistryEntry } from "./area_registry";
|
||||
import {
|
||||
@@ -27,8 +26,7 @@ export interface FloorNestedComboBoxItem extends PickerComboBoxItem {
|
||||
areas: FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
export interface UnassignedAreasFloorComboBoxItem {
|
||||
id: undefined;
|
||||
export interface UnassignedAreasFloorComboBoxItem extends PickerComboBoxItem {
|
||||
areas: FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -100,29 +98,6 @@ export const getAreasAndFloors = (
|
||||
excludeFloors
|
||||
) as FloorComboBoxItem[];
|
||||
|
||||
export const areaFloorComboBoxKeys: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "search_labels.name",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.aliases",
|
||||
weight: 8,
|
||||
},
|
||||
{
|
||||
name: "search_labels.floorName",
|
||||
weight: 6,
|
||||
},
|
||||
{
|
||||
name: "search_labels.relatedAreas",
|
||||
weight: 4,
|
||||
},
|
||||
{
|
||||
name: "search_labels.id",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const getAreasAndFloorsItems = (
|
||||
states: HomeAssistant["states"],
|
||||
haFloors: HomeAssistant["floors"],
|
||||
@@ -329,12 +304,12 @@ const getAreasAndFloorsItems = (
|
||||
primary: floorName,
|
||||
floor: floor,
|
||||
icon: floor.icon || undefined,
|
||||
search_labels: {
|
||||
id: floor.floor_id,
|
||||
name: floorName || null,
|
||||
aliases: floor.aliases.join(", ") || null,
|
||||
relatedAreas: areaSearchLabels.join(" ") || null,
|
||||
},
|
||||
search_labels: [
|
||||
floor.floor_id,
|
||||
floorName,
|
||||
...floor.aliases,
|
||||
...areaSearchLabels,
|
||||
],
|
||||
};
|
||||
|
||||
items.push(floorItem);
|
||||
@@ -347,12 +322,11 @@ const getAreasAndFloorsItems = (
|
||||
primary: areaName || area.area_id,
|
||||
area: area,
|
||||
icon: area.icon || undefined,
|
||||
search_labels: {
|
||||
id: area.area_id,
|
||||
name: areaName || null,
|
||||
aliases: area.aliases.join(", ") || null,
|
||||
floorName: floorName || null,
|
||||
},
|
||||
search_labels: [
|
||||
area.area_id,
|
||||
...(areaName ? [areaName] : []),
|
||||
...area.aliases,
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -365,24 +339,19 @@ const getAreasAndFloorsItems = (
|
||||
|
||||
const unassignedAreaItems = hierarchy.areas.map((areaId) => {
|
||||
const area = haAreas[areaId];
|
||||
const areaName = computeAreaName(area);
|
||||
const areaName = computeAreaName(area) || area.area_id;
|
||||
return {
|
||||
id: formatId({ id: area.area_id, type: "area" }),
|
||||
type: "area" as const,
|
||||
primary: areaName || area.area_id,
|
||||
primary: areaName,
|
||||
area: area,
|
||||
icon: area.icon || undefined,
|
||||
search_labels: {
|
||||
id: area.area_id,
|
||||
name: areaName || null,
|
||||
aliases: area.aliases.join(", ") || null,
|
||||
},
|
||||
search_labels: [area.area_id, areaName, ...area.aliases],
|
||||
};
|
||||
});
|
||||
|
||||
if (nested && unassignedAreaItems.length) {
|
||||
items.push({
|
||||
id: undefined,
|
||||
areas: unassignedAreaItems,
|
||||
} as UnassignedAreasFloorComboBoxItem);
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,6 @@ import { getDeviceContext } from "../common/entity/context/get_device_context";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { ConfigEntry } from "./config_entries";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
@@ -182,25 +181,6 @@ export interface DevicePickerItem extends PickerComboBoxItem {
|
||||
domain_name?: string;
|
||||
}
|
||||
|
||||
export const deviceComboBoxKeys: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "search_labels.deviceName",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.areaName",
|
||||
weight: 8,
|
||||
},
|
||||
{
|
||||
name: "search_labels.domainName",
|
||||
weight: 4,
|
||||
},
|
||||
{
|
||||
name: "search_labels.domain",
|
||||
weight: 4,
|
||||
},
|
||||
];
|
||||
|
||||
export const getDevices = (
|
||||
hass: HomeAssistant,
|
||||
configEntryLookup: Record<string, ConfigEntry>,
|
||||
@@ -331,12 +311,9 @@ export const getDevices = (
|
||||
secondary: areaName,
|
||||
domain: configEntry?.domain,
|
||||
domain_name: domainName,
|
||||
search_labels: {
|
||||
deviceName,
|
||||
areaName: areaName || null,
|
||||
domain: domain || null,
|
||||
domainName: domainName || null,
|
||||
},
|
||||
search_labels: [deviceName, areaName, domain, domainName].filter(
|
||||
Boolean
|
||||
) as string[],
|
||||
sorting_label: deviceName || "zzz",
|
||||
};
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@ import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity";
|
||||
import { domainToName } from "./integration";
|
||||
@@ -336,33 +335,6 @@ export interface EntityComboBoxItem extends PickerComboBoxItem {
|
||||
stateObj?: HassEntity;
|
||||
}
|
||||
|
||||
export const entityComboBoxKeys: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "search_labels.entityName",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.friendlyName",
|
||||
weight: 8,
|
||||
},
|
||||
{
|
||||
name: "search_labels.deviceName",
|
||||
weight: 7,
|
||||
},
|
||||
{
|
||||
name: "search_labels.areaName",
|
||||
weight: 6,
|
||||
},
|
||||
{
|
||||
name: "search_labels.domainName",
|
||||
weight: 6,
|
||||
},
|
||||
{
|
||||
name: "search_labels.entityId",
|
||||
weight: 3,
|
||||
},
|
||||
];
|
||||
|
||||
export const getEntities = (
|
||||
hass: HomeAssistant,
|
||||
includeDomains?: string[],
|
||||
@@ -431,14 +403,14 @@ export const getEntities = (
|
||||
secondary: secondary,
|
||||
domain_name: domainName,
|
||||
sorting_label: [deviceName, entityName].filter(Boolean).join("_"),
|
||||
search_labels: {
|
||||
entityName: entityName || null,
|
||||
deviceName: deviceName || null,
|
||||
areaName: areaName || null,
|
||||
domainName: domainName || null,
|
||||
friendlyName: friendlyName || null,
|
||||
entityId: entityId,
|
||||
},
|
||||
search_labels: [
|
||||
entityName,
|
||||
deviceName,
|
||||
areaName,
|
||||
domainName,
|
||||
friendlyName,
|
||||
entityId,
|
||||
].filter(Boolean) as string[],
|
||||
stateObj: stateObj,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -47,7 +47,8 @@ export interface HassioFullBackupCreateParams {
|
||||
confirm_password?: string;
|
||||
background?: boolean;
|
||||
}
|
||||
export interface HassioPartialBackupCreateParams extends HassioFullBackupCreateParams {
|
||||
export interface HassioPartialBackupCreateParams
|
||||
extends HassioFullBackupCreateParams {
|
||||
folders?: string[];
|
||||
addons?: string[];
|
||||
homeassistant?: boolean;
|
||||
|
||||
@@ -7,7 +7,6 @@ import { stringCompare } from "../common/string/compare";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "../components/device/ha-device-picker";
|
||||
import type { PickerComboBoxItem } from "../components/ha-picker-combo-box";
|
||||
import type { FuseWeightedKey } from "../resources/fuseMultiTerm";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import {
|
||||
getDeviceEntityDisplayLookup,
|
||||
@@ -101,21 +100,6 @@ export const deleteLabelRegistryEntry = (
|
||||
label_id: labelId,
|
||||
});
|
||||
|
||||
export const labelComboBoxKeys: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "search_labels.name",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "search_labels.description",
|
||||
weight: 5,
|
||||
},
|
||||
{
|
||||
name: "search_labels.id",
|
||||
weight: 4,
|
||||
},
|
||||
];
|
||||
|
||||
export const getLabels = (
|
||||
hassStates: HomeAssistant["states"],
|
||||
hassAreas: HomeAssistant["areas"],
|
||||
@@ -289,11 +273,9 @@ export const getLabels = (
|
||||
icon: label.icon || undefined,
|
||||
icon_path: label.icon ? undefined : mdiLabel,
|
||||
sorting_label: label.name,
|
||||
search_labels: {
|
||||
name: label.name,
|
||||
description: label.description,
|
||||
id: label.label_id,
|
||||
},
|
||||
search_labels: [label.name, label.label_id, label.description].filter(
|
||||
(v): v is string => Boolean(v)
|
||||
),
|
||||
}));
|
||||
|
||||
return items;
|
||||
|
||||
@@ -18,7 +18,8 @@ export const enum LawnMowerEntityFeature {
|
||||
}
|
||||
|
||||
interface LawnMowerEntityAttributes
|
||||
extends HassEntityAttributeBase, Record<string, any> {}
|
||||
extends HassEntityAttributeBase,
|
||||
Record<string, any> {}
|
||||
|
||||
export interface LawnMowerEntity extends HassEntityBase {
|
||||
attributes: LawnMowerEntityAttributes;
|
||||
|
||||
@@ -18,7 +18,8 @@ export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
|
||||
cards?: LovelaceCardConfig[];
|
||||
}
|
||||
|
||||
export interface LovelaceStrategySectionConfig extends LovelaceBaseSectionConfig {
|
||||
export interface LovelaceStrategySectionConfig
|
||||
extends LovelaceBaseSectionConfig {
|
||||
strategy: LovelaceStrategyConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ export interface LovelaceConfig extends LovelaceDashboardBaseConfig {
|
||||
views: LovelaceViewRawConfig[];
|
||||
}
|
||||
|
||||
export interface LovelaceDashboardStrategyConfig extends LovelaceDashboardBaseConfig {
|
||||
export interface LovelaceDashboardStrategyConfig
|
||||
extends LovelaceDashboardBaseConfig {
|
||||
strategy: LovelaceStrategyConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,8 @@ export interface LovelaceDashboardMutableParams {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface LovelaceDashboardCreateParams extends LovelaceDashboardMutableParams {
|
||||
export interface LovelaceDashboardCreateParams
|
||||
extends LovelaceDashboardMutableParams {
|
||||
url_path: string;
|
||||
mode: "storage";
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@ export interface AutomationTrace extends BaseTrace {
|
||||
}
|
||||
|
||||
export interface AutomationTraceExtended
|
||||
extends AutomationTrace, BaseTraceExtended {
|
||||
extends AutomationTrace,
|
||||
BaseTraceExtended {
|
||||
config: ManualAutomationConfig;
|
||||
blueprint_inputs?: BlueprintAutomationConfig;
|
||||
}
|
||||
|
||||
@@ -82,12 +82,6 @@ export interface WeatherEntity extends HassEntityBase {
|
||||
attributes: WeatherEntityAttributes;
|
||||
}
|
||||
|
||||
export const WEATHER_TEMPERATURE_ATTRIBUTES = new Set<string>([
|
||||
"temperature",
|
||||
"apparent_temperature",
|
||||
"dew_point",
|
||||
]);
|
||||
|
||||
export const weatherSVGs = new Set<string>([
|
||||
"clear-night",
|
||||
"cloudy",
|
||||
@@ -262,15 +256,9 @@ export const getWeatherUnit = (
|
||||
export const getSecondaryWeatherAttribute = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: WeatherEntity,
|
||||
forecast: ForecastAttribute[],
|
||||
temperatureFractionDigits?: number
|
||||
forecast: ForecastAttribute[]
|
||||
): TemplateResult | undefined => {
|
||||
const extrema = getWeatherExtrema(
|
||||
hass,
|
||||
stateObj,
|
||||
forecast,
|
||||
temperatureFractionDigits
|
||||
);
|
||||
const extrema = getWeatherExtrema(hass, stateObj, forecast);
|
||||
|
||||
if (extrema) {
|
||||
return extrema;
|
||||
@@ -310,8 +298,7 @@ export const getSecondaryWeatherAttribute = (
|
||||
const getWeatherExtrema = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: WeatherEntity,
|
||||
forecast: ForecastAttribute[],
|
||||
temperatureFractionDigits?: number
|
||||
forecast: ForecastAttribute[]
|
||||
): TemplateResult | undefined => {
|
||||
if (!forecast?.length) {
|
||||
return undefined;
|
||||
@@ -326,22 +313,13 @@ const getWeatherExtrema = (
|
||||
break;
|
||||
}
|
||||
if (!tempHigh || fc.temperature > tempHigh) {
|
||||
tempHigh =
|
||||
temperatureFractionDigits === undefined
|
||||
? fc.temperature
|
||||
: round(fc.temperature, temperatureFractionDigits);
|
||||
tempHigh = fc.temperature;
|
||||
}
|
||||
if (fc.templow !== undefined && (!tempLow || fc.templow < tempLow)) {
|
||||
tempLow =
|
||||
temperatureFractionDigits === undefined
|
||||
? fc.templow
|
||||
: round(fc.templow, temperatureFractionDigits);
|
||||
if (!tempLow || (fc.templow && fc.templow < tempLow)) {
|
||||
tempLow = fc.templow;
|
||||
}
|
||||
if (!fc.templow && (!tempLow || fc.temperature < tempLow)) {
|
||||
tempLow =
|
||||
temperatureFractionDigits === undefined
|
||||
? fc.temperature
|
||||
: round(fc.temperature, temperatureFractionDigits);
|
||||
tempLow = fc.temperature;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ export interface PromptDialogParams extends BaseDialogBoxParams {
|
||||
}
|
||||
|
||||
export interface DialogBoxParams
|
||||
extends ConfirmationDialogParams, PromptDialogParams {
|
||||
extends ConfirmationDialogParams,
|
||||
PromptDialogParams {
|
||||
confirm?: (out?: string) => void;
|
||||
confirmation?: boolean;
|
||||
prompt?: boolean;
|
||||
|
||||
@@ -19,9 +19,8 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export interface HassDialog<
|
||||
T = HASSDomEvents[ValidHassDomEvent],
|
||||
> extends HTMLElement {
|
||||
export interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]>
|
||||
extends HTMLElement {
|
||||
showDialog(params: T);
|
||||
closeDialog?: (historyState?: any) => boolean;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/ha-camera-stream";
|
||||
@@ -9,7 +9,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import { showToast } from "../../../util/toast";
|
||||
|
||||
@customElement("more-info-camera")
|
||||
class MoreInfoCamera extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -113,6 +112,8 @@ class MoreInfoCamera extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
customElements.define("more-info-camera", MoreInfoCamera);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-camera": MoreInfoCamera;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-attribute-icon";
|
||||
@@ -32,7 +32,6 @@ import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||
|
||||
type MainControl = "temperature" | "humidity";
|
||||
|
||||
@customElement("more-info-climate")
|
||||
class MoreInfoClimate extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -568,6 +567,8 @@ class MoreInfoClimate extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-climate", MoreInfoClimate);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-climate": MoreInfoClimate;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
import type { GroupEntity } from "../../../data/group";
|
||||
import { computeGroupDomain } from "../../../data/group";
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
importMoreInfoControl,
|
||||
} from "../state_more_info_control";
|
||||
|
||||
@customElement("more-info-group")
|
||||
class MoreInfoGroup extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -107,6 +106,8 @@ class MoreInfoGroup extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-group", MoreInfoGroup);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-group": MoreInfoGroup;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiPower, mdiTuneVariant } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-select-menu";
|
||||
@@ -15,7 +15,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import "../components/ha-more-info-control-select-container";
|
||||
import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||
|
||||
@customElement("more-info-humidifier")
|
||||
class MoreInfoHumidifier extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -250,6 +249,8 @@ class MoreInfoHumidifier extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-humidifier", MoreInfoHumidifier);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-humidifier": MoreInfoHumidifier;
|
||||
|
||||
@@ -14,9 +14,8 @@ import {
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { cache } from "lit/directives/cache";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -51,12 +50,7 @@ import { lightSupportsFavoriteColors } from "../../data/light";
|
||||
import type { ItemType } from "../../data/search";
|
||||
import { SearchableDomains } from "../../data/search";
|
||||
import { getSensorNumericDeviceClasses } from "../../data/sensor";
|
||||
import { ScrollableFadeMixin } from "../../mixins/scrollable-fade-mixin";
|
||||
import {
|
||||
haStyleDialog,
|
||||
haStyleDialogFixedTop,
|
||||
haStyleScrollbar,
|
||||
} from "../../resources/styles";
|
||||
import { haStyleDialog, haStyleDialogFixedTop } from "../../resources/styles";
|
||||
import "../../state-summary/state-card-content";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
@@ -102,15 +96,13 @@ declare global {
|
||||
const DEFAULT_VIEW: View = "info";
|
||||
|
||||
@customElement("ha-more-info-dialog")
|
||||
export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
|
||||
export class MoreInfoDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public large = false;
|
||||
|
||||
@state() private _parentEntityIds: string[] = [];
|
||||
|
||||
@query(".content") private _contentElement?: HTMLDivElement;
|
||||
|
||||
@state() private _entityId?: string | null;
|
||||
|
||||
@state() private _data?: Record<string, any>;
|
||||
@@ -129,12 +121,6 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
@state() private _sensorNumericDeviceClasses?: string[] = [];
|
||||
|
||||
protected scrollFadeThreshold = 24;
|
||||
|
||||
protected get scrollableElement(): HTMLElement | null {
|
||||
return this._contentElement || null;
|
||||
}
|
||||
|
||||
public showDialog(params: MoreInfoDialogParams) {
|
||||
this._entityId = params.entityId;
|
||||
if (!this._entityId) {
|
||||
@@ -608,86 +594,78 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
|
||||
`
|
||||
: nothing}
|
||||
</ha-dialog-header>
|
||||
<div
|
||||
class=${classMap({
|
||||
"content-wrapper": true,
|
||||
"settings-view": this._currView === "settings",
|
||||
})}
|
||||
>
|
||||
${keyed(
|
||||
this._entityId,
|
||||
html`
|
||||
<div
|
||||
class="content ha-scrollbar"
|
||||
tabindex="-1"
|
||||
dialogInitialFocus
|
||||
@show-child-view=${this._showChildView}
|
||||
@entity-entry-updated=${this._entryUpdated}
|
||||
@toggle-edit-mode=${this._handleToggleInfoEditModeEvent}
|
||||
@hass-more-info=${this._handleMoreInfoEvent}
|
||||
>
|
||||
${cache(
|
||||
this._childView
|
||||
${keyed(
|
||||
this._entityId,
|
||||
html`
|
||||
<div
|
||||
class="content"
|
||||
tabindex="-1"
|
||||
dialogInitialFocus
|
||||
@show-child-view=${this._showChildView}
|
||||
@entity-entry-updated=${this._entryUpdated}
|
||||
@toggle-edit-mode=${this._handleToggleInfoEditModeEvent}
|
||||
@hass-more-info=${this._handleMoreInfoEvent}
|
||||
>
|
||||
${cache(
|
||||
this._childView
|
||||
? html`
|
||||
<div class="child-view">
|
||||
${dynamicElement(this._childView.viewTag, {
|
||||
hass: this.hass,
|
||||
entry: this._entry,
|
||||
params: this._childView.viewParams,
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
: this._currView === "info"
|
||||
? html`
|
||||
<div class="child-view">
|
||||
${dynamicElement(this._childView.viewTag, {
|
||||
hass: this.hass,
|
||||
entry: this._entry,
|
||||
params: this._childView.viewParams,
|
||||
})}
|
||||
</div>
|
||||
<ha-more-info-info
|
||||
dialogInitialFocus
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.entry=${this._entry}
|
||||
.editMode=${this._infoEditMode}
|
||||
.data=${this._data}
|
||||
></ha-more-info-info>
|
||||
`
|
||||
: this._currView === "info"
|
||||
: this._currView === "history"
|
||||
? html`
|
||||
<ha-more-info-info
|
||||
dialogInitialFocus
|
||||
<ha-more-info-history-and-logbook
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.entry=${this._entry}
|
||||
.editMode=${this._infoEditMode}
|
||||
.data=${this._data}
|
||||
></ha-more-info-info>
|
||||
></ha-more-info-history-and-logbook>
|
||||
`
|
||||
: this._currView === "history"
|
||||
: this._currView === "settings"
|
||||
? html`
|
||||
<ha-more-info-history-and-logbook
|
||||
<ha-more-info-settings
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-history-and-logbook>
|
||||
.entry=${this._entry}
|
||||
></ha-more-info-settings>
|
||||
`
|
||||
: this._currView === "settings"
|
||||
: this._currView === "related"
|
||||
? html`
|
||||
<ha-more-info-settings
|
||||
<ha-related-items
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.entry=${this._entry}
|
||||
></ha-more-info-settings>
|
||||
.itemId=${entityId}
|
||||
.itemType=${SearchableDomains.has(domain)
|
||||
? (domain as ItemType)
|
||||
: "entity"}
|
||||
></ha-related-items>
|
||||
`
|
||||
: this._currView === "related"
|
||||
: this._currView === "add_to"
|
||||
? html`
|
||||
<ha-related-items
|
||||
<ha-more-info-add-to
|
||||
.hass=${this.hass}
|
||||
.itemId=${entityId}
|
||||
.itemType=${SearchableDomains.has(domain)
|
||||
? (domain as ItemType)
|
||||
: "entity"}
|
||||
></ha-related-items>
|
||||
.entityId=${entityId}
|
||||
@add-to-action-selected=${this._goBack}
|
||||
></ha-more-info-add-to>
|
||||
`
|
||||
: this._currView === "add_to"
|
||||
? html`
|
||||
<ha-more-info-add-to
|
||||
.hass=${this.hass}
|
||||
.entityId=${entityId}
|
||||
@add-to-action-selected=${this._goBack}
|
||||
></ha-more-info-add-to>
|
||||
`
|
||||
: nothing
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
${this.renderScrollableFades()}
|
||||
</div>
|
||||
: nothing
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
@@ -742,31 +720,18 @@ export class MoreInfoDialog extends ScrollableFadeMixin(LitElement) {
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...super.styles,
|
||||
haStyleDialog,
|
||||
haStyleDialogFixedTop,
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
ha-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
outline: none;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.content-wrapper.settings-view .fade-bottom {
|
||||
bottom: var(--ha-space-18);
|
||||
}
|
||||
|
||||
.child-view {
|
||||
|
||||
@@ -45,7 +45,7 @@ import { domainToName } from "../../data/integration";
|
||||
import { getPanelNameTranslationKey } from "../../data/panel";
|
||||
import type { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
import { configSections } from "../../panels/config/ha-panel-config";
|
||||
import { multiTermSortedSearch } from "../../resources/fuseMultiTerm";
|
||||
import { HaFuse } from "../../resources/fuse";
|
||||
import {
|
||||
haStyleDialog,
|
||||
haStyleDialogFixedTop,
|
||||
@@ -58,17 +58,7 @@ import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import { showShortcutsDialog } from "../shortcuts/show-shortcuts-dialog";
|
||||
import { QuickBarMode, type QuickBarParams } from "./show-dialog-quick-bar";
|
||||
|
||||
const SEARCH_KEYS = [
|
||||
{ name: "primaryText", weight: 10 },
|
||||
{ name: "altText", weight: 8 },
|
||||
{ name: "friendlyName", weight: 8 },
|
||||
{ name: "area", weight: 6 },
|
||||
{ name: "translatedDomain", weight: 5 },
|
||||
{ name: "entityId", weight: 4 }, // for technical search
|
||||
];
|
||||
|
||||
interface QuickBarItem extends ScorableTextItem {
|
||||
id: string;
|
||||
primaryText: string;
|
||||
iconPath?: string;
|
||||
action(data?: any): void;
|
||||
@@ -603,7 +593,6 @@ export class QuickBar extends LitElement {
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
|
||||
const deviceItem = {
|
||||
id: device.id,
|
||||
primaryText: deviceName,
|
||||
deviceId: device.id,
|
||||
area: areaName,
|
||||
@@ -677,7 +666,6 @@ export class QuickBar extends LitElement {
|
||||
);
|
||||
|
||||
const entityItem = {
|
||||
id: `entity-${entityId}`,
|
||||
primaryText: primary,
|
||||
altText: secondary,
|
||||
icon: html`
|
||||
@@ -779,9 +767,8 @@ export class QuickBar extends LitElement {
|
||||
),
|
||||
});
|
||||
|
||||
return commands.map((command, index) => ({
|
||||
return commands.map((command) => ({
|
||||
...command,
|
||||
id: `command_${index}_${command.primaryText}`,
|
||||
categoryKey: "reload",
|
||||
strings: [`${command.categoryText} ${command.primaryText}`],
|
||||
}));
|
||||
@@ -790,11 +777,10 @@ export class QuickBar extends LitElement {
|
||||
private _generateServerControlCommands(): CommandItem[] {
|
||||
const serverActions = ["restart", "stop"] as const;
|
||||
|
||||
return serverActions.map((action, index) => {
|
||||
return serverActions.map((action) => {
|
||||
const categoryKey: CommandItem["categoryKey"] = "server_control";
|
||||
|
||||
const item = {
|
||||
id: `server_control_${index}_${action}`,
|
||||
primaryText: this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.server_control.perform_action",
|
||||
{
|
||||
@@ -954,11 +940,10 @@ export class QuickBar extends LitElement {
|
||||
private _finalizeNavigationCommands(
|
||||
items: BaseNavigationCommand[]
|
||||
): CommandItem[] {
|
||||
return items.map((item, index) => {
|
||||
return items.map((item) => {
|
||||
const categoryKey: CommandItem["categoryKey"] = "navigation";
|
||||
|
||||
const navItem = {
|
||||
id: `navigation_${index}_${item.path}`,
|
||||
iconPath: mdiEarth,
|
||||
categoryText: this.hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.types.${categoryKey}`
|
||||
@@ -976,20 +961,28 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
private _fuseIndex = memoizeOne((items: QuickBarItem[]) =>
|
||||
Fuse.createIndex(SEARCH_KEYS, items)
|
||||
Fuse.createIndex(
|
||||
[
|
||||
"primaryText",
|
||||
"altText",
|
||||
"friendlyName",
|
||||
"translatedDomain",
|
||||
"entityId", // for technical search
|
||||
],
|
||||
items
|
||||
)
|
||||
);
|
||||
|
||||
private _filterItems = memoizeOne(
|
||||
(items: QuickBarItem[], filter: string): QuickBarItem[] => {
|
||||
const index = this._fuseIndex(items);
|
||||
const fuse = new HaFuse(items, {}, index);
|
||||
|
||||
return multiTermSortedSearch(
|
||||
items,
|
||||
filter,
|
||||
SEARCH_KEYS,
|
||||
(item) => item.id,
|
||||
index
|
||||
);
|
||||
const results = fuse.multiTermsSearch(filter.trim());
|
||||
if (!results || !results.length) {
|
||||
return items;
|
||||
}
|
||||
return results.map((result) => result.item);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import "../../components/ha-alert";
|
||||
import "../../components/ha-expansion-panel";
|
||||
import "../../components/ha-fade-in";
|
||||
import "../../components/ha-icon-next";
|
||||
import "../../components/ha-adaptive-dialog";
|
||||
import "../../components/ha-wa-dialog";
|
||||
import "../../components/ha-md-list";
|
||||
import "../../components/ha-md-list-item";
|
||||
import "../../components/ha-spinner";
|
||||
@@ -109,7 +109,7 @@ class DialogRestart extends LitElement {
|
||||
const dialogTitle = this.hass.localize("ui.dialogs.restart.heading");
|
||||
|
||||
return html`
|
||||
<ha-adaptive-dialog
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._dialogOpen}
|
||||
header-title=${dialogTitle}
|
||||
@@ -257,7 +257,7 @@ class DialogRestart extends LitElement {
|
||||
</ha-expansion-panel>
|
||||
`}
|
||||
</div>
|
||||
</ha-adaptive-dialog>
|
||||
</ha-wa-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ class DialogRestart extends LitElement {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-adaptive-dialog {
|
||||
ha-wa-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import { mdiDotsVertical, mdiRestart } from "@mdi/js";
|
||||
import { css, html, LitElement, type TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { mdiClose, mdiDotsVertical, mdiRestart } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing, type TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-dialog-footer";
|
||||
import "../../components/ha-dialog-header";
|
||||
import "../../components/ha-fade-in";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-items-display-editor";
|
||||
@@ -15,8 +14,9 @@ import type {
|
||||
DisplayValue,
|
||||
} from "../../components/ha-items-display-editor";
|
||||
import "../../components/ha-md-button-menu";
|
||||
import "../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../components/ha-md-dialog";
|
||||
import "../../components/ha-md-menu-item";
|
||||
import "../../components/ha-wa-dialog";
|
||||
import { computePanels } from "../../components/ha-sidebar";
|
||||
import "../../components/ha-spinner";
|
||||
import "../../components/ha-svg-icon";
|
||||
@@ -40,6 +40,8 @@ class DialogEditSidebar extends LitElement {
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||
|
||||
@state() private _order?: string[];
|
||||
|
||||
@state() private _hidden?: string[];
|
||||
@@ -53,6 +55,7 @@ class DialogEditSidebar extends LitElement {
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
this._open = true;
|
||||
|
||||
this._getData();
|
||||
}
|
||||
|
||||
@@ -84,7 +87,7 @@ class DialogEditSidebar extends LitElement {
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._open = false;
|
||||
this._dialog?.close();
|
||||
}
|
||||
|
||||
private _panels = memoizeOne((panels: HomeAssistant["panels"]) =>
|
||||
@@ -164,52 +167,57 @@ class DialogEditSidebar extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._open) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const dialogTitle = this.hass.localize("ui.sidebar.edit_sidebar");
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${dialogTitle}
|
||||
header-subtitle=${!this._migrateToUserData
|
||||
? this.hass.localize("ui.sidebar.edit_subtitle")
|
||||
: ""}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-md-button-menu
|
||||
slot="headerActionItems"
|
||||
positioning="popover"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-md-dialog open @closed=${this._dialogClosed}>
|
||||
<ha-dialog-header slot="headline">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-md-menu-item .clickAction=${this._resetToDefaults}>
|
||||
<ha-svg-icon slot="start" .path=${mdiRestart}></ha-svg-icon>
|
||||
${this.hass.localize("ui.sidebar.reset_to_defaults")}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
<div class="content">${this._renderContent()}</div>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
slot="navigationIcon"
|
||||
.label=${this.hass.localize("ui.common.close") ?? "Close"}
|
||||
.path=${mdiClose}
|
||||
@click=${this.closeDialog}
|
||||
></ha-icon-button>
|
||||
<span slot="title" .title=${dialogTitle}>${dialogTitle}</span>
|
||||
${!this._migrateToUserData
|
||||
? html`<span slot="subtitle"
|
||||
>${this.hass.localize("ui.sidebar.edit_subtitle")}</span
|
||||
>`
|
||||
: nothing}
|
||||
<ha-md-button-menu
|
||||
slot="actionItems"
|
||||
positioning="popover"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-md-menu-item .clickAction=${this._resetToDefaults}>
|
||||
<ha-svg-icon slot="start" .path=${mdiRestart}></ha-svg-icon>
|
||||
${this.hass.localize("ui.sidebar.reset_to_defaults")}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dialog-header>
|
||||
<div slot="content" class="content">${this._renderContent()}</div>
|
||||
<div slot="actions">
|
||||
<ha-button appearance="plain" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
.disabled=${!this._order || !this._hidden}
|
||||
@click=${this._save}
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -264,13 +272,15 @@ class DialogEditSidebar extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-wa-dialog {
|
||||
ha-md-dialog {
|
||||
min-width: 600px;
|
||||
max-height: 90%;
|
||||
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6);
|
||||
--dialog-content-padding: 8px 24px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 580px), all and (max-height: 500px) {
|
||||
ha-wa-dialog {
|
||||
@media all and (max-width: 600px), all and (max-height: 500px) {
|
||||
ha-md-dialog {
|
||||
--md-dialog-container-shape: 0;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import "../components/ha-spinner";
|
||||
import "../components/ha-button";
|
||||
|
||||
@customElement("ha-init-page")
|
||||
class HaInitPage extends LitElement {
|
||||
@property({ type: Boolean }) public error = false;
|
||||
|
||||
@@ -121,6 +120,8 @@ class HaInitPage extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
customElements.define("ha-init-page", HaInitPage);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-init-page": HaInitPage;
|
||||
|
||||
@@ -128,8 +128,6 @@ class HassSubpage extends LitElement {
|
||||
ha-menu-button,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
|
||||
@@ -621,9 +621,9 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
} else if (this._sortDirection === "asc") {
|
||||
this._sortDirection = "desc";
|
||||
} else {
|
||||
this._sortDirection = "asc";
|
||||
this._sortDirection = null;
|
||||
}
|
||||
this._sortColumn = columnId;
|
||||
this._sortColumn = this._sortDirection === null ? undefined : columnId;
|
||||
|
||||
fireEvent(this, "sorting-changed", {
|
||||
column: columnId,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
import "../components/ha-button";
|
||||
import "../components/ha-icon-button";
|
||||
@@ -26,7 +26,6 @@ export interface ToastActionParams {
|
||||
| { translationKey: LocalizeKeys; args?: Record<string, string> };
|
||||
}
|
||||
|
||||
@customElement("notification-manager")
|
||||
class NotificationManager extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -116,6 +115,8 @@ class NotificationManager extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("notification-manager", NotificationManager);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"notification-manager": NotificationManager;
|
||||
|
||||
@@ -13,27 +13,19 @@ import type { Constructor } from "../types";
|
||||
const stylesArray = (styles?: CSSResultGroup | CSSResultGroup[]) =>
|
||||
styles === undefined ? [] : Array.isArray(styles) ? styles : [styles];
|
||||
|
||||
/**
|
||||
* Mixin that adds top and bottom fade overlays for scrollable content.
|
||||
* @param superClass - The LitElement class to extend.
|
||||
* @returns Extended class with scrollable fade functionality.
|
||||
*/
|
||||
export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
|
||||
superClass: T
|
||||
) => {
|
||||
class ScrollableFadeClass extends superClass {
|
||||
/** Whether content has scrolled past the threshold. Controls top fade visibility. */
|
||||
@state() protected _contentScrolled = false;
|
||||
|
||||
/** Whether content extends beyond the viewport. Controls bottom fade visibility. */
|
||||
@state() protected _contentScrollable = false;
|
||||
|
||||
private _scrollTarget?: HTMLElement | null;
|
||||
|
||||
private _onScroll = (ev: Event) => {
|
||||
const target = ev.currentTarget as HTMLElement;
|
||||
this._contentScrolled =
|
||||
(target.scrollTop ?? 0) > this.scrollFadeThreshold;
|
||||
this._contentScrolled = (target.scrollTop ?? 0) > 0;
|
||||
this._updateScrollableState(target);
|
||||
};
|
||||
|
||||
@@ -47,26 +39,15 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Safe area padding in pixels for the scrollable element.
|
||||
*/
|
||||
protected scrollFadeSafeAreaPadding = 16;
|
||||
private static readonly DEFAULT_SAFE_AREA_PADDING = 16;
|
||||
|
||||
/**
|
||||
* Scroll threshold in pixels for showing the fades.
|
||||
*/
|
||||
protected scrollFadeThreshold = 4;
|
||||
|
||||
/**
|
||||
* Default scrollable element value.
|
||||
*/
|
||||
private static readonly DEFAULT_SCROLLABLE_ELEMENT: HTMLElement | null =
|
||||
null;
|
||||
|
||||
/**
|
||||
* Element to observe for scroll and resize events. Override with a getter to specify target.
|
||||
* Kept as a getter to allow subclasses to return query results.
|
||||
*/
|
||||
protected get scrollFadeSafeAreaPadding() {
|
||||
return ScrollableFadeClass.DEFAULT_SAFE_AREA_PADDING;
|
||||
}
|
||||
|
||||
protected get scrollableElement(): HTMLElement | null {
|
||||
return ScrollableFadeClass.DEFAULT_SCROLLABLE_ELEMENT;
|
||||
}
|
||||
@@ -86,11 +67,6 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders top and bottom fade overlays. Call in render method.
|
||||
* @param rounded - Whether to apply rounded corners.
|
||||
* @returns Template containing fade elements.
|
||||
*/
|
||||
protected renderScrollableFades(rounded = false): TemplateResult {
|
||||
return html`
|
||||
<div
|
||||
@@ -134,6 +110,7 @@ export const ScrollableFadeMixin = <T extends Constructor<LitElement>>(
|
||||
transparent
|
||||
);
|
||||
border-radius: var(--ha-border-radius-square);
|
||||
z-index: 100;
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-top {
|
||||
|
||||
@@ -90,7 +90,9 @@ class OnboardingRestoreBackupCloudLogin extends LitElement {
|
||||
this._email = this._cloudLoginElement.emailField.value;
|
||||
}
|
||||
|
||||
await import("../../panels/config/cloud/forgot-password/cloud-forgot-password-card");
|
||||
await import(
|
||||
"../../panels/config/cloud/forgot-password/cloud-forgot-password-card"
|
||||
);
|
||||
this._view = "forgot-password";
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TZDate } from "@date-fns/tz";
|
||||
import { addDays, isSameDay } from "date-fns";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { formatDate } from "../../common/datetime/format_date";
|
||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||
import { formatTime } from "../../common/datetime/format_time";
|
||||
@@ -26,7 +26,6 @@ import type { CalendarEventDetailDialogParams } from "./show-dialog-calendar-eve
|
||||
import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor";
|
||||
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
|
||||
|
||||
@customElement("dialog-calendar-event-detail")
|
||||
class DialogCalendarEventDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -272,3 +271,8 @@ declare global {
|
||||
"dialog-calendar-event-detail": DialogCalendarEventDetail;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
"dialog-calendar-event-detail",
|
||||
DialogCalendarEventDetail
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
||||
@@ -40,7 +40,6 @@ const SENSOR_DOMAINS = ["sensor"];
|
||||
const TEMPERATURE_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_TEMPERATURE];
|
||||
const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY];
|
||||
|
||||
@customElement("dialog-area-registry-detail")
|
||||
class DialogAreaDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -427,3 +426,5 @@ declare global {
|
||||
"dialog-area-registry-detail": DialogAreaDetail;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("dialog-area-registry-detail", DialogAreaDetail);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiDragHorizontalVariant, mdiTextureBox } from "@mdi/js";
|
||||
import { mdiClose, mdiDragHorizontalVariant, mdiTextureBox } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import {
|
||||
type AreasFloorHierarchy,
|
||||
@@ -11,10 +11,12 @@ import {
|
||||
} from "../../../common/areas/areas-floor-hierarchy";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-dialog-header";
|
||||
import "../../../components/ha-floor-icon";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-wa-dialog";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../../components/ha-md-dialog";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-sortable";
|
||||
@@ -47,6 +49,8 @@ class DialogAreasFloorsOrder extends LitElement {
|
||||
|
||||
@state() private _saving = false;
|
||||
|
||||
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||
|
||||
public async showDialog(
|
||||
_params: AreasFloorsOrderDialogParams
|
||||
): Promise<void> {
|
||||
@@ -62,8 +66,7 @@ class DialogAreasFloorsOrder extends LitElement {
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._saving = false;
|
||||
this._open = false;
|
||||
this._dialog?.close();
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
@@ -74,7 +77,7 @@ class DialogAreasFloorsOrder extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._hierarchy) {
|
||||
if (!this._open || !this._hierarchy) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
@@ -86,13 +89,17 @@ class DialogAreasFloorsOrder extends LitElement {
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${dialogTitle}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<div class="content">
|
||||
<ha-md-dialog open @closed=${this._dialogClosed}>
|
||||
<ha-dialog-header slot="headline">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
@click=${this.closeDialog}
|
||||
></ha-icon-button>
|
||||
<span slot="title" .title=${dialogTitle}>${dialogTitle}</span>
|
||||
</ha-dialog-header>
|
||||
<div slot="content" class="content">
|
||||
<ha-sortable
|
||||
handle-selector=".floor-handle"
|
||||
draggable-selector=".floor"
|
||||
@@ -109,23 +116,15 @@ class DialogAreasFloorsOrder extends LitElement {
|
||||
</ha-sortable>
|
||||
${this._renderUnassignedAreas()}
|
||||
</div>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
appearance="plain"
|
||||
>
|
||||
<div slot="actions">
|
||||
<ha-button @click=${this.closeDialog} appearance="plain">
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.disabled=${this._saving}
|
||||
>
|
||||
<ha-button @click=${this._save} .disabled=${this._saving}>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -392,13 +391,15 @@ class DialogAreasFloorsOrder extends LitElement {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-wa-dialog {
|
||||
ha-md-dialog {
|
||||
min-width: 600px;
|
||||
max-height: 90%;
|
||||
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6);
|
||||
--dialog-content-padding: 8px 24px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 580px), all and (max-height: 500px) {
|
||||
ha-wa-dialog {
|
||||
@media all and (max-width: 600px), all and (max-height: 500px) {
|
||||
ha-md-dialog {
|
||||
--md-dialog-container-shape: 0;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiTextureBox } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
@@ -27,7 +27,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { showAreaRegistryDetailDialog } from "./show-dialog-area-registry-detail";
|
||||
import type { FloorRegistryDetailDialogParams } from "./show-dialog-floor-registry-detail";
|
||||
|
||||
@customElement("dialog-floor-registry-detail")
|
||||
class DialogFloorDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -362,3 +361,5 @@ declare global {
|
||||
"dialog-floor-registry-detail": DialogFloorDetail;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("dialog-floor-registry-detail", DialogFloorDetail);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAlertCircleCheck,
|
||||
@@ -33,11 +32,11 @@ import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-service-icon";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import {
|
||||
@@ -289,12 +288,15 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
</ha-tooltip>`
|
||||
: nothing}
|
||||
|
||||
<ha-dropdown
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -302,24 +304,30 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-dropdown-item value="run">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
|
||||
<ha-md-menu-item .clickAction=${this._runAction}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize("ui.panel.config.automation.editor.actions.run")
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -328,10 +336,13 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -340,6 +351,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -350,10 +362,13 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span>C</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -362,6 +377,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -372,48 +388,51 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span>X</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-dropdown-item
|
||||
value="toggle_yaml_mode"
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this._uiModeAvailable || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${this.action.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
@@ -424,15 +443,15 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.actions.${this.action.enabled === false ? "enable" : "disable"}`
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -444,6 +463,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -458,8 +478,8 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
@@ -870,47 +890,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "run":
|
||||
this._runAction();
|
||||
break;
|
||||
case "rename":
|
||||
this._renameAction();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateAction();
|
||||
break;
|
||||
case "copy":
|
||||
this._copyAction();
|
||||
break;
|
||||
case "cut":
|
||||
this._cutAction();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "disable":
|
||||
this._onDisable();
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [rowStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -64,15 +64,8 @@ export class HaStopAction extends LitElement implements ActionElement {
|
||||
|
||||
private _responseChanged(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
const newAction = { ...this.action };
|
||||
const newValue = (ev.target as any).value;
|
||||
if (newValue) {
|
||||
newAction.response_variable = newValue;
|
||||
} else {
|
||||
delete newAction.response_variable;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newAction,
|
||||
value: { ...this.action, response_variable: (ev.target as any).value },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { LitVirtualizer } from "@lit-labs/virtualizer";
|
||||
import { consume } from "@lit/context";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import { mdiPlus, mdiTextureBox } from "@mdi/js";
|
||||
import type { IFuseOptions } from "fuse.js";
|
||||
import Fuse from "fuse.js";
|
||||
import { css, html, LitElement, nothing, type PropertyValues } from "lit";
|
||||
import {
|
||||
@@ -33,7 +34,6 @@ import "../../../../components/ha-section-title";
|
||||
import "../../../../components/ha-tree-indicator";
|
||||
import { ACTION_BUILDING_BLOCKS_GROUP } from "../../../../data/action";
|
||||
import {
|
||||
areaFloorComboBoxKeys,
|
||||
getAreasAndFloors,
|
||||
type AreaFloorValue,
|
||||
type FloorComboBoxItem,
|
||||
@@ -42,30 +42,23 @@ import { CONDITION_BUILDING_BLOCKS_GROUP } from "../../../../data/condition";
|
||||
import type { ConfigEntry } from "../../../../data/config_entries";
|
||||
import { labelsContext } from "../../../../data/context";
|
||||
import {
|
||||
deviceComboBoxKeys,
|
||||
getDevices,
|
||||
type DevicePickerItem,
|
||||
} from "../../../../data/device_registry";
|
||||
import {
|
||||
entityComboBoxKeys,
|
||||
getEntities,
|
||||
type EntityComboBoxItem,
|
||||
} from "../../../../data/entity_registry";
|
||||
import type { DomainManifestLookup } from "../../../../data/integration";
|
||||
import {
|
||||
getLabels,
|
||||
labelComboBoxKeys,
|
||||
type LabelRegistryEntry,
|
||||
} from "../../../../data/label_registry";
|
||||
import {
|
||||
getTargetComboBoxItemType,
|
||||
TARGET_SEPARATOR,
|
||||
} from "../../../../data/target";
|
||||
import {
|
||||
multiTermSearch,
|
||||
multiTermSortedSearch,
|
||||
type FuseWeightedKey,
|
||||
} from "../../../../resources/fuseMultiTerm";
|
||||
import { HaFuse } from "../../../../resources/fuse";
|
||||
import { loadVirtualizer } from "../../../../resources/virtualizer";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type {
|
||||
@@ -83,17 +76,6 @@ const TARGET_SEARCH_SECTIONS = [
|
||||
"label",
|
||||
] as const;
|
||||
|
||||
export const ITEM_SEARCH_KEYS: FuseWeightedKey[] = [
|
||||
{
|
||||
name: "name",
|
||||
weight: 10,
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
weight: 7,
|
||||
},
|
||||
];
|
||||
|
||||
type SearchSection = "item" | "block" | "entity" | "device" | "area" | "label";
|
||||
|
||||
@customElement("ha-automation-add-search")
|
||||
@@ -452,27 +434,27 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
private _keyFunction = (item: PickerComboBoxItem | string) =>
|
||||
typeof item === "string" ? item : item.id;
|
||||
|
||||
private _createFuseIndex = (states, keys: FuseWeightedKey[]) =>
|
||||
Fuse.createIndex(keys, states);
|
||||
private _createFuseIndex = (states) =>
|
||||
Fuse.createIndex(["search_labels"], states);
|
||||
|
||||
private _fuseIndexes = {
|
||||
area: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, areaFloorComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
entity: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, entityComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
device: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, deviceComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
label: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, labelComboBoxKeys)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
item: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, ITEM_SEARCH_KEYS)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
block: memoizeOne((states: PickerComboBoxItem[]) =>
|
||||
this._createFuseIndex(states, ITEM_SEARCH_KEYS)
|
||||
this._createFuseIndex(states)
|
||||
),
|
||||
};
|
||||
|
||||
@@ -496,12 +478,11 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
if (!selectedSection || selectedSection === "item") {
|
||||
let items = this._convertItemsToComboBoxItems(automationItems, type);
|
||||
if (searchTerm) {
|
||||
items = this._filterGroup(
|
||||
"item",
|
||||
items,
|
||||
searchTerm,
|
||||
ITEM_SEARCH_KEYS
|
||||
) as AutomationItemComboBoxItem[];
|
||||
items = this._filterGroup("item", items, searchTerm, {
|
||||
ignoreLocation: true,
|
||||
includeScore: true,
|
||||
minMatchCharLength: Math.min(2, this.filter.length),
|
||||
}) as AutomationItemComboBoxItem[];
|
||||
}
|
||||
|
||||
if (!selectedSection && items.length) {
|
||||
@@ -533,12 +514,11 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
blocks = this._filterGroup(
|
||||
"block",
|
||||
blocks,
|
||||
searchTerm,
|
||||
ITEM_SEARCH_KEYS
|
||||
) as AutomationItemComboBoxItem[];
|
||||
blocks = this._filterGroup("block", blocks, searchTerm, {
|
||||
ignoreLocation: true,
|
||||
includeScore: true,
|
||||
minMatchCharLength: Math.min(2, this.filter.length),
|
||||
}) as AutomationItemComboBoxItem[];
|
||||
}
|
||||
|
||||
if (!selectedSection && blocks.length) {
|
||||
@@ -570,7 +550,9 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
"entity",
|
||||
entityItems,
|
||||
searchTerm,
|
||||
entityComboBoxKeys
|
||||
undefined,
|
||||
(item: EntityComboBoxItem) =>
|
||||
item.stateObj?.entity_id === searchTerm
|
||||
) as EntityComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -599,12 +581,7 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
deviceItems = this._filterGroup(
|
||||
"device",
|
||||
deviceItems,
|
||||
searchTerm,
|
||||
deviceComboBoxKeys
|
||||
);
|
||||
deviceItems = this._filterGroup("device", deviceItems, searchTerm);
|
||||
}
|
||||
|
||||
if (!selectedSection && deviceItems.length) {
|
||||
@@ -640,9 +617,7 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
areasAndFloors = this._filterGroup(
|
||||
"area",
|
||||
areasAndFloors,
|
||||
searchTerm,
|
||||
areaFloorComboBoxKeys,
|
||||
false
|
||||
searchTerm
|
||||
) as FloorComboBoxItem[];
|
||||
}
|
||||
|
||||
@@ -689,12 +664,7 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
);
|
||||
|
||||
if (searchTerm) {
|
||||
labels = this._filterGroup(
|
||||
"label",
|
||||
labels,
|
||||
searchTerm,
|
||||
labelComboBoxKeys
|
||||
);
|
||||
labels = this._filterGroup("label", labels, searchTerm);
|
||||
}
|
||||
|
||||
if (!selectedSection && labels.length) {
|
||||
@@ -721,28 +691,50 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
| AutomationItemComboBoxItem
|
||||
)[],
|
||||
searchTerm: string,
|
||||
searchKeys: FuseWeightedKey[],
|
||||
sort = true
|
||||
fuseOptions?: IFuseOptions<any>,
|
||||
checkExact?: (
|
||||
item:
|
||||
| FloorComboBoxItem
|
||||
| PickerComboBoxItem
|
||||
| EntityComboBoxItem
|
||||
| AutomationItemComboBoxItem
|
||||
) => boolean
|
||||
) {
|
||||
const fuseIndex = this._fuseIndexes[type](items);
|
||||
const fuse = new HaFuse<
|
||||
| FloorComboBoxItem
|
||||
| PickerComboBoxItem
|
||||
| EntityComboBoxItem
|
||||
| AutomationItemComboBoxItem
|
||||
>(
|
||||
items,
|
||||
fuseOptions || {
|
||||
shouldSort: false,
|
||||
minMatchCharLength: Math.min(searchTerm.length, 2),
|
||||
},
|
||||
fuseIndex
|
||||
);
|
||||
|
||||
if (sort) {
|
||||
return multiTermSortedSearch<PickerComboBoxItem>(
|
||||
items,
|
||||
searchTerm,
|
||||
searchKeys,
|
||||
(item) => item.id,
|
||||
fuseIndex
|
||||
);
|
||||
const results = fuse.multiTermsSearch(searchTerm);
|
||||
let filteredItems = items;
|
||||
if (results) {
|
||||
filteredItems = results.map((result) => result.item);
|
||||
}
|
||||
|
||||
return multiTermSearch<PickerComboBoxItem>(
|
||||
items,
|
||||
searchTerm,
|
||||
searchKeys,
|
||||
fuseIndex,
|
||||
{ ignoreLocation: true }
|
||||
);
|
||||
if (!checkExact) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
// If there is exact match for entity id, put it first
|
||||
const index = filteredItems.findIndex((item) => checkExact(item));
|
||||
if (index === -1) {
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
const [exactMatch] = filteredItems.splice(index, 1);
|
||||
filteredItems.unshift(exactMatch);
|
||||
|
||||
return filteredItems;
|
||||
}
|
||||
|
||||
private _toggleSection(ev: Event) {
|
||||
@@ -795,11 +787,7 @@ export class HaAutomationAddSearch extends LitElement {
|
||||
iconPath,
|
||||
renderedIcon: icon,
|
||||
type,
|
||||
search_labels: {
|
||||
key,
|
||||
name,
|
||||
description,
|
||||
},
|
||||
search_labels: [key, name, description],
|
||||
}));
|
||||
|
||||
private _focusSearchList = () => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -34,11 +33,11 @@ import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-condition-icon";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
@@ -195,12 +194,15 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
<ha-dropdown
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -209,28 +211,34 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
>
|
||||
</ha-icon-button>
|
||||
|
||||
<ha-dropdown-item value="test">
|
||||
<ha-svg-icon slot="icon" .path=${mdiFlask}></ha-svg-icon>
|
||||
<ha-md-menu-item .clickAction=${this._testCondition}>
|
||||
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.test"
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.rename"
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -238,10 +246,13 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon
|
||||
>${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -250,6 +261,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -260,10 +272,13 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span>C</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon
|
||||
>${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -272,6 +287,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -282,45 +298,48 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span>X</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || this.first}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-dropdown-item value="toggle_yaml_mode">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-md-menu-item .clickAction=${this._toggleYamlMode}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${this.condition.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
@@ -331,15 +350,15 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.actions.${this.condition.enabled === false ? "enable" : "disable"}`
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
variant="danger"
|
||||
value="delete"
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -350,6 +369,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -364,8 +384,8 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
@@ -817,47 +837,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "test":
|
||||
this._testCondition();
|
||||
break;
|
||||
case "rename":
|
||||
this._renameCondition();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateCondition();
|
||||
break;
|
||||
case "copy":
|
||||
this._copyCondition();
|
||||
break;
|
||||
case "cut":
|
||||
this._cutCondition();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "disable":
|
||||
this._onDisable();
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
|
||||
@@ -124,9 +124,9 @@ export class HaPlatformCondition extends LitElement {
|
||||
|
||||
const hasOptional = Boolean(
|
||||
conditionDesc?.fields &&
|
||||
Object.values(conditionDesc.fields).some((field) =>
|
||||
showOptionalToggle(field)
|
||||
)
|
||||
Object.values(conditionDesc.fields).some((field) =>
|
||||
showOptionalToggle(field)
|
||||
)
|
||||
);
|
||||
|
||||
return html`
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -24,22 +23,20 @@ import {
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { goBack, navigate } from "../../../common/navigate";
|
||||
import { promiseTimeout } from "../../../common/util/promise-timeout";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-fade-in";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
@@ -77,6 +74,7 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
|
||||
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { Entries, HomeAssistant, Route } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
@@ -88,10 +86,10 @@ import {
|
||||
type EntityRegistryUpdate,
|
||||
showAutomationSaveDialog,
|
||||
} from "./automation-save-dialog/show-dialog-automation-save";
|
||||
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
||||
import "./blueprint-automation-editor";
|
||||
import "./manual-automation-editor";
|
||||
import type { HaManualAutomationEditor } from "./manual-automation-editor";
|
||||
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -114,7 +112,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-automation-editor")
|
||||
export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
KeyboardShortcutMixin(LitElement)
|
||||
) {
|
||||
@@ -294,10 +291,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
</ha-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
@@ -305,73 +299,99 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
></ha-icon-button>
|
||||
|
||||
${this._mode === "gui" && this.narrow
|
||||
? html`<ha-dropdown-item
|
||||
value="undo"
|
||||
? html`<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._undo}
|
||||
.disabled=${!this._undoRedoController.canUndo}
|
||||
>
|
||||
${this.hass.localize("ui.common.undo")}
|
||||
<ha-svg-icon slot="icon" .path=${mdiUndo}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="redo"
|
||||
<ha-svg-icon slot="graphic" .path=${mdiUndo}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._redo}
|
||||
.disabled=${!this._undoRedoController.canRedo}
|
||||
>
|
||||
${this.hass.localize("ui.common.redo")}
|
||||
<ha-svg-icon slot="icon" .path=${mdiRedo}></ha-svg-icon>
|
||||
</ha-dropdown-item>`
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRedo}></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
: nothing}
|
||||
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="info">
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showInfo}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="graphic"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="settings">
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showSettings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.show_settings"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="category">
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._editCategory}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.scene.picker.${this._registryEntry?.categories?.automation ? "edit_category" : "assign_category"}`
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiTag}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiTag}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="run">
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._runActions}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.run")}
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
|
||||
${stateObj && this.narrow
|
||||
? html`<ha-dropdown-item value="trace">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.show_trace"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiTransitConnection}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>`
|
||||
? html`<a
|
||||
href="/config/automation/trace/${encodeURIComponent(
|
||||
this._config.id!
|
||||
)}"
|
||||
>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.show_trace"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiTransitConnection}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
: nothing}
|
||||
|
||||
<ha-dropdown-item
|
||||
value="rename"
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._promptAutomationAlias}
|
||||
.disabled=${this._readOnly ||
|
||||
!this.automationId ||
|
||||
this._mode === "yaml"}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.rename")}
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
${!useBlueprint
|
||||
? html`
|
||||
<ha-dropdown-item
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._promptAutomationMode}
|
||||
.disabled=${this._readOnly || this._mode === "yaml"}
|
||||
>
|
||||
@@ -379,17 +399,18 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
"ui.panel.config.automation.editor.change_mode"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="graphic"
|
||||
.path=${mdiDebugStepOver}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-dropdown-item
|
||||
.disabled=${!!this._blueprintConfig ||
|
||||
<ha-list-item
|
||||
.disabled=${this._blueprintConfig ||
|
||||
(!this._readOnly && !this.automationId)}
|
||||
value="duplicate"
|
||||
graphic="icon"
|
||||
@click=${this._duplicate}
|
||||
>
|
||||
${this.hass.localize(
|
||||
this._readOnly
|
||||
@@ -397,60 +418,74 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
: "ui.panel.config.automation.editor.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="graphic"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
|
||||
${useBlueprint
|
||||
? html`
|
||||
<ha-dropdown-item
|
||||
value="take_control"
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._takeControl}
|
||||
.disabled=${this._readOnly}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.take_control"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiFileEdit}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiFileEdit}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-dropdown-item value="toggle_yaml_mode">
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._mode === "gui"
|
||||
? this._switchYamlMode
|
||||
: this._switchUiMode}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${this._mode === "gui" ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
<li divider role="separator"></li>
|
||||
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="disable">
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._toggle}
|
||||
>
|
||||
${stateObj?.state === "off"
|
||||
? this.hass.localize("ui.panel.config.automation.editor.enable")
|
||||
: this.hass.localize("ui.panel.config.automation.editor.disable")}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="graphic"
|
||||
.path=${stateObj?.state === "off"
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
|
||||
<ha-dropdown-item
|
||||
<ha-list-item
|
||||
.disabled=${!this.automationId}
|
||||
.variant=${this.automationId ? "danger" : "default"}
|
||||
value="delete"
|
||||
class=${classMap({ warning: Boolean(this.automationId) })}
|
||||
graphic="icon"
|
||||
@click=${this._deleteConfirm}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.picker.delete")}
|
||||
<ha-svg-icon
|
||||
class=${classMap({ warning: Boolean(this.automationId) })}
|
||||
slot="icon"
|
||||
slot="graphic"
|
||||
.path=${mdiDelete}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<div
|
||||
class=${this._mode === "yaml" ? "yaml-mode" : ""}
|
||||
@subscribe-automation-config=${this._subscribeAutomationConfig}
|
||||
@@ -1213,63 +1248,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
this._undoRedoController.redo();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "undo":
|
||||
this._undo();
|
||||
break;
|
||||
case "redo":
|
||||
this._redo();
|
||||
break;
|
||||
case "info":
|
||||
this._showInfo();
|
||||
break;
|
||||
case "settings":
|
||||
this._showSettings();
|
||||
break;
|
||||
case "category":
|
||||
this._editCategory();
|
||||
break;
|
||||
case "run":
|
||||
this._runActions();
|
||||
break;
|
||||
case "rename":
|
||||
this._promptAutomationAlias();
|
||||
break;
|
||||
case "change_mode":
|
||||
this._promptAutomationMode();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicate();
|
||||
break;
|
||||
case "take_control":
|
||||
this._takeControl();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
if (this._mode === "gui") {
|
||||
this._switchYamlMode();
|
||||
break;
|
||||
}
|
||||
this._switchUiMode();
|
||||
break;
|
||||
case "disable":
|
||||
this._toggle();
|
||||
break;
|
||||
case "delete":
|
||||
this._deleteConfirm();
|
||||
break;
|
||||
case "trace":
|
||||
this._showTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -1322,6 +1300,13 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
ha-button-menu a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -1354,3 +1339,5 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-automation-editor", HaAutomationEditor);
|
||||
|
||||
@@ -35,8 +35,6 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/ha-tooltip";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import {
|
||||
hasRejectedItems,
|
||||
@@ -329,19 +327,14 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
const date = new Date(automation.last_triggered);
|
||||
const now = new Date();
|
||||
const dayDifference = differenceInDays(now, date);
|
||||
const formattedTime = formatShortDateTimeWithConditionalYear(
|
||||
date,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
const elementId = "last-triggered-" + slugify(automation.entity_id);
|
||||
return html`
|
||||
${dayDifference > 3
|
||||
? formattedTime
|
||||
: html`
|
||||
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
|
||||
<span id=${elementId}>${relativeTime(date, locale)}</span>
|
||||
`}
|
||||
? formatShortDateTimeWithConditionalYear(
|
||||
date,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)
|
||||
: relativeTime(date, locale)}
|
||||
`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiDownload,
|
||||
@@ -16,13 +15,11 @@ import { repeat } from "lit/directives/repeat";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/trace/ha-trace-blueprint-config";
|
||||
import "../../../components/trace/ha-trace-config";
|
||||
import "../../../components/trace/ha-trace-logbook";
|
||||
@@ -107,7 +104,9 @@ export class HaAutomationTrace extends LitElement {
|
||||
appearance="plain"
|
||||
size="small"
|
||||
class="trace-link"
|
||||
@click=${this._navigateToAutomation}
|
||||
href="/config/automation/edit/${encodeURIComponent(
|
||||
stateObj.attributes.id
|
||||
)}"
|
||||
slot="toolbar-icon"
|
||||
>
|
||||
${this.hass.localize(
|
||||
@@ -115,50 +114,65 @@ export class HaAutomationTrace extends LitElement {
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
: ""}
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="show_info">
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showInfo}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="graphic"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
|
||||
${stateObj?.attributes.id && this.narrow
|
||||
? html`
|
||||
<ha-dropdown-item value="edit_automation">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.edit_automation"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<a
|
||||
class="trace-link"
|
||||
href="/config/automation/edit/${encodeURIComponent(
|
||||
stateObj.attributes.id
|
||||
)}"
|
||||
>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.edit_automation"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPencil}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
`
|
||||
: nothing}
|
||||
: ""}
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
<li divider role="separator"></li>
|
||||
|
||||
<ha-dropdown-item value="refresh">
|
||||
<ha-list-item graphic="icon" @click=${this._refreshTraces}>
|
||||
${this.hass.localize("ui.panel.config.automation.trace.refresh")}
|
||||
<ha-svg-icon slot="icon" .path=${mdiRefresh}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRefresh}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
|
||||
<ha-dropdown-item .disabled=${!this._trace} value="download_trace">
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!this._trace}
|
||||
@click=${this._downloadTrace}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.download_trace"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
|
||||
<div class="toolbar">
|
||||
${this._traces && this._traces.length > 0
|
||||
@@ -506,37 +520,6 @@ export class HaAutomationTrace extends LitElement {
|
||||
fireEvent(this, "hass-more-info", { entityId: this._entityId });
|
||||
}
|
||||
|
||||
private _navigateToAutomation() {
|
||||
if (this._entityId && this.hass.states[this._entityId]) {
|
||||
navigate(
|
||||
`/config/automation/edit/${encodeURIComponent(this.hass.states[this._entityId].attributes.id)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "show_info":
|
||||
this._showInfo();
|
||||
break;
|
||||
case "refresh":
|
||||
this._refreshTraces();
|
||||
break;
|
||||
case "download_trace":
|
||||
this._downloadTrace();
|
||||
break;
|
||||
case "edit_automation":
|
||||
this._navigateToAutomation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
||||
@@ -20,11 +20,10 @@ import { capitalizeFirstLetter } from "../../../../common/string/capitalize-firs
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type {
|
||||
Condition,
|
||||
@@ -37,7 +36,6 @@ import type { Action, Option } from "../../../../data/script";
|
||||
import { showPromptDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import "../action/ha-automation-action";
|
||||
import type HaAutomationAction from "../action/ha-automation-action";
|
||||
import "../condition/ha-automation-condition";
|
||||
@@ -48,6 +46,7 @@ import {
|
||||
overflowStyles,
|
||||
rowStyles,
|
||||
} from "../styles";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
|
||||
@customElement("ha-automation-option-row")
|
||||
export default class HaAutomationOptionRow extends LitElement {
|
||||
@@ -156,12 +155,15 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
|
||||
${this.option
|
||||
? html`
|
||||
<ha-dropdown
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -169,18 +171,24 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-md-menu-item
|
||||
@click=${this._renameOption}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-md-menu-item
|
||||
@click=${this._duplicateOption}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -189,42 +197,45 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiArrowUp}
|
||||
></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiArrowDown}
|
||||
></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
></ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
<ha-md-menu-item
|
||||
@click=${this._removeOption}
|
||||
class="warning"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -235,6 +246,7 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -249,8 +261,8 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
`
|
||||
: nothing}
|
||||
${!this.optionsInSidebar ? this._renderContent() : nothing}
|
||||
@@ -349,32 +361,6 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
fireEvent(this, "move-down");
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this._renameOption();
|
||||
break;
|
||||
case "delete":
|
||||
this._removeOption();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateOption();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _removeOption = () => {
|
||||
if (this.option) {
|
||||
fireEvent(this, "value-changed", {
|
||||
@@ -527,6 +513,9 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
overflowStyles,
|
||||
indentStyle,
|
||||
css`
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
h4 {
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
@@ -17,8 +16,8 @@ import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
|
||||
import type { ActionSidebarConfig } from "../../../../data/automation";
|
||||
import { domainToName } from "../../../../data/integration";
|
||||
@@ -117,7 +116,6 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle"
|
||||
@@ -128,35 +126,38 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
: ""}</span
|
||||
>
|
||||
|
||||
<ha-dropdown-item slot="menu-items" value="run">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.run}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.actions.run")}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="rename"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
value="duplicate"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -165,9 +166,9 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item slot="menu-items" value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -177,6 +178,7 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -188,13 +190,13 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="cut"
|
||||
.clickAction=${this.config.cut}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -204,6 +206,7 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -215,29 +218,32 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="toggle_yaml_mode"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
value="disable"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -246,14 +252,14 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -263,6 +269,7 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -278,7 +285,7 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: keyed(
|
||||
@@ -334,41 +341,6 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "run":
|
||||
this.config.run();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "copy":
|
||||
this.config.copy();
|
||||
break;
|
||||
case "cut":
|
||||
this.config.cut();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "disable":
|
||||
this.config.disable();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { preventDefaultStopPropagation } from "../../../../common/dom/prevent_default_stop_propagation";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import { haStyleScrollbar } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../ha-automation-editor-warning";
|
||||
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
|
||||
|
||||
export interface SidebarOverflowMenuEntry {
|
||||
clickAction: () => void;
|
||||
@@ -36,10 +36,6 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
|
||||
@property({ attribute: false }) public warnings?: string[];
|
||||
|
||||
@property({ attribute: false }) public handleDropdownSelect!: (
|
||||
ev: CustomEvent
|
||||
) => void;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@query(".card-content") private _contentElement!: HTMLDivElement;
|
||||
@@ -67,10 +63,14 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
<slot slot="title" name="title"></slot>
|
||||
<slot slot="subtitle" name="subtitle"></slot>
|
||||
<slot name="overflow-menu" slot="actionItems">
|
||||
<ha-dropdown
|
||||
@click=${preventDefaultStopPropagation}
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
@click=${this._openOverflowMenu}
|
||||
@keydown=${stopPropagation}
|
||||
placement="bottom-end"
|
||||
@closed=${stopPropagation}
|
||||
.positioning=${this.narrow ? "absolute" : "fixed"}
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -78,7 +78,7 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<slot name="menu-items"></slot>
|
||||
</ha-dropdown>
|
||||
</ha-md-button-menu>
|
||||
</slot>
|
||||
</ha-dialog-header>
|
||||
${this.warnings
|
||||
@@ -100,6 +100,11 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
|
||||
private _openOverflowMenu(ev: MouseEvent) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...super.styles,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
@@ -17,11 +16,9 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type {
|
||||
ConditionSidebarConfig,
|
||||
LegacyCondition,
|
||||
ConditionSidebarConfig,
|
||||
} from "../../../../data/automation";
|
||||
import { testCondition } from "../../../../data/automation";
|
||||
import {
|
||||
@@ -120,7 +117,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle"
|
||||
@@ -128,38 +124,42 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
|
||||
: ""}</span
|
||||
>
|
||||
<ha-dropdown-item slot="menu-items" value="test">
|
||||
<ha-svg-icon slot="icon" .path=${mdiFlask}></ha-svg-icon>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this._testCondition}>
|
||||
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.test"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="rename"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
|
||||
<ha-dropdown-item
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
value="duplicate"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -168,10 +168,10 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item slot="menu-items" value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -181,6 +181,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -192,14 +193,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="cut"
|
||||
.clickAction=${this.config.cut}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -209,6 +210,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -220,29 +222,32 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="toggle_yaml_mode"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
value="disable"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -251,14 +256,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -268,6 +273,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -283,7 +289,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: keyed(
|
||||
@@ -413,41 +419,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "test":
|
||||
this._testCondition();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "copy":
|
||||
this.config.copy();
|
||||
break;
|
||||
case "cut":
|
||||
this.config.cut();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "disable":
|
||||
this.config.disable();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [
|
||||
sidebarEditorStyles,
|
||||
overflowStyles,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiDelete,
|
||||
@@ -7,9 +6,8 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { OptionSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -52,34 +50,33 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.isWide=${this.isWide}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
${this.config.defaultOption
|
||||
? html`<span slot="overflow-menu"></span>`
|
||||
: html`
|
||||
<ha-dropdown-item
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="rename"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="duplicate"
|
||||
@click=${this.config.duplicate}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -88,15 +85,19 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
value="delete"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
variant="danger"
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||
@@ -122,33 +123,13 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
`}
|
||||
|
||||
<div class="description">${description}</div>
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
@@ -64,30 +62,29 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-dropdown-item
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="toggle_yaml_mode"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="delete"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
variant="danger"
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -97,6 +94,7 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -112,7 +110,7 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-selector-editor
|
||||
@@ -162,23 +160,6 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = sidebarEditorStyles;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
@@ -58,29 +56,28 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<ha-dropdown-item
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="toggle_yaml_mode"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="delete"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
variant="danger"
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -90,6 +87,7 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -105,7 +103,7 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-editor
|
||||
@@ -156,23 +154,6 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
@@ -16,8 +15,6 @@ import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type {
|
||||
LegacyTrigger,
|
||||
TriggerSidebarConfig,
|
||||
@@ -102,7 +99,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle"
|
||||
@@ -110,56 +106,60 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
|
||||
: ""}</span
|
||||
>
|
||||
<ha-dropdown-item
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="rename"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
${!this.yamlMode &&
|
||||
!("id" in this.config.config) &&
|
||||
!this._requestShowId
|
||||
? html`<ha-dropdown-item
|
||||
? html`<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="show_id"
|
||||
.clickAction=${this._showTriggerId}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.edit_id"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>`
|
||||
</ha-md-menu-item>`
|
||||
: nothing}
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
|
||||
<ha-dropdown-item
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
value="duplicate"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item slot="menu-items" value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -169,6 +169,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -180,14 +181,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="cut"
|
||||
.clickAction=${this.config.cut}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -197,6 +198,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -208,28 +210,32 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="toggle_yaml_mode"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
value="disable"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -238,14 +244,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
value="delete"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
variant="danger"
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -255,6 +261,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -270,7 +277,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-automation-trigger-editor
|
||||
@@ -328,41 +335,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
this._requestShowId = true;
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "show_id":
|
||||
this._showTriggerId();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "copy":
|
||||
this.config.copy();
|
||||
break;
|
||||
case "cut":
|
||||
this.config.cut();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "disable":
|
||||
this.config.disable();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -35,11 +34,11 @@ import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { TRIGGER_ICONS } from "../../../../components/ha-trigger-icon";
|
||||
import type {
|
||||
@@ -209,35 +208,41 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
<ha-dropdown
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown-item
|
||||
value="rename"
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameTrigger}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -246,10 +251,13 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -258,6 +266,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -268,10 +277,13 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span>C</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -280,6 +292,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -290,51 +303,51 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span>X</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-dropdown-item
|
||||
value="toggle_yaml_mode"
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!supported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}`
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<wa-divider></wa-divider>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-dropdown-item
|
||||
value="disable"
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${"enabled" in this.trigger && this.trigger.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
@@ -345,15 +358,15 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.actions.${"enabled" in this.trigger && this.trigger.enabled === false ? "enable" : "disable"}`
|
||||
)
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDelete}
|
||||
class="warning"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="icon"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -364,6 +377,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -378,8 +392,8 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
@@ -790,44 +804,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this._renameTrigger();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateTrigger();
|
||||
break;
|
||||
case "copy":
|
||||
this._copyTrigger();
|
||||
break;
|
||||
case "cut":
|
||||
this._cutTrigger();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "disable":
|
||||
this._onDisable();
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
|
||||
@@ -160,9 +160,9 @@ export class HaPlatformTrigger extends LitElement {
|
||||
|
||||
const hasOptional = Boolean(
|
||||
triggerDesc?.fields &&
|
||||
Object.values(triggerDesc.fields).some((field) =>
|
||||
showOptionalToggle(field)
|
||||
)
|
||||
Object.values(triggerDesc.fields).some((field) =>
|
||||
showOptionalToggle(field)
|
||||
)
|
||||
);
|
||||
|
||||
return html`
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-icon-button-prev";
|
||||
import "../../../../components/ha-wa-dialog";
|
||||
import "../../../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-password-field";
|
||||
@@ -30,18 +31,20 @@ type Step = (typeof STEPS)[number];
|
||||
class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _open = false;
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _step?: Step;
|
||||
|
||||
@state() private _params?: ChangeBackupEncryptionKeyDialogParams;
|
||||
|
||||
@query("ha-md-dialog") private _dialog!: HaMdDialog;
|
||||
|
||||
@state() private _newEncryptionKey?: string;
|
||||
|
||||
public showDialog(params: ChangeBackupEncryptionKeyDialogParams): void {
|
||||
this._params = params;
|
||||
this._step = STEPS[0];
|
||||
this._open = true;
|
||||
this._opened = true;
|
||||
this._newEncryptionKey = generateEncryptionKey();
|
||||
}
|
||||
|
||||
@@ -49,10 +52,10 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
if (this._params!.cancel) {
|
||||
this._params!.cancel();
|
||||
}
|
||||
if (this._open) {
|
||||
if (this._opened) {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
this._open = false;
|
||||
this._opened = false;
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
this._newEncryptionKey = undefined;
|
||||
@@ -61,7 +64,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
|
||||
private _done() {
|
||||
this._params?.submit!(true);
|
||||
this.closeDialog();
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
private _previousStep() {
|
||||
@@ -81,6 +84,10 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._opened || !this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const dialogTitle =
|
||||
this._step === "current" || this._step === "new"
|
||||
? this.hass.localize(
|
||||
@@ -89,40 +96,36 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
: "";
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${dialogTitle}
|
||||
prevent-scrim-close
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
${this._step === "new"
|
||||
? html`
|
||||
<ha-icon-button-prev
|
||||
slot="headerNavigationIcon"
|
||||
@click=${this._previousStep}
|
||||
></ha-icon-button-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button
|
||||
slot="headerNavigationIcon"
|
||||
data-dialog="close"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
`}
|
||||
${this._renderStepContent()}
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
|
||||
<ha-dialog-header slot="headline">
|
||||
${this._step === "new"
|
||||
? html`
|
||||
<ha-icon-button-prev
|
||||
slot="navigationIcon"
|
||||
@click=${this._previousStep}
|
||||
></ha-icon-button-prev>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
@click=${this.closeDialog}
|
||||
></ha-icon-button>
|
||||
`}
|
||||
<span slot="title">${dialogTitle}</span>
|
||||
</ha-dialog-header>
|
||||
<div slot="content">${this._renderStepContent()}</div>
|
||||
<div slot="actions">
|
||||
${this._step === "current"
|
||||
? html`
|
||||
<ha-button slot="primaryAction" @click=${this._nextStep}>
|
||||
<ha-button @click=${this._nextStep}>
|
||||
${this.hass.localize("ui.common.next")}
|
||||
</ha-button>
|
||||
`
|
||||
: this._step === "new"
|
||||
? html`
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._submit}
|
||||
.disabled=${!this._newEncryptionKey}
|
||||
variant="danger"
|
||||
@@ -133,14 +136,14 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
</ha-button>
|
||||
`
|
||||
: html`
|
||||
<ha-button slot="primaryAction" @click=${this._done}>
|
||||
<ha-button @click=${this._done}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.change_encryption_key.actions.done"
|
||||
)}
|
||||
</ha-button>
|
||||
`}
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -284,8 +287,10 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-wa-dialog {
|
||||
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6);
|
||||
ha-md-dialog {
|
||||
width: 90vw;
|
||||
max-width: 560px;
|
||||
--dialog-content-padding: 8px 24px;
|
||||
}
|
||||
ha-md-list {
|
||||
background: none;
|
||||
@@ -316,7 +321,14 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
flex: none;
|
||||
margin: -16px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-md-dialog {
|
||||
max-width: none;
|
||||
}
|
||||
div[slot="content"] {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-wa-dialog";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import "../../../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-password-field";
|
||||
import "../../../../components/ha-alert";
|
||||
import {
|
||||
canDecryptBackupOnDownload,
|
||||
getPreferredAgentForDownload,
|
||||
@@ -21,96 +27,102 @@ import type { DownloadDecryptedBackupDialogParams } from "./show-dialog-download
|
||||
class DialogDownloadDecryptedBackup extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _open = false;
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _params?: DownloadDecryptedBackupDialogParams;
|
||||
|
||||
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||
|
||||
@state() private _encryptionKey = "";
|
||||
|
||||
@state() private _error = "";
|
||||
|
||||
public showDialog(params: DownloadDecryptedBackupDialogParams): void {
|
||||
this._open = true;
|
||||
this._opened = true;
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._open = false;
|
||||
this._dialog?.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
private _dialogClosed() {
|
||||
if (this._open) {
|
||||
if (this._opened) {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
this._open = false;
|
||||
this._opened = false;
|
||||
this._params = undefined;
|
||||
this._encryptionKey = "";
|
||||
this._error = "";
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
if (!this._opened || !this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.title"
|
||||
)}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.description"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.download_backup_encrypted",
|
||||
{
|
||||
download_it_encrypted: html`<button
|
||||
class="link"
|
||||
@click=${this._downloadEncrypted}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.download_it_encrypted"
|
||||
)}
|
||||
</button>`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<ha-md-dialog open @closed=${this._dialogClosed} disable-cancel-action>
|
||||
<ha-dialog-header slot="headline">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
@click=${this.closeDialog}
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
<span slot="title">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.title"
|
||||
)}
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
|
||||
<ha-password-field
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.encryption_key"
|
||||
)}
|
||||
@input=${this._keyChanged}
|
||||
></ha-password-field>
|
||||
<div slot="content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.description"
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.download_backup_encrypted",
|
||||
{
|
||||
download_it_encrypted: html`<button
|
||||
class="link"
|
||||
@click=${this._downloadEncrypted}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.download_it_encrypted"
|
||||
)}
|
||||
</button>`,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
@click=${this._cancel}
|
||||
>
|
||||
<ha-password-field
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.encryption_key"
|
||||
)}
|
||||
@input=${this._keyChanged}
|
||||
></ha-password-field>
|
||||
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<ha-button appearance="plain" @click=${this._cancel}>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
|
||||
<ha-button slot="primaryAction" @click=${this._submit}>
|
||||
<ha-button @click=${this._submit}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.download"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -179,8 +191,17 @@ class DialogDownloadDecryptedBackup extends LitElement implements HassDialog {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-wa-dialog {
|
||||
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6);
|
||||
ha-md-dialog {
|
||||
--dialog-content-padding: 8px 24px;
|
||||
max-width: 500px;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-md-dialog {
|
||||
max-width: none;
|
||||
}
|
||||
div[slot="content"] {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button.link {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-icon-button-prev";
|
||||
import "../../../../components/ha-wa-dialog";
|
||||
import "../../../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-password-field";
|
||||
@@ -23,83 +24,89 @@ import type { ShowBackupEncryptionKeyDialogParams } from "./show-dialog-show-bac
|
||||
class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _params?: ShowBackupEncryptionKeyDialogParams;
|
||||
|
||||
@query("ha-md-dialog") private _dialog!: HaMdDialog;
|
||||
|
||||
public showDialog(params: ShowBackupEncryptionKeyDialogParams): void {
|
||||
this._params = params;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
if (this._open) {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
this._open = false;
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
return true;
|
||||
}
|
||||
|
||||
private _closeDialog() {
|
||||
this._dialog.close();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.title"
|
||||
)}
|
||||
prevent-scrim-close
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="headerNavigationIcon"
|
||||
data-dialog="close"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.description"
|
||||
)}
|
||||
</p>
|
||||
<div class="encryption-key">
|
||||
<p>${this._params?.currentKey}</p>
|
||||
<ha-md-dialog disable-cancel-action open @closed=${this.closeDialog}>
|
||||
<ha-dialog-header slot="headline">
|
||||
<ha-icon-button
|
||||
.path=${mdiContentCopy}
|
||||
@click=${this._copyKeyToClipboard}
|
||||
slot="navigationIcon"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
@click=${this._closeDialog}
|
||||
></ha-icon-button>
|
||||
<span slot="title">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.title"
|
||||
)}
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
<div slot="content">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.description"
|
||||
)}
|
||||
</p>
|
||||
<div class="encryption-key">
|
||||
<p>${this._params?.currentKey}</p>
|
||||
<ha-icon-button
|
||||
.path=${mdiContentCopy}
|
||||
@click=${this._copyKeyToClipboard}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button
|
||||
size="small"
|
||||
appearance="plain"
|
||||
slot="end"
|
||||
@click=${this._download}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</div>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-button slot="end" @click=${this._download}>
|
||||
<ha-svg-icon .path=${mdiDownload} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.encryption_key.download_emergency_kit_action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
<div slot="actions">
|
||||
<ha-button @click=${this._closeDialog}>
|
||||
${this.hass.localize("ui.common.close")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -128,8 +135,10 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-wa-dialog {
|
||||
--dialog-content-padding: var(--ha-space-2) var(--ha-space-6);
|
||||
ha-md-dialog {
|
||||
width: 90vw;
|
||||
max-width: 560px;
|
||||
--dialog-content-padding: 8px 24px;
|
||||
}
|
||||
ha-md-list {
|
||||
background: none;
|
||||
@@ -160,7 +169,14 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
flex: none;
|
||||
margin: -16px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-md-dialog {
|
||||
max-width: none;
|
||||
}
|
||||
div[slot="content"] {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { mdiFolderUpload } from "@mdi/js";
|
||||
import { mdiClose, mdiFolderUpload } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||
import {
|
||||
fireEvent,
|
||||
type HASSDomEvent,
|
||||
} from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-file-upload";
|
||||
import "../../../../components/ha-wa-dialog";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
||||
import {
|
||||
CORE_LOCAL_AGENT,
|
||||
HASSIO_LOCAL_AGENT,
|
||||
@@ -41,12 +43,11 @@ export class DialogUploadBackup
|
||||
|
||||
@state() private _formData?: BackupUploadFileFormData;
|
||||
|
||||
@state() private _open = false;
|
||||
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||
|
||||
public async showDialog(params: UploadBackupDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._formData = INITIAL_UPLOAD_FORM_DATA;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
private _dialogClosed() {
|
||||
@@ -55,12 +56,11 @@ export class DialogUploadBackup
|
||||
}
|
||||
this._formData = undefined;
|
||||
this._params = undefined;
|
||||
this._open = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._open = false;
|
||||
this._dialog?.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -74,44 +74,52 @@ export class DialogUploadBackup
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.title"
|
||||
)}
|
||||
?prevent-scrim-close=${this._uploading}
|
||||
<ha-md-dialog
|
||||
open
|
||||
@closed=${this._dialogClosed}
|
||||
.disableCancelAction=${this._uploading}
|
||||
>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
<ha-file-upload
|
||||
.hass=${this.hass}
|
||||
.uploading=${this._uploading}
|
||||
.icon=${mdiFolderUpload}
|
||||
.accept=${SUPPORTED_UPLOAD_FORMAT}
|
||||
.localize=${this.hass.localize}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.input_label"
|
||||
)}
|
||||
.supports=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.supports_tar"
|
||||
)}
|
||||
@file-picked=${this._filePicked}
|
||||
@files-cleared=${this._filesCleared}
|
||||
></ha-file-upload>
|
||||
<ha-dialog-footer slot="footer">
|
||||
<ha-dialog-header slot="headline">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
@click=${this.closeDialog}
|
||||
.disabled=${this._uploading}
|
||||
></ha-icon-button>
|
||||
|
||||
<span slot="title">
|
||||
${this.hass.localize("ui.panel.config.backup.dialogs.upload.title")}
|
||||
</span>
|
||||
</ha-dialog-header>
|
||||
<div slot="content">
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
<ha-file-upload
|
||||
.hass=${this.hass}
|
||||
.uploading=${this._uploading}
|
||||
.icon=${mdiFolderUpload}
|
||||
.accept=${SUPPORTED_UPLOAD_FORMAT}
|
||||
.localize=${this.hass.localize}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.input_label"
|
||||
)}
|
||||
.supports=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.supports_tar"
|
||||
)}
|
||||
@file-picked=${this._filePicked}
|
||||
@files-cleared=${this._filesCleared}
|
||||
></ha-file-upload>
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<ha-button
|
||||
slot="secondaryAction"
|
||||
appearance="plain"
|
||||
@click=${this.closeDialog}
|
||||
.disabled=${this._uploading}
|
||||
>${this.hass.localize("ui.common.cancel")}</ha-button
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._upload}
|
||||
.disabled=${!this._formValid() || this._uploading}
|
||||
>
|
||||
@@ -119,8 +127,8 @@ export class DialogUploadBackup
|
||||
"ui.panel.config.backup.dialogs.upload.action"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
</ha-wa-dialog>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -175,9 +183,15 @@ export class DialogUploadBackup
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-md-dialog {
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
max-height: 100%;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: var(--ha-space-4);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user