Compare commits
	
		
			3 Commits
		
	
	
		
			20220901.0
			...
			disabled-t
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d011794884 | ||
|   | 9b782cabe2 | ||
|   | a355bedd77 | 
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE.md
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w | |||||||
| <!-- | <!-- | ||||||
|   Provide details about the versions you are using, which helps us reproducing |   Provide details about the versions you are using, which helps us reproducing | ||||||
|   and finding the issue quicker. Version information is found in the |   and finding the issue quicker. Version information is found in the | ||||||
|   Home Assistant frontend: Settings -> About. |   Home Assistant frontend: Configuration -> Info. | ||||||
|  |  | ||||||
|   Browser version and operating system is important! Please try to replicate |   Browser version and operating system is important! Please try to replicate | ||||||
|   your issue in a different browser and be sure to include your findings. |   your issue in a different browser and be sure to include your findings. | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,4 +1,4 @@ | |||||||
| name: Report a bug with the UI / Dashboards | name: Report a bug with the UI, Frontend or Lovelace | ||||||
| description: Report an issue related to the Home Assistant frontend. | description: Report an issue related to the Home Assistant frontend. | ||||||
| labels: bug | labels: bug | ||||||
| body: | body: | ||||||
| @@ -9,7 +9,7 @@ body: | |||||||
|  |  | ||||||
|         If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue. |         If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue. | ||||||
|  |  | ||||||
|         **Please not not report issues for custom cards.** |         **Please not not report issues for custom Lovelace cards.** | ||||||
|  |  | ||||||
|         [fr]: https://github.com/home-assistant/frontend/discussions |         [fr]: https://github.com/home-assistant/frontend/discussions | ||||||
|         [releases]: https://github.com/home-assistant/home-assistant/releases |         [releases]: https://github.com/home-assistant/home-assistant/releases | ||||||
| @@ -64,7 +64,7 @@ body: | |||||||
|       label: What version of Home Assistant Core has the issue? |       label: What version of Home Assistant Core has the issue? | ||||||
|       placeholder: core- |       placeholder: core- | ||||||
|       description: > |       description: > | ||||||
|         Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/). |         Can be found in the Configuration panel -> Info. | ||||||
|   - type: input |   - type: input | ||||||
|     attributes: |     attributes: | ||||||
|       label: What was the last working version of Home Assistant Core? |       label: What was the last working version of Home Assistant Core? | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,17 +1,17 @@ | |||||||
| blank_issues_enabled: false | blank_issues_enabled: false | ||||||
| contact_links: | contact_links: | ||||||
|   - name: Request a feature for the UI / Dashboards |   - name: Request a feature for the UI, Frontend or Lovelace | ||||||
|     url: https://github.com/home-assistant/frontend/discussions/category_choices |     url: https://github.com/home-assistant/frontend/discussions/category_choices | ||||||
|     about: Request an new feature for the Home Assistant frontend. |     about: Request an new feature for the Home Assistant frontend. | ||||||
|   - name: Report a bug that is NOT related to the UI / Dashboards |   - name: Report a bug that is NOT related to the UI, Frontend or Lovelace | ||||||
|     url: https://github.com/home-assistant/core/issues |     url: https://github.com/home-assistant/core/issues | ||||||
|     about: This is the issue tracker for our frontend. Please report other issues in the backend ("core") repository. |     about: This is the issue tracker for our frontend. Please report other issues with the backend repository. | ||||||
|   - name: Report incorrect or missing information on our website |   - name: Report incorrect or missing information on our website | ||||||
|     url: https://github.com/home-assistant/home-assistant.io/issues |     url: https://github.com/home-assistant/home-assistant.io/issues | ||||||
|     about: Our documentation has its own issue tracker. Please report issues with the website there. |     about: Our documentation has its own issue tracker. Please report issues with the website there. | ||||||
|   - name: I have a question or need support |   - name: I have a question or need support | ||||||
|     url: https://www.home-assistant.io/help |     url: https://www.home-assistant.io/help | ||||||
|     about: We use GitHub for tracking bugs. Check our website for resources on getting help. |     about: We use GitHub for tracking bugs, check our website for resources on getting help. | ||||||
|   - name: I'm unsure where to go |   - name: I'm unsure where to go | ||||||
|     url: https://www.home-assistant.io/join-chat |     url: https://www.home-assistant.io/join-chat | ||||||
|     about: If you are unsure where to go, then joining our chat is recommended; Just ask! |     about: If you are unsure where to go, then joining our chat is recommended; Just ask! | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,8 +0,0 @@ | |||||||
| version: 2 |  | ||||||
| updates: |  | ||||||
|   - package-ecosystem: "github-actions" |  | ||||||
|     directory: "/" |  | ||||||
|     schedule: |  | ||||||
|       interval: weekly |  | ||||||
|       time: "06:00" |  | ||||||
|     open-pull-requests-limit: 10 |  | ||||||
							
								
								
									
										18
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -11,7 +11,7 @@ on: | |||||||
|       - master |       - master | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   NODE_VERSION: 16 |   NODE_VERSION: 14 | ||||||
|   NODE_OPTIONS: --max_old_space_size=6144 |   NODE_OPTIONS: --max_old_space_size=6144 | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
| @@ -19,9 +19,9 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v2 | ||||||
|       - name: Set up Node ${{ env.NODE_VERSION }} |       - name: Set up Node ${{ env.NODE_VERSION }} | ||||||
|         uses: actions/setup-node@v3 |         uses: actions/setup-node@v2 | ||||||
|         with: |         with: | ||||||
|           node-version: ${{ env.NODE_VERSION }} |           node-version: ${{ env.NODE_VERSION }} | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -43,9 +43,9 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v2 | ||||||
|       - name: Set up Node ${{ env.NODE_VERSION }} |       - name: Set up Node ${{ env.NODE_VERSION }} | ||||||
|         uses: actions/setup-node@v3 |         uses: actions/setup-node@v2 | ||||||
|         with: |         with: | ||||||
|           node-version: ${{ env.NODE_VERSION }} |           node-version: ${{ env.NODE_VERSION }} | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -62,9 +62,9 @@ jobs: | |||||||
|     needs: [lint, test] |     needs: [lint, test] | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v2 | ||||||
|       - name: Set up Node ${{ env.NODE_VERSION }} |       - name: Set up Node ${{ env.NODE_VERSION }} | ||||||
|         uses: actions/setup-node@v3 |         uses: actions/setup-node@v2 | ||||||
|         with: |         with: | ||||||
|           node-version: ${{ env.NODE_VERSION }} |           node-version: ${{ env.NODE_VERSION }} | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -81,9 +81,9 @@ jobs: | |||||||
|     needs: [lint, test] |     needs: [lint, test] | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v2 | ||||||
|       - name: Set up Node ${{ env.NODE_VERSION }} |       - name: Set up Node ${{ env.NODE_VERSION }} | ||||||
|         uses: actions/setup-node@v3 |         uses: actions/setup-node@v2 | ||||||
|         with: |         with: | ||||||
|           node-version: ${{ env.NODE_VERSION }} |           node-version: ${{ env.NODE_VERSION }} | ||||||
|           cache: yarn |           cache: yarn | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -23,7 +23,7 @@ jobs: | |||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|     - name: Checkout repository |     - name: Checkout repository | ||||||
|       uses: actions/checkout@v3 |       uses: actions/checkout@v2 | ||||||
|       with: |       with: | ||||||
|         # We must fetch at least the immediate parents so that if this is |         # We must fetch at least the immediate parents so that if this is | ||||||
|         # a pull request then we can checkout the head. |         # a pull request then we can checkout the head. | ||||||
| @@ -36,14 +36,14 @@ jobs: | |||||||
|  |  | ||||||
|     # Initializes the CodeQL tools for scanning. |     # Initializes the CodeQL tools for scanning. | ||||||
|     - name: Initialize CodeQL |     - name: Initialize CodeQL | ||||||
|       uses: github/codeql-action/init@v2 |       uses: github/codeql-action/init@v1 | ||||||
|       with: |       with: | ||||||
|         languages: ${{ matrix.language }} |         languages: ${{ matrix.language }} | ||||||
|  |  | ||||||
|     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java). |     # 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) |     # If this step fails, then you should remove it and run the build manually (see below) | ||||||
|     - name: Autobuild |     - name: Autobuild | ||||||
|       uses: github/codeql-action/autobuild@v2 |       uses: github/codeql-action/autobuild@v1 | ||||||
|  |  | ||||||
|     # ℹ️ Command-line programs to run using the OS shell. |     # ℹ️ Command-line programs to run using the OS shell. | ||||||
|     # 📚 https://git.io/JvXDl |     # 📚 https://git.io/JvXDl | ||||||
| @@ -57,4 +57,4 @@ jobs: | |||||||
|     #   make release |     #   make release | ||||||
|  |  | ||||||
|     - name: Perform CodeQL Analysis |     - name: Perform CodeQL Analysis | ||||||
|       uses: github/codeql-action/analyze@v2 |       uses: github/codeql-action/analyze@v1 | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								.github/workflows/demo.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -6,7 +6,7 @@ on: | |||||||
|       - dev |       - dev | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   NODE_VERSION: 16 |   NODE_VERSION: 14 | ||||||
|   NODE_OPTIONS: --max_old_space_size=6144 |   NODE_OPTIONS: --max_old_space_size=6144 | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
| @@ -14,9 +14,9 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v2 | ||||||
|       - name: Set up Node ${{ env.NODE_VERSION }} |       - name: Set up Node ${{ env.NODE_VERSION }} | ||||||
|         uses: actions/setup-node@v3 |         uses: actions/setup-node@v2 | ||||||
|         with: |         with: | ||||||
|           node-version: ${{ env.NODE_VERSION }} |           node-version: ${{ env.NODE_VERSION }} | ||||||
|           cache: yarn |           cache: yarn | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/lock.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -9,7 +9,7 @@ jobs: | |||||||
|   lock: |   lock: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - uses: dessant/lock-threads@v3.0.0 |       - uses: dessant/lock-threads@v2.0.1 | ||||||
|         with: |         with: | ||||||
|           github-token: ${{ github.token }} |           github-token: ${{ github.token }} | ||||||
|           issue-lock-inactive-days: "30" |           issue-lock-inactive-days: "30" | ||||||
|   | |||||||
							
								
								
									
										73
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,73 +0,0 @@ | |||||||
| name: Nightly |  | ||||||
|  |  | ||||||
| on: |  | ||||||
|   workflow_dispatch: |  | ||||||
|   schedule: |  | ||||||
|     - cron: "0 1 * * *" |  | ||||||
|  |  | ||||||
| env: |  | ||||||
|   PYTHON_VERSION: "3.10" |  | ||||||
|   NODE_VERSION: 16 |  | ||||||
|   NODE_OPTIONS: --max_old_space_size=6144 |  | ||||||
|  |  | ||||||
| permissions: |  | ||||||
|   actions: none |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   nightly: |  | ||||||
|     name: Nightly |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     permissions: |  | ||||||
|       contents: write |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout the repository |  | ||||||
|         uses: actions/checkout@v3 |  | ||||||
|  |  | ||||||
|       - name: Set up Python ${{ env.PYTHON_VERSION }} |  | ||||||
|         uses: actions/setup-python@v4 |  | ||||||
|         with: |  | ||||||
|           python-version: ${{ env.PYTHON_VERSION }} |  | ||||||
|  |  | ||||||
|       - name: Set up Node ${{ env.NODE_VERSION }} |  | ||||||
|         uses: actions/setup-node@v3 |  | ||||||
|         with: |  | ||||||
|           node-version: ${{ env.NODE_VERSION }} |  | ||||||
|           cache: yarn |  | ||||||
|  |  | ||||||
|       - name: Install dependencies |  | ||||||
|         run: yarn install |  | ||||||
|  |  | ||||||
|       - name: Download translations |  | ||||||
|         run: ./script/translations_download |  | ||||||
|         env: |  | ||||||
|           LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} |  | ||||||
|  |  | ||||||
|       - name: Bump version |  | ||||||
|         run: script/version_bump.js nightly |  | ||||||
|  |  | ||||||
|       - name: Build nightly Python wheels |  | ||||||
|         run: | |  | ||||||
|           pip install build |  | ||||||
|           yarn install |  | ||||||
|  |  | ||||||
|           script/build_frontend |  | ||||||
|  |  | ||||||
|           rm -rf dist home_assistant_frontend.egg-info |  | ||||||
|           python3 -m build |  | ||||||
|  |  | ||||||
|       - name: Archive translations |  | ||||||
|         run: tar -czvf translations.tar.gz translations |  | ||||||
|  |  | ||||||
|       - name: Upload build artifacts |  | ||||||
|         uses: actions/upload-artifact@v3 |  | ||||||
|         with: |  | ||||||
|           name: wheels |  | ||||||
|           path: dist/home_assistant_frontend*.whl |  | ||||||
|           if-no-files-found: error |  | ||||||
|  |  | ||||||
|       - name: Upload translations |  | ||||||
|         uses: actions/upload-artifact@v3 |  | ||||||
|         with: |  | ||||||
|           name: translations |  | ||||||
|           path: translations.tar.gz |  | ||||||
|           if-no-files-found: error |  | ||||||
							
								
								
									
										42
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -6,8 +6,8 @@ on: | |||||||
|       - published |       - published | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   PYTHON_VERSION: "3.10" |   PYTHON_VERSION: 3.8 | ||||||
|   NODE_VERSION: 16 |   NODE_VERSION: 14 | ||||||
|   NODE_OPTIONS: --max_old_space_size=6144 |   NODE_OPTIONS: --max_old_space_size=6144 | ||||||
|  |  | ||||||
| # Set default workflow permissions | # Set default workflow permissions | ||||||
| @@ -24,18 +24,18 @@ jobs: | |||||||
|       contents: write  # Required to upload release assets |       contents: write  # Required to upload release assets | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout the repository |       - name: Checkout the repository | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v2 | ||||||
|  |  | ||||||
|       - name: Verify version |       - name: Verify version | ||||||
|         uses: home-assistant/actions/helpers/verify-version@master |         uses: home-assistant/actions/helpers/verify-version@master | ||||||
|  |  | ||||||
|       - name: Set up Python ${{ env.PYTHON_VERSION }} |       - name: Set up Python ${{ env.PYTHON_VERSION }} | ||||||
|         uses: actions/setup-python@v4 |         uses: actions/setup-python@v2 | ||||||
|         with: |         with: | ||||||
|           python-version: ${{ env.PYTHON_VERSION }} |           python-version: ${{ env.PYTHON_VERSION }} | ||||||
|  |  | ||||||
|       - name: Set up Node ${{ env.NODE_VERSION }} |       - name: Set up Node ${{ env.NODE_VERSION }} | ||||||
|         uses: actions/setup-node@v3 |         uses: actions/setup-node@v2 | ||||||
|         with: |         with: | ||||||
|           node-version: ${{ env.NODE_VERSION }} |           node-version: ${{ env.NODE_VERSION }} | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -74,11 +74,33 @@ jobs: | |||||||
|           version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' ) |           version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' ) | ||||||
|           echo "home-assistant-frontend==$version" > ./requirements.txt |           echo "home-assistant-frontend==$version" > ./requirements.txt | ||||||
|  |  | ||||||
|       - name: Build wheels |       - name: Upload requirements.txt | ||||||
|         uses: home-assistant/wheels@2022.06.7 |         uses: actions/upload-artifact@v2 | ||||||
|         with: |         with: | ||||||
|           abi: cp310 |           name: requirements | ||||||
|           tag: musllinux_1_2 |           path: ./requirements.txt | ||||||
|           arch: amd64 |  | ||||||
|  |   build-wheels: | ||||||
|  |     name: Build wheels for ${{ matrix.arch }} | ||||||
|  |     needs: wheels-init | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         arch: ["aarch64", "armhf", "armv7", "amd64", "i386"] | ||||||
|  |         tag: | ||||||
|  |           - "3.9-alpine3.14" | ||||||
|  |     steps: | ||||||
|  |       - name: Download requirements.txt | ||||||
|  |         uses: actions/download-artifact@v2 | ||||||
|  |         with: | ||||||
|  |           name: requirements | ||||||
|  |  | ||||||
|  |       - name: Build wheels | ||||||
|  |         uses: home-assistant/wheels@master | ||||||
|  |         with: | ||||||
|  |           tag: ${{ matrix.tag }} | ||||||
|  |           arch: ${{ matrix.arch }} | ||||||
|  |           wheels-host: ${{ secrets.WHEELS_HOST }} | ||||||
|           wheels-key: ${{ secrets.WHEELS_KEY }} |           wheels-key: ${{ secrets.WHEELS_KEY }} | ||||||
|  |           wheels-user: wheels | ||||||
|           requirements: "requirements.txt" |           requirements: "requirements.txt" | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -10,7 +10,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: 90 days stale policy |       - name: 90 days stale policy | ||||||
|         uses: actions/stale@v5.1.1 |         uses: actions/stale@v3.0.13 | ||||||
|         with: |         with: | ||||||
|           repo-token: ${{ secrets.GITHUB_TOKEN }} |           repo-token: ${{ secrets.GITHUB_TOKEN }} | ||||||
|           days-before-stale: 90 |           days-before-stale: 90 | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -8,7 +8,7 @@ on: | |||||||
|       - src/translations/en.json |       - src/translations/en.json | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   NODE_VERSION: 16 |   NODE_VERSION: 14 | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   upload: |   upload: | ||||||
| @@ -16,7 +16,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout the repository |       - name: Checkout the repository | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v2 | ||||||
|  |  | ||||||
|       - name: Upload Translations |       - name: Upload Translations | ||||||
|         run: | |         run: | | ||||||
|   | |||||||
| @@ -1,4 +0,0 @@ | |||||||
| #!/usr/bin/env sh |  | ||||||
| . "$(dirname -- "$0")/_/husky.sh" |  | ||||||
|  |  | ||||||
| yarn run lint-staged --relative --shell "/bin/bash" |  | ||||||
							
								
								
									
										2
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -181,7 +181,7 @@ | |||||||
|     { |     { | ||||||
|       "label": "Run HA Core for Supervisor in devcontainer", |       "label": "Run HA Core for Supervisor in devcontainer", | ||||||
|       "type": "shell", |       "type": "shell", | ||||||
|       "command": "SUPERVISOR=${input:supervisorHost} SUPERVISOR_TOKEN=${input:supervisorToken} script/core", |       "command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core", | ||||||
|       "isBackground": true, |       "isBackground": true, | ||||||
|       "group": { |       "group": { | ||||||
|         "kind": "build", |         "kind": "build", | ||||||
|   | |||||||
| @@ -26,8 +26,8 @@ module.exports = { | |||||||
|   }, |   }, | ||||||
|   version() { |   version() { | ||||||
|     const version = fs |     const version = fs | ||||||
|       .readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8") |       .readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8") | ||||||
|       .match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/); |       .match(/version\W+=\W(\d{8}\.\d)/); | ||||||
|     if (!version) { |     if (!version) { | ||||||
|       throw Error("Version not found"); |       throw Error("Version not found"); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -156,12 +156,3 @@ gulp.task("gen-icons-json", (done) => { | |||||||
|  |  | ||||||
|   done(); |   done(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| gulp.task("gen-dummy-icons-json", (done) => { |  | ||||||
|   if (!fs.existsSync(OUTPUT_DIR)) { |  | ||||||
|     fs.mkdirSync(OUTPUT_DIR, { recursive: true }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   fs.writeFileSync(path.resolve(OUTPUT_DIR, "iconList.json"), "[]"); |  | ||||||
|   done(); |  | ||||||
| }); |  | ||||||
|   | |||||||
| @@ -9,7 +9,6 @@ require("./compress.js"); | |||||||
| require("./rollup.js"); | require("./rollup.js"); | ||||||
| require("./gather-static.js"); | require("./gather-static.js"); | ||||||
| require("./translations.js"); | require("./translations.js"); | ||||||
| require("./gen-icons-json.js"); |  | ||||||
|  |  | ||||||
| gulp.task( | gulp.task( | ||||||
|   "develop-hassio", |   "develop-hassio", | ||||||
| @@ -18,7 +17,6 @@ gulp.task( | |||||||
|       process.env.NODE_ENV = "development"; |       process.env.NODE_ENV = "development"; | ||||||
|     }, |     }, | ||||||
|     "clean-hassio", |     "clean-hassio", | ||||||
|     "gen-dummy-icons-json", |  | ||||||
|     "gen-index-hassio-dev", |     "gen-index-hassio-dev", | ||||||
|     "build-supervisor-translations", |     "build-supervisor-translations", | ||||||
|     "copy-translations-supervisor", |     "copy-translations-supervisor", | ||||||
| @@ -35,7 +33,6 @@ gulp.task( | |||||||
|       process.env.NODE_ENV = "production"; |       process.env.NODE_ENV = "production"; | ||||||
|     }, |     }, | ||||||
|     "clean-hassio", |     "clean-hassio", | ||||||
|     "gen-dummy-icons-json", |  | ||||||
|     "build-supervisor-translations", |     "build-supervisor-translations", | ||||||
|     "copy-translations-supervisor", |     "copy-translations-supervisor", | ||||||
|     "build-locale-data", |     "build-locale-data", | ||||||
|   | |||||||
| @@ -1,30 +1 @@ | |||||||
| [ | [] | ||||||
|   { |  | ||||||
|     "path": "M20,20H7A2,2 0 0,1 5,18V8.94L2.23,5.64C2.09,5.47 2,5.24 2,5A1,1 0 0,1 3,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20M8.5,7A0.5,0.5 0 0,0 8,7.5V8.5A0.5,0.5 0 0,0 8.5,9H18.5A0.5,0.5 0 0,0 19,8.5V7.5A0.5,0.5 0 0,0 18.5,7H8.5M8.5,11A0.5,0.5 0 0,0 8,11.5V12.5A0.5,0.5 0 0,0 8.5,13H18.5A0.5,0.5 0 0,0 19,12.5V11.5A0.5,0.5 0 0,0 18.5,11H8.5M8.5,15A0.5,0.5 0 0,0 8,15.5V16.5A0.5,0.5 0 0,0 8.5,17H13.5A0.5,0.5 0 0,0 14,16.5V15.5A0.5,0.5 0 0,0 13.5,15H8.5Z", |  | ||||||
|     "name": "android-messages" |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     "path": "M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,12L17.5,10.5L15,12V4H20V12Z", |  | ||||||
|     "name": "book-variant-multiple" |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     "path": "M21,14H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10L8,21V22H16V21L14,18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z", |  | ||||||
|     "name": "desktop-mac" |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     "path": "M21,14V4H3V14H21M21,2A2,2 0 0,1 23,4V16A2,2 0 0,1 21,18H14L16,21V22H8V21L10,18H3C1.89,18 1,17.1 1,16V4C1,2.89 1.89,2 3,2H21M4,5H15V10H4V5M16,5H20V7H16V5M20,8V13H16V8H20M4,11H9V13H4V11M10,11H15V13H10V11Z", |  | ||||||
|     "name": "desktop-mac-dashboard" |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     "path": "M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z", |  | ||||||
|     "name": "discord" |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     "path": "M8.06,7.78C7.5,7.78 7.17,7.73 7.08,7.64L6.66,13.73C7.19,14.05 7.88,14.3 8.72,14.5C9.56,14.71 10.78,14.77 12.38,14.67C13.97,14.58 15.63,14.23 17.34,13.64L16.55,4.22C15.67,5.09 14.38,5.91 12.66,6.66C11.13,7.31 9.81,7.69 8.72,7.78H8.06M7.97,5.34C7.28,5.94 7,6.34 7.13,6.56C7.22,6.78 7.7,6.84 8.58,6.75C9.67,6.66 10.91,6.31 12.28,5.72C13.22,5.31 14.03,4.88 14.72,4.41C15.41,3.94 15.88,3.55 16.13,3.23C16.38,2.92 16.47,2.7 16.41,2.58C16.34,2.42 16.03,2.34 15.47,2.34C14.34,2.34 12.94,2.7 11.25,3.42C9.81,4.05 8.72,4.69 7.97,5.34M17.34,2.2C17.41,2.33 17.44,2.47 17.44,2.63L18.61,17C18.61,18.73 18,20.09 16.83,21.07C15.64,22.05 14.03,22.55 12,22.55C10,22.55 8.4,22.04 7.2,21C6,20 5.39,18.64 5.39,16.92L6.09,6.47C6.09,6.22 6.2,5.94 6.42,5.63C6.64,5.31 6.84,5.06 7.03,4.88L7.36,4.59C8.33,3.78 9.5,3.08 10.88,2.5C11.81,2.08 12.73,1.77 13.62,1.57C14.5,1.37 15.3,1.3 16,1.38C16.71,1.46 17.16,1.73 17.34,2.2Z", |  | ||||||
|     "name": "google-home" |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     "path": "M19.25,19H4.75V3H19.25M14,22H10V21H14M18,0H6A3,3 0 0,0 3,3V21A3,3 0 0,0 6,24H18A3,3 0 0,0 21,21V3A3,3 0 0,0 18,0Z", |  | ||||||
|     "name": "tablet-android" |  | ||||||
|   } |  | ||||||
| ] |  | ||||||
|   | |||||||
| @@ -76,7 +76,7 @@ const createWebpackConfig = ({ | |||||||
|       chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", |       chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", | ||||||
|     }, |     }, | ||||||
|     plugins: [ |     plugins: [ | ||||||
|       !isStatsBuild && new WebpackBar({ fancy: !isProdBuild }), |       new WebpackBar({ fancy: !isProdBuild }), | ||||||
|       new WebpackManifestPlugin({ |       new WebpackManifestPlugin({ | ||||||
|         // Only include the JS of entrypoints |         // Only include the JS of entrypoints | ||||||
|         filter: (file) => file.isInitial && !file.name.endsWith(".map"), |         filter: (file) => file.isInitial && !file.name.endsWith(".map"), | ||||||
|   | |||||||
| @@ -1,9 +0,0 @@ | |||||||
| # These redirects are handled by Netlify |  | ||||||
| # |  | ||||||
|  |  | ||||||
| # Some custom cards are not prefixing the instance URL when fetching data |  | ||||||
| # and can end up fetching the data from the Cast domain instead of HA. |  | ||||||
| # This will make sure that some common ones are replaced with a placeholder. |  | ||||||
| /api/camera_proxy/* /images/google-nest-hub.png |  | ||||||
| /api/camera_proxy_stream/* /images/google-nest-hub.png |  | ||||||
| /api/media_player_proxy/* /images/google-nest-hub.png |  | ||||||
| @@ -194,7 +194,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | |||||||
|                     type: "state-icon", |                     type: "state-icon", | ||||||
|                     tap_action: { |                     tap_action: { | ||||||
|                       action: "call-service", |                       action: "call-service", | ||||||
|                       data: { |                       service_data: { | ||||||
|                         entity_id: "group.downstairs_lights", |                         entity_id: "group.downstairs_lights", | ||||||
|                       }, |                       }, | ||||||
|                       service: "homeassistant.toggle", |                       service: "homeassistant.toggle", | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|       attributes: { |       attributes: { | ||||||
|         hidden: true, |         hidden: true, | ||||||
|         radius: 50, |         radius: 50, | ||||||
|         friendly_name: "School", |         friendly_name: "Skolan", | ||||||
|         icon: "mdi:school", |         icon: "mdi:school", | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
| @@ -137,7 +137,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|       state: "73", |       state: "73", | ||||||
|       attributes: { |       attributes: { | ||||||
|         unit_of_measurement: "%", |         unit_of_measurement: "%", | ||||||
|         friendly_name: "Oskar battery", |         friendly_name: "oskar batteri", | ||||||
|         device_class: "battery", |         device_class: "battery", | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
| @@ -146,7 +146,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|       state: "88", |       state: "88", | ||||||
|       attributes: { |       attributes: { | ||||||
|         unit_of_measurement: "%", |         unit_of_measurement: "%", | ||||||
|         friendly_name: "Bella battery", |         friendly_name: "bella batteri", | ||||||
|         device_class: "battery", |         device_class: "battery", | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
| @@ -154,7 +154,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|       entity_id: "binary_sensor.unifi_camera", |       entity_id: "binary_sensor.unifi_camera", | ||||||
|       state: "off", |       state: "off", | ||||||
|       attributes: { |       attributes: { | ||||||
|         friendly_name: "Motion sensor camera", |         friendly_name: "R\u00f6relsesensor kamera", | ||||||
|         icon: "mdi:walk", |         icon: "mdi:walk", | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
| @@ -707,7 +707,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|           }, |           }, | ||||||
|         ], |         ], | ||||||
|         cloudiness: 25, |         cloudiness: 25, | ||||||
|         friendly_name: "Weather", |         friendly_name: "V\u00e4der", | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|     "binary_sensor.ubiquiti_switch": { |     "binary_sensor.ubiquiti_switch": { | ||||||
| @@ -731,7 +731,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|         round_trip_time_max: "0.626", |         round_trip_time_max: "0.626", | ||||||
|         round_trip_time_mdev: "", |         round_trip_time_mdev: "", | ||||||
|         round_trip_time_min: "0.358", |         round_trip_time_min: "0.358", | ||||||
|         friendly_name: "Entrance camera", |         friendly_name: "Entr\u00e9 kamera", | ||||||
|         device_class: "connectivity", |         device_class: "connectivity", | ||||||
|         icon: "mdi:cctv", |         icon: "mdi:cctv", | ||||||
|       }, |       }, | ||||||
| @@ -797,7 +797,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|       attributes: { |       attributes: { | ||||||
|         battery_level: 34, |         battery_level: 34, | ||||||
|         on: true, |         on: true, | ||||||
|         friendly_name: "Porch motion sensor", |         friendly_name: "altan_motion_sensor", | ||||||
|         device_class: "motion", |         device_class: "motion", | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
| @@ -807,7 +807,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|       attributes: { |       attributes: { | ||||||
|         battery_level: 88, |         battery_level: 88, | ||||||
|         on: true, |         on: true, | ||||||
|         friendly_name: "Back door sensor", |         friendly_name: "Altand\u00f6rren sensor", | ||||||
|         device_class: "opening", |         device_class: "opening", | ||||||
|         icon: "mdi:door", |         icon: "mdi:door", | ||||||
|       }, |       }, | ||||||
| @@ -818,7 +818,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|       attributes: { |       attributes: { | ||||||
|         battery_level: 74, |         battery_level: 74, | ||||||
|         on: true, |         on: true, | ||||||
|         friendly_name: "Bathroom motion sensor", |         friendly_name: "badrumssensor", | ||||||
|         device_class: "motion", |         device_class: "motion", | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
| @@ -829,7 +829,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|         battery_level: 47, |         battery_level: 47, | ||||||
|         on: true, |         on: true, | ||||||
|         dark: true, |         dark: true, | ||||||
|         friendly_name: "Basement motion sensor", |         friendly_name: "R\u00f6relsesensor k\u00e4llaren 1", | ||||||
|         device_class: "motion", |         device_class: "motion", | ||||||
|         icon: "mdi:walk", |         icon: "mdi:walk", | ||||||
|       }, |       }, | ||||||
| @@ -841,7 +841,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|         battery_level: 60, |         battery_level: 60, | ||||||
|         on: true, |         on: true, | ||||||
|         dark: true, |         dark: true, | ||||||
|         friendly_name: "Laundy room motion sensor", |         friendly_name: "R\u00f6relsesensor tv\u00e4ttstugan", | ||||||
|         device_class: "motion", |         device_class: "motion", | ||||||
|         icon: "mdi:walk", |         icon: "mdi:walk", | ||||||
|       }, |       }, | ||||||
| @@ -863,7 +863,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|       attributes: { |       attributes: { | ||||||
|         battery_level: 60, |         battery_level: 60, | ||||||
|         on: true, |         on: true, | ||||||
|         friendly_name: "Pantry motion sensor", |         friendly_name: "R\u00f6relsesensor skafferiet", | ||||||
|         device_class: "motion", |         device_class: "motion", | ||||||
|         icon: "mdi:walk", |         icon: "mdi:walk", | ||||||
|       }, |       }, | ||||||
| @@ -875,7 +875,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|         battery_level: 60, |         battery_level: 60, | ||||||
|         on: true, |         on: true, | ||||||
|         dark: true, |         dark: true, | ||||||
|         friendly_name: "Stair motion sensor", |         friendly_name: "R\u00f6relsesensor k\u00e4llaren 2", | ||||||
|         device_class: "motion", |         device_class: "motion", | ||||||
|         icon: "mdi:walk", |         icon: "mdi:walk", | ||||||
|       }, |       }, | ||||||
| @@ -887,7 +887,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | |||||||
|         battery_level: 47, |         battery_level: 47, | ||||||
|         on: true, |         on: true, | ||||||
|         dark: true, |         dark: true, | ||||||
|         friendly_name: "Bench sensor", |         friendly_name: "B\u00e4nksensor", | ||||||
|         device_class: "motion", |         device_class: "motion", | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -277,7 +277,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | |||||||
|           ], |           ], | ||||||
|           show_header_toggle: false, |           show_header_toggle: false, | ||||||
|           type: "entities", |           type: "entities", | ||||||
|           title: "Bandwidth", |           title: "Bandbredd", | ||||||
|         }, |         }, | ||||||
|         // { |         // { | ||||||
|         //   title: "Updater", |         //   title: "Updater", | ||||||
|   | |||||||
| @@ -377,7 +377,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | |||||||
|               name: "AC bed", |               name: "AC bed", | ||||||
|               tap_action: { |               tap_action: { | ||||||
|                 action: "call-service", |                 action: "call-service", | ||||||
|                 data: { |                 service_data: { | ||||||
|                   entity_id: "script.air_cleaner_quiet", |                   entity_id: "script.air_cleaner_quiet", | ||||||
|                 }, |                 }, | ||||||
|                 service: "script.turn_on", |                 service: "script.turn_on", | ||||||
| @@ -390,7 +390,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | |||||||
|               name: "AC bed", |               name: "AC bed", | ||||||
|               tap_action: { |               tap_action: { | ||||||
|                 action: "call-service", |                 action: "call-service", | ||||||
|                 data: { |                 service_data: { | ||||||
|                   entity_id: "script.air_cleaner_auto", |                   entity_id: "script.air_cleaner_auto", | ||||||
|                 }, |                 }, | ||||||
|                 service: "script.turn_on", |                 service: "script.turn_on", | ||||||
| @@ -403,7 +403,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | |||||||
|               name: "AC bed", |               name: "AC bed", | ||||||
|               tap_action: { |               tap_action: { | ||||||
|                 action: "call-service", |                 action: "call-service", | ||||||
|                 data: { |                 service_data: { | ||||||
|                   entity_id: "script.air_cleaner_turbo", |                   entity_id: "script.air_cleaner_turbo", | ||||||
|                 }, |                 }, | ||||||
|                 service: "script.turn_on", |                 service: "script.turn_on", | ||||||
| @@ -416,7 +416,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | |||||||
|               name: "AC", |               name: "AC", | ||||||
|               tap_action: { |               tap_action: { | ||||||
|                 action: "call-service", |                 action: "call-service", | ||||||
|                 data: { |                 service_data: { | ||||||
|                   entity_id: "script.ac_off", |                   entity_id: "script.ac_off", | ||||||
|                 }, |                 }, | ||||||
|                 service: "script.turn_on", |                 service: "script.turn_on", | ||||||
| @@ -429,7 +429,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | |||||||
|               name: "AC", |               name: "AC", | ||||||
|               tap_action: { |               tap_action: { | ||||||
|                 action: "call-service", |                 action: "call-service", | ||||||
|                 data: { |                 service_data: { | ||||||
|                   entity_id: "script.ac_on", |                   entity_id: "script.ac_on", | ||||||
|                 }, |                 }, | ||||||
|                 service: "script.turn_on", |                 service: "script.turn_on", | ||||||
| @@ -629,7 +629,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | |||||||
|                   entity: "scene.morning_lights", |                   entity: "scene.morning_lights", | ||||||
|                   tap_action: { |                   tap_action: { | ||||||
|                     action: "call-service", |                     action: "call-service", | ||||||
|                     data: { |                     service_data: { | ||||||
|                       entity_id: "scene.morning_lights", |                       entity_id: "scene.morning_lights", | ||||||
|                     }, |                     }, | ||||||
|                     service: "scene.turn_on", |                     service: "scene.turn_on", | ||||||
| @@ -641,7 +641,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | |||||||
|                   entity: "scene.movie_time", |                   entity: "scene.movie_time", | ||||||
|                   tap_action: { |                   tap_action: { | ||||||
|                     action: "call-service", |                     action: "call-service", | ||||||
|                     data: { |                     service_data: { | ||||||
|                       entity_id: "scene.movie_time", |                       entity_id: "scene.movie_time", | ||||||
|                     }, |                     }, | ||||||
|                     service: "scene.turn_on", |                     service: "scene.turn_on", | ||||||
| @@ -702,7 +702,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | |||||||
|                   entity: "light.downstairs_lights", |                   entity: "light.downstairs_lights", | ||||||
|                   tap_action: { |                   tap_action: { | ||||||
|                     action: "call-service", |                     action: "call-service", | ||||||
|                     data: { |                     service_data: { | ||||||
|                       entity_id: "light.downstairs_lights", |                       entity_id: "light.downstairs_lights", | ||||||
|                     }, |                     }, | ||||||
|                     service: "light.toggle", |                     service: "light.toggle", | ||||||
| @@ -714,7 +714,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | |||||||
|                   entity: "light.upstairs_lights", |                   entity: "light.upstairs_lights", | ||||||
|                   tap_action: { |                   tap_action: { | ||||||
|                     action: "call-service", |                     action: "call-service", | ||||||
|                     data: { |                     service_data: { | ||||||
|                       entity_id: "light.upstairs_lights", |                       entity_id: "light.upstairs_lights", | ||||||
|                     }, |                     }, | ||||||
|                     service: "light.toggle", |                     service: "light.toggle", | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Compat needs to be first import | // Compat needs to be first import | ||||||
|  | import "../../src/resources/compatibility"; | ||||||
| import { isNavigationClick } from "../../src/common/dom/is-navigation-click"; | import { isNavigationClick } from "../../src/common/dom/is-navigation-click"; | ||||||
| import { navigate } from "../../src/common/navigate"; | import { navigate } from "../../src/common/navigate"; | ||||||
| import { | import { | ||||||
| @@ -6,14 +7,9 @@ import { | |||||||
|   provideHass, |   provideHass, | ||||||
| } from "../../src/fake_data/provide_hass"; | } from "../../src/fake_data/provide_hass"; | ||||||
| import { HomeAssistantAppEl } from "../../src/layouts/home-assistant"; | import { HomeAssistantAppEl } from "../../src/layouts/home-assistant"; | ||||||
| import "../../src/resources/compatibility"; |  | ||||||
| import { HomeAssistant } from "../../src/types"; | import { HomeAssistant } from "../../src/types"; | ||||||
| import { selectedDemoConfig } from "./configs/demo-configs"; | import { selectedDemoConfig } from "./configs/demo-configs"; | ||||||
| import { mockAuth } from "./stubs/auth"; | import { mockAuth } from "./stubs/auth"; | ||||||
| import { mockConfigEntries } from "./stubs/config_entries"; |  | ||||||
| import { mockEnergy } from "./stubs/energy"; |  | ||||||
| import { energyEntities } from "./stubs/entities"; |  | ||||||
| import { mockEntityRegistry } from "./stubs/entity_registry"; |  | ||||||
| import { mockEvents } from "./stubs/events"; | import { mockEvents } from "./stubs/events"; | ||||||
| import { mockFrontend } from "./stubs/frontend"; | import { mockFrontend } from "./stubs/frontend"; | ||||||
| import { mockHistory } from "./stubs/history"; | import { mockHistory } from "./stubs/history"; | ||||||
| @@ -24,6 +20,9 @@ import { mockShoppingList } from "./stubs/shopping_list"; | |||||||
| import { mockSystemLog } from "./stubs/system_log"; | import { mockSystemLog } from "./stubs/system_log"; | ||||||
| import { mockTemplate } from "./stubs/template"; | import { mockTemplate } from "./stubs/template"; | ||||||
| import { mockTranslations } from "./stubs/translations"; | import { mockTranslations } from "./stubs/translations"; | ||||||
|  | import { mockEnergy } from "./stubs/energy"; | ||||||
|  | import { mockConfig } from "./stubs/config"; | ||||||
|  | import { energyEntities } from "./stubs/entities"; | ||||||
|  |  | ||||||
| class HaDemo extends HomeAssistantAppEl { | class HaDemo extends HomeAssistantAppEl { | ||||||
|   protected async _initializeHass() { |   protected async _initializeHass() { | ||||||
| @@ -52,38 +51,8 @@ class HaDemo extends HomeAssistantAppEl { | |||||||
|     mockMediaPlayer(hass); |     mockMediaPlayer(hass); | ||||||
|     mockFrontend(hass); |     mockFrontend(hass); | ||||||
|     mockEnergy(hass); |     mockEnergy(hass); | ||||||
|  |     mockConfig(hass); | ||||||
|     mockPersistentNotification(hass); |     mockPersistentNotification(hass); | ||||||
|     mockConfigEntries(hass); |  | ||||||
|     mockEntityRegistry(hass, [ |  | ||||||
|       { |  | ||||||
|         config_entry_id: "co2signal", |  | ||||||
|         device_id: "co2signal", |  | ||||||
|         area_id: null, |  | ||||||
|         disabled_by: null, |  | ||||||
|         entity_id: "sensor.co2_intensity", |  | ||||||
|         id: "sensor.co2_intensity", |  | ||||||
|         name: null, |  | ||||||
|         icon: null, |  | ||||||
|         platform: "co2signal", |  | ||||||
|         hidden_by: null, |  | ||||||
|         entity_category: null, |  | ||||||
|         has_entity_name: false, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         config_entry_id: "co2signal", |  | ||||||
|         device_id: "co2signal", |  | ||||||
|         area_id: null, |  | ||||||
|         disabled_by: null, |  | ||||||
|         entity_id: "sensor.grid_fossil_fuel_percentage", |  | ||||||
|         id: "sensor.co2_intensity", |  | ||||||
|         name: null, |  | ||||||
|         icon: null, |  | ||||||
|         platform: "co2signal", |  | ||||||
|         hidden_by: null, |  | ||||||
|         entity_category: null, |  | ||||||
|         has_entity_name: false, |  | ||||||
|       }, |  | ||||||
|     ]); |  | ||||||
|  |  | ||||||
|     hass.addEntities(energyEntities()); |     hass.addEntities(energyEntities()); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										41
									
								
								demo/src/stubs/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,41 @@ | |||||||
|  | import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; | ||||||
|  |  | ||||||
|  | export const mockConfig = (hass: MockHomeAssistant) => { | ||||||
|  |   hass.mockAPI("config/config_entries/entry", () => [ | ||||||
|  |     { | ||||||
|  |       entry_id: "co2signal", | ||||||
|  |       domain: "co2signal", | ||||||
|  |       title: "CO2 Signal", | ||||||
|  |       source: "user", | ||||||
|  |       state: "loaded", | ||||||
|  |       supports_options: false, | ||||||
|  |       supports_unload: true, | ||||||
|  |       pref_disable_new_entities: false, | ||||||
|  |       pref_disable_polling: false, | ||||||
|  |       disabled_by: null, | ||||||
|  |       reason: null, | ||||||
|  |     }, | ||||||
|  |   ]); | ||||||
|  |   hass.mockWS("config/entity_registry/list", () => [ | ||||||
|  |     { | ||||||
|  |       config_entry_id: "co2signal", | ||||||
|  |       device_id: "co2signal", | ||||||
|  |       area_id: null, | ||||||
|  |       disabled_by: null, | ||||||
|  |       entity_id: "sensor.co2_intensity", | ||||||
|  |       name: null, | ||||||
|  |       icon: null, | ||||||
|  |       platform: "co2signal", | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       config_entry_id: "co2signal", | ||||||
|  |       device_id: "co2signal", | ||||||
|  |       area_id: null, | ||||||
|  |       disabled_by: null, | ||||||
|  |       entity_id: "sensor.grid_fossil_fuel_percentage", | ||||||
|  |       name: null, | ||||||
|  |       icon: null, | ||||||
|  |       platform: "co2signal", | ||||||
|  |     }, | ||||||
|  |   ]); | ||||||
|  | }; | ||||||
| @@ -1,20 +0,0 @@ | |||||||
| import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; |  | ||||||
|  |  | ||||||
| export const mockConfigEntries = (hass: MockHomeAssistant) => { |  | ||||||
|   hass.mockWS("config_entries/get", () => [ |  | ||||||
|     { |  | ||||||
|       entry_id: "co2signal", |  | ||||||
|       domain: "co2signal", |  | ||||||
|       title: "CO2 Signal", |  | ||||||
|       source: "user", |  | ||||||
|       state: "loaded", |  | ||||||
|       supports_options: false, |  | ||||||
|       supports_remove_device: false, |  | ||||||
|       supports_unload: true, |  | ||||||
|       pref_disable_new_entities: false, |  | ||||||
|       pref_disable_polling: false, |  | ||||||
|       disabled_by: null, |  | ||||||
|       reason: null, |  | ||||||
|     }, |  | ||||||
|   ]); |  | ||||||
| }; |  | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { format, startOfToday, startOfTomorrow } from "date-fns/esm"; | import { format, startOfToday, startOfTomorrow } from "date-fns"; | ||||||
| import { EnergySolarForecasts } from "../../../src/data/energy"; | import { EnergySolarForecasts } from "../../../src/data/energy"; | ||||||
| import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; | import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,4 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; | |||||||
| export const mockEntityRegistry = ( | export const mockEntityRegistry = ( | ||||||
|   hass: MockHomeAssistant, |   hass: MockHomeAssistant, | ||||||
|   data: EntityRegistryEntry[] = [] |   data: EntityRegistryEntry[] = [] | ||||||
| ) => { | ) => hass.mockWS("config/entity_registry/list", () => data); | ||||||
|   hass.mockWS("config/entity_registry/list", () => data); |  | ||||||
| }; |  | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { | |||||||
|   addMonths, |   addMonths, | ||||||
|   differenceInHours, |   differenceInHours, | ||||||
|   endOfDay, |   endOfDay, | ||||||
| } from "date-fns/esm"; | } from "date-fns"; | ||||||
| import { HassEntity } from "home-assistant-js-websocket"; | import { HassEntity } from "home-assistant-js-websocket"; | ||||||
| import { StatisticValue } from "../../../src/data/history"; | import { StatisticValue } from "../../../src/data/history"; | ||||||
| import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; | import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; | ||||||
| @@ -466,7 +466,6 @@ export const mockHistory = (mockHass: MockHomeAssistant) => { | |||||||
|       return results; |       return results; | ||||||
|     } |     } | ||||||
|   ); |   ); | ||||||
|   mockHass.mockWS("recorder/get_statistics_metadata", () => []); |  | ||||||
|   mockHass.mockWS("history/list_statistic_ids", () => []); |   mockHass.mockWS("history/list_statistic_ids", () => []); | ||||||
|   mockHass.mockWS( |   mockHass.mockWS( | ||||||
|     "history/statistics_during_period", |     "history/statistics_during_period", | ||||||
|   | |||||||
| Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 35 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 67 KiB | 
| Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 27 KiB | 
| Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 94 KiB | 
| Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 32 KiB | 
| @@ -8,7 +8,7 @@ module.exports = [ | |||||||
|   { |   { | ||||||
|     category: "lovelace", |     category: "lovelace", | ||||||
|     // Label for in the sidebar |     // Label for in the sidebar | ||||||
|     header: "Dashboards", |     header: "Lovelace", | ||||||
|     // Specify order of pages. Any pages in the category folder but not listed here will |     // Specify order of pages. Any pages in the category folder but not listed here will | ||||||
|     // automatically be added after the pages listed here. |     // automatically be added after the pages listed here. | ||||||
|     pages: ["introduction"], |     pages: ["introduction"], | ||||||
| @@ -34,7 +34,7 @@ module.exports = [ | |||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     category: "misc", |     category: "misc", | ||||||
|     header: "Miscellaneous", |     header: "Miscelaneous", | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     category: "brand", |     category: "brand", | ||||||
|   | |||||||
| @@ -119,7 +119,7 @@ export const basicTrace: DemoTrace = { | |||||||
|             params: { |             params: { | ||||||
|               domain: "input_boolean", |               domain: "input_boolean", | ||||||
|               service: "toggle", |               service: "toggle", | ||||||
|               data: {}, |               service_data: {}, | ||||||
|               target: { |               target: { | ||||||
|                 entity_id: ["input_boolean.toggle_4"], |                 entity_id: ["input_boolean.toggle_4"], | ||||||
|               }, |               }, | ||||||
| @@ -164,7 +164,7 @@ export const basicTrace: DemoTrace = { | |||||||
|             params: { |             params: { | ||||||
|               domain: "input_boolean", |               domain: "input_boolean", | ||||||
|               service: "toggle", |               service: "toggle", | ||||||
|               data: {}, |               service_data: {}, | ||||||
|               target: { |               target: { | ||||||
|                 entity_id: ["input_boolean.toggle_2"], |                 entity_id: ["input_boolean.toggle_2"], | ||||||
|               }, |               }, | ||||||
| @@ -182,7 +182,7 @@ export const basicTrace: DemoTrace = { | |||||||
|             params: { |             params: { | ||||||
|               domain: "input_boolean", |               domain: "input_boolean", | ||||||
|               service: "toggle", |               service: "toggle", | ||||||
|               data: {}, |               service_data: {}, | ||||||
|               target: { |               target: { | ||||||
|                 entity_id: ["input_boolean.toggle_3"], |                 entity_id: ["input_boolean.toggle_3"], | ||||||
|               }, |               }, | ||||||
| @@ -200,7 +200,7 @@ export const basicTrace: DemoTrace = { | |||||||
|             params: { |             params: { | ||||||
|               domain: "input_boolean", |               domain: "input_boolean", | ||||||
|               service: "toggle", |               service: "toggle", | ||||||
|               data: {}, |               service_data: {}, | ||||||
|               target: { |               target: { | ||||||
|                 entity_id: ["input_boolean.toggle_4"], |                 entity_id: ["input_boolean.toggle_4"], | ||||||
|               }, |               }, | ||||||
| @@ -298,11 +298,11 @@ export const basicTrace: DemoTrace = { | |||||||
|       source: "state of input_boolean.toggle_1", |       source: "state of input_boolean.toggle_1", | ||||||
|       entity_id: "automation.toggle_toggles", |       entity_id: "automation.toggle_toggles", | ||||||
|       context_id: "6cfcae368e7b3686fad6c59e83ae76c9", |       context_id: "6cfcae368e7b3686fad6c59e83ae76c9", | ||||||
|       when: 1616647011.240832, |       when: "2021-03-25T04:36:51.240832+00:00", | ||||||
|       domain: "automation", |       domain: "automation", | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       when: 1616647011.249828, |       when: "2021-03-25T04:36:51.249828+00:00", | ||||||
|       name: "Toggle 4", |       name: "Toggle 4", | ||||||
|       state: "on", |       state: "on", | ||||||
|       entity_id: "input_boolean.toggle_4", |       entity_id: "input_boolean.toggle_4", | ||||||
| @@ -313,7 +313,7 @@ export const basicTrace: DemoTrace = { | |||||||
|       context_name: "Ensure Party mode", |       context_name: "Ensure Party mode", | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       when: 1616647011.258947, |       when: "2021-03-25T04:36:51.258947+00:00", | ||||||
|       name: "Toggle 2", |       name: "Toggle 2", | ||||||
|       state: "on", |       state: "on", | ||||||
|       entity_id: "input_boolean.toggle_2", |       entity_id: "input_boolean.toggle_2", | ||||||
| @@ -324,7 +324,7 @@ export const basicTrace: DemoTrace = { | |||||||
|       context_name: "Ensure Party mode", |       context_name: "Ensure Party mode", | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       when: 1616647011.261806, |       when: "2021-03-25T04:36:51.261806+00:00", | ||||||
|       name: "Toggle 3", |       name: "Toggle 3", | ||||||
|       state: "off", |       state: "off", | ||||||
|       entity_id: "input_boolean.toggle_3", |       entity_id: "input_boolean.toggle_3", | ||||||
| @@ -335,7 +335,7 @@ export const basicTrace: DemoTrace = { | |||||||
|       context_name: "Ensure Party mode", |       context_name: "Ensure Party mode", | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       when: 1616647011.265246, |       when: "2021-03-25T04:36:51.265246+00:00", | ||||||
|       name: "Toggle 4", |       name: "Toggle 4", | ||||||
|       state: "off", |       state: "off", | ||||||
|       entity_id: "input_boolean.toggle_4", |       entity_id: "input_boolean.toggle_4", | ||||||
|   | |||||||
| @@ -185,11 +185,11 @@ export const motionLightTrace: DemoTrace = { | |||||||
|         "has been triggered by state of binary_sensor.pauluss_macbook_pro_camera_in_use", |         "has been triggered by state of binary_sensor.pauluss_macbook_pro_camera_in_use", | ||||||
|       source: "state of binary_sensor.pauluss_macbook_pro_camera_in_use", |       source: "state of binary_sensor.pauluss_macbook_pro_camera_in_use", | ||||||
|       entity_id: "automation.auto_elgato", |       entity_id: "automation.auto_elgato", | ||||||
|       when: 1615702021.768492, |       when: "2021-03-14T06:07:01.768492+00:00", | ||||||
|       domain: "automation", |       domain: "automation", | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       when: 1615702021.872187, |       when: "2021-03-14T06:07:01.872187+00:00", | ||||||
|       name: "Elgato Key Light Air", |       name: "Elgato Key Light Air", | ||||||
|       state: "on", |       state: "on", | ||||||
|       entity_id: "light.elgato_key_light_air", |       entity_id: "light.elgato_key_light_air", | ||||||
| @@ -200,7 +200,7 @@ export const motionLightTrace: DemoTrace = { | |||||||
|       context_name: "Auto Elgato", |       context_name: "Auto Elgato", | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       when: 1615702073.284505, |       when: "2021-03-14T06:07:53.284505+00:00", | ||||||
|       name: "Elgato Key Light Air", |       name: "Elgato Key Light Air", | ||||||
|       state: "off", |       state: "off", | ||||||
|       entity_id: "light.elgato_key_light_air", |       entity_id: "light.elgato_key_light_air", | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit"; | |||||||
| import { customElement, property, query } from "lit/decorators"; | import { customElement, property, query } from "lit/decorators"; | ||||||
| import "../../src/components/ha-icon-button"; | import "../../src/components/ha-icon-button"; | ||||||
| import "../../src/managers/notification-manager"; | import "../../src/managers/notification-manager"; | ||||||
| import { HaExpansionPanel } from "../../src/components/ha-expansion-panel"; | import "../../src/components/ha-expansion-panel"; | ||||||
| import { haStyle } from "../../src/resources/styles"; | import { haStyle } from "../../src/resources/styles"; | ||||||
| import { PAGES, SIDEBAR } from "../build/import-pages"; | import { PAGES, SIDEBAR } from "../build/import-pages"; | ||||||
| import { dynamicElement } from "../../src/common/dom/dynamic-element-directive"; | import { dynamicElement } from "../../src/common/dom/dynamic-element-directive"; | ||||||
| @@ -174,10 +174,9 @@ class HaGallery extends LitElement { | |||||||
|     const menuItem = this.shadowRoot!.querySelector( |     const menuItem = this.shadowRoot!.querySelector( | ||||||
|       `a[href="#${this._page}"]` |       `a[href="#${this._page}"]` | ||||||
|     )!; |     )!; | ||||||
|  |  | ||||||
|     // Make sure section is expanded |     // Make sure section is expanded | ||||||
|     if (menuItem.parentElement instanceof HaExpansionPanel) { |     if (menuItem.parentElement instanceof HTMLDetailsElement) { | ||||||
|       menuItem.parentElement.expanded = true; |       menuItem.parentElement.open = true; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,9 +1,7 @@ | |||||||
| import { dump } from "js-yaml"; | import { dump } from "js-yaml"; | ||||||
| import { html, css, LitElement, TemplateResult } from "lit"; | import { html, css, LitElement, TemplateResult } from "lit"; | ||||||
| import { customElement, property, state } from "lit/decorators"; | import { customElement, property } from "lit/decorators"; | ||||||
| import "../../../../src/components/ha-card"; | import "../../../../src/components/ha-card"; | ||||||
| import "../../../../src/components/ha-yaml-editor"; |  | ||||||
| import { Action } from "../../../../src/data/script"; |  | ||||||
| import { describeAction } from "../../../../src/data/script_i18n"; | import { describeAction } from "../../../../src/data/script_i18n"; | ||||||
| import { getEntity } from "../../../../src/fake_data/entity"; | import { getEntity } from "../../../../src/fake_data/entity"; | ||||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; | import { provideHass } from "../../../../src/fake_data/provide_hass"; | ||||||
| @@ -90,15 +88,6 @@ const ACTIONS = [ | |||||||
|     then: [{ delay: "00:00:01" }], |     then: [{ delay: "00:00:01" }], | ||||||
|     else: [{ delay: "00:00:05" }], |     else: [{ delay: "00:00:05" }], | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     if: [{ condition: "state" }], |  | ||||||
|     then: [{ delay: "00:00:01" }], |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     if: [{ condition: "state" }, { condition: "state" }], |  | ||||||
|     then: [{ delay: "00:00:01" }], |  | ||||||
|     else: [{ delay: "00:00:05" }], |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|     choose: [ |     choose: [ | ||||||
|       { |       { | ||||||
| @@ -114,38 +103,16 @@ const ACTIONS = [ | |||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const initialAction: Action = { |  | ||||||
|   service: "light.turn_on", |  | ||||||
|   target: { |  | ||||||
|     entity_id: "light.kitchen", |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| @customElement("demo-automation-describe-action") | @customElement("demo-automation-describe-action") | ||||||
| export class DemoAutomationDescribeAction extends LitElement { | export class DemoAutomationDescribeAction extends LitElement { | ||||||
|   @property({ attribute: false }) hass!: HomeAssistant; |   @property({ attribute: false }) hass!: HomeAssistant; | ||||||
|  |  | ||||||
|   @state() _action = initialAction; |  | ||||||
|  |  | ||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     if (!this.hass) { |     if (!this.hass) { | ||||||
|       return html``; |       return html``; | ||||||
|     } |     } | ||||||
|     return html` |     return html` | ||||||
|       <ha-card header="Actions"> |       <ha-card header="Actions"> | ||||||
|         <div class="action"> |  | ||||||
|           <span> |  | ||||||
|             ${this._action |  | ||||||
|               ? describeAction(this.hass, this._action) |  | ||||||
|               : "<invalid YAML>"} |  | ||||||
|           </span> |  | ||||||
|           <ha-yaml-editor |  | ||||||
|             label="Action Config" |  | ||||||
|             .defaultValue=${initialAction} |  | ||||||
|             @value-changed=${this._dataChanged} |  | ||||||
|           ></ha-yaml-editor> |  | ||||||
|         </div> |  | ||||||
|  |  | ||||||
|         ${ACTIONS.map( |         ${ACTIONS.map( | ||||||
|           (conf) => html` |           (conf) => html` | ||||||
|             <div class="action"> |             <div class="action"> | ||||||
| @@ -165,11 +132,6 @@ export class DemoAutomationDescribeAction extends LitElement { | |||||||
|     hass.addEntities(ENTITIES); |     hass.addEntities(ENTITIES); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _dataChanged(ev: CustomEvent): void { |  | ||||||
|     ev.stopPropagation(); |  | ||||||
|     this._action = ev.detail.isValid ? ev.detail.value : undefined; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static get styles() { |   static get styles() { | ||||||
|     return css` |     return css` | ||||||
|       ha-card { |       ha-card { | ||||||
| @@ -185,9 +147,6 @@ export class DemoAutomationDescribeAction extends LitElement { | |||||||
|       span { |       span { | ||||||
|         margin-right: 16px; |         margin-right: 16px; | ||||||
|       } |       } | ||||||
|       ha-yaml-editor { |  | ||||||
|         width: 50%; |  | ||||||
|       } |  | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,81 +1,31 @@ | |||||||
| import { dump } from "js-yaml"; | import { dump } from "js-yaml"; | ||||||
| import { css, html, LitElement, TemplateResult } from "lit"; | import { html, css, LitElement, TemplateResult } from "lit"; | ||||||
| import { customElement, property, state } from "lit/decorators"; | import { customElement } from "lit/decorators"; | ||||||
| import "../../../../src/components/ha-card"; | import "../../../../src/components/ha-card"; | ||||||
| import "../../../../src/components/ha-yaml-editor"; |  | ||||||
| import { Condition } from "../../../../src/data/automation"; |  | ||||||
| import { describeCondition } from "../../../../src/data/automation_i18n"; | import { describeCondition } from "../../../../src/data/automation_i18n"; | ||||||
| import { getEntity } from "../../../../src/fake_data/entity"; |  | ||||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; |  | ||||||
| import { HomeAssistant } from "../../../../src/types"; |  | ||||||
|  |  | ||||||
| const ENTITIES = [ |  | ||||||
|   getEntity("light", "kitchen", "on", { |  | ||||||
|     friendly_name: "Kitchen Light", |  | ||||||
|   }), |  | ||||||
|   getEntity("device_tracker", "person", "home", { |  | ||||||
|     friendly_name: "Person", |  | ||||||
|   }), |  | ||||||
|   getEntity("zone", "home", "", { |  | ||||||
|     friendly_name: "Home", |  | ||||||
|   }), |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| const conditions = [ | const conditions = [ | ||||||
|   { condition: "and" }, |   { condition: "and" }, | ||||||
|   { condition: "not" }, |   { condition: "not" }, | ||||||
|   { condition: "or" }, |   { condition: "or" }, | ||||||
|   { condition: "state", entity_id: "light.kitchen", state: "on" }, |   { condition: "state" }, | ||||||
|   { |   { condition: "numeric_state" }, | ||||||
|     condition: "numeric_state", |  | ||||||
|     entity_id: "light.kitchen", |  | ||||||
|     attribute: "brightness", |  | ||||||
|     below: 80, |  | ||||||
|     above: 20, |  | ||||||
|   }, |  | ||||||
|   { condition: "sun", after: "sunset" }, |   { condition: "sun", after: "sunset" }, | ||||||
|   { condition: "sun", after: "sunrise", offset: "-01:00" }, |   { condition: "sun", after: "sunrise" }, | ||||||
|   { condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" }, |   { condition: "zone" }, | ||||||
|   { condition: "time" }, |   { condition: "time" }, | ||||||
|   { condition: "template" }, |   { condition: "template" }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const initialCondition: Condition = { |  | ||||||
|   condition: "state", |  | ||||||
|   entity_id: "light.kitchen", |  | ||||||
|   state: "on", |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| @customElement("demo-automation-describe-condition") | @customElement("demo-automation-describe-condition") | ||||||
| export class DemoAutomationDescribeCondition extends LitElement { | export class DemoAutomationDescribeCondition extends LitElement { | ||||||
|   @property({ attribute: false }) hass!: HomeAssistant; |  | ||||||
|  |  | ||||||
|   @state() _condition = initialCondition; |  | ||||||
|  |  | ||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     if (!this.hass) { |  | ||||||
|       return html``; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return html` |     return html` | ||||||
|       <ha-card header="Conditions"> |       <ha-card header="Conditions"> | ||||||
|         <div class="condition"> |  | ||||||
|           <span> |  | ||||||
|             ${this._condition |  | ||||||
|               ? describeCondition(this._condition, this.hass) |  | ||||||
|               : "<invalid YAML>"} |  | ||||||
|           </span> |  | ||||||
|           <ha-yaml-editor |  | ||||||
|             label="Condition Config" |  | ||||||
|             .defaultValue=${initialCondition} |  | ||||||
|             @value-changed=${this._dataChanged} |  | ||||||
|           ></ha-yaml-editor> |  | ||||||
|         </div> |  | ||||||
|  |  | ||||||
|         ${conditions.map( |         ${conditions.map( | ||||||
|           (conf) => html` |           (conf) => html` | ||||||
|             <div class="condition"> |             <div class="condition"> | ||||||
|               <span>${describeCondition(conf as any, this.hass)}</span> |               <span>${describeCondition(conf as any)}</span> | ||||||
|               <pre>${dump(conf)}</pre> |               <pre>${dump(conf)}</pre> | ||||||
|             </div> |             </div> | ||||||
|           ` |           ` | ||||||
| @@ -84,18 +34,6 @@ export class DemoAutomationDescribeCondition extends LitElement { | |||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected firstUpdated(changedProps) { |  | ||||||
|     super.firstUpdated(changedProps); |  | ||||||
|     const hass = provideHass(this); |  | ||||||
|     hass.updateTranslations(null, "en"); |  | ||||||
|     hass.addEntities(ENTITIES); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _dataChanged(ev: CustomEvent): void { |  | ||||||
|     ev.stopPropagation(); |  | ||||||
|     this._condition = ev.detail.isValid ? ev.detail.value : undefined; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static get styles() { |   static get styles() { | ||||||
|     return css` |     return css` | ||||||
|       ha-card { |       ha-card { | ||||||
| @@ -111,9 +49,6 @@ export class DemoAutomationDescribeCondition extends LitElement { | |||||||
|       span { |       span { | ||||||
|         margin-right: 16px; |         margin-right: 16px; | ||||||
|       } |       } | ||||||
|       ha-yaml-editor { |  | ||||||
|         width: 50%; |  | ||||||
|       } |  | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,92 +1,34 @@ | |||||||
| import { dump } from "js-yaml"; | import { dump } from "js-yaml"; | ||||||
| import { css, html, LitElement, TemplateResult } from "lit"; | import { html, css, LitElement, TemplateResult } from "lit"; | ||||||
| import { customElement, property, state } from "lit/decorators"; | import { customElement } from "lit/decorators"; | ||||||
| import "../../../../src/components/ha-card"; | import "../../../../src/components/ha-card"; | ||||||
| import "../../../../src/components/ha-yaml-editor"; |  | ||||||
| import { Trigger } from "../../../../src/data/automation"; |  | ||||||
| import { describeTrigger } from "../../../../src/data/automation_i18n"; | import { describeTrigger } from "../../../../src/data/automation_i18n"; | ||||||
| import { getEntity } from "../../../../src/fake_data/entity"; |  | ||||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; |  | ||||||
| import { HomeAssistant } from "../../../../src/types"; |  | ||||||
|  |  | ||||||
| const ENTITIES = [ |  | ||||||
|   getEntity("light", "kitchen", "on", { |  | ||||||
|     friendly_name: "Kitchen Light", |  | ||||||
|   }), |  | ||||||
|   getEntity("person", "person", "", { |  | ||||||
|     friendly_name: "Person", |  | ||||||
|   }), |  | ||||||
|   getEntity("zone", "home", "", { |  | ||||||
|     friendly_name: "Home", |  | ||||||
|   }), |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| const triggers = [ | const triggers = [ | ||||||
|   { platform: "state", entity_id: "light.kitchen", from: "off", to: "on" }, |   { platform: "state" }, | ||||||
|   { platform: "mqtt" }, |   { platform: "mqtt" }, | ||||||
|   { |   { platform: "geo_location" }, | ||||||
|     platform: "geo_location", |   { platform: "homeassistant" }, | ||||||
|     source: "test_source", |   { platform: "numeric_state" }, | ||||||
|     zone: "zone.home", |   { platform: "sun" }, | ||||||
|     event: "enter", |  | ||||||
|   }, |  | ||||||
|   { platform: "homeassistant", event: "start" }, |  | ||||||
|   { |  | ||||||
|     platform: "numeric_state", |  | ||||||
|     entity_id: "light.kitchen", |  | ||||||
|     attribute: "brightness", |  | ||||||
|     below: 80, |  | ||||||
|     above: 20, |  | ||||||
|   }, |  | ||||||
|   { platform: "sun", event: "sunset" }, |  | ||||||
|   { platform: "time_pattern" }, |   { platform: "time_pattern" }, | ||||||
|   { platform: "webhook" }, |   { platform: "webhook" }, | ||||||
|   { |   { platform: "zone" }, | ||||||
|     platform: "zone", |  | ||||||
|     entity_id: "person.person", |  | ||||||
|     zone: "zone.home", |  | ||||||
|     event: "enter", |  | ||||||
|   }, |  | ||||||
|   { platform: "tag" }, |   { platform: "tag" }, | ||||||
|   { platform: "time", at: "15:32" }, |   { platform: "time" }, | ||||||
|   { platform: "template" }, |   { platform: "template" }, | ||||||
|   { platform: "event", event_type: "homeassistant_started" }, |   { platform: "event" }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const initialTrigger: Trigger = { |  | ||||||
|   platform: "state", |  | ||||||
|   entity_id: "light.kitchen", |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| @customElement("demo-automation-describe-trigger") | @customElement("demo-automation-describe-trigger") | ||||||
| export class DemoAutomationDescribeTrigger extends LitElement { | export class DemoAutomationDescribeTrigger extends LitElement { | ||||||
|   @property({ attribute: false }) hass!: HomeAssistant; |  | ||||||
|  |  | ||||||
|   @state() _trigger = initialTrigger; |  | ||||||
|  |  | ||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     if (!this.hass) { |  | ||||||
|       return html``; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return html` |     return html` | ||||||
|       <ha-card header="Triggers"> |       <ha-card header="Triggers"> | ||||||
|         <div class="trigger"> |  | ||||||
|           <span> |  | ||||||
|             ${this._trigger |  | ||||||
|               ? describeTrigger(this._trigger, this.hass) |  | ||||||
|               : "<invalid YAML>"} |  | ||||||
|           </span> |  | ||||||
|           <ha-yaml-editor |  | ||||||
|             label="Trigger Config" |  | ||||||
|             .defaultValue=${initialTrigger} |  | ||||||
|             @value-changed=${this._dataChanged} |  | ||||||
|           ></ha-yaml-editor> |  | ||||||
|         </div> |  | ||||||
|         ${triggers.map( |         ${triggers.map( | ||||||
|           (conf) => html` |           (conf) => html` | ||||||
|             <div class="trigger"> |             <div class="trigger"> | ||||||
|               <span>${describeTrigger(conf as any, this.hass)}</span> |               <span>${describeTrigger(conf as any)}</span> | ||||||
|               <pre>${dump(conf)}</pre> |               <pre>${dump(conf)}</pre> | ||||||
|             </div> |             </div> | ||||||
|           ` |           ` | ||||||
| @@ -95,18 +37,6 @@ export class DemoAutomationDescribeTrigger extends LitElement { | |||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected firstUpdated(changedProps) { |  | ||||||
|     super.firstUpdated(changedProps); |  | ||||||
|     const hass = provideHass(this); |  | ||||||
|     hass.updateTranslations(null, "en"); |  | ||||||
|     hass.addEntities(ENTITIES); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _dataChanged(ev: CustomEvent): void { |  | ||||||
|     ev.stopPropagation(); |  | ||||||
|     this._trigger = ev.detail.isValid ? ev.detail.value : undefined; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static get styles() { |   static get styles() { | ||||||
|     return css` |     return css` | ||||||
|       ha-card { |       ha-card { | ||||||
| @@ -122,9 +52,6 @@ export class DemoAutomationDescribeTrigger extends LitElement { | |||||||
|       span { |       span { | ||||||
|         margin-right: 16px; |         margin-right: 16px; | ||||||
|       } |       } | ||||||
|       ha-yaml-editor { |  | ||||||
|         width: 50%; |  | ||||||
|       } |  | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,32 +0,0 @@ | |||||||
| --- |  | ||||||
| title: Dialgos |  | ||||||
| subtitle: Dialogs provide important prompts in a user flow. |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| # Material Design 3 |  | ||||||
|  |  | ||||||
| Our dialogs are based on the latest version of Material Design. Specs and guidelines can be found on it's [website](https://m3.material.io/components/dialogs/overview).  |  | ||||||
|  |  | ||||||
| # Highlighted guidelines |  | ||||||
|  |  | ||||||
| ## Content |  | ||||||
| * A best practice is to always use a title, even if it is optional by Material guidelines. |  | ||||||
| * People mainly read the title and a button. Put the most important information in those two. |  | ||||||
| * Try to avoid user generated content in the title, this could make the title unreadable long. |  | ||||||
| * If users become unsure, they read the description. Make sure this explains what will happen. |  | ||||||
| * Strive for minimalism. |  | ||||||
|  |  | ||||||
| ## Buttons and X-icon |  | ||||||
| * Keep the labels short, for example `Save`, `Delete`, `Enable`. |  | ||||||
| * Dialog with actions must always have a discard button. On desktop a `Cancel` button and X-icon, on mobile only the X-icon. |  | ||||||
| * Destructive actions should be a red warning button. |  | ||||||
| * Alert or confirmation dialogs only have buttons and no X-icon. |  | ||||||
| * Try to avoid three buttons in one dialog. Especially when you leave the dialog task unfinished. |  | ||||||
|  |  | ||||||
| ## Example |  | ||||||
| ### Confirmation dialog |  | ||||||
| > **Delete dashboard?** |  | ||||||
| >  |  | ||||||
| > Dashboard [dashboard name] will be permanently deleted from Home Assistant. |  | ||||||
| >  |  | ||||||
| > Cancel / Delete |  | ||||||
| @@ -3,13 +3,6 @@ title: Alerts | |||||||
| subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task. | subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task. | ||||||
| --- | --- | ||||||
|  |  | ||||||
| <style> |  | ||||||
|   ha-alert { |  | ||||||
|     display: block; |  | ||||||
|     margin: 4px 0; |  | ||||||
|   } |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
| # Alert `<ha-alert>` | # Alert `<ha-alert>` | ||||||
| The alert offers four severity levels that set a distinctive icon and color. | The alert offers four severity levels that set a distinctive icon and color. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +0,0 @@ | |||||||
| --- |  | ||||||
| title: Expansion Panel |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| Expansion panel following all the ARIA guidelines. |  | ||||||
| @@ -1,157 +0,0 @@ | |||||||
| import { mdiPacMan } from "@mdi/js"; |  | ||||||
| import { css, html, LitElement, TemplateResult } from "lit"; |  | ||||||
| import { customElement } from "lit/decorators"; |  | ||||||
| import "../../../../src/components/ha-card"; |  | ||||||
| import "../../../../src/components/ha-expansion-panel"; |  | ||||||
| import "../../../../src/components/ha-markdown"; |  | ||||||
| import "../../components/demo-black-white-row"; |  | ||||||
| import { LONG_TEXT } from "../../data/text"; |  | ||||||
|  |  | ||||||
| const SHORT_TEXT = LONG_TEXT.substring(0, 113); |  | ||||||
|  |  | ||||||
| const SAMPLES: { |  | ||||||
|   template: (slot: string, leftChevron: boolean) => TemplateResult; |  | ||||||
| }[] = [ |  | ||||||
|   { |  | ||||||
|     template(slot, leftChevron) { |  | ||||||
|       return html` |  | ||||||
|         <ha-expansion-panel |  | ||||||
|           slot=${slot} |  | ||||||
|           .leftChevron=${leftChevron} |  | ||||||
|           header="Attr header" |  | ||||||
|         > |  | ||||||
|           ${SHORT_TEXT} |  | ||||||
|         </ha-expansion-panel> |  | ||||||
|       `; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     template(slot, leftChevron) { |  | ||||||
|       return html` |  | ||||||
|         <ha-expansion-panel |  | ||||||
|           slot=${slot} |  | ||||||
|           .leftChevron=${leftChevron} |  | ||||||
|           header="Attr header" |  | ||||||
|           secondary="Attr secondary" |  | ||||||
|         > |  | ||||||
|           ${SHORT_TEXT} |  | ||||||
|         </ha-expansion-panel> |  | ||||||
|       `; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     template(slot, leftChevron) { |  | ||||||
|       return html` |  | ||||||
|         <ha-expansion-panel |  | ||||||
|           slot=${slot} |  | ||||||
|           .leftChevron=${leftChevron} |  | ||||||
|           .header=${"Prop header"} |  | ||||||
|         > |  | ||||||
|           ${SHORT_TEXT} |  | ||||||
|         </ha-expansion-panel> |  | ||||||
|       `; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     template(slot, leftChevron) { |  | ||||||
|       return html` |  | ||||||
|         <ha-expansion-panel |  | ||||||
|           slot=${slot} |  | ||||||
|           .leftChevron=${leftChevron} |  | ||||||
|           .header=${"Prop header"} |  | ||||||
|           .secondary=${"Prop secondary"} |  | ||||||
|         > |  | ||||||
|           ${SHORT_TEXT} |  | ||||||
|         </ha-expansion-panel> |  | ||||||
|       `; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     template(slot, leftChevron) { |  | ||||||
|       return html` |  | ||||||
|         <ha-expansion-panel |  | ||||||
|           slot=${slot} |  | ||||||
|           .leftChevron=${leftChevron} |  | ||||||
|           .header=${"Prop header"} |  | ||||||
|         > |  | ||||||
|           <span slot="secondary">Slot Secondary</span> |  | ||||||
|           ${SHORT_TEXT} |  | ||||||
|         </ha-expansion-panel> |  | ||||||
|       `; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     template(slot, leftChevron) { |  | ||||||
|       return html` |  | ||||||
|         <ha-expansion-panel slot=${slot} .leftChevron=${leftChevron}> |  | ||||||
|           <span slot="header">Slot header</span> |  | ||||||
|           ${SHORT_TEXT} |  | ||||||
|         </ha-expansion-panel> |  | ||||||
|       `; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     template(slot, leftChevron) { |  | ||||||
|       return html` |  | ||||||
|         <ha-expansion-panel slot=${slot} .leftChevron=${leftChevron}> |  | ||||||
|           <span slot="header">Slot header with actions</span> |  | ||||||
|           <ha-icon-button |  | ||||||
|             slot="icons" |  | ||||||
|             label="Some Action" |  | ||||||
|             .path=${mdiPacMan} |  | ||||||
|           ></ha-icon-button> |  | ||||||
|           ${SHORT_TEXT} |  | ||||||
|         </ha-expansion-panel> |  | ||||||
|       `; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     template(slot, leftChevron) { |  | ||||||
|       return html` |  | ||||||
|         <ha-expansion-panel |  | ||||||
|           slot=${slot} |  | ||||||
|           .leftChevron=${leftChevron} |  | ||||||
|           header="Attr Header with actions" |  | ||||||
|         > |  | ||||||
|           <ha-icon-button |  | ||||||
|             slot="icons" |  | ||||||
|             label="Some Action" |  | ||||||
|             .path=${mdiPacMan} |  | ||||||
|           ></ha-icon-button> |  | ||||||
|           ${SHORT_TEXT} |  | ||||||
|         </ha-expansion-panel> |  | ||||||
|       `; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| @customElement("demo-components-ha-expansion-panel") |  | ||||||
| export class DemoHaExpansionPanel extends LitElement { |  | ||||||
|   protected render(): TemplateResult { |  | ||||||
|     return html` |  | ||||||
|       ${SAMPLES.map( |  | ||||||
|         (sample) => html` |  | ||||||
|           <demo-black-white-row> |  | ||||||
|             ${["light", "dark"].map((slot) => |  | ||||||
|               sample.template(slot, slot === "dark") |  | ||||||
|             )} |  | ||||||
|           </demo-black-white-row> |  | ||||||
|         ` |  | ||||||
|       )} |  | ||||||
|     `; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static get styles() { |  | ||||||
|     return css` |  | ||||||
|       ha-expansion-panel { |  | ||||||
|         margin: -16px; |  | ||||||
|       } |  | ||||||
|     `; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| declare global { |  | ||||||
|   interface HTMLElementTagNameMap { |  | ||||||
|     "demo-components-ha-expansion-panel": DemoHaExpansionPanel; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -3,7 +3,6 @@ import "@material/mwc-button"; | |||||||
| import { html, LitElement, TemplateResult } from "lit"; | import { html, LitElement, TemplateResult } from "lit"; | ||||||
| import { customElement, state } from "lit/decorators"; | import { customElement, state } from "lit/decorators"; | ||||||
| import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; | import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; | ||||||
| import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries"; |  | ||||||
| import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; | import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; | ||||||
| import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; | import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; | ||||||
| import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; | import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; | ||||||
| @@ -21,22 +20,16 @@ const ENTITIES = [ | |||||||
|   }), |   }), | ||||||
|   getEntity("media_player", "livingroom", "playing", { |   getEntity("media_player", "livingroom", "playing", { | ||||||
|     friendly_name: "Livingroom", |     friendly_name: "Livingroom", | ||||||
|     media_content_type: "music", |  | ||||||
|     device_class: "tv", |  | ||||||
|   }), |   }), | ||||||
|   getEntity("media_player", "lounge", "idle", { |   getEntity("media_player", "lounge", "idle", { | ||||||
|     friendly_name: "Lounge", |     friendly_name: "Lounge", | ||||||
|     supported_features: 444983, |     supported_features: 444983, | ||||||
|     device_class: "speaker", |  | ||||||
|   }), |   }), | ||||||
|   getEntity("light", "bedroom", "on", { |   getEntity("light", "bedroom", "on", { | ||||||
|     friendly_name: "Bedroom", |     friendly_name: "Bedroom", | ||||||
|     effect: "colorloop", |  | ||||||
|     effect_list: ["colorloop", "random"], |  | ||||||
|   }), |   }), | ||||||
|   getEntity("switch", "coffee", "off", { |   getEntity("switch", "coffee", "off", { | ||||||
|     friendly_name: "Coffee", |     friendly_name: "Coffee", | ||||||
|     device_class: "switch", |  | ||||||
|   }), |   }), | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @@ -148,13 +141,7 @@ const SCHEMAS: { | |||||||
|         selector: { attribute: { entity_id: "" } }, |         selector: { attribute: { entity_id: "" } }, | ||||||
|         context: { filter_entity: "entity" }, |         context: { filter_entity: "entity" }, | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         name: "State", |  | ||||||
|         selector: { state: { entity_id: "" } }, |  | ||||||
|         context: { filter_entity: "entity", filter_attribute: "Attribute" }, |  | ||||||
|       }, |  | ||||||
|       { name: "Device", selector: { device: {} } }, |       { name: "Device", selector: { device: {} } }, | ||||||
|       { name: "Config entry", selector: { config_entry: {} } }, |  | ||||||
|       { name: "Duration", selector: { duration: {} } }, |       { name: "Duration", selector: { duration: {} } }, | ||||||
|       { name: "area", selector: { area: {} } }, |       { name: "area", selector: { area: {} } }, | ||||||
|       { name: "target", selector: { target: {} } }, |       { name: "target", selector: { target: {} } }, | ||||||
| @@ -436,7 +423,6 @@ class DemoHaForm extends LitElement { | |||||||
|     hass.addEntities(ENTITIES); |     hass.addEntities(ENTITIES); | ||||||
|     mockEntityRegistry(hass); |     mockEntityRegistry(hass); | ||||||
|     mockDeviceRegistry(hass, DEVICES); |     mockDeviceRegistry(hass, DEVICES); | ||||||
|     mockConfigEntries(hass); |  | ||||||
|     mockAreaRegistry(hass, AREAS); |     mockAreaRegistry(hass, AREAS); | ||||||
|     mockHassioSupervisor(hass); |     mockHassioSupervisor(hass); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ import "@material/mwc-button"; | |||||||
| import { css, html, LitElement, TemplateResult } from "lit"; | import { css, html, LitElement, TemplateResult } from "lit"; | ||||||
| import { customElement, state } from "lit/decorators"; | import { customElement, state } from "lit/decorators"; | ||||||
| import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; | import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry"; | ||||||
| import { mockConfigEntries } from "../../../../demo/src/stubs/config_entries"; |  | ||||||
| import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; | import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry"; | ||||||
| import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; | import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry"; | ||||||
| import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; | import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor"; | ||||||
| @@ -116,19 +115,11 @@ const SCHEMAS: { | |||||||
|     name: "One of each", |     name: "One of each", | ||||||
|     input: { |     input: { | ||||||
|       entity: { name: "Entity", selector: { entity: {} } }, |       entity: { name: "Entity", selector: { entity: {} } }, | ||||||
|       state: { |  | ||||||
|         name: "State", |  | ||||||
|         selector: { state: { entity_id: "alarm_control_panel.alarm" } }, |  | ||||||
|       }, |  | ||||||
|       attribute: { |       attribute: { | ||||||
|         name: "Attribute", |         name: "Attribute", | ||||||
|         selector: { attribute: { entity_id: "" } }, |         selector: { attribute: { entity_id: "" } }, | ||||||
|       }, |       }, | ||||||
|       device: { name: "Device", selector: { device: {} } }, |       device: { name: "Device", selector: { device: {} } }, | ||||||
|       config_entry: { |  | ||||||
|         name: "Integration", |  | ||||||
|         selector: { config_entry: {} }, |  | ||||||
|       }, |  | ||||||
|       duration: { name: "Duration", selector: { duration: {} } }, |       duration: { name: "Duration", selector: { duration: {} } }, | ||||||
|       addon: { name: "Addon", selector: { addon: {} } }, |       addon: { name: "Addon", selector: { addon: {} } }, | ||||||
|       area: { name: "Area", selector: { area: {} } }, |       area: { name: "Area", selector: { area: {} } }, | ||||||
| @@ -285,7 +276,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement { | |||||||
|     hass.addEntities(ENTITIES); |     hass.addEntities(ENTITIES); | ||||||
|     mockEntityRegistry(hass); |     mockEntityRegistry(hass); | ||||||
|     mockDeviceRegistry(hass, DEVICES); |     mockDeviceRegistry(hass, DEVICES); | ||||||
|     mockConfigEntries(hass); |  | ||||||
|     mockAreaRegistry(hass, AREAS); |     mockAreaRegistry(hass, AREAS); | ||||||
|     mockHassioSupervisor(hass); |     mockHassioSupervisor(hass); | ||||||
|     hass.mockWS("auth/sign_path", (params) => params); |     hass.mockWS("auth/sign_path", (params) => params); | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ const ENTITIES = [ | |||||||
|     friendly_name: "Office Light", |     friendly_name: "Office Light", | ||||||
|   }), |   }), | ||||||
|   getEntity("fan", "kitchen", "on", { |   getEntity("fan", "kitchen", "on", { | ||||||
|     friendly_name: "Kitchen Fan", |     friendly_name: "Second Office Fan", | ||||||
|   }), |   }), | ||||||
|   getEntity("binary_sensor", "kitchen_door", "on", { |   getEntity("binary_sensor", "kitchen_door", "on", { | ||||||
|     friendly_name: "Office Door", |     friendly_name: "Office Door", | ||||||
| @@ -102,7 +102,7 @@ class DemoArea extends LitElement { | |||||||
|         picture: "/images/office.jpg", |         picture: "/images/office.jpg", | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         name: "Kitchen", |         name: "Second Office", | ||||||
|         area_id: "kitchen", |         area_id: "kitchen", | ||||||
|         picture: "/images/kitchen.png", |         picture: "/images/kitchen.png", | ||||||
|       }, |       }, | ||||||
|   | |||||||
| @@ -75,10 +75,6 @@ const ENTITIES = [ | |||||||
|     timestamp: 1641801600, |     timestamp: 1641801600, | ||||||
|     friendly_name: "Date and Time", |     friendly_name: "Date and Time", | ||||||
|   }), |   }), | ||||||
|   getEntity("sensor", "humidity", "23.2", { |  | ||||||
|     friendly_name: "Humidity", |  | ||||||
|     unit_of_measurement: "%", |  | ||||||
|   }), |  | ||||||
|   getEntity("input_select", "dropdown", "Soda", { |   getEntity("input_select", "dropdown", "Soda", { | ||||||
|     friendly_name: "Dropdown", |     friendly_name: "Dropdown", | ||||||
|     options: ["Soda", "Beer", "Wine"], |     options: ["Soda", "Beer", "Wine"], | ||||||
| @@ -146,7 +142,6 @@ const CONFIGS = [ | |||||||
|     - light.non_existing |     - light.non_existing | ||||||
|     - climate.ecobee |     - climate.ecobee | ||||||
|     - input_number.number |     - input_number.number | ||||||
|     - sensor.humidity |  | ||||||
|     `, |     `, | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
| @@ -254,7 +249,7 @@ const CONFIGS = [ | |||||||
|       name: Bed light |       name: Bed light | ||||||
|       action_name: Toggle light |       action_name: Toggle light | ||||||
|       service: light.toggle |       service: light.toggle | ||||||
|       data: |       service_data: | ||||||
|         entity_id: light.bed_light |         entity_id: light.bed_light | ||||||
|     - type: section |     - type: section | ||||||
|       label: Links |       label: Links | ||||||
|   | |||||||
| @@ -199,7 +199,7 @@ const CONFIGS = [ | |||||||
|       tap_action: |       tap_action: | ||||||
|         action: call-service |         action: call-service | ||||||
|         service: light.turn_on |         service: light.turn_on | ||||||
|         data: |         service_data: | ||||||
|           entity_id: light.ceiling_lights |           entity_id: light.ceiling_lights | ||||||
|     - entity: sun.sun |     - entity: sun.sun | ||||||
|       name: Regular |       name: Regular | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| --- | --- | ||||||
| title: Introduction | title: Introduction | ||||||
| --- | --- | ||||||
| Dashboards have many different cards. Each card allows the user to tell | Lovelace has many different cards. Each card allows the user to tell | ||||||
| a different story about what is going on in their house. These cards | a different story about what is going on in their house. These cards | ||||||
| are very customizable, as no household is the same. | are very customizable, as no household is the same. | ||||||
|  |  | ||||||
| This gallery helps our developers and designers to see all the | This gallery helps our developers and designers to see all the | ||||||
| different states that each card can be in. | different states that each card can be in. | ||||||
|  |  | ||||||
| Check [the Dashboards documentation](https://www.home-assistant.io/dashboards/) for instructions on how to get started with Dashboards. | Check [the Lovelace documentation](https://www.home-assistant.io/lovelace) for instructions on how to get started with Lovelace. | ||||||
|   | |||||||
| @@ -40,7 +40,7 @@ const CONFIGS = [ | |||||||
|         left: 90% |         left: 90% | ||||||
|         padding: 0px |         padding: 0px | ||||||
|       service: light.turn_off |       service: light.turn_off | ||||||
|       data: |       service_data: | ||||||
|         entity_id: group.all_lights |         entity_id: group.all_lights | ||||||
|     - type: icon |     - type: icon | ||||||
|       icon: mdi:cctv |       icon: mdi:cctv | ||||||
| @@ -88,7 +88,7 @@ const CONFIGS = [ | |||||||
|         left: 90% |         left: 90% | ||||||
|         padding: 0px |         padding: 0px | ||||||
|       service: light.turn_off |       service: light.turn_off | ||||||
|       data: |       service_data: | ||||||
|         entity_id: group.all_lights |         entity_id: group.all_lights | ||||||
|     - type: icon |     - type: icon | ||||||
|       icon: mdi:cctv |       icon: mdi:cctv | ||||||
|   | |||||||
| @@ -191,11 +191,9 @@ const createEntityRegistryEntries = ( | |||||||
|     hidden_by: null, |     hidden_by: null, | ||||||
|     entity_category: null, |     entity_category: null, | ||||||
|     entity_id: "binary_sensor.updater", |     entity_id: "binary_sensor.updater", | ||||||
|     id: "binary_sensor.updater", |  | ||||||
|     name: null, |     name: null, | ||||||
|     icon: null, |     icon: null, | ||||||
|     platform: "updater", |     platform: "updater", | ||||||
|     has_entity_name: false, |  | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ const ENTITIES = [ | |||||||
|     effect_list: ["random", "colorloop"], |     effect_list: ["random", "colorloop"], | ||||||
|   }), |   }), | ||||||
|   getEntity("light", "color_RGB_light", "on", { |   getEntity("light", "color_RGB_light", "on", { | ||||||
|     friendly_name: "Color Effects Light", |     friendly_name: "Color Effets Light", | ||||||
|     brightness: 255, |     brightness: 255, | ||||||
|     rgb_color: [30, 100, 255], |     rgb_color: [30, 100, 255], | ||||||
|     supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION, |     supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION, | ||||||
|   | |||||||
| @@ -6,8 +6,10 @@ import { atLeastVersion } from "../../../src/common/config/version"; | |||||||
| import { navigate } from "../../../src/common/navigate"; | import { navigate } from "../../../src/common/navigate"; | ||||||
| import { caseInsensitiveStringCompare } from "../../../src/common/string/compare"; | import { caseInsensitiveStringCompare } from "../../../src/common/string/compare"; | ||||||
| import "../../../src/components/ha-card"; | import "../../../src/components/ha-card"; | ||||||
| import { HassioAddonRepository } from "../../../src/data/hassio/addon"; | import { | ||||||
| import { StoreAddon } from "../../../src/data/supervisor/store"; |   HassioAddonInfo, | ||||||
|  |   HassioAddonRepository, | ||||||
|  | } from "../../../src/data/hassio/addon"; | ||||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||||
| import { HomeAssistant } from "../../../src/types"; | import { HomeAssistant } from "../../../src/types"; | ||||||
| import "../components/hassio-card-content"; | import "../components/hassio-card-content"; | ||||||
| @@ -21,16 +23,20 @@ class HassioAddonRepositoryEl extends LitElement { | |||||||
|  |  | ||||||
|   @property({ attribute: false }) public repo!: HassioAddonRepository; |   @property({ attribute: false }) public repo!: HassioAddonRepository; | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public addons!: StoreAddon[]; |   @property({ attribute: false }) public addons!: HassioAddonInfo[]; | ||||||
|  |  | ||||||
|   @property() public filter!: string; |   @property() public filter!: string; | ||||||
|  |  | ||||||
|   private _getAddons = memoizeOne((addons: StoreAddon[], filter?: string) => { |   private _getAddons = memoizeOne( | ||||||
|  |     (addons: HassioAddonInfo[], filter?: string) => { | ||||||
|       if (filter) { |       if (filter) { | ||||||
|         return filterAndSort(addons, filter); |         return filterAndSort(addons, filter); | ||||||
|       } |       } | ||||||
|     return addons.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)); |       return addons.sort((a, b) => | ||||||
|   }); |         caseInsensitiveStringCompare(a.name, b.name) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     const repo = this.repo; |     const repo = this.repo; | ||||||
| @@ -62,7 +68,6 @@ class HassioAddonRepositoryEl extends LitElement { | |||||||
|           ${addons.map( |           ${addons.map( | ||||||
|             (addon) => html` |             (addon) => html` | ||||||
|               <ha-card |               <ha-card | ||||||
|                 outlined |  | ||||||
|                 .addon=${addon} |                 .addon=${addon} | ||||||
|                 class=${addon.available ? "" : "not_available"} |                 class=${addon.available ? "" : "not_available"} | ||||||
|                 @click=${this._addonTapped} |                 @click=${this._addonTapped} | ||||||
| @@ -81,10 +86,10 @@ class HassioAddonRepositoryEl extends LitElement { | |||||||
|                         ? this.supervisor.localize( |                         ? this.supervisor.localize( | ||||||
|                             "common.new_version_available" |                             "common.new_version_available" | ||||||
|                           ) |                           ) | ||||||
|                         : this.supervisor.localize("addon.state.installed") |                         : this.supervisor.localize("addon.installed") | ||||||
|                       : addon.available |                       : addon.available | ||||||
|                       ? this.supervisor.localize("addon.state.not_installed") |                       ? this.supervisor.localize("addon.not_installed") | ||||||
|                       : this.supervisor.localize("addon.state.not_available")} |                       : this.supervisor.localize("addon.not_available")} | ||||||
|                     .iconClass=${addon.installed |                     .iconClass=${addon.installed | ||||||
|                       ? addon.update_available |                       ? addon.update_available | ||||||
|                         ? "update" |                         ? "update" | ||||||
|   | |||||||
| @@ -14,18 +14,16 @@ import memoizeOne from "memoize-one"; | |||||||
| import { atLeastVersion } from "../../../src/common/config/version"; | import { atLeastVersion } from "../../../src/common/config/version"; | ||||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||||
| import { navigate } from "../../../src/common/navigate"; | import { navigate } from "../../../src/common/navigate"; | ||||||
|  | import "../../../src/components/search-input"; | ||||||
| import { extractSearchParam } from "../../../src/common/url/search-params"; | import { extractSearchParam } from "../../../src/common/url/search-params"; | ||||||
| import "../../../src/components/ha-button-menu"; | import "../../../src/components/ha-button-menu"; | ||||||
| import "../../../src/components/ha-icon-button"; | import "../../../src/components/ha-icon-button"; | ||||||
| import "../../../src/components/search-input"; |  | ||||||
| import { | import { | ||||||
|  |   HassioAddonInfo, | ||||||
|   HassioAddonRepository, |   HassioAddonRepository, | ||||||
|   reloadHassioAddons, |   reloadHassioAddons, | ||||||
| } from "../../../src/data/hassio/addon"; | } from "../../../src/data/hassio/addon"; | ||||||
| import { extractApiErrorMessage } from "../../../src/data/hassio/common"; |  | ||||||
| import { StoreAddon } from "../../../src/data/supervisor/store"; |  | ||||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||||
| import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; |  | ||||||
| import "../../../src/layouts/hass-loading-screen"; | import "../../../src/layouts/hass-loading-screen"; | ||||||
| import "../../../src/layouts/hass-subpage"; | import "../../../src/layouts/hass-subpage"; | ||||||
| import { HomeAssistant, Route } from "../../../src/types"; | import { HomeAssistant, Route } from "../../../src/types"; | ||||||
| @@ -61,24 +59,17 @@ class HassioAddonStore extends LitElement { | |||||||
|   @state() private _filter?: string; |   @state() private _filter?: string; | ||||||
|  |  | ||||||
|   public async refreshData() { |   public async refreshData() { | ||||||
|     try { |  | ||||||
|     await reloadHassioAddons(this.hass); |     await reloadHassioAddons(this.hass); | ||||||
|     } catch (err) { |  | ||||||
|       showAlertDialog(this, { |  | ||||||
|         text: extractApiErrorMessage(err), |  | ||||||
|       }); |  | ||||||
|     } finally { |  | ||||||
|     await this._loadData(); |     await this._loadData(); | ||||||
|   } |   } | ||||||
|   } |  | ||||||
|  |  | ||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     let repos: TemplateResult[] = []; |     let repos: TemplateResult[] = []; | ||||||
|  |  | ||||||
|     if (this.supervisor.store.repositories) { |     if (this.supervisor.addon.repositories) { | ||||||
|       repos = this.addonRepositories( |       repos = this.addonRepositories( | ||||||
|         this.supervisor.store.repositories, |         this.supervisor.addon.repositories, | ||||||
|         this.supervisor.store.addons, |         this.supervisor.addon.addons, | ||||||
|         this._filter |         this._filter | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| @@ -154,7 +145,7 @@ class HassioAddonStore extends LitElement { | |||||||
|   private addonRepositories = memoizeOne( |   private addonRepositories = memoizeOne( | ||||||
|     ( |     ( | ||||||
|       repositories: HassioAddonRepository[], |       repositories: HassioAddonRepository[], | ||||||
|       addons: StoreAddon[], |       addons: HassioAddonInfo[], | ||||||
|       filter?: string |       filter?: string | ||||||
|     ) => |     ) => | ||||||
|       repositories.sort(sortRepos).map((repo) => { |       repositories.sort(sortRepos).map((repo) => { | ||||||
|   | |||||||
| @@ -50,7 +50,6 @@ class HassioAddonAudio extends LitElement { | |||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     return html` |     return html` | ||||||
|       <ha-card |       <ha-card | ||||||
|         outlined |  | ||||||
|         .header=${this.supervisor.localize("addon.configuration.audio.header")} |         .header=${this.supervisor.localize("addon.configuration.audio.header")} | ||||||
|       > |       > | ||||||
|         <div class="card-content"> |         <div class="card-content"> | ||||||
|   | |||||||
| @@ -162,7 +162,7 @@ class HassioAddonConfig extends LitElement { | |||||||
|         ); |         ); | ||||||
|     return html` |     return html` | ||||||
|       <h1>${this.addon.name}</h1> |       <h1>${this.addon.name}</h1> | ||||||
|       <ha-card outlined> |       <ha-card> | ||||||
|         <div class="header"> |         <div class="header"> | ||||||
|           <h2> |           <h2> | ||||||
|             ${this.supervisor.localize("addon.configuration.options.header")} |             ${this.supervisor.localize("addon.configuration.options.header")} | ||||||
| @@ -336,7 +336,7 @@ class HassioAddonConfig extends LitElement { | |||||||
|       fireEvent(this, "hass-api-called", eventdata); |       fireEvent(this, "hass-api-called", eventdata); | ||||||
|     } catch (err: any) { |     } catch (err: any) { | ||||||
|       this._error = this.supervisor.localize( |       this._error = this.supervisor.localize( | ||||||
|         "addon.failed_to_reset", |         "addon.common.update_available", | ||||||
|         "error", |         "error", | ||||||
|         extractApiErrorMessage(err) |         extractApiErrorMessage(err) | ||||||
|       ); |       ); | ||||||
|   | |||||||
| @@ -58,7 +58,6 @@ class HassioAddonNetwork extends LitElement { | |||||||
|  |  | ||||||
|     return html` |     return html` | ||||||
|       <ha-card |       <ha-card | ||||||
|         outlined |  | ||||||
|         .header=${this.supervisor.localize( |         .header=${this.supervisor.localize( | ||||||
|           "addon.configuration.network.header" |           "addon.configuration.network.header" | ||||||
|         )} |         )} | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ class HassioAddonDocumentationDashboard extends LitElement { | |||||||
|     } |     } | ||||||
|     return html` |     return html` | ||||||
|       <div class="content"> |       <div class="content"> | ||||||
|         <ha-card outlined> |         <ha-card> | ||||||
|           ${this._error |           ${this._error | ||||||
|             ? html`<ha-alert alert-type="error">${this._error}</ha-alert>` |             ? html`<ha-alert alert-type="error">${this._error}</ha-alert>` | ||||||
|             : ""} |             : ""} | ||||||
| @@ -81,7 +81,7 @@ class HassioAddonDocumentationDashboard extends LitElement { | |||||||
|       ); |       ); | ||||||
|     } catch (err: any) { |     } catch (err: any) { | ||||||
|       this._error = this.supervisor.localize( |       this._error = this.supervisor.localize( | ||||||
|         "addon.documentation.get_documentation", |         "addon.documentation.get_logs", | ||||||
|         "error", |         "error", | ||||||
|         extractApiErrorMessage(err) |         extractApiErrorMessage(err) | ||||||
|       ); |       ); | ||||||
|   | |||||||
| @@ -12,17 +12,12 @@ import { navigate } from "../../../src/common/navigate"; | |||||||
| import { extractSearchParam } from "../../../src/common/url/search-params"; | import { extractSearchParam } from "../../../src/common/url/search-params"; | ||||||
| import "../../../src/components/ha-circular-progress"; | import "../../../src/components/ha-circular-progress"; | ||||||
| import { | import { | ||||||
|   fetchAddonInfo, |  | ||||||
|   fetchHassioAddonInfo, |   fetchHassioAddonInfo, | ||||||
|   fetchHassioAddonsInfo, |   fetchHassioAddonsInfo, | ||||||
|   HassioAddonDetails, |   HassioAddonDetails, | ||||||
| } from "../../../src/data/hassio/addon"; | } from "../../../src/data/hassio/addon"; | ||||||
| import { extractApiErrorMessage } from "../../../src/data/hassio/common"; | import { extractApiErrorMessage } from "../../../src/data/hassio/common"; | ||||||
| import { | import { setSupervisorOption } from "../../../src/data/hassio/supervisor"; | ||||||
|   addStoreRepository, |  | ||||||
|   fetchSupervisorStore, |  | ||||||
|   StoreAddonDetails, |  | ||||||
| } from "../../../src/data/supervisor/store"; |  | ||||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||||
| import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box"; | import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box"; | ||||||
| import "../../../src/layouts/hass-error-screen"; | import "../../../src/layouts/hass-error-screen"; | ||||||
| @@ -47,9 +42,7 @@ class HassioAddonDashboard extends LitElement { | |||||||
|  |  | ||||||
|   @property({ attribute: false }) public route!: Route; |   @property({ attribute: false }) public route!: Route; | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public addon?: |   @property({ attribute: false }) public addon?: HassioAddonDetails; | ||||||
|     | HassioAddonDetails |  | ||||||
|     | StoreAddonDetails; |  | ||||||
|  |  | ||||||
|   @property({ type: Boolean }) public narrow!: boolean; |   @property({ type: Boolean }) public narrow!: boolean; | ||||||
|  |  | ||||||
| @@ -75,7 +68,7 @@ class HassioAddonDashboard extends LitElement { | |||||||
|       ></hass-error-screen>`; |       ></hass-error-screen>`; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!this.addon || !this.supervisor?.addon) { |     if (!this.addon) { | ||||||
|       return html`<hass-loading-screen></hass-loading-screen>`; |       return html`<hass-loading-screen></hass-loading-screen>`; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -176,11 +169,10 @@ class HassioAddonDashboard extends LitElement { | |||||||
|     if (this.route.path === "") { |     if (this.route.path === "") { | ||||||
|       const requestedAddon = extractSearchParam("addon"); |       const requestedAddon = extractSearchParam("addon"); | ||||||
|       const requestedAddonRepository = extractSearchParam("repository_url"); |       const requestedAddonRepository = extractSearchParam("repository_url"); | ||||||
|       if (requestedAddonRepository) { |  | ||||||
|         const storeInfo = await fetchSupervisorStore(this.hass); |  | ||||||
|       if ( |       if ( | ||||||
|           !storeInfo.repositories.find( |         requestedAddonRepository && | ||||||
|             (repo) => repo.source === requestedAddonRepository |         !this.supervisor.supervisor.addons_repositories.find( | ||||||
|  |           (repo) => repo === requestedAddonRepository | ||||||
|         ) |         ) | ||||||
|       ) { |       ) { | ||||||
|         if ( |         if ( | ||||||
| @@ -201,16 +193,20 @@ class HassioAddonDashboard extends LitElement { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             await addStoreRepository(this.hass, requestedAddonRepository); |           await setSupervisorOption(this.hass, { | ||||||
|  |             addons_repositories: [ | ||||||
|  |               ...this.supervisor.supervisor.addons_repositories, | ||||||
|  |               requestedAddonRepository, | ||||||
|  |             ], | ||||||
|  |           }); | ||||||
|         } catch (err: any) { |         } catch (err: any) { | ||||||
|           this._error = extractApiErrorMessage(err); |           this._error = extractApiErrorMessage(err); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (requestedAddon) { |       if (requestedAddon) { | ||||||
|         const store = await fetchSupervisorStore(this.hass); |         const addonsInfo = await fetchHassioAddonsInfo(this.hass); | ||||||
|         const validAddon = store.addons.some( |         const validAddon = addonsInfo.addons.some( | ||||||
|           (addon) => addon.slug === requestedAddon |           (addon) => addon.slug === requestedAddon | ||||||
|         ); |         ); | ||||||
|         if (!validAddon) { |         if (!validAddon) { | ||||||
| @@ -238,14 +234,12 @@ class HassioAddonDashboard extends LitElement { | |||||||
|  |  | ||||||
|     if (["uninstall", "install", "update", "start", "stop"].includes(path)) { |     if (["uninstall", "install", "update", "start", "stop"].includes(path)) { | ||||||
|       fireEvent(this, "supervisor-collection-refresh", { |       fireEvent(this, "supervisor-collection-refresh", { | ||||||
|         collection: "addon", |         collection: "supervisor", | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (path === "uninstall") { |     if (path === "uninstall") { | ||||||
|       window.history.back(); |       window.history.back(); | ||||||
|     } else if (path === "install") { |  | ||||||
|       this.addon = await fetchHassioAddonInfo(this.hass, this.addon!.slug); |  | ||||||
|     } else { |     } else { | ||||||
|       await this._routeDataChanged(); |       await this._routeDataChanged(); | ||||||
|     } |     } | ||||||
| @@ -263,11 +257,8 @@ class HassioAddonDashboard extends LitElement { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     try { |     try { | ||||||
|       if (!this.supervisor.addon) { |       const addoninfo = await fetchHassioAddonInfo(this.hass, addon); | ||||||
|         const addonsInfo = await fetchHassioAddonsInfo(this.hass); |       this.addon = addoninfo; | ||||||
|         fireEvent(this, "supervisor-update", { addon: addonsInfo }); |  | ||||||
|       } |  | ||||||
|       this.addon = await fetchAddonInfo(this.hass, this.supervisor, addon); |  | ||||||
|     } catch (err: any) { |     } catch (err: any) { | ||||||
|       this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`; |       this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`; | ||||||
|       this.addon = undefined; |       this.addon = undefined; | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import { customElement, property } from "lit/decorators"; | import { customElement, property } from "lit/decorators"; | ||||||
| import { HassioAddonDetails } from "../../../src/data/hassio/addon"; | import { HassioAddonDetails } from "../../../src/data/hassio/addon"; | ||||||
| import { StoreAddonDetails } from "../../../src/data/supervisor/store"; |  | ||||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||||
| import { | import { | ||||||
|   HassRouterPage, |   HassRouterPage, | ||||||
| @@ -21,9 +20,7 @@ class HassioAddonRouter extends HassRouterPage { | |||||||
|  |  | ||||||
|   @property({ attribute: false }) public supervisor!: Supervisor; |   @property({ attribute: false }) public supervisor!: Supervisor; | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public addon!: |   @property({ attribute: false }) public addon!: HassioAddonDetails; | ||||||
|     | HassioAddonDetails |  | ||||||
|     | StoreAddonDetails; |  | ||||||
|  |  | ||||||
|   protected routerOptions: RouterOptions = { |   protected routerOptions: RouterOptions = { | ||||||
|     defaultPage: "info", |     defaultPage: "info", | ||||||
|   | |||||||
| @@ -40,7 +40,6 @@ import "../../../../src/components/ha-settings-row"; | |||||||
| import "../../../../src/components/ha-svg-icon"; | import "../../../../src/components/ha-svg-icon"; | ||||||
| import "../../../../src/components/ha-switch"; | import "../../../../src/components/ha-switch"; | ||||||
| import { | import { | ||||||
|   AddonCapability, |  | ||||||
|   fetchHassioAddonChangelog, |   fetchHassioAddonChangelog, | ||||||
|   fetchHassioAddonInfo, |   fetchHassioAddonInfo, | ||||||
|   HassioAddonDetails, |   HassioAddonDetails, | ||||||
| @@ -60,10 +59,7 @@ import { | |||||||
|   fetchHassioStats, |   fetchHassioStats, | ||||||
|   HassioStats, |   HassioStats, | ||||||
| } from "../../../../src/data/hassio/common"; | } from "../../../../src/data/hassio/common"; | ||||||
| import { | import { StoreAddon } from "../../../../src/data/supervisor/store"; | ||||||
|   StoreAddon, |  | ||||||
|   StoreAddonDetails, |  | ||||||
| } from "../../../../src/data/supervisor/store"; |  | ||||||
| import { Supervisor } from "../../../../src/data/supervisor/supervisor"; | import { Supervisor } from "../../../../src/data/supervisor/supervisor"; | ||||||
| import { | import { | ||||||
|   showAlertDialog, |   showAlertDialog, | ||||||
| @@ -104,9 +100,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|  |  | ||||||
|   @property({ attribute: false }) public hass!: HomeAssistant; |   @property({ attribute: false }) public hass!: HomeAssistant; | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public addon!: |   @property({ attribute: false }) public addon!: HassioAddonDetails; | ||||||
|     | HassioAddonDetails |  | ||||||
|     | StoreAddonDetails; |  | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public supervisor!: Supervisor; |   @property({ attribute: false }) public supervisor!: Supervisor; | ||||||
|  |  | ||||||
| @@ -149,7 +143,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|             ></update-available-card> |             ></update-available-card> | ||||||
|           ` |           ` | ||||||
|         : ""} |         : ""} | ||||||
|       ${"protected" in this.addon && !this.addon.protected |       ${!this.addon.protected | ||||||
|         ? html` |         ? html` | ||||||
|             <ha-alert |             <ha-alert | ||||||
|               alert-type="error" |               alert-type="error" | ||||||
| @@ -172,7 +166,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|           ` |           ` | ||||||
|         : ""} |         : ""} | ||||||
|  |  | ||||||
|       <ha-card outlined> |       <ha-card> | ||||||
|         <div class="card-content"> |         <div class="card-content"> | ||||||
|           <div class="addon-header"> |           <div class="addon-header"> | ||||||
|             ${!this.narrow ? this.addon.name : ""} |             ${!this.narrow ? this.addon.name : ""} | ||||||
| @@ -524,7 +518,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|                 : ""} |                 : ""} | ||||||
|             </div> |             </div> | ||||||
|             <div> |             <div> | ||||||
|               ${this.addon.version && this.addon.state === "started" |               ${this.addon.state === "started" | ||||||
|                 ? html`<ha-settings-row ?three-line=${this.narrow}> |                 ? html`<ha-settings-row ?three-line=${this.narrow}> | ||||||
|                       <span slot="heading"> |                       <span slot="heading"> | ||||||
|                         ${this.supervisor.localize("addon.dashboard.hostname")} |                         ${this.supervisor.localize("addon.dashboard.hostname")} | ||||||
| @@ -655,7 +649,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|  |  | ||||||
|       ${this.addon.long_description |       ${this.addon.long_description | ||||||
|         ? html` |         ? html` | ||||||
|             <ha-card outlined> |             <ha-card> | ||||||
|               <div class="card-content"> |               <div class="card-content"> | ||||||
|                 <ha-markdown |                 <ha-markdown | ||||||
|                   .content=${this.addon.long_description} |                   .content=${this.addon.long_description} | ||||||
| @@ -675,7 +669,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   private async _loadData(): Promise<void> { |   private async _loadData(): Promise<void> { | ||||||
|     if ("state" in this.addon && this.addon.state === "started") { |     if (this.addon.state === "started") { | ||||||
|       this._metrics = await fetchHassioStats( |       this._metrics = await fetchHassioStats( | ||||||
|         this.hass, |         this.hass, | ||||||
|         `addons/${this.addon.slug}` |         `addons/${this.addon.slug}` | ||||||
| @@ -702,7 +696,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _showMoreInfo(ev): void { |   private _showMoreInfo(ev): void { | ||||||
|     const id = ev.currentTarget.id as AddonCapability; |     const id = ev.currentTarget.id; | ||||||
|     showHassioMarkdownDialog(this, { |     showHassioMarkdownDialog(this, { | ||||||
|       title: this.supervisor.localize(`addon.dashboard.capability.${id}.title`), |       title: this.supervisor.localize(`addon.dashboard.capability.${id}.title`), | ||||||
|       content: |       content: | ||||||
| @@ -723,22 +717,18 @@ class HassioAddonInfo extends LitElement { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   private get _computeIsRunning(): boolean { |   private get _computeIsRunning(): boolean { | ||||||
|     return (this.addon as HassioAddonDetails)?.state === "started"; |     return this.addon?.state === "started"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private get _pathWebui(): string | null { |   private get _pathWebui(): string | null { | ||||||
|     return (this.addon as HassioAddonDetails).webui!.replace( |     return ( | ||||||
|       "[HOST]", |       this.addon.webui && | ||||||
|       document.location.hostname |       this.addon.webui.replace("[HOST]", document.location.hostname) | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private get _computeShowWebUI(): boolean | "" | null { |   private get _computeShowWebUI(): boolean | "" | null { | ||||||
|     return ( |     return !this.addon.ingress && this.addon.webui && this._computeIsRunning; | ||||||
|       !this.addon.ingress && |  | ||||||
|       (this.addon as HassioAddonDetails).webui && |  | ||||||
|       this._computeIsRunning |  | ||||||
|     ); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _openIngress(): void { |   private _openIngress(): void { | ||||||
| @@ -764,8 +754,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|   private async _startOnBootToggled(): Promise<void> { |   private async _startOnBootToggled(): Promise<void> { | ||||||
|     this._error = undefined; |     this._error = undefined; | ||||||
|     const data: HassioAddonSetOptionParams = { |     const data: HassioAddonSetOptionParams = { | ||||||
|       boot: |       boot: this.addon.boot === "auto" ? "manual" : "auto", | ||||||
|         (this.addon as HassioAddonDetails).boot === "auto" ? "manual" : "auto", |  | ||||||
|     }; |     }; | ||||||
|     try { |     try { | ||||||
|       await setHassioAddonOption(this.hass, this.addon.slug, data); |       await setHassioAddonOption(this.hass, this.addon.slug, data); | ||||||
| @@ -787,7 +776,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|   private async _watchdogToggled(): Promise<void> { |   private async _watchdogToggled(): Promise<void> { | ||||||
|     this._error = undefined; |     this._error = undefined; | ||||||
|     const data: HassioAddonSetOptionParams = { |     const data: HassioAddonSetOptionParams = { | ||||||
|       watchdog: !(this.addon as HassioAddonDetails).watchdog, |       watchdog: !this.addon.watchdog, | ||||||
|     }; |     }; | ||||||
|     try { |     try { | ||||||
|       await setHassioAddonOption(this.hass, this.addon.slug, data); |       await setHassioAddonOption(this.hass, this.addon.slug, data); | ||||||
| @@ -809,7 +798,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|   private async _autoUpdateToggled(): Promise<void> { |   private async _autoUpdateToggled(): Promise<void> { | ||||||
|     this._error = undefined; |     this._error = undefined; | ||||||
|     const data: HassioAddonSetOptionParams = { |     const data: HassioAddonSetOptionParams = { | ||||||
|       auto_update: !(this.addon as HassioAddonDetails).auto_update, |       auto_update: !this.addon.auto_update, | ||||||
|     }; |     }; | ||||||
|     try { |     try { | ||||||
|       await setHassioAddonOption(this.hass, this.addon.slug, data); |       await setHassioAddonOption(this.hass, this.addon.slug, data); | ||||||
| @@ -831,7 +820,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|   private async _protectionToggled(): Promise<void> { |   private async _protectionToggled(): Promise<void> { | ||||||
|     this._error = undefined; |     this._error = undefined; | ||||||
|     const data: HassioAddonSetSecurityParams = { |     const data: HassioAddonSetSecurityParams = { | ||||||
|       protected: !(this.addon as HassioAddonDetails).protected, |       protected: !this.addon.protected, | ||||||
|     }; |     }; | ||||||
|     try { |     try { | ||||||
|       await setHassioAddonSecurity(this.hass, this.addon.slug, data); |       await setHassioAddonSecurity(this.hass, this.addon.slug, data); | ||||||
| @@ -853,7 +842,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|   private async _panelToggled(): Promise<void> { |   private async _panelToggled(): Promise<void> { | ||||||
|     this._error = undefined; |     this._error = undefined; | ||||||
|     const data: HassioAddonSetOptionParams = { |     const data: HassioAddonSetOptionParams = { | ||||||
|       ingress_panel: !(this.addon as HassioAddonDetails).ingress_panel, |       ingress_panel: !this.addon.ingress_panel, | ||||||
|     }; |     }; | ||||||
|     try { |     try { | ||||||
|       await setHassioAddonOption(this.hass, this.addon.slug, data); |       await setHassioAddonOption(this.hass, this.addon.slug, data); | ||||||
| @@ -881,7 +870,7 @@ class HassioAddonInfo extends LitElement { | |||||||
|  |  | ||||||
|       showHassioMarkdownDialog(this, { |       showHassioMarkdownDialog(this, { | ||||||
|         title: this.supervisor.localize("addon.dashboard.changelog"), |         title: this.supervisor.localize("addon.dashboard.changelog"), | ||||||
|         content: extractChangelog(this.addon as HassioAddonDetails, content), |         content: extractChangelog(this.addon, content), | ||||||
|       }); |       }); | ||||||
|     } catch (err: any) { |     } catch (err: any) { | ||||||
|       showAlertDialog(this, { |       showAlertDialog(this, { | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ class HassioAddonLogs extends LitElement { | |||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     return html` |     return html` | ||||||
|       <h1>${this.addon.name}</h1> |       <h1>${this.addon.name}</h1> | ||||||
|       <ha-card outlined> |       <ha-card> | ||||||
|         ${this._error |         ${this._error | ||||||
|           ? html`<ha-alert alert-type="error">${this._error}</ha-alert>` |           ? html`<ha-alert alert-type="error">${this._error}</ha-alert>` | ||||||
|           : ""} |           : ""} | ||||||
|   | |||||||
| @@ -98,8 +98,9 @@ export class HassioBackups extends LitElement { | |||||||
|     if (backup.content.addons.length !== 0) { |     if (backup.content.addons.length !== 0) { | ||||||
|       for (const addon of backup.content.addons) { |       for (const addon of backup.content.addons) { | ||||||
|         content.push( |         content.push( | ||||||
|           this.supervisor.addon.addons.find((entry) => entry.slug === addon) |           this.supervisor.supervisor.addons.find( | ||||||
|             ?.name || addon |             (entry) => entry.slug === addon | ||||||
|  |           )?.name || addon | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -176,7 +177,7 @@ export class HassioBackups extends LitElement { | |||||||
|           : supervisorTabs(this.hass)} |           : supervisorTabs(this.hass)} | ||||||
|         .hass=${this.hass} |         .hass=${this.hass} | ||||||
|         .localizeFunc=${this.supervisor.localize} |         .localizeFunc=${this.supervisor.localize} | ||||||
|         .searchLabel=${this.supervisor.localize("backup.search")} |         .searchLabel=${this.supervisor.localize("search")} | ||||||
|         .noDataText=${this.supervisor.localize("backup.no_backups")} |         .noDataText=${this.supervisor.localize("backup.no_backups")} | ||||||
|         .narrow=${this.narrow} |         .narrow=${this.narrow} | ||||||
|         .route=${this.route} |         .route=${this.route} | ||||||
| @@ -240,7 +241,7 @@ export class HassioBackups extends LitElement { | |||||||
|                   : html` |                   : html` | ||||||
|                       <ha-icon-button |                       <ha-icon-button | ||||||
|                         .label=${this.supervisor.localize( |                         .label=${this.supervisor.localize( | ||||||
|                           "backup.delete_selected" |                           "snapshot.delete_selected" | ||||||
|                         )} |                         )} | ||||||
|                         .path=${mdiDelete} |                         .path=${mdiDelete} | ||||||
|                         id="delete-btn" |                         id="delete-btn" | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| import Fuse from "fuse.js"; | import Fuse from "fuse.js"; | ||||||
| import { StoreAddon } from "../../../src/data/supervisor/store"; | import { HassioAddonInfo } from "../../../src/data/hassio/addon"; | ||||||
|  |  | ||||||
| export function filterAndSort(addons: StoreAddon[], filter: string) { | export function filterAndSort(addons: HassioAddonInfo[], filter: string) { | ||||||
|   const options: Fuse.IFuseOptions<StoreAddon> = { |   const options: Fuse.IFuseOptions<HassioAddonInfo> = { | ||||||
|     keys: ["name", "description", "slug"], |     keys: ["name", "description", "slug"], | ||||||
|     isCaseSensitive: false, |     isCaseSensitive: false, | ||||||
|     minMatchCharLength: 2, |     minMatchCharLength: 2, | ||||||
|   | |||||||
| @@ -17,12 +17,9 @@ import { | |||||||
| } from "../../../src/data/hassio/backup"; | } from "../../../src/data/hassio/backup"; | ||||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||||
| import { PolymerChangedEvent } from "../../../src/polymer-types"; | import { PolymerChangedEvent } from "../../../src/polymer-types"; | ||||||
| import { HomeAssistant, TranslationDict } from "../../../src/types"; | import { HomeAssistant } from "../../../src/types"; | ||||||
| import "./supervisor-formfield-label"; | import "./supervisor-formfield-label"; | ||||||
|  |  | ||||||
| type BackupOrRestoreKey = keyof TranslationDict["supervisor"]["backup"] & |  | ||||||
|   keyof TranslationDict["ui"]["panel"]["page-onboarding"]["restore"]; |  | ||||||
|  |  | ||||||
| interface CheckboxItem { | interface CheckboxItem { | ||||||
|   slug: string; |   slug: string; | ||||||
|   checked: boolean; |   checked: boolean; | ||||||
| @@ -99,7 +96,7 @@ export class SupervisorBackupContent extends LitElement { | |||||||
|           : ["ssl", "share", "media", "addons/local"] |           : ["ssl", "share", "media", "addons/local"] | ||||||
|       ); |       ); | ||||||
|       this.addons = _computeAddons( |       this.addons = _computeAddons( | ||||||
|         this.backup ? this.backup.addons : this.supervisor?.addon.addons |         this.backup ? this.backup.addons : this.supervisor?.supervisor.addons | ||||||
|       ); |       ); | ||||||
|       this.backupType = this.backup?.type || "full"; |       this.backupType = this.backup?.type || "full"; | ||||||
|       this.backupName = this.backup?.name || ""; |       this.backupName = this.backup?.name || ""; | ||||||
| @@ -111,9 +108,9 @@ export class SupervisorBackupContent extends LitElement { | |||||||
|     this._focusTarget?.focus(); |     this._focusTarget?.focus(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _localize = (key: BackupOrRestoreKey) => |   private _localize = (string: string) => | ||||||
|     this.supervisor?.localize(`backup.${key}`) || |     this.supervisor?.localize(`backup.${string}`) || | ||||||
|     this.localize!(`ui.panel.page-onboarding.restore.${key}`); |     this.localize!(`ui.panel.page-onboarding.restore.${string}`); | ||||||
|  |  | ||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     if (!this.onboarding && !this.supervisor) { |     if (!this.onboarding && !this.supervisor) { | ||||||
| @@ -171,8 +168,7 @@ export class SupervisorBackupContent extends LitElement { | |||||||
|         : ""} |         : ""} | ||||||
|       ${this.backupType === "partial" |       ${this.backupType === "partial" | ||||||
|         ? html`<div class="partial-picker"> |         ? html`<div class="partial-picker"> | ||||||
|             ${!this.backup || this.backup.homeassistant |             <ha-formfield | ||||||
|               ? html`<ha-formfield |  | ||||||
|               .label=${html`<supervisor-formfield-label |               .label=${html`<supervisor-formfield-label | ||||||
|                 label="Home Assistant" |                 label="Home Assistant" | ||||||
|                 .iconPath=${mdiHomeAssistant} |                 .iconPath=${mdiHomeAssistant} | ||||||
| @@ -187,8 +183,8 @@ export class SupervisorBackupContent extends LitElement { | |||||||
|                 @change=${this.toggleHomeAssistant} |                 @change=${this.toggleHomeAssistant} | ||||||
|               > |               > | ||||||
|               </ha-checkbox> |               </ha-checkbox> | ||||||
|                 </ha-formfield>` |             </ha-formfield> | ||||||
|               : ""} |  | ||||||
|             ${foldersSection?.templates.length |             ${foldersSection?.templates.length | ||||||
|               ? html` |               ? html` | ||||||
|                   <ha-formfield |                   <ha-formfield | ||||||
|   | |||||||
| @@ -24,9 +24,9 @@ class HassioAddons extends LitElement { | |||||||
|           ? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> ` |           ? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> ` | ||||||
|           : ""} |           : ""} | ||||||
|         <div class="card-group"> |         <div class="card-group"> | ||||||
|           ${!this.supervisor.addon.addons.length |           ${!this.supervisor.supervisor.addons?.length | ||||||
|             ? html` |             ? html` | ||||||
|                 <ha-card outlined> |                 <ha-card> | ||||||
|                   <div class="card-content"> |                   <div class="card-content"> | ||||||
|                     <button class="link" @click=${this._openStore}> |                     <button class="link" @click=${this._openStore}> | ||||||
|                       ${this.supervisor.localize("dashboard.no_addons")} |                       ${this.supervisor.localize("dashboard.no_addons")} | ||||||
| @@ -34,15 +34,11 @@ class HassioAddons extends LitElement { | |||||||
|                   </div> |                   </div> | ||||||
|                 </ha-card> |                 </ha-card> | ||||||
|               ` |               ` | ||||||
|             : this.supervisor.addon.addons |             : this.supervisor.supervisor.addons | ||||||
|                 .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) |                 .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) | ||||||
|                 .map( |                 .map( | ||||||
|                   (addon) => html` |                   (addon) => html` | ||||||
|                     <ha-card |                     <ha-card .addon=${addon} @click=${this._addonTapped}> | ||||||
|                       outlined |  | ||||||
|                       .addon=${addon} |  | ||||||
|                       @click=${this._addonTapped} |  | ||||||
|                     > |  | ||||||
|                       <div class="card-content"> |                       <div class="card-content"> | ||||||
|                         <hassio-card-content |                         <hassio-card-content | ||||||
|                           .hass=${this.hass} |                           .hass=${this.hass} | ||||||
|   | |||||||
| @@ -85,7 +85,7 @@ export class HassioUpdate extends LitElement { | |||||||
|       return html``; |       return html``; | ||||||
|     } |     } | ||||||
|     return html` |     return html` | ||||||
|       <ha-card outlined> |       <ha-card> | ||||||
|         <div class="card-content"> |         <div class="card-content"> | ||||||
|           <div class="icon"> |           <div class="icon"> | ||||||
|             <ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon> |             <ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon> | ||||||
|   | |||||||
| @@ -201,8 +201,7 @@ class HassioBackupDialog | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!this._dialogParams?.onboarding) { |     if (!this._dialogParams?.onboarding) { | ||||||
|       try { |       this.hass!.callApi( | ||||||
|         await this.hass!.callApi( |  | ||||||
|         "POST", |         "POST", | ||||||
|  |  | ||||||
|         `hassio/${ |         `hassio/${ | ||||||
| @@ -211,14 +210,17 @@ class HassioBackupDialog | |||||||
|             : "snapshots" |             : "snapshots" | ||||||
|         }/${this._backup!.slug}/restore/partial`, |         }/${this._backup!.slug}/restore/partial`, | ||||||
|         backupDetails |         backupDetails | ||||||
|         ); |       ).then( | ||||||
|  |         () => { | ||||||
|           this.closeDialog(); |           this.closeDialog(); | ||||||
|       } catch (error: any) { |         }, | ||||||
|  |         (error) => { | ||||||
|           this._error = error.body.message; |           this._error = error.body.message; | ||||||
|         } |         } | ||||||
|  |       ); | ||||||
|     } else { |     } else { | ||||||
|       fireEvent(this, "restoring"); |       fireEvent(this, "restoring"); | ||||||
|       await fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, { |       fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, { | ||||||
|         method: "POST", |         method: "POST", | ||||||
|         body: JSON.stringify(backupDetails), |         body: JSON.stringify(backupDetails), | ||||||
|       }); |       }); | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; | |||||||
| import { customElement, property, state } from "lit/decorators"; | import { customElement, property, state } from "lit/decorators"; | ||||||
| import { createCloseHeading } from "../../../../src/components/ha-dialog"; | import { createCloseHeading } from "../../../../src/components/ha-dialog"; | ||||||
| import "../../../../src/components/ha-form/ha-form"; | import "../../../../src/components/ha-form/ha-form"; | ||||||
| import type { SchemaUnion } from "../../../../src/components/ha-form/types"; | import { HaFormSchema } from "../../../../src/components/ha-form/types"; | ||||||
| import "../../../../src/components/ha-icon-button"; | import "../../../../src/components/ha-icon-button"; | ||||||
| import "../../../../src/components/ha-settings-row"; | import "../../../../src/components/ha-settings-row"; | ||||||
| import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | ||||||
| @@ -19,7 +19,7 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; | |||||||
| import type { HomeAssistant } from "../../../../src/types"; | import type { HomeAssistant } from "../../../../src/types"; | ||||||
| import { RegistriesDialogParams } from "./show-dialog-registries"; | import { RegistriesDialogParams } from "./show-dialog-registries"; | ||||||
|  |  | ||||||
| const SCHEMA = [ | const SCHEMA: HaFormSchema[] = [ | ||||||
|   { |   { | ||||||
|     name: "registry", |     name: "registry", | ||||||
|     required: true, |     required: true, | ||||||
| @@ -35,7 +35,7 @@ const SCHEMA = [ | |||||||
|     required: true, |     required: true, | ||||||
|     selector: { text: { type: "password" } }, |     selector: { text: { type: "password" } }, | ||||||
|   }, |   }, | ||||||
| ] as const; | ]; | ||||||
|  |  | ||||||
| @customElement("dialog-hassio-registries") | @customElement("dialog-hassio-registries") | ||||||
| class HassioRegistriesDialog extends LitElement { | class HassioRegistriesDialog extends LitElement { | ||||||
| @@ -135,8 +135,8 @@ class HassioRegistriesDialog extends LitElement { | |||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _computeLabel = (schema: SchemaUnion<typeof SCHEMA>) => |   private _computeLabel = (schema: HaFormSchema) => | ||||||
|     this.supervisor.localize(`dialog.registries.${schema.name}`); |     this.supervisor.localize(`dialog.registries.${schema.name}`) || schema.name; | ||||||
|  |  | ||||||
|   private _valueChanged(ev: CustomEvent) { |   private _valueChanged(ev: CustomEvent) { | ||||||
|     this._input = ev.detail.value; |     this._input = ev.detail.value; | ||||||
|   | |||||||
| @@ -15,18 +15,15 @@ import "../../../../src/components/ha-circular-progress"; | |||||||
| import { createCloseHeading } from "../../../../src/components/ha-dialog"; | import { createCloseHeading } from "../../../../src/components/ha-dialog"; | ||||||
| import "../../../../src/components/ha-icon-button"; | import "../../../../src/components/ha-icon-button"; | ||||||
| import { | import { | ||||||
|  |   fetchHassioAddonsInfo, | ||||||
|   HassioAddonInfo, |   HassioAddonInfo, | ||||||
|   HassioAddonRepository, |   HassioAddonRepository, | ||||||
| } from "../../../../src/data/hassio/addon"; | } from "../../../../src/data/hassio/addon"; | ||||||
| import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | ||||||
|  | import { setSupervisorOption } from "../../../../src/data/hassio/supervisor"; | ||||||
| import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; | import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; | ||||||
| import type { HomeAssistant } from "../../../../src/types"; | import type { HomeAssistant } from "../../../../src/types"; | ||||||
| import { HassioRepositoryDialogParams } from "./show-dialog-repositories"; | import { HassioRepositoryDialogParams } from "./show-dialog-repositories"; | ||||||
| import { |  | ||||||
|   addStoreRepository, |  | ||||||
|   fetchStoreRepositories, |  | ||||||
|   removeStoreRepository, |  | ||||||
| } from "../../../../src/data/supervisor/store"; |  | ||||||
|  |  | ||||||
| @customElement("dialog-hassio-repositories") | @customElement("dialog-hassio-repositories") | ||||||
| class HassioRepositoriesDialog extends LitElement { | class HassioRepositoriesDialog extends LitElement { | ||||||
| @@ -61,13 +58,7 @@ class HassioRepositoriesDialog extends LitElement { | |||||||
|  |  | ||||||
|   private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) => |   private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) => | ||||||
|     repos |     repos | ||||||
|       .filter( |       .filter((repo) => repo.slug !== "core" && repo.slug !== "local") | ||||||
|         (repo) => |  | ||||||
|           repo.slug !== "core" && // The core add-ons repository |  | ||||||
|           repo.slug !== "local" && // Locally managed add-ons |  | ||||||
|           repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons |  | ||||||
|           repo.slug !== "5c53de3b" // The ESPHome repository |  | ||||||
|       ) |  | ||||||
|       .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) |       .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
| @@ -87,7 +78,7 @@ class HassioRepositoriesDialog extends LitElement { | |||||||
|     const repositories = this._filteredRepositories(this._repositories); |     const repositories = this._filteredRepositories(this._repositories); | ||||||
|     const usedRepositories = this._filteredUsedRepositories( |     const usedRepositories = this._filteredUsedRepositories( | ||||||
|       repositories, |       repositories, | ||||||
|       this._dialogParams.supervisor.addon.addons |       this._dialogParams.supervisor.supervisor.addons | ||||||
|     ); |     ); | ||||||
|     return html` |     return html` | ||||||
|       <ha-dialog |       <ha-dialog | ||||||
| @@ -224,7 +215,9 @@ class HassioRepositoriesDialog extends LitElement { | |||||||
|  |  | ||||||
|   private async _loadData(): Promise<void> { |   private async _loadData(): Promise<void> { | ||||||
|     try { |     try { | ||||||
|       this._repositories = await fetchStoreRepositories(this.hass); |       const addonsinfo = await fetchHassioAddonsInfo(this.hass); | ||||||
|  |  | ||||||
|  |       this._repositories = addonsinfo.repositories; | ||||||
|  |  | ||||||
|       fireEvent(this, "supervisor-collection-refresh", { collection: "addon" }); |       fireEvent(this, "supervisor-collection-refresh", { collection: "addon" }); | ||||||
|     } catch (err: any) { |     } catch (err: any) { | ||||||
| @@ -238,9 +231,14 @@ class HassioRepositoriesDialog extends LitElement { | |||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     this._processing = true; |     this._processing = true; | ||||||
|  |     const repositories = this._filteredRepositories(this._repositories!); | ||||||
|  |     const newRepositories = repositories.map((repo) => repo.source); | ||||||
|  |     newRepositories.push(input.value); | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       await addStoreRepository(this.hass, input.value); |       await setSupervisorOption(this.hass, { | ||||||
|  |         addons_repositories: newRepositories, | ||||||
|  |       }); | ||||||
|       await this._loadData(); |       await this._loadData(); | ||||||
|  |  | ||||||
|       input.value = ""; |       input.value = ""; | ||||||
| @@ -252,8 +250,19 @@ class HassioRepositoriesDialog extends LitElement { | |||||||
|  |  | ||||||
|   private async _removeRepository(ev: Event) { |   private async _removeRepository(ev: Event) { | ||||||
|     const slug = (ev.currentTarget as any).slug; |     const slug = (ev.currentTarget as any).slug; | ||||||
|  |     const repositories = this._filteredRepositories(this._repositories!); | ||||||
|  |     const repository = repositories.find((repo) => repo.slug === slug); | ||||||
|  |     if (!repository) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const newRepositories = repositories | ||||||
|  |       .map((repo) => repo.source) | ||||||
|  |       .filter((repo) => repo !== repository.source); | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       await removeStoreRepository(this.hass, slug); |       await setSupervisorOption(this.hass, { | ||||||
|  |         addons_repositories: newRepositories, | ||||||
|  |       }); | ||||||
|       await this._loadData(); |       await this._loadData(); | ||||||
|     } catch (err: any) { |     } catch (err: any) { | ||||||
|       this._error = extractApiErrorMessage(err); |       this._error = extractApiErrorMessage(err); | ||||||
|   | |||||||
| @@ -22,7 +22,6 @@ import { | |||||||
|   Supervisor, |   Supervisor, | ||||||
|   SupervisorObject, |   SupervisorObject, | ||||||
|   supervisorCollection, |   supervisorCollection, | ||||||
|   SupervisorKeys, |  | ||||||
| } from "../../src/data/supervisor/supervisor"; | } from "../../src/data/supervisor/supervisor"; | ||||||
| import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin"; | import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin"; | ||||||
| import { urlSyncMixin } from "../../src/state/url-sync-mixin"; | import { urlSyncMixin } from "../../src/state/url-sync-mixin"; | ||||||
| @@ -125,13 +124,9 @@ export class SupervisorBaseElement extends urlSyncMixin( | |||||||
|  |  | ||||||
|     this.supervisor = { |     this.supervisor = { | ||||||
|       ...this.supervisor, |       ...this.supervisor, | ||||||
|       localize: await computeLocalize<SupervisorKeys>( |       localize: await computeLocalize(this.constructor.prototype, language, { | ||||||
|         this.constructor.prototype, |  | ||||||
|         language, |  | ||||||
|         { |  | ||||||
|         [language]: data, |         [language]: data, | ||||||
|         } |       }), | ||||||
|       ), |  | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -48,7 +48,7 @@ class HassioCoreInfo extends LitElement { | |||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     return html` |     return html` | ||||||
|       <ha-card header="Core" outlined> |       <ha-card header="Core"> | ||||||
|         <div class="card-content"> |         <div class="card-content"> | ||||||
|           <div> |           <div> | ||||||
|             <ha-settings-row> |             <ha-settings-row> | ||||||
|   | |||||||
| @@ -66,7 +66,7 @@ class HassioHostInfo extends LitElement { | |||||||
|       }, |       }, | ||||||
|     ]; |     ]; | ||||||
|     return html` |     return html` | ||||||
|       <ha-card header="Host" outlined> |       <ha-card header="Host"> | ||||||
|         <div class="card-content"> |         <div class="card-content"> | ||||||
|           <div> |           <div> | ||||||
|             ${this.supervisor.host.features.includes("hostname") |             ${this.supervisor.host.features.includes("hostname") | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ import { | |||||||
| import { | import { | ||||||
|   UNHEALTHY_REASON_URL, |   UNHEALTHY_REASON_URL, | ||||||
|   UNSUPPORTED_REASON_URL, |   UNSUPPORTED_REASON_URL, | ||||||
| } from "../../../src/panels/config/repairs/dialog-system-information"; | } from "../../../src/panels/config/system-health/ha-config-system-health"; | ||||||
| import { haStyle } from "../../../src/resources/styles"; | import { haStyle } from "../../../src/resources/styles"; | ||||||
| import { HomeAssistant } from "../../../src/types"; | import { HomeAssistant } from "../../../src/types"; | ||||||
| import { bytesToString } from "../../../src/util/bytes-to-string"; | import { bytesToString } from "../../../src/util/bytes-to-string"; | ||||||
| @@ -57,7 +57,7 @@ class HassioSupervisorInfo extends LitElement { | |||||||
|       }, |       }, | ||||||
|     ]; |     ]; | ||||||
|     return html` |     return html` | ||||||
|       <ha-card header="Supervisor" outlined> |       <ha-card header="Supervisor"> | ||||||
|         <div class="card-content"> |         <div class="card-content"> | ||||||
|           <div> |           <div> | ||||||
|             <ha-settings-row> |             <ha-settings-row> | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ class HassioSupervisorLog extends LitElement { | |||||||
|  |  | ||||||
|   protected render(): TemplateResult | void { |   protected render(): TemplateResult | void { | ||||||
|     return html` |     return html` | ||||||
|       <ha-card outlined> |       <ha-card> | ||||||
|         ${this._error |         ${this._error | ||||||
|           ? html`<ha-alert alert-type="error">${this._error}</ha-alert>` |           ? html`<ha-alert alert-type="error">${this._error}</ha-alert>` | ||||||
|           : ""} |           : ""} | ||||||
|   | |||||||
| @@ -128,7 +128,6 @@ class UpdateAvailableCard extends LitElement { | |||||||
|  |  | ||||||
|     return html` |     return html` | ||||||
|       <ha-card |       <ha-card | ||||||
|         outlined |  | ||||||
|         .header=${this.supervisor.localize("update_available.update_name", { |         .header=${this.supervisor.localize("update_available.update_name", { | ||||||
|           name: this._name, |           name: this._name, | ||||||
|         })} |         })} | ||||||
|   | |||||||
| @@ -1,11 +1,4 @@ | |||||||
| module.exports = { | module.exports = { | ||||||
|   "*.{js,ts}": [ |   "*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix', | ||||||
|     "prettier --write", |   "!(/translations)*.{js,ts,json,css,md,html}": "prettier --write", | ||||||
|     'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix', |  | ||||||
|   ], |  | ||||||
|   "!(/translations)*.{json,css,md,html}": "prettier --write", |  | ||||||
|   "translations/*/*.json": (files) => |  | ||||||
|     'printf "%s\n" "These files should not be modified.  Instead, make the necessary modifications in src/translations/en.json.  Please see translations/README.md for details." ' + |  | ||||||
|     files.join(" ") + |  | ||||||
|     " >&2 && exit 1", |  | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -16,9 +16,6 @@ | |||||||
|     "lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md", |     "lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md", | ||||||
|     "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types", |     "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types", | ||||||
|     "format": "yarn run format:eslint && yarn run format:prettier", |     "format": "yarn run format:eslint && yarn run format:prettier", | ||||||
|     "postinstall": "husky install", |  | ||||||
|     "prepack": "pinst --disable", |  | ||||||
|     "postpack": "pinst --enable", |  | ||||||
|     "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\"" |     "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\"" | ||||||
|   }, |   }, | ||||||
|   "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", |   "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", | ||||||
| @@ -49,7 +46,6 @@ | |||||||
|     "@fullcalendar/daygrid": "5.9.0", |     "@fullcalendar/daygrid": "5.9.0", | ||||||
|     "@fullcalendar/interaction": "5.9.0", |     "@fullcalendar/interaction": "5.9.0", | ||||||
|     "@fullcalendar/list": "5.9.0", |     "@fullcalendar/list": "5.9.0", | ||||||
|     "@fullcalendar/timegrid": "5.9.0", |  | ||||||
|     "@lit-labs/motion": "^1.0.2", |     "@lit-labs/motion": "^1.0.2", | ||||||
|     "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch", |     "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch", | ||||||
|     "@material/chips": "14.0.0-canary.261f2db59.0", |     "@material/chips": "14.0.0-canary.261f2db59.0", | ||||||
| @@ -76,8 +72,8 @@ | |||||||
|     "@material/mwc-textfield": "0.25.3", |     "@material/mwc-textfield": "0.25.3", | ||||||
|     "@material/mwc-top-app-bar-fixed": "^0.25.3", |     "@material/mwc-top-app-bar-fixed": "^0.25.3", | ||||||
|     "@material/top-app-bar": "14.0.0-canary.261f2db59.0", |     "@material/top-app-bar": "14.0.0-canary.261f2db59.0", | ||||||
|     "@mdi/js": "7.0.96", |     "@mdi/js": "6.6.95", | ||||||
|     "@mdi/svg": "7.0.96", |     "@mdi/svg": "6.6.95", | ||||||
|     "@polymer/app-layout": "^3.1.0", |     "@polymer/app-layout": "^3.1.0", | ||||||
|     "@polymer/iron-flex-layout": "^3.0.1", |     "@polymer/iron-flex-layout": "^3.0.1", | ||||||
|     "@polymer/iron-icon": "^3.0.1", |     "@polymer/iron-icon": "^3.0.1", | ||||||
| @@ -93,8 +89,8 @@ | |||||||
|     "@polymer/paper-tooltip": "^3.0.1", |     "@polymer/paper-tooltip": "^3.0.1", | ||||||
|     "@polymer/polymer": "3.4.1", |     "@polymer/polymer": "3.4.1", | ||||||
|     "@thomasloven/round-slider": "0.5.4", |     "@thomasloven/round-slider": "0.5.4", | ||||||
|     "@vaadin/combo-box": "^23.1.5", |     "@vaadin/combo-box": "^22.0.4", | ||||||
|     "@vaadin/vaadin-themable-mixin": "^23.1.5", |     "@vaadin/vaadin-themable-mixin": "^22.0.4", | ||||||
|     "@vibrant/color": "^3.2.1-alpha.1", |     "@vibrant/color": "^3.2.1-alpha.1", | ||||||
|     "@vibrant/core": "^3.2.1-alpha.1", |     "@vibrant/core": "^3.2.1-alpha.1", | ||||||
|     "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", |     "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", | ||||||
| @@ -110,15 +106,17 @@ | |||||||
|     "deep-clone-simple": "^1.1.1", |     "deep-clone-simple": "^1.1.1", | ||||||
|     "deep-freeze": "^0.0.1", |     "deep-freeze": "^0.0.1", | ||||||
|     "fuse.js": "^6.0.0", |     "fuse.js": "^6.0.0", | ||||||
|  |     "fuzzysort": "^1.2.1", | ||||||
|     "google-timezones-json": "^1.0.2", |     "google-timezones-json": "^1.0.2", | ||||||
|     "hls.js": "^1.2.1", |     "hls.js": "^1.1.5", | ||||||
|     "home-assistant-js-websocket": "^8.0.0", |     "home-assistant-js-websocket": "^7.0.3", | ||||||
|     "idb-keyval": "^5.1.3", |     "idb-keyval": "^5.1.3", | ||||||
|     "intl-messageformat": "^9.9.1", |     "intl-messageformat": "^9.9.1", | ||||||
|     "js-yaml": "^4.1.0", |     "js-yaml": "^4.1.0", | ||||||
|     "leaflet": "^1.7.1", |     "leaflet": "^1.7.1", | ||||||
|     "leaflet-draw": "^1.0.4", |     "leaflet-draw": "^1.0.4", | ||||||
|     "lit": "^2.1.2", |     "lit": "^2.1.2", | ||||||
|  |     "lit-vaadin-helpers": "^0.3.0", | ||||||
|     "marked": "^4.0.12", |     "marked": "^4.0.12", | ||||||
|     "memoize-one": "^5.2.1", |     "memoize-one": "^5.2.1", | ||||||
|     "node-vibrant": "3.2.1-alpha.1", |     "node-vibrant": "3.2.1-alpha.1", | ||||||
| @@ -205,9 +203,9 @@ | |||||||
|     "gulp-rename": "^2.0.0", |     "gulp-rename": "^2.0.0", | ||||||
|     "gulp-zopfli-green": "^3.0.1", |     "gulp-zopfli-green": "^3.0.1", | ||||||
|     "html-minifier": "^4.0.0", |     "html-minifier": "^4.0.0", | ||||||
|     "husky": "^8.0.1", |     "husky": "^1.3.1", | ||||||
|     "instant-mocha": "^1.3.1", |     "instant-mocha": "^1.3.1", | ||||||
|     "lint-staged": "^13.0.3", |     "lint-staged": "^11.1.2", | ||||||
|     "lit-analyzer": "^1.2.1", |     "lit-analyzer": "^1.2.1", | ||||||
|     "lodash.template": "^4.5.0", |     "lodash.template": "^4.5.0", | ||||||
|     "magic-string": "^0.25.7", |     "magic-string": "^0.25.7", | ||||||
| @@ -216,7 +214,6 @@ | |||||||
|     "mocha": "^8.4.0", |     "mocha": "^8.4.0", | ||||||
|     "object-hash": "^2.0.3", |     "object-hash": "^2.0.3", | ||||||
|     "open": "^7.0.4", |     "open": "^7.0.4", | ||||||
|     "pinst": "^3.0.0", |  | ||||||
|     "prettier": "^2.4.1", |     "prettier": "^2.4.1", | ||||||
|     "require-dir": "^1.2.0", |     "require-dir": "^1.2.0", | ||||||
|     "rollup": "^2.8.2", |     "rollup": "^2.8.2", | ||||||
| @@ -249,6 +246,11 @@ | |||||||
|     "@lit/reactive-element": "1.2.1" |     "@lit/reactive-element": "1.2.1" | ||||||
|   }, |   }, | ||||||
|   "main": "src/home-assistant.js", |   "main": "src/home-assistant.js", | ||||||
|  |   "husky": { | ||||||
|  |     "hooks": { | ||||||
|  |       "pre-commit": "lint-staged" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|   "prettier": { |   "prettier": { | ||||||
|     "trailingComma": "es5", |     "trailingComma": "es5", | ||||||
|     "arrowParens": "always" |     "arrowParens": "always" | ||||||
|   | |||||||
| @@ -1,25 +1,3 @@ | |||||||
| [build-system] | [build-system] | ||||||
| requires = ["setuptools~=62.3", "wheel~=0.37.1"] | requires = ["setuptools~=60.5", "wheel~=0.37.1"] | ||||||
| build-backend = "setuptools.build_meta" | build-backend = "setuptools.build_meta" | ||||||
|  |  | ||||||
| [project] |  | ||||||
| name         = "home-assistant-frontend" |  | ||||||
| version      = "20220901.0" |  | ||||||
| license      = {text = "Apache-2.0"} |  | ||||||
| description  = "The Home Assistant frontend" |  | ||||||
| readme       = "README.md" |  | ||||||
| authors      = [ |  | ||||||
|     {name = "The Home Assistant Authors", email = "hello@home-assistant.io"} |  | ||||||
| ] |  | ||||||
| requires-python = ">=3.4.0" |  | ||||||
|  |  | ||||||
| [project.urls] |  | ||||||
| "Homepage" = "https://github.com/home-assistant/frontend" |  | ||||||
|  |  | ||||||
| [tool.setuptools] |  | ||||||
| platforms = ["any"] |  | ||||||
| zip-safe  = false |  | ||||||
| include-package-data = true |  | ||||||
|  |  | ||||||
| [tool.setuptools.packages.find] |  | ||||||
| include = ["hass_frontend*"] |  | ||||||
|   | |||||||
| @@ -24,15 +24,10 @@ function auto(version) { | |||||||
|   return patch(version); |   return patch(version); | ||||||
| } | } | ||||||
|  |  | ||||||
| function nightly() { |  | ||||||
|   return `${today()}.dev`; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const methods = { | const methods = { | ||||||
|   patch, |   patch, | ||||||
|   today, |   today, | ||||||
|   auto, |   auto, | ||||||
|   nightly, |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| async function main(args) { | async function main(args) { | ||||||
| @@ -55,18 +50,14 @@ async function main(args) { | |||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   const setup = fs.readFileSync("pyproject.toml", "utf8"); |   const setup = fs.readFileSync("setup.cfg", "utf8"); | ||||||
|   const version = setup.match(/version\W+=\W"(\d{8}\.\d)"/)[1]; |   const version = setup.match(/\d{8}\.\d+/)[0]; | ||||||
|   const newVersion = method(version); |   const newVersion = method(version); | ||||||
|  |  | ||||||
|   console.log("Current version:", version); |   console.log("Current version:", version); | ||||||
|   console.log("New version:", newVersion); |   console.log("New version:", newVersion); | ||||||
|  |  | ||||||
|   fs.writeFileSync( |   fs.writeFileSync("setup.cfg", setup.replace(version, newVersion), "utf-8"); | ||||||
|     "pyproject.toml", |  | ||||||
|     setup.replace(version, newVersion), |  | ||||||
|     "utf-8" |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   if (!commit) { |   if (!commit) { | ||||||
|     return; |     return; | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						| @@ -1,2 +1,26 @@ | |||||||
| # Setuptools v62.3 doesn't support editable installs with just 'pyproject.toml' (PEP 660). | [metadata] | ||||||
| # Keep this file until it does! | name         = home-assistant-frontend | ||||||
|  | version      = 20220502.0 | ||||||
|  | author       = The Home Assistant Authors | ||||||
|  | author_email = hello@home-assistant.io | ||||||
|  | license      = Apache-2.0 | ||||||
|  | platforms    = any | ||||||
|  | description  = The Home Assistant frontend | ||||||
|  | long_description = file: README.md | ||||||
|  | long_description_content_type = text/markdown | ||||||
|  | url          = https://github.com/home-assistant/frontend | ||||||
|  |  | ||||||
|  | [options] | ||||||
|  | packages = find: | ||||||
|  | zip_safe = False | ||||||
|  | include_package_data = True | ||||||
|  | python_requires = >= 3.4.0 | ||||||
|  |  | ||||||
|  | [options.packages.find] | ||||||
|  | include = | ||||||
|  |     hass_frontend* | ||||||
|  |  | ||||||
|  | [mypy] | ||||||
|  | python_version = 3.4 | ||||||
|  | show_error_codes = True | ||||||
|  | strict = True | ||||||
|   | |||||||
| @@ -314,8 +314,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _computeStepDescription(step: DataEntryFlowStepForm) { |   private _computeStepDescription(step: DataEntryFlowStepForm) { | ||||||
|     const resourceKey = |     const resourceKey = `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`; | ||||||
|       `ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description` as const; |  | ||||||
|     const args: string[] = []; |     const args: string[] = []; | ||||||
|     const placeholders = step.description_placeholders || {}; |     const placeholders = step.description_placeholders || {}; | ||||||
|     Object.keys(placeholders).forEach((key) => { |     Object.keys(placeholders).forEach((key) => { | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ import { | |||||||
|   mdiRobotVacuum, |   mdiRobotVacuum, | ||||||
|   mdiScriptText, |   mdiScriptText, | ||||||
|   mdiSineWave, |   mdiSineWave, | ||||||
|   mdiMicrophoneMessage, |   mdiTextToSpeech, | ||||||
|   mdiThermometer, |   mdiThermometer, | ||||||
|   mdiThermostat, |   mdiThermostat, | ||||||
|   mdiTimerOutline, |   mdiTimerOutline, | ||||||
| @@ -74,9 +74,8 @@ export const FIXED_DOMAIN_ICONS = { | |||||||
|   camera: mdiVideo, |   camera: mdiVideo, | ||||||
|   climate: mdiThermostat, |   climate: mdiThermostat, | ||||||
|   configurator: mdiCog, |   configurator: mdiCog, | ||||||
|   conversation: mdiMicrophoneMessage, |   conversation: mdiTextToSpeech, | ||||||
|   counter: mdiCounter, |   counter: mdiCounter, | ||||||
|   demo: mdiHomeAssistant, |  | ||||||
|   fan: mdiFan, |   fan: mdiFan, | ||||||
|   google_assistant: mdiGoogleAssistant, |   google_assistant: mdiGoogleAssistant, | ||||||
|   group: mdiGoogleCirclesCommunities, |   group: mdiGoogleCirclesCommunities, | ||||||
| @@ -98,7 +97,6 @@ export const FIXED_DOMAIN_ICONS = { | |||||||
|   proximity: mdiAppleSafari, |   proximity: mdiAppleSafari, | ||||||
|   remote: mdiRemote, |   remote: mdiRemote, | ||||||
|   scene: mdiPalette, |   scene: mdiPalette, | ||||||
|   schedule: mdiCalendarClock, |  | ||||||
|   script: mdiScriptText, |   script: mdiScriptText, | ||||||
|   select: mdiFormatListBulleted, |   select: mdiFormatListBulleted, | ||||||
|   sensor: mdiEye, |   sensor: mdiEye, | ||||||
| @@ -126,7 +124,6 @@ export const FIXED_DEVICE_CLASS_ICONS = { | |||||||
|   gas: mdiGasCylinder, |   gas: mdiGasCylinder, | ||||||
|   humidity: mdiWaterPercent, |   humidity: mdiWaterPercent, | ||||||
|   illuminance: mdiBrightness5, |   illuminance: mdiBrightness5, | ||||||
|   moisture: mdiWaterPercent, |  | ||||||
|   monetary: mdiCash, |   monetary: mdiCash, | ||||||
|   nitrogen_dioxide: mdiMolecule, |   nitrogen_dioxide: mdiMolecule, | ||||||
|   nitrogen_monoxide: mdiMolecule, |   nitrogen_monoxide: mdiMolecule, | ||||||
| @@ -168,6 +165,46 @@ export const DOMAINS_WITH_CARD = [ | |||||||
|   "water_heater", |   "water_heater", | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
|  | /** Domains with separate more info dialog. */ | ||||||
|  | export const DOMAINS_WITH_MORE_INFO = [ | ||||||
|  |   "alarm_control_panel", | ||||||
|  |   "automation", | ||||||
|  |   "camera", | ||||||
|  |   "climate", | ||||||
|  |   "configurator", | ||||||
|  |   "counter", | ||||||
|  |   "cover", | ||||||
|  |   "fan", | ||||||
|  |   "group", | ||||||
|  |   "humidifier", | ||||||
|  |   "input_datetime", | ||||||
|  |   "light", | ||||||
|  |   "lock", | ||||||
|  |   "media_player", | ||||||
|  |   "person", | ||||||
|  |   "remote", | ||||||
|  |   "script", | ||||||
|  |   "scene", | ||||||
|  |   "sun", | ||||||
|  |   "timer", | ||||||
|  |   "update", | ||||||
|  |   "vacuum", | ||||||
|  |   "water_heater", | ||||||
|  |   "weather", | ||||||
|  | ]; | ||||||
|  |  | ||||||
|  | /** Domains that do not show the default more info dialog content (e.g. the attribute section) | ||||||
|  |  *  and do not have a separate more info (so not in DOMAINS_WITH_MORE_INFO). */ | ||||||
|  | export const DOMAINS_HIDE_DEFAULT_MORE_INFO = [ | ||||||
|  |   "input_number", | ||||||
|  |   "input_select", | ||||||
|  |   "input_text", | ||||||
|  |   "number", | ||||||
|  |   "scene", | ||||||
|  |   "update", | ||||||
|  |   "select", | ||||||
|  | ]; | ||||||
|  |  | ||||||
| /** Domains that render an input element instead of a text value when displayed in a row. | /** Domains that render an input element instead of a text value when displayed in a row. | ||||||
|  *  Those rows should then not show a cursor pointer when hovered (which would normally |  *  Those rows should then not show a cursor pointer when hovered (which would normally | ||||||
|  *  be the default) unless the element itself enforces it (e.g. a button). Also those elements |  *  be the default) unless the element itself enforces it (e.g. a button). Also those elements | ||||||
| @@ -199,6 +236,9 @@ export const DOMAINS_INPUT_ROW = [ | |||||||
|   "vacuum", |   "vacuum", | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
|  | /** Domains that should have the history hidden in the more info dialog. */ | ||||||
|  | export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator"]; | ||||||
|  |  | ||||||
| /** States that we consider "off". */ | /** States that we consider "off". */ | ||||||
| export const STATES_OFF = ["closed", "locked", "off"]; | export const STATES_OFF = ["closed", "locked", "off"]; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import memoizeOne from "memoize-one"; | import memoizeOne from "memoize-one"; | ||||||
| import { FrontendLocaleData } from "../../data/translation"; | import { FrontendLocaleData } from "../../data/translation"; | ||||||
| import { polyfillsLoaded } from "../translations/localize"; |  | ||||||
| import { useAmPm } from "./use_am_pm"; | import { useAmPm } from "./use_am_pm"; | ||||||
|  | import { polyfillsLoaded } from "../translations/localize"; | ||||||
|  |  | ||||||
| if (__BUILD__ === "latest" && polyfillsLoaded) { | if (__BUILD__ === "latest" && polyfillsLoaded) { | ||||||
|   await polyfillsLoaded; |   await polyfillsLoaded; | ||||||
| @@ -28,28 +28,6 @@ const formatDateTimeMem = memoizeOne( | |||||||
|     ) |     ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| // Aug 9, 8:23 AM |  | ||||||
| export const formatShortDateTime = ( |  | ||||||
|   dateObj: Date, |  | ||||||
|   locale: FrontendLocaleData |  | ||||||
| ) => formatShortDateTimeMem(locale).format(dateObj); |  | ||||||
|  |  | ||||||
| const formatShortDateTimeMem = memoizeOne( |  | ||||||
|   (locale: FrontendLocaleData) => |  | ||||||
|     new Intl.DateTimeFormat( |  | ||||||
|       locale.language === "en" && !useAmPm(locale) |  | ||||||
|         ? "en-u-hc-h23" |  | ||||||
|         : locale.language, |  | ||||||
|       { |  | ||||||
|         month: "short", |  | ||||||
|         day: "numeric", |  | ||||||
|         hour: useAmPm(locale) ? "numeric" : "2-digit", |  | ||||||
|         minute: "2-digit", |  | ||||||
|         hour12: useAmPm(locale), |  | ||||||
|       } |  | ||||||
|     ) |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| // August 9, 2021, 8:23:15 AM | // August 9, 2021, 8:23:15 AM | ||||||
| export const formatDateTimeWithSeconds = ( | export const formatDateTimeWithSeconds = ( | ||||||
|   dateObj: Date, |   dateObj: Date, | ||||||
|   | |||||||
| @@ -1,28 +0,0 @@ | |||||||
| import { HaDurationData } from "../../components/ha-duration-input"; |  | ||||||
|  |  | ||||||
| const leftPad = (num: number) => (num < 10 ? `0${num}` : num); |  | ||||||
|  |  | ||||||
| export const formatDuration = (duration: HaDurationData) => { |  | ||||||
|   const d = duration.days || 0; |  | ||||||
|   const h = duration.hours || 0; |  | ||||||
|   const m = duration.minutes || 0; |  | ||||||
|   const s = duration.seconds || 0; |  | ||||||
|   const ms = duration.milliseconds || 0; |  | ||||||
|  |  | ||||||
|   if (d > 0) { |  | ||||||
|     return `${d} days ${h}:${leftPad(m)}:${leftPad(s)}`; |  | ||||||
|   } |  | ||||||
|   if (h > 0) { |  | ||||||
|     return `${h}:${leftPad(m)}:${leftPad(s)}`; |  | ||||||
|   } |  | ||||||
|   if (m > 0) { |  | ||||||
|     return `${m}:${leftPad(s)}`; |  | ||||||
|   } |  | ||||||
|   if (s > 0) { |  | ||||||
|     return `${s} seconds`; |  | ||||||
|   } |  | ||||||
|   if (ms > 0) { |  | ||||||
|     return `${ms} milliseconds`; |  | ||||||
|   } |  | ||||||
|   return null; |  | ||||||
| }; |  | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| import memoizeOne from "memoize-one"; | import memoizeOne from "memoize-one"; | ||||||
| import { FrontendLocaleData } from "../../data/translation"; | import { FrontendLocaleData } from "../../data/translation"; | ||||||
| import { polyfillsLoaded } from "../translations/localize"; |  | ||||||
| import { useAmPm } from "./use_am_pm"; | import { useAmPm } from "./use_am_pm"; | ||||||
|  | import { polyfillsLoaded } from "../translations/localize"; | ||||||
|  |  | ||||||
| if (__BUILD__ === "latest" && polyfillsLoaded) { | if (__BUILD__ === "latest" && polyfillsLoaded) { | ||||||
|   await polyfillsLoaded; |   await polyfillsLoaded; | ||||||
| @@ -64,17 +64,3 @@ const formatTimeWeekdayMem = memoizeOne( | |||||||
|       } |       } | ||||||
|     ) |     ) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| // 21:15 |  | ||||||
| export const formatTime24h = (dateObj: Date) => |  | ||||||
|   formatTime24hMem().format(dateObj); |  | ||||||
|  |  | ||||||
| const formatTime24hMem = memoizeOne( |  | ||||||
|   () => |  | ||||||
|     // en-GB to fix Chrome 24:59 to 0:59 https://stackoverflow.com/a/60898146 |  | ||||||
|     new Intl.DateTimeFormat("en-GB", { |  | ||||||
|       hour: "numeric", |  | ||||||
|       minute: "2-digit", |  | ||||||
|       hour12: false, |  | ||||||
|     }) |  | ||||||
| ); |  | ||||||
|   | |||||||
| @@ -76,11 +76,7 @@ class Storage { | |||||||
|   public setValue(storageKey: string, value: any): any { |   public setValue(storageKey: string, value: any): any { | ||||||
|     this._storage[storageKey] = value; |     this._storage[storageKey] = value; | ||||||
|     try { |     try { | ||||||
|       if (value === undefined) { |  | ||||||
|         window.localStorage.removeItem(storageKey); |  | ||||||
|       } else { |  | ||||||
|       window.localStorage.setItem(storageKey, JSON.stringify(value)); |       window.localStorage.setItem(storageKey, JSON.stringify(value)); | ||||||
|       } |  | ||||||
|     } catch (err: any) { |     } catch (err: any) { | ||||||
|       // Safari in private mode doesn't allow localstorage |       // Safari in private mode doesn't allow localstorage | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,41 +0,0 @@ | |||||||
| const DEFAULT_OWN = true; |  | ||||||
|  |  | ||||||
| // Finds the closest ancestor of an element that has a specific optionally owned property, |  | ||||||
| // traversing slot and shadow root boundaries until the body element is reached |  | ||||||
| export const closestWithProperty = ( |  | ||||||
|   element: Element | null, |  | ||||||
|   property: string | symbol, |  | ||||||
|   own = DEFAULT_OWN |  | ||||||
| ) => { |  | ||||||
|   if (!element || element === document.body) return null; |  | ||||||
|  |  | ||||||
|   element = element.assignedSlot ?? element; |  | ||||||
|   if (element.parentElement) { |  | ||||||
|     element = element.parentElement; |  | ||||||
|   } else { |  | ||||||
|     const root = element.getRootNode(); |  | ||||||
|     element = root instanceof ShadowRoot ? root.host : null; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if ( |  | ||||||
|     own |  | ||||||
|       ? Object.prototype.hasOwnProperty.call(element, property) |  | ||||||
|       : element && property in element |  | ||||||
|   ) |  | ||||||
|     return element; |  | ||||||
|   return closestWithProperty(element, property, own); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| // Finds the set of all such ancestors and includes starting element as first in the set |  | ||||||
| export const ancestorsWithProperty = ( |  | ||||||
|   element: Element | null, |  | ||||||
|   property: string | symbol, |  | ||||||
|   own = DEFAULT_OWN |  | ||||||
| ) => { |  | ||||||
|   const ancestors: Set<Element> = new Set(); |  | ||||||
|   while (element) { |  | ||||||
|     ancestors.add(element); |  | ||||||
|     element = closestWithProperty(element, property, own); |  | ||||||
|   } |  | ||||||
|   return ancestors; |  | ||||||
| }; |  | ||||||
| @@ -5,7 +5,8 @@ export type LeafletModuleType = typeof import("leaflet"); | |||||||
| export type LeafletDrawModuleType = typeof import("leaflet-draw"); | export type LeafletDrawModuleType = typeof import("leaflet-draw"); | ||||||
|  |  | ||||||
| export const setupLeafletMap = async ( | export const setupLeafletMap = async ( | ||||||
|   mapElement: HTMLElement |   mapElement: HTMLElement, | ||||||
|  |   darkMode?: boolean | ||||||
| ): Promise<[Map, LeafletModuleType, TileLayer]> => { | ): Promise<[Map, LeafletModuleType, TileLayer]> => { | ||||||
|   if (!mapElement.parentNode) { |   if (!mapElement.parentNode) { | ||||||
|     throw new Error("Cannot setup Leaflet map on disconnected element"); |     throw new Error("Cannot setup Leaflet map on disconnected element"); | ||||||
| @@ -22,7 +23,7 @@ export const setupLeafletMap = async ( | |||||||
|   mapElement.parentNode.appendChild(style); |   mapElement.parentNode.appendChild(style); | ||||||
|   map.setView([52.3731339, 4.8903147], 13); |   map.setView([52.3731339, 4.8903147], 13); | ||||||
|  |  | ||||||
|   const tileLayer = createTileLayer(Leaflet).addTo(map); |   const tileLayer = createTileLayer(Leaflet, Boolean(darkMode)).addTo(map); | ||||||
|  |  | ||||||
|   return [map, Leaflet, tileLayer]; |   return [map, Leaflet, tileLayer]; | ||||||
| }; | }; | ||||||
| @@ -30,19 +31,23 @@ export const setupLeafletMap = async ( | |||||||
| export const replaceTileLayer = ( | export const replaceTileLayer = ( | ||||||
|   leaflet: LeafletModuleType, |   leaflet: LeafletModuleType, | ||||||
|   map: Map, |   map: Map, | ||||||
|   tileLayer: TileLayer |   tileLayer: TileLayer, | ||||||
|  |   darkMode: boolean | ||||||
| ): TileLayer => { | ): TileLayer => { | ||||||
|   map.removeLayer(tileLayer); |   map.removeLayer(tileLayer); | ||||||
|   tileLayer = createTileLayer(leaflet); |   tileLayer = createTileLayer(leaflet, darkMode); | ||||||
|   tileLayer.addTo(map); |   tileLayer.addTo(map); | ||||||
|   return tileLayer; |   return tileLayer; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const createTileLayer = (leaflet: LeafletModuleType): TileLayer => | const createTileLayer = ( | ||||||
|  |   leaflet: LeafletModuleType, | ||||||
|  |   darkMode: boolean | ||||||
|  | ): TileLayer => | ||||||
|   leaflet.tileLayer( |   leaflet.tileLayer( | ||||||
|     `https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}${ |     `https://{s}.basemaps.cartocdn.com/${ | ||||||
|       leaflet.Browser.retina ? "@2x.png" : ".png" |       darkMode ? "dark_all" : "light_all" | ||||||
|     }`, |     }/{z}/{x}/{y}${leaflet.Browser.retina ? "@2x.png" : ".png"}`, | ||||||
|     { |     { | ||||||
|       attribution: |       attribution: | ||||||
|         '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, © <a href="https://carto.com/attributions">CARTO</a>', |         '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>, © <a href="https://carto.com/attributions">CARTO</a>', | ||||||
|   | |||||||
| @@ -1,11 +1,6 @@ | |||||||
| import { HassEntity } from "home-assistant-js-websocket"; | import { HassEntity } from "home-assistant-js-websocket"; | ||||||
| import { UNAVAILABLE_STATES } from "../../data/entity"; |  | ||||||
|  |  | ||||||
| export const computeActiveState = (stateObj: HassEntity): string => { | export const computeActiveState = (stateObj: HassEntity): string => { | ||||||
|   if (UNAVAILABLE_STATES.includes(stateObj.state)) { |  | ||||||
|     return stateObj.state; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   const domain = stateObj.entity_id.split(".")[0]; |   const domain = stateObj.entity_id.split(".")[0]; | ||||||
|   let state = stateObj.state; |   let state = stateObj.state; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,77 +2,67 @@ import { HassEntity } from "home-assistant-js-websocket"; | |||||||
| import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; | import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; | ||||||
| import { FrontendLocaleData } from "../../data/translation"; | import { FrontendLocaleData } from "../../data/translation"; | ||||||
| import { | import { | ||||||
|  |   updateIsInstalling, | ||||||
|  |   UpdateEntity, | ||||||
|   UPDATE_SUPPORT_PROGRESS, |   UPDATE_SUPPORT_PROGRESS, | ||||||
|   updateIsInstallingFromAttributes, |  | ||||||
| } from "../../data/update"; | } from "../../data/update"; | ||||||
| import { formatDate } from "../datetime/format_date"; | import { formatDate } from "../datetime/format_date"; | ||||||
| import { formatDateTime } from "../datetime/format_date_time"; | import { formatDateTime } from "../datetime/format_date_time"; | ||||||
| import { formatTime } from "../datetime/format_time"; | import { formatTime } from "../datetime/format_time"; | ||||||
| import { formatNumber, isNumericFromAttributes } from "../number/format_number"; | import { formatNumber, isNumericState } from "../number/format_number"; | ||||||
| import { LocalizeFunc } from "../translations/localize"; | import { LocalizeFunc } from "../translations/localize"; | ||||||
| import { supportsFeatureFromAttributes } from "./supports-feature"; | import { computeStateDomain } from "./compute_state_domain"; | ||||||
|  | import { supportsFeature } from "./supports-feature"; | ||||||
| import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration"; | import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration"; | ||||||
| import { computeDomain } from "./compute_domain"; |  | ||||||
|  |  | ||||||
| export const computeStateDisplay = ( | export const computeStateDisplay = ( | ||||||
|   localize: LocalizeFunc, |   localize: LocalizeFunc, | ||||||
|   stateObj: HassEntity, |   stateObj: HassEntity, | ||||||
|   locale: FrontendLocaleData, |   locale: FrontendLocaleData, | ||||||
|   state?: string |   state?: string | ||||||
| ): string => |  | ||||||
|   computeStateDisplayFromEntityAttributes( |  | ||||||
|     localize, |  | ||||||
|     locale, |  | ||||||
|     stateObj.entity_id, |  | ||||||
|     stateObj.attributes, |  | ||||||
|     state !== undefined ? state : stateObj.state |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
| export const computeStateDisplayFromEntityAttributes = ( |  | ||||||
|   localize: LocalizeFunc, |  | ||||||
|   locale: FrontendLocaleData, |  | ||||||
|   entityId: string, |  | ||||||
|   attributes: any, |  | ||||||
|   state: string |  | ||||||
| ): string => { | ): string => { | ||||||
|   if (state === UNKNOWN || state === UNAVAILABLE) { |   const compareState = state !== undefined ? state : stateObj.state; | ||||||
|     return localize(`state.default.${state}`); |  | ||||||
|  |   if (compareState === UNKNOWN || compareState === UNAVAILABLE) { | ||||||
|  |     return localize(`state.default.${compareState}`); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` |   // Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber` | ||||||
|   if (isNumericFromAttributes(attributes)) { |   if (isNumericState(stateObj)) { | ||||||
|     // state is duration |     // state is duration | ||||||
|     if ( |     if ( | ||||||
|       attributes.device_class === "duration" && |       stateObj.attributes.device_class === "duration" && | ||||||
|       attributes.unit_of_measurement && |       stateObj.attributes.unit_of_measurement && | ||||||
|       UNIT_TO_SECOND_CONVERT[attributes.unit_of_measurement] |       UNIT_TO_SECOND_CONVERT[stateObj.attributes.unit_of_measurement] | ||||||
|     ) { |     ) { | ||||||
|       try { |       try { | ||||||
|         return formatDuration(state, attributes.unit_of_measurement); |         return formatDuration( | ||||||
|  |           compareState, | ||||||
|  |           stateObj.attributes.unit_of_measurement | ||||||
|  |         ); | ||||||
|       } catch (_err) { |       } catch (_err) { | ||||||
|         // fallback to default |         // fallback to default | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (attributes.device_class === "monetary") { |     if (stateObj.attributes.device_class === "monetary") { | ||||||
|       try { |       try { | ||||||
|         return formatNumber(state, locale, { |         return formatNumber(compareState, locale, { | ||||||
|           style: "currency", |           style: "currency", | ||||||
|           currency: attributes.unit_of_measurement, |           currency: stateObj.attributes.unit_of_measurement, | ||||||
|           minimumFractionDigits: 2, |           minimumFractionDigits: 2, | ||||||
|         }); |         }); | ||||||
|       } catch (_err) { |       } catch (_err) { | ||||||
|         // fallback to default |         // fallback to default | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     const unit = !attributes.unit_of_measurement |     return `${formatNumber(compareState, locale)}${ | ||||||
|       ? "" |       stateObj.attributes.unit_of_measurement | ||||||
|       : attributes.unit_of_measurement === "%" |         ? " " + stateObj.attributes.unit_of_measurement | ||||||
|       ? "%" |         : "" | ||||||
|       : ` ${attributes.unit_of_measurement}`; |     }`; | ||||||
|     return `${formatNumber(state, locale)}${unit}`; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   const domain = computeDomain(entityId); |   const domain = computeStateDomain(stateObj); | ||||||
|  |  | ||||||
|   if (domain === "input_datetime") { |   if (domain === "input_datetime") { | ||||||
|     if (state !== undefined) { |     if (state !== undefined) { | ||||||
| @@ -107,32 +97,36 @@ export const computeStateDisplayFromEntityAttributes = ( | |||||||
|     } else { |     } else { | ||||||
|       // If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format. |       // If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format. | ||||||
|       let date: Date; |       let date: Date; | ||||||
|       if (attributes.has_date && attributes.has_time) { |       if (stateObj.attributes.has_date && stateObj.attributes.has_time) { | ||||||
|         date = new Date( |         date = new Date( | ||||||
|           attributes.year, |           stateObj.attributes.year, | ||||||
|           attributes.month - 1, |           stateObj.attributes.month - 1, | ||||||
|           attributes.day, |           stateObj.attributes.day, | ||||||
|           attributes.hour, |           stateObj.attributes.hour, | ||||||
|           attributes.minute |           stateObj.attributes.minute | ||||||
|         ); |         ); | ||||||
|         return formatDateTime(date, locale); |         return formatDateTime(date, locale); | ||||||
|       } |       } | ||||||
|       if (attributes.has_date) { |       if (stateObj.attributes.has_date) { | ||||||
|         date = new Date(attributes.year, attributes.month - 1, attributes.day); |         date = new Date( | ||||||
|  |           stateObj.attributes.year, | ||||||
|  |           stateObj.attributes.month - 1, | ||||||
|  |           stateObj.attributes.day | ||||||
|  |         ); | ||||||
|         return formatDate(date, locale); |         return formatDate(date, locale); | ||||||
|       } |       } | ||||||
|       if (attributes.has_time) { |       if (stateObj.attributes.has_time) { | ||||||
|         date = new Date(); |         date = new Date(); | ||||||
|         date.setHours(attributes.hour, attributes.minute); |         date.setHours(stateObj.attributes.hour, stateObj.attributes.minute); | ||||||
|         return formatTime(date, locale); |         return formatTime(date, locale); | ||||||
|       } |       } | ||||||
|       return state; |       return stateObj.state; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (domain === "humidifier") { |   if (domain === "humidifier") { | ||||||
|     if (state === "on" && attributes.humidity) { |     if (compareState === "on" && stateObj.attributes.humidity) { | ||||||
|       return `${attributes.humidity} %`; |       return `${stateObj.attributes.humidity} %`; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -142,7 +136,7 @@ export const computeStateDisplayFromEntityAttributes = ( | |||||||
|     domain === "number" || |     domain === "number" || | ||||||
|     domain === "input_number" |     domain === "input_number" | ||||||
|   ) { |   ) { | ||||||
|     return formatNumber(state, locale); |     return formatNumber(compareState, locale); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // state of button is a timestamp |   // state of button is a timestamp | ||||||
| @@ -150,12 +144,12 @@ export const computeStateDisplayFromEntityAttributes = ( | |||||||
|     domain === "button" || |     domain === "button" || | ||||||
|     domain === "input_button" || |     domain === "input_button" || | ||||||
|     domain === "scene" || |     domain === "scene" || | ||||||
|     (domain === "sensor" && attributes.device_class === "timestamp") |     (domain === "sensor" && stateObj.attributes.device_class === "timestamp") | ||||||
|   ) { |   ) { | ||||||
|     try { |     try { | ||||||
|       return formatDateTime(new Date(state), locale); |       return formatDateTime(new Date(compareState), locale); | ||||||
|     } catch (_err) { |     } catch (_err) { | ||||||
|       return state; |       return compareState; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -166,28 +160,30 @@ export const computeStateDisplayFromEntityAttributes = ( | |||||||
|     // When the latest version is skipped, show the latest version |     // When the latest version is skipped, show the latest version | ||||||
|     // When update is not available, show "Up-to-date" |     // When update is not available, show "Up-to-date" | ||||||
|     // When update is not available and there is no latest_version show "Unavailable" |     // When update is not available and there is no latest_version show "Unavailable" | ||||||
|     return state === "on" |     return compareState === "on" | ||||||
|       ? updateIsInstallingFromAttributes(attributes) |       ? updateIsInstalling(stateObj as UpdateEntity) | ||||||
|         ? supportsFeatureFromAttributes(attributes, UPDATE_SUPPORT_PROGRESS) |         ? supportsFeature(stateObj, UPDATE_SUPPORT_PROGRESS) | ||||||
|           ? localize("ui.card.update.installing_with_progress", { |           ? localize("ui.card.update.installing_with_progress", { | ||||||
|               progress: attributes.in_progress, |               progress: stateObj.attributes.in_progress, | ||||||
|             }) |             }) | ||||||
|           : localize("ui.card.update.installing") |           : localize("ui.card.update.installing") | ||||||
|         : attributes.latest_version |         : stateObj.attributes.latest_version | ||||||
|       : attributes.skipped_version === attributes.latest_version |       : stateObj.attributes.skipped_version === | ||||||
|       ? attributes.latest_version ?? localize("state.default.unavailable") |         stateObj.attributes.latest_version | ||||||
|  |       ? stateObj.attributes.latest_version ?? | ||||||
|  |         localize("state.default.unavailable") | ||||||
|       : localize("ui.card.update.up_to_date"); |       : localize("ui.card.update.up_to_date"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     // Return device class translation |     // Return device class translation | ||||||
|     (attributes.device_class && |     (stateObj.attributes.device_class && | ||||||
|       localize( |       localize( | ||||||
|         `component.${domain}.state.${attributes.device_class}.${state}` |         `component.${domain}.state.${stateObj.attributes.device_class}.${compareState}` | ||||||
|       )) || |       )) || | ||||||
|     // Return default translation |     // Return default translation | ||||||
|     localize(`component.${domain}.state._.${state}`) || |     localize(`component.${domain}.state._.${compareState}`) || | ||||||
|     // We don't know! Return the raw state. |     // We don't know! Return the raw state. | ||||||
|     state |     compareState | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,13 +1,7 @@ | |||||||
| import { HassEntity } from "home-assistant-js-websocket"; | import { HassEntity } from "home-assistant-js-websocket"; | ||||||
| import { computeObjectId } from "./compute_object_id"; | import { computeObjectId } from "./compute_object_id"; | ||||||
|  |  | ||||||
| export const computeStateNameFromEntityAttributes = ( |  | ||||||
|   entityId: string, |  | ||||||
|   attributes: { [key: string]: any } |  | ||||||
| ): string => |  | ||||||
|   attributes.friendly_name === undefined |  | ||||||
|     ? computeObjectId(entityId).replace(/_/g, " ") |  | ||||||
|     : attributes.friendly_name || ""; |  | ||||||
|  |  | ||||||
| export const computeStateName = (stateObj: HassEntity): string => | export const computeStateName = (stateObj: HassEntity): string => | ||||||
|   computeStateNameFromEntityAttributes(stateObj.entity_id, stateObj.attributes); |   stateObj.attributes.friendly_name === undefined | ||||||
|  |     ? computeObjectId(stateObj.entity_id).replace(/_/g, " ") | ||||||
|  |     : stateObj.attributes.friendly_name || ""; | ||||||
|   | |||||||
| @@ -8,8 +8,6 @@ import { | |||||||
|   mdiCalendar, |   mdiCalendar, | ||||||
|   mdiCast, |   mdiCast, | ||||||
|   mdiCastConnected, |   mdiCastConnected, | ||||||
|   mdiCastOff, |  | ||||||
|   mdiChartSankey, |  | ||||||
|   mdiCheckCircleOutline, |   mdiCheckCircleOutline, | ||||||
|   mdiClock, |   mdiClock, | ||||||
|   mdiCloseCircleOutline, |   mdiCloseCircleOutline, | ||||||
| @@ -26,22 +24,12 @@ import { | |||||||
|   mdiPowerPlug, |   mdiPowerPlug, | ||||||
|   mdiPowerPlugOff, |   mdiPowerPlugOff, | ||||||
|   mdiRestart, |   mdiRestart, | ||||||
|   mdiSpeaker, |  | ||||||
|   mdiSpeakerOff, |  | ||||||
|   mdiSpeakerPause, |  | ||||||
|   mdiSpeakerPlay, |  | ||||||
|   mdiSwapHorizontal, |  | ||||||
|   mdiTelevision, |  | ||||||
|   mdiTelevisionOff, |  | ||||||
|   mdiTelevisionPause, |  | ||||||
|   mdiTelevisionPlay, |  | ||||||
|   mdiToggleSwitchVariant, |   mdiToggleSwitchVariant, | ||||||
|   mdiToggleSwitchVariantOff, |   mdiToggleSwitchVariantOff, | ||||||
|   mdiWeatherNight, |   mdiWeatherNight, | ||||||
| } from "@mdi/js"; | } from "@mdi/js"; | ||||||
| import { HassEntity } from "home-assistant-js-websocket"; | import { HassEntity } from "home-assistant-js-websocket"; | ||||||
| import { UpdateEntity, updateIsInstalling } from "../../data/update"; | import { updateIsInstalling, UpdateEntity } from "../../data/update"; | ||||||
| import { weatherIcon } from "../../data/weather"; |  | ||||||
| /** | /** | ||||||
|  * Return the icon to be used for a domain. |  * Return the icon to be used for a domain. | ||||||
|  * |  * | ||||||
| @@ -58,20 +46,6 @@ export const domainIcon = ( | |||||||
|   stateObj?: HassEntity, |   stateObj?: HassEntity, | ||||||
|   state?: string |   state?: string | ||||||
| ): string => { | ): string => { | ||||||
|   const icon = domainIconWithoutDefault(domain, stateObj, state); |  | ||||||
|   if (icon) { |  | ||||||
|     return icon; |  | ||||||
|   } |  | ||||||
|   // eslint-disable-next-line |  | ||||||
|   console.warn(`Unable to find icon for domain ${domain}`); |  | ||||||
|   return DEFAULT_DOMAIN_ICON; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const domainIconWithoutDefault = ( |  | ||||||
|   domain: string, |  | ||||||
|   stateObj?: HassEntity, |  | ||||||
|   state?: string |  | ||||||
| ): string | undefined => { |  | ||||||
|   const compareState = state !== undefined ? state : stateObj?.state; |   const compareState = state !== undefined ? state : stateObj?.state; | ||||||
|  |  | ||||||
|   switch (domain) { |   switch (domain) { | ||||||
| @@ -113,15 +87,6 @@ export const domainIconWithoutDefault = ( | |||||||
|         ? mdiCheckCircleOutline |         ? mdiCheckCircleOutline | ||||||
|         : mdiCloseCircleOutline; |         : mdiCloseCircleOutline; | ||||||
|  |  | ||||||
|     case "input_datetime": |  | ||||||
|       if (!stateObj?.attributes.has_date) { |  | ||||||
|         return mdiClock; |  | ||||||
|       } |  | ||||||
|       if (!stateObj.attributes.has_time) { |  | ||||||
|         return mdiCalendar; |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|  |  | ||||||
|     case "lock": |     case "lock": | ||||||
|       switch (compareState) { |       switch (compareState) { | ||||||
|         case "unlocked": |         case "unlocked": | ||||||
| @@ -136,40 +101,7 @@ export const domainIconWithoutDefault = ( | |||||||
|       } |       } | ||||||
|  |  | ||||||
|     case "media_player": |     case "media_player": | ||||||
|       switch (stateObj?.attributes.device_class) { |       return compareState === "playing" ? mdiCastConnected : mdiCast; | ||||||
|         case "speaker": |  | ||||||
|           switch (compareState) { |  | ||||||
|             case "playing": |  | ||||||
|               return mdiSpeakerPlay; |  | ||||||
|             case "paused": |  | ||||||
|               return mdiSpeakerPause; |  | ||||||
|             case "off": |  | ||||||
|               return mdiSpeakerOff; |  | ||||||
|             default: |  | ||||||
|               return mdiSpeaker; |  | ||||||
|           } |  | ||||||
|         case "tv": |  | ||||||
|           switch (compareState) { |  | ||||||
|             case "playing": |  | ||||||
|               return mdiTelevisionPlay; |  | ||||||
|             case "paused": |  | ||||||
|               return mdiTelevisionPause; |  | ||||||
|             case "off": |  | ||||||
|               return mdiTelevisionOff; |  | ||||||
|             default: |  | ||||||
|               return mdiTelevision; |  | ||||||
|           } |  | ||||||
|         default: |  | ||||||
|           switch (compareState) { |  | ||||||
|             case "playing": |  | ||||||
|             case "paused": |  | ||||||
|               return mdiCastConnected; |  | ||||||
|             case "off": |  | ||||||
|               return mdiCastOff; |  | ||||||
|             default: |  | ||||||
|               return mdiCast; |  | ||||||
|           } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|     case "switch": |     case "switch": | ||||||
|       switch (stateObj?.attributes.device_class) { |       switch (stateObj?.attributes.device_class) { | ||||||
| @@ -192,31 +124,33 @@ export const domainIconWithoutDefault = ( | |||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     case "input_datetime": | ||||||
|  |       if (!stateObj?.attributes.has_date) { | ||||||
|  |         return mdiClock; | ||||||
|  |       } | ||||||
|  |       if (!stateObj.attributes.has_time) { | ||||||
|  |         return mdiCalendar; | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |  | ||||||
|     case "sun": |     case "sun": | ||||||
|       return stateObj?.state === "above_horizon" |       return stateObj?.state === "above_horizon" | ||||||
|         ? FIXED_DOMAIN_ICONS[domain] |         ? FIXED_DOMAIN_ICONS[domain] | ||||||
|         : mdiWeatherNight; |         : mdiWeatherNight; | ||||||
|  |  | ||||||
|     case "switch_as_x": |  | ||||||
|       return mdiSwapHorizontal; |  | ||||||
|  |  | ||||||
|     case "threshold": |  | ||||||
|       return mdiChartSankey; |  | ||||||
|  |  | ||||||
|     case "update": |     case "update": | ||||||
|       return compareState === "on" |       return compareState === "on" | ||||||
|         ? updateIsInstalling(stateObj as UpdateEntity) |         ? updateIsInstalling(stateObj as UpdateEntity) | ||||||
|           ? mdiPackageDown |           ? mdiPackageDown | ||||||
|           : mdiPackageUp |           : mdiPackageUp | ||||||
|         : mdiPackage; |         : mdiPackage; | ||||||
|  |  | ||||||
|     case "weather": |  | ||||||
|       return weatherIcon(stateObj?.state); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (domain in FIXED_DOMAIN_ICONS) { |   if (domain in FIXED_DOMAIN_ICONS) { | ||||||
|     return FIXED_DOMAIN_ICONS[domain]; |     return FIXED_DOMAIN_ICONS[domain]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return undefined; |   // eslint-disable-next-line | ||||||
|  |   console.warn(`Unable to find icon for domain ${domain}`); | ||||||
|  |   return DEFAULT_DOMAIN_ICON; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,277 +0,0 @@ | |||||||
| import { HassEntity } from "home-assistant-js-websocket"; |  | ||||||
| import { computeStateDomain } from "./compute_state_domain"; |  | ||||||
| import { UNAVAILABLE_STATES } from "../../data/entity"; |  | ||||||
|  |  | ||||||
| const FIXED_DOMAIN_STATES = { |  | ||||||
|   alarm_control_panel: [ |  | ||||||
|     "armed_away", |  | ||||||
|     "armed_custom_bypass", |  | ||||||
|     "armed_home", |  | ||||||
|     "armed_night", |  | ||||||
|     "armed_vacation", |  | ||||||
|     "arming", |  | ||||||
|     "disarmed", |  | ||||||
|     "disarming", |  | ||||||
|     "pending", |  | ||||||
|     "triggered", |  | ||||||
|   ], |  | ||||||
|   automation: ["on", "off"], |  | ||||||
|   binary_sensor: ["on", "off"], |  | ||||||
|   button: [], |  | ||||||
|   calendar: ["on", "off"], |  | ||||||
|   camera: ["idle", "recording", "streaming"], |  | ||||||
|   cover: ["closed", "closing", "open", "opening"], |  | ||||||
|   device_tracker: ["home", "not_home"], |  | ||||||
|   fan: ["on", "off"], |  | ||||||
|   humidifier: ["on", "off"], |  | ||||||
|   input_boolean: ["on", "off"], |  | ||||||
|   input_button: [], |  | ||||||
|   light: ["on", "off"], |  | ||||||
|   lock: ["jammed", "locked", "locking", "unlocked", "unlocking"], |  | ||||||
|   media_player: ["idle", "off", "paused", "playing", "standby"], |  | ||||||
|   person: ["home", "not_home"], |  | ||||||
|   remote: ["on", "off"], |  | ||||||
|   scene: [], |  | ||||||
|   schedule: ["on", "off"], |  | ||||||
|   script: ["on", "off"], |  | ||||||
|   siren: ["on", "off"], |  | ||||||
|   sun: ["above_horizon", "below_horizon"], |  | ||||||
|   switch: ["on", "off"], |  | ||||||
|   update: ["on", "off"], |  | ||||||
|   vacuum: ["cleaning", "docked", "error", "idle", "paused", "returning"], |  | ||||||
|   weather: [ |  | ||||||
|     "clear-night", |  | ||||||
|     "cloudy", |  | ||||||
|     "exceptional", |  | ||||||
|     "fog", |  | ||||||
|     "hail", |  | ||||||
|     "lightning-rainy", |  | ||||||
|     "lightning", |  | ||||||
|     "partlycloudy", |  | ||||||
|     "pouring", |  | ||||||
|     "rainy", |  | ||||||
|     "snowy-rainy", |  | ||||||
|     "snowy", |  | ||||||
|     "sunny", |  | ||||||
|     "windy-variant", |  | ||||||
|     "windy", |  | ||||||
|   ], |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const FIXED_DOMAIN_ATTRIBUTE_STATES = { |  | ||||||
|   alarm_control_panel: { |  | ||||||
|     code_format: ["number", "text"], |  | ||||||
|   }, |  | ||||||
|   binary_sensor: { |  | ||||||
|     device_class: [ |  | ||||||
|       "battery", |  | ||||||
|       "battery_charging", |  | ||||||
|       "co", |  | ||||||
|       "cold", |  | ||||||
|       "connectivity", |  | ||||||
|       "door", |  | ||||||
|       "garage_door", |  | ||||||
|       "gas", |  | ||||||
|       "heat", |  | ||||||
|       "light", |  | ||||||
|       "lock", |  | ||||||
|       "moisture", |  | ||||||
|       "motion", |  | ||||||
|       "moving", |  | ||||||
|       "occupancy", |  | ||||||
|       "opening", |  | ||||||
|       "plug", |  | ||||||
|       "power", |  | ||||||
|       "presence", |  | ||||||
|       "problem", |  | ||||||
|       "running", |  | ||||||
|       "safety", |  | ||||||
|       "smoke", |  | ||||||
|       "sound", |  | ||||||
|       "tamper", |  | ||||||
|       "update", |  | ||||||
|       "vibration", |  | ||||||
|       "window", |  | ||||||
|     ], |  | ||||||
|   }, |  | ||||||
|   button: { |  | ||||||
|     device_class: ["restart", "update"], |  | ||||||
|   }, |  | ||||||
|   camera: { |  | ||||||
|     frontend_stream_type: ["hls", "web_rtc"], |  | ||||||
|   }, |  | ||||||
|   climate: { |  | ||||||
|     hvac_action: ["off", "idle", "heating", "cooling", "drying", "fan"], |  | ||||||
|   }, |  | ||||||
|   cover: { |  | ||||||
|     device_class: [ |  | ||||||
|       "awning", |  | ||||||
|       "blind", |  | ||||||
|       "curtain", |  | ||||||
|       "damper", |  | ||||||
|       "door", |  | ||||||
|       "garage", |  | ||||||
|       "gate", |  | ||||||
|       "shade", |  | ||||||
|       "shutter", |  | ||||||
|       "window", |  | ||||||
|     ], |  | ||||||
|   }, |  | ||||||
|   humidifier: { |  | ||||||
|     device_class: ["humidifier", "dehumidifier"], |  | ||||||
|   }, |  | ||||||
|   media_player: { |  | ||||||
|     device_class: ["tv", "speaker", "receiver"], |  | ||||||
|     media_content_type: [ |  | ||||||
|       "app", |  | ||||||
|       "channel", |  | ||||||
|       "episode", |  | ||||||
|       "game", |  | ||||||
|       "image", |  | ||||||
|       "movie", |  | ||||||
|       "music", |  | ||||||
|       "playlist", |  | ||||||
|       "tvshow", |  | ||||||
|       "url", |  | ||||||
|       "video", |  | ||||||
|     ], |  | ||||||
|   }, |  | ||||||
|   number: { |  | ||||||
|     device_class: ["temperature"], |  | ||||||
|   }, |  | ||||||
|   sensor: { |  | ||||||
|     device_class: [ |  | ||||||
|       "apparent_power", |  | ||||||
|       "aqi", |  | ||||||
|       "battery", |  | ||||||
|       "carbon_dioxide", |  | ||||||
|       "carbon_monoxide", |  | ||||||
|       "current", |  | ||||||
|       "date", |  | ||||||
|       "duration", |  | ||||||
|       "energy", |  | ||||||
|       "frequency", |  | ||||||
|       "gas", |  | ||||||
|       "humidity", |  | ||||||
|       "illuminance", |  | ||||||
|       "monetary", |  | ||||||
|       "nitrogen_dioxide", |  | ||||||
|       "nitrogen_monoxide", |  | ||||||
|       "nitrous_oxide", |  | ||||||
|       "ozone", |  | ||||||
|       "pm1", |  | ||||||
|       "pm10", |  | ||||||
|       "pm25", |  | ||||||
|       "power_factor", |  | ||||||
|       "power", |  | ||||||
|       "pressure", |  | ||||||
|       "reactive_power", |  | ||||||
|       "signal_strength", |  | ||||||
|       "sulphur_dioxide", |  | ||||||
|       "temperature", |  | ||||||
|       "timestamp", |  | ||||||
|       "volatile_organic_compounds", |  | ||||||
|       "voltage", |  | ||||||
|     ], |  | ||||||
|     state_class: ["measurement", "total", "total_increasing"], |  | ||||||
|   }, |  | ||||||
|   switch: { |  | ||||||
|     device_class: ["outlet", "switch"], |  | ||||||
|   }, |  | ||||||
|   update: { |  | ||||||
|     device_class: ["firmware"], |  | ||||||
|   }, |  | ||||||
|   water_heater: { |  | ||||||
|     away_mode: ["on", "off"], |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const getStates = ( |  | ||||||
|   state: HassEntity, |  | ||||||
|   attribute: string | undefined = undefined |  | ||||||
| ): string[] => { |  | ||||||
|   const domain = computeStateDomain(state); |  | ||||||
|   const result: string[] = []; |  | ||||||
|  |  | ||||||
|   if (!attribute && domain in FIXED_DOMAIN_STATES) { |  | ||||||
|     result.push(...FIXED_DOMAIN_STATES[domain]); |  | ||||||
|   } else if ( |  | ||||||
|     attribute && |  | ||||||
|     domain in FIXED_DOMAIN_ATTRIBUTE_STATES && |  | ||||||
|     attribute in FIXED_DOMAIN_ATTRIBUTE_STATES[domain] |  | ||||||
|   ) { |  | ||||||
|     result.push(...FIXED_DOMAIN_ATTRIBUTE_STATES[domain][attribute]); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Dynamic values based on the entities |  | ||||||
|   switch (domain) { |  | ||||||
|     case "climate": |  | ||||||
|       if (!attribute) { |  | ||||||
|         result.push(...state.attributes.hvac_modes); |  | ||||||
|       } else if (attribute === "fan_mode") { |  | ||||||
|         result.push(...state.attributes.fan_modes); |  | ||||||
|       } else if (attribute === "preset_mode") { |  | ||||||
|         result.push(...state.attributes.preset_modes); |  | ||||||
|       } else if (attribute === "swing_mode") { |  | ||||||
|         result.push(...state.attributes.swing_modes); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case "device_tracker": |  | ||||||
|     case "person": |  | ||||||
|       if (!attribute) { |  | ||||||
|         result.push("home", "not_home"); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case "fan": |  | ||||||
|       if (attribute === "preset_mode") { |  | ||||||
|         result.push(...state.attributes.preset_modes); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case "humidifier": |  | ||||||
|       if (attribute === "mode") { |  | ||||||
|         result.push(...state.attributes.available_modes); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case "input_select": |  | ||||||
|     case "select": |  | ||||||
|       if (!attribute) { |  | ||||||
|         result.push(...state.attributes.options); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case "light": |  | ||||||
|       if (attribute === "effect") { |  | ||||||
|         result.push(...state.attributes.effect_list); |  | ||||||
|       } else if (attribute === "color_mode") { |  | ||||||
|         result.push(...state.attributes.color_modes); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case "media_player": |  | ||||||
|       if (attribute === "sound_mode") { |  | ||||||
|         result.push(...state.attributes.sound_mode_list); |  | ||||||
|       } else if (attribute === "source") { |  | ||||||
|         result.push(...state.attributes.source_list); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case "remote": |  | ||||||
|       if (attribute === "current_activity") { |  | ||||||
|         result.push(...state.attributes.activity_list); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case "vacuum": |  | ||||||
|       if (attribute === "fan_speed") { |  | ||||||
|         result.push(...state.attributes.fan_speed_list); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|     case "water_heater": |  | ||||||
|       if (!attribute || attribute === "operation_mode") { |  | ||||||
|         result.push(...state.attributes.operation_list); |  | ||||||
|       } |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (!attribute) { |  | ||||||
|     // All entities can have unavailable states |  | ||||||
|     result.push(...UNAVAILABLE_STATES); |  | ||||||
|   } |  | ||||||
|   return [...new Set(result)]; |  | ||||||
| }; |  | ||||||
| @@ -3,13 +3,6 @@ import { HassEntity } from "home-assistant-js-websocket"; | |||||||
| export const supportsFeature = ( | export const supportsFeature = ( | ||||||
|   stateObj: HassEntity, |   stateObj: HassEntity, | ||||||
|   feature: number |   feature: number | ||||||
| ): boolean => supportsFeatureFromAttributes(stateObj.attributes, feature); |  | ||||||
|  |  | ||||||
| export const supportsFeatureFromAttributes = ( |  | ||||||
|   attributes: { |  | ||||||
|     [key: string]: any; |  | ||||||
|   }, |  | ||||||
|   feature: number |  | ||||||
| ): boolean => | ): boolean => | ||||||
|   // eslint-disable-next-line no-bitwise |   // eslint-disable-next-line no-bitwise | ||||||
|   (attributes.supported_features! & feature) !== 0; |   (stateObj.attributes.supported_features! & feature) !== 0; | ||||||
|   | |||||||