mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-31 14:39:38 +00:00 
			
		
		
		
	Compare commits
	
		
			3 Commits
		
	
	
		
			combine-ap
			...
			attributes
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 39200b62d5 | ||
|   | 0eb28ea733 | ||
|   | 62d471888f | 
| @@ -1,13 +0,0 @@ | ||||
| # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile | ||||
| FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9 | ||||
|  | ||||
| ENV \ | ||||
|   DEBIAN_FRONTEND=noninteractive \ | ||||
|   DEVCONTAINER=true \ | ||||
|   PATH=$PATH:./node_modules/.bin | ||||
|  | ||||
| # Install nvm | ||||
| COPY .nvmrc /tmp/.nvmrc | ||||
| RUN \ | ||||
|   su vscode -c \ | ||||
|     "source /usr/local/share/nvm/nvm.sh && nvm install $(cat /tmp/.nvmrc) 2>&1" | ||||
| @@ -1,34 +0,0 @@ | ||||
| { | ||||
|   "name": "Home Assistant Frontend", | ||||
|   "build": { | ||||
|     "dockerfile": "Dockerfile", | ||||
|     "context": ".." | ||||
|   }, | ||||
|   "appPort": "8124:8123", | ||||
|   "context": "..", | ||||
|   "postCreateCommand": "script/bootstrap", | ||||
|   "extensions": [ | ||||
|     "github.vscode-pull-request-github", | ||||
|     "dbaeumer.vscode-eslint", | ||||
|     "ms-vscode.vscode-typescript-tslint-plugin", | ||||
|     "esbenp.prettier-vscode", | ||||
|     "bierner.lit-html", | ||||
|     "runem.lit-plugin", | ||||
|     "ms-python.vscode-pylance" | ||||
|   ], | ||||
|   "settings": { | ||||
|     "terminal.integrated.shell.linux": "/bin/bash", | ||||
|     "files.eol": "\n", | ||||
|     "editor.tabSize": 2, | ||||
|     "editor.formatOnPaste": false, | ||||
|     "editor.formatOnSave": true, | ||||
|     "editor.formatOnType": true, | ||||
|     "[typescript]": { | ||||
|       "editor.defaultFormatter": "esbenp.prettier-vscode" | ||||
|     }, | ||||
|     "[javascript]": { | ||||
|       "editor.defaultFormatter": "esbenp.prettier-vscode" | ||||
|     }, | ||||
|     "files.trimTrailingWhitespace": true | ||||
|   } | ||||
| } | ||||
| @@ -74,12 +74,12 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w | ||||
| 
 | ||||
| ``` | ||||
| 
 | ||||
| ## Problem-relevant frontend configuration | ||||
| ## Problem-relevant configuration | ||||
| 
 | ||||
| <!-- | ||||
|   An example configuration that caused the problem for you, e.g. the YAML configuration | ||||
|   of the used cards. Fill this out even if it seems unimportant to you. Please be sure | ||||
|   to remove personal information like passwords, private URLs and other credentials. | ||||
|   An example configuration that caused the problem for you. Fill this out even | ||||
|   if it seems unimportant to you. Please be sure to remove personal information | ||||
|   like passwords, private URLs and other credentials. | ||||
| --> | ||||
| 
 | ||||
| ```yaml | ||||
| @@ -89,7 +89,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w | ||||
| ## Javascript errors shown in your browser console/inspector | ||||
| 
 | ||||
| <!-- | ||||
|   If you come across any Javascript or other error logs, e.g. in your browser | ||||
|   If you come across any javascript or other error logs, e.g., in your browser | ||||
|   console/inspector please provide them. | ||||
| --> | ||||
| 
 | ||||
							
								
								
									
										138
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										138
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,138 +0,0 @@ | ||||
| name: Report a bug with the UI, Frontend or Lovelace | ||||
| about: Report an issue related to the Home Assistant frontend. | ||||
| labels: bug | ||||
| title: "" | ||||
| issue_body: true | ||||
| body: | ||||
|   - type: markdown | ||||
|     attributes: | ||||
|       value: | | ||||
|         Make sure you are running the [latest version of Home Assistant][releases] before reporting 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 Lovelace cards.** | ||||
|  | ||||
|         [fr]: https://github.com/home-assistant/frontend/discussions | ||||
|         [releases]: https://github.com/home-assistant/home-assistant/releases | ||||
|   - type: checkboxes | ||||
|     attributes: | ||||
|       label: Checklist | ||||
|       description: Please verify that you've followed these steps | ||||
|       options: | ||||
|         - label: I have updated to the latest available Home Assistant version. | ||||
|           required: true | ||||
|         - label: I have cleared the cache of my browser. | ||||
|           required: true | ||||
|         - label: I have tried a different browser to see if it is related to my browser. | ||||
|           required: true | ||||
|   - type: markdown | ||||
|     attributes: | ||||
|       value: | | ||||
|         ## The problem | ||||
|   - type: textarea | ||||
|     validations: | ||||
|       required: true | ||||
|     attributes: | ||||
|       label: Describe the issue you are experiencing | ||||
|       description: Provide a clear and concise description of what the bug is. | ||||
|   - type: textarea | ||||
|     validations: | ||||
|       required: true | ||||
|     attributes: | ||||
|       label: Describe the behavior you expected | ||||
|       description: Describe what you expected to happen or it should look/behave. | ||||
|   - type: textarea | ||||
|     validations: | ||||
|       required: true | ||||
|     attributes: | ||||
|       label: Steps to reproduce the issue | ||||
|       description: | | ||||
|         Please tell us exactly how to reproduce your issue. | ||||
|         Provide clear and concise step by step instructions and add code snippets if needed. | ||||
|       value: | | ||||
|         1. | ||||
|         2. | ||||
|         3. | ||||
|         ... | ||||
|   - type: markdown | ||||
|     attributes: | ||||
|       value: | | ||||
|         ## Environment | ||||
|   - type: input | ||||
|     validations: | ||||
|       required: true | ||||
|     attributes: | ||||
|       label: What version of Home Assistant Core has the issue? | ||||
|       placeholder: core- | ||||
|       description: > | ||||
|         Can be found in the Configuration panel -> Info. | ||||
|   - type: input | ||||
|     attributes: | ||||
|       label: What was the last working version of Home Assistant Core? | ||||
|       placeholder: core- | ||||
|       description: > | ||||
|         If known, otherwise leave blank. | ||||
|   - type: input | ||||
|     attributes: | ||||
|       label: In which browser are you experiencing the issue with? | ||||
|       placeholder: Google Chrome 88.0.4324.150 | ||||
|       description: > | ||||
|         Provide the full name and don't forget to add the version! | ||||
|   - type: input | ||||
|     attributes: | ||||
|       label: Which operating system are you using to run this browser? | ||||
|       placeholder: macOS Big Sur (1.11) | ||||
|       description: > | ||||
|         Don't forget to add the version! | ||||
|   - type: markdown | ||||
|     attributes: | ||||
|       value: | | ||||
|         # Details | ||||
|  | ||||
|   - type: textarea | ||||
|     attributes: | ||||
|       label: State of relevant entities | ||||
|       description: > | ||||
|         If your issue is about how an entity is shown in the UI, please add the | ||||
|         state and attributes for all situations. You can find this information | ||||
|         at Developer Tools -> States. | ||||
|       value: | | ||||
|         ```yaml | ||||
|         # Paste your state here. | ||||
|  | ||||
|         ``` | ||||
|   - type: textarea | ||||
|     attributes: | ||||
|       label: Problem-relevant frontend configuration | ||||
|       description: > | ||||
|         An example configuration that caused the problem for you, e.g., the YAML | ||||
|         configuration of the used cards. Fill this out even if it seems | ||||
|         unimportant to you. Please be sure to remove personal information like | ||||
|         passwords, private URLs and other credentials. | ||||
|       value: | | ||||
|         ```yaml | ||||
|         # Paste your YAML here. | ||||
|  | ||||
|         ``` | ||||
|   - type: textarea | ||||
|     attributes: | ||||
|       label: Javascript errors shown in your browser console/inspector | ||||
|       description: > | ||||
|         If you come across any Javascript or other error logs, e.g., in your | ||||
|         browser console/inspector please provide them. | ||||
|       value: | | ||||
|         ```txt | ||||
|         # Paste your logs here. | ||||
|  | ||||
|         ``` | ||||
|   - type: markdown | ||||
|     attributes: | ||||
|       value: | | ||||
|         ## Additional information | ||||
|   - type: markdown | ||||
|     attributes: | ||||
|       value: | | ||||
|         If you have any additional information for us, use the field below. | ||||
|         Please note, you can attach screenshots or screen recordings here, | ||||
|         by dragging and dropping files in the field below. | ||||
							
								
								
									
										19
									
								
								.github/workflows/netflify.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								.github/workflows/netflify.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,19 +0,0 @@ | ||||
| name: Netlify | ||||
|  | ||||
| on: | ||||
|   schedule: | ||||
|     - cron: "0 0 * * *" | ||||
|  | ||||
| jobs: | ||||
|   trigger_builds: | ||||
|     name: Trigger netlify build preview | ||||
|     runs-on: "ubuntu-latest" | ||||
|     steps: | ||||
|       - name: Trigger Cast build | ||||
|         run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_CAST_DEV_BUILD_HOOK }} | ||||
|  | ||||
|       - name: Trigger Demo build | ||||
|         run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }} | ||||
|  | ||||
|       - name: Trigger Gallery build | ||||
|         run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }} | ||||
							
								
								
									
										81
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										81
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -1,81 +0,0 @@ | ||||
| name: Release | ||||
|  | ||||
| on: | ||||
|   release: | ||||
|     types: | ||||
|       - published | ||||
|  | ||||
| env: | ||||
|   WHEELS_TAG: 3.7-alpine3.11 | ||||
|   PYTHON_VERSION: 3.7 | ||||
|   NODE_VERSION: 12.1 | ||||
|  | ||||
| jobs: | ||||
|   release: | ||||
|     name: Release | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@v2 | ||||
|  | ||||
|       - name: Verify version | ||||
|         uses: home-assistant/actions/helpers/verify-version@master | ||||
|  | ||||
|       - name: Set up Python ${{ env.PYTHON_VERSION }} | ||||
|         uses: actions/setup-python@v2 | ||||
|         with: | ||||
|           python-version: ${{ env.PYTHON_VERSION }} | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|         uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|  | ||||
|       - name: Build and release package | ||||
|         run: | | ||||
|           python3 -m pip install twine | ||||
|           export TWINE_USERNAME="__token__" | ||||
|           export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}" | ||||
|  | ||||
|           script/release | ||||
|  | ||||
|   wheels-init: | ||||
|     name: Init wheels build | ||||
|     needs: release | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Generate requirements.txt | ||||
|         run: | | ||||
|           # Sleep to give pypi time to populate the new version across mirrors | ||||
|           sleep 240 | ||||
|           version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' ) | ||||
|           echo "home-assistant-frontend==$version" > ./requirements.txt | ||||
|  | ||||
|       - name: Upload requirements.txt | ||||
|         uses: actions/upload-artifact@v2 | ||||
|         with: | ||||
|           name: requirements | ||||
|           path: ./requirements.txt | ||||
|  | ||||
|   build-wheels: | ||||
|     name: Build wheels for ${{ matrix.arch }} | ||||
|     needs: wheels-init | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       matrix: | ||||
|         arch: ["aarch64", "armhf", "armv7", "amd64", "i386"] | ||||
|     steps: | ||||
|       - name: Download requirements.txt | ||||
|         uses: actions/download-artifact@v2 | ||||
|         with: | ||||
|           name: requirements | ||||
|  | ||||
|       - name: Build wheels | ||||
|         uses: home-assistant/wheels@master | ||||
|         with: | ||||
|           tag: ${{ env.WHEELS_TAG }} | ||||
|           arch: ${{ matrix.arch }} | ||||
|           wheels-host: ${{ secrets.WHEELS_HOST }} | ||||
|           wheels-key: ${{ secrets.WHEELS_KEY }} | ||||
|           wheels-user: wheels | ||||
|           requirements: "requirements.txt" | ||||
							
								
								
									
										65
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										65
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -1,65 +0,0 @@ | ||||
| name: Translations | ||||
|  | ||||
| on: | ||||
|   schedule: | ||||
|     - cron: "30 0 * * *" | ||||
|   push: | ||||
|     branches: | ||||
|       - dev | ||||
|     paths: | ||||
|       - translations/en.json | ||||
|  | ||||
| env: | ||||
|   NODE_VERSION: 12 | ||||
|  | ||||
| jobs: | ||||
|   upload: | ||||
|     name: Upload | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@v2 | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|         uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|  | ||||
|       - name: Upload Translations | ||||
|         run: | | ||||
|           export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}" | ||||
|  | ||||
|           ./script/translations_upload_base | ||||
|  | ||||
|   download: | ||||
|     name: Download | ||||
|     needs: upload | ||||
|     if: github.event_name == 'schedule' | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout the repository | ||||
|         uses: actions/checkout@v2 | ||||
|  | ||||
|       - name: Set up Node ${{ env.NODE_VERSION }} | ||||
|         uses: actions/setup-node@v2 | ||||
|         with: | ||||
|           node-version: ${{ env.NODE_VERSION }} | ||||
|  | ||||
|       - name: Download Translations | ||||
|         run: | | ||||
|           export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}" | ||||
|  | ||||
|           npm install | ||||
|           ./script/translations_download | ||||
|  | ||||
|       - name: Initialize git | ||||
|         uses: home-assistant/actions/helpers/git-init@master | ||||
|         with: | ||||
|           name: GitHub Action | ||||
|           email: github-action@users.noreply.github.com | ||||
|  | ||||
|       - name: Update translation | ||||
|         run: | | ||||
|           git add translations | ||||
|           git commit -am "Translation update" | ||||
|           git push | ||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -35,6 +35,3 @@ yarn-error.log | ||||
|  | ||||
| #asdf | ||||
| .tool-versions | ||||
|  | ||||
| # Home Assistant config | ||||
| /config | ||||
|   | ||||
							
								
								
									
										6
									
								
								.hound.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.hound.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| jshint: | ||||
|   enabled: false | ||||
|  | ||||
| eslint: | ||||
|   enabled: true | ||||
|   config_file: .eslintrc-hound.json | ||||
							
								
								
									
										71
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										71
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							| @@ -37,37 +37,6 @@ | ||||
|         "instanceLimit": 1 | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "label": "Develop Supervisor panel", | ||||
|       "type": "gulp", | ||||
|       "task": "develop-hassio", | ||||
|       "problemMatcher": { | ||||
|         "owner": "ha-build", | ||||
|         "source": "ha-build", | ||||
|         "fileLocation": "absolute", | ||||
|         "severity": "error", | ||||
|         "pattern": [ | ||||
|           { | ||||
|             "regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)", | ||||
|             "severity": 1, | ||||
|             "file": 2, | ||||
|             "message": 3, | ||||
|             "line": 4, | ||||
|             "column": 5 | ||||
|           } | ||||
|         ], | ||||
|         "background": { | ||||
|           "activeOnStart": true, | ||||
|           "beginsPattern": "Changes detected. Starting compilation", | ||||
|           "endsPattern": "Build done @" | ||||
|         } | ||||
|       }, | ||||
|       "isBackground": true, | ||||
|       "group": "build", | ||||
|       "runOptions": { | ||||
|         "instanceLimit": 1 | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "label": "Develop Gallery", | ||||
|       "type": "gulp", | ||||
| @@ -164,45 +133,5 @@ | ||||
|         "instanceLimit": 1 | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "label": "Run HA Core in devcontainer", | ||||
|       "type": "shell", | ||||
|       "command": "script/core", | ||||
|       "isBackground": true, | ||||
|       "group": { | ||||
|         "kind": "build", | ||||
|         "isDefault": true | ||||
|       }, | ||||
|       "problemMatcher": [], | ||||
|       "runOptions": { | ||||
|         "instanceLimit": 1 | ||||
|       } | ||||
|     }, | ||||
|     { | ||||
|       "label": "Run HA Core for Supervisor in devcontainer", | ||||
|       "type": "shell", | ||||
|       "command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core", | ||||
|       "isBackground": true, | ||||
|       "group": { | ||||
|         "kind": "build", | ||||
|         "isDefault": true | ||||
|       }, | ||||
|       "problemMatcher": [], | ||||
|       "runOptions": { | ||||
|         "instanceLimit": 1 | ||||
|       } | ||||
|     } | ||||
|   ], | ||||
|   "inputs": [ | ||||
|     { | ||||
|       "id": "supervisorHost", | ||||
|       "type": "promptString", | ||||
|       "description": "The IP of the Supervisor host running the Remote API proxy add-on" | ||||
|     }, | ||||
|     { | ||||
|       "id": "supervisorToken", | ||||
|       "type": "promptString", | ||||
|       "description": "The token for the Remote API proxy add-on" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|   | ||||
| @@ -14,7 +14,7 @@ This is the repository for the official [Home Assistant](https://home-assistant. | ||||
| - Development: [Instructions](https://developers.home-assistant.io/docs/frontend/development/) | ||||
| - Production build: `script/build_frontend` | ||||
| - Gallery: `cd gallery && script/develop_gallery` | ||||
| - Supervisor: [Instructions](https://developers.home-assistant.io/docs/supervisor/developing) | ||||
| - Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html) | ||||
|  | ||||
| ## Frontend development | ||||
|  | ||||
|   | ||||
							
								
								
									
										30
									
								
								azure-pipelines-netlify.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								azure-pipelines-netlify.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| # https://dev.azure.com/home-assistant | ||||
|  | ||||
| trigger: none | ||||
| pr: none | ||||
| schedules: | ||||
|   - cron: "0 0 * * *" | ||||
|     displayName: "build preview" | ||||
|     branches: | ||||
|       include: | ||||
|       - dev | ||||
|     always: true | ||||
| variables: | ||||
|   - group: netlify | ||||
|  | ||||
| jobs: | ||||
|  | ||||
| - job: 'Netlify_preview' | ||||
|   pool: | ||||
|     vmImage: 'ubuntu-latest' | ||||
|   steps: | ||||
|   - script: | | ||||
|       # Cast | ||||
|       curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_CAST} | ||||
|  | ||||
|       # Demo | ||||
|       curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_DEMO} | ||||
|  | ||||
|       # Gallery | ||||
|       curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_GALLERY} | ||||
|     displayName: 'Trigger netlify build preview' | ||||
							
								
								
									
										59
									
								
								azure-pipelines-release.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								azure-pipelines-release.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| # https://dev.azure.com/home-assistant | ||||
|  | ||||
| trigger: | ||||
|   batch: true | ||||
|   tags: | ||||
|     include: | ||||
|       - "*" | ||||
| pr: none | ||||
| variables: | ||||
|   - name: versionWheels | ||||
|     value: '1.10.1-3.7-alpine3.11' | ||||
|   - name: versionNode | ||||
|     value: '12.1' | ||||
|   - group: twine | ||||
| resources: | ||||
|   repositories: | ||||
|     - repository: azure | ||||
|       type: github | ||||
|       name: 'home-assistant/ci-azure' | ||||
|       endpoint: 'home-assistant' | ||||
|  | ||||
|  | ||||
| stages: | ||||
|   - stage: "Validate" | ||||
|     jobs: | ||||
|     - template: templates/azp-job-version.yaml@azure | ||||
|  | ||||
|   - stage: "Build" | ||||
|     jobs: | ||||
|       - job: "ReleasePython" | ||||
|         pool: | ||||
|           vmImage: "ubuntu-latest" | ||||
|         steps: | ||||
|           - task: UsePythonVersion@0 | ||||
|             displayName: "Use Python 3.7" | ||||
|             inputs: | ||||
|               versionSpec: "3.7" | ||||
|           - task: NodeTool@0 | ||||
|             displayName: "Use Node $(versionNode)" | ||||
|             inputs: | ||||
|               versionSpec: "$(versionNode)" | ||||
|           - script: pip install twine wheel | ||||
|             displayName: "Install tools" | ||||
|           - script: | | ||||
|               export TWINE_USERNAME="$(twineUser)" | ||||
|               export TWINE_PASSWORD="$(twinePassword)" | ||||
|  | ||||
|               script/release | ||||
|             displayName: "Build and release package" | ||||
|   - stage: "Wheels" | ||||
|     jobs: | ||||
|       - template: templates/azp-job-wheels.yaml@azure | ||||
|         parameters: | ||||
|           builderVersion: '$(versionWheels)' | ||||
|           wheelsRequirement: 'requirement.txt' | ||||
|           preBuild: | ||||
|           - script: | | ||||
|               sleep 240 | ||||
|               echo "home-assistant-frontend==$(Build.SourceBranchName)" > requirement.txt | ||||
							
								
								
									
										70
									
								
								azure-pipelines-translation.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								azure-pipelines-translation.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| # https://dev.azure.com/home-assistant | ||||
|  | ||||
| trigger: | ||||
|   batch: true | ||||
|   branches: | ||||
|     include: | ||||
|     - dev | ||||
|   paths: | ||||
|     include: | ||||
|     - translations/en.json | ||||
| pr: none | ||||
| schedules: | ||||
|   - cron: "30 0 * * *" | ||||
|     displayName: "frontend translation update" | ||||
|     branches: | ||||
|       include: | ||||
|       - dev | ||||
|     always: true | ||||
| variables: | ||||
| - group: translation | ||||
| resources: | ||||
|   repositories: | ||||
|   - repository: azure | ||||
|     type: github | ||||
|     name: 'home-assistant/ci-azure' | ||||
|     endpoint: 'home-assistant' | ||||
|  | ||||
|  | ||||
| jobs: | ||||
|  | ||||
| - job: 'Upload' | ||||
|   pool: | ||||
|     vmImage: 'ubuntu-latest' | ||||
|   steps: | ||||
|   - task: NodeTool@0 | ||||
|     displayName: 'Use Node 12.x' | ||||
|     inputs: | ||||
|       versionSpec: '12.x' | ||||
|   - script: | | ||||
|       export LOKALISE_TOKEN="$(lokaliseToken)" | ||||
|       export AZURE_BRANCH="$(Build.SourceBranchName)" | ||||
|  | ||||
|       ./script/translations_upload_base | ||||
|     displayName: 'Upload Translation' | ||||
|  | ||||
| - job: 'Download' | ||||
|   dependsOn: | ||||
|   - 'Upload' | ||||
|   condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual')) | ||||
|   pool: | ||||
|     vmImage: 'ubuntu-latest' | ||||
|   steps: | ||||
|   - task: NodeTool@0 | ||||
|     displayName: 'Use Node 12.x' | ||||
|     inputs: | ||||
|       versionSpec: '12.x' | ||||
|   - template: templates/azp-step-git-init.yaml@azure | ||||
|   - script: | | ||||
|       export LOKALISE_TOKEN="$(lokaliseToken)" | ||||
|       export AZURE_BRANCH="$(Build.SourceBranchName)" | ||||
|  | ||||
|       npm install | ||||
|       ./script/translations_download | ||||
|     displayName: 'Download Translation' | ||||
|   - script: | | ||||
|       git checkout dev | ||||
|       git add translation | ||||
|       git commit -am "[ci skip] Translation update" | ||||
|       git push | ||||
|     displayName: 'Update translation' | ||||
| @@ -1,39 +0,0 @@ | ||||
| # Bundling Home Assistant Frontend | ||||
|  | ||||
| The Home Assistant build pipeline contains various steps to prepare a build. | ||||
|  | ||||
| - Generating icon files to be included | ||||
| - Generating translation files to be included | ||||
| - Converting TypeScript, CSS and JSON files to JavaScript | ||||
| - Bundling | ||||
| - Minifying the files | ||||
| - Generating the HTML entrypoint files | ||||
| - Generating the service worker | ||||
| - Compressing the files | ||||
|  | ||||
| ## Converting files | ||||
|  | ||||
| Currently in Home Assistant we use a bundler to convert TypeScript, CSS and JSON files to JavaScript files that the browser understands. | ||||
|  | ||||
| We currently rely on Webpack but also have experimental Rollup support. Both of these programs bundle the converted files in both production and development. | ||||
|  | ||||
| For development, bundling is optional. We just want to get the right files in the browser. | ||||
|  | ||||
| Responsibilities of the converter during development: | ||||
|  | ||||
| - Convert TypeScript to JavaScript | ||||
| - Convert CSS to JavaScript that sets the content as the default export | ||||
| - Convert JSON to JavaScript that sets the content as the default export | ||||
| - Make sure import, dynamic import and web worker references work | ||||
|   - Add extensions where missing | ||||
|   - Resolve absolute package imports | ||||
| - Filter out specific imports/packages | ||||
| - Replace constants with values | ||||
|  | ||||
| In production, the following responsibilities are added: | ||||
|  | ||||
| - Minify HTML | ||||
| - Bundle multiple imports so that the browser can fetch less files | ||||
| - Generate a second version that is ES5 compatible | ||||
|  | ||||
| Configuration for all these steps are specified in [bundle.js](bundle.js). | ||||
| @@ -44,7 +44,7 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ | ||||
| }); | ||||
|  | ||||
| module.exports.terserOptions = (latestBuild) => ({ | ||||
|   safari10: !latestBuild, | ||||
|   safari10: true, | ||||
|   ecma: latestBuild ? undefined : 5, | ||||
|   output: { comments: false }, | ||||
| }); | ||||
| @@ -117,7 +117,7 @@ BundleConfig { | ||||
| */ | ||||
|  | ||||
| module.exports.config = { | ||||
|   app({ isProdBuild, latestBuild, isStatsBuild, isWDS }) { | ||||
|   app({ isProdBuild, latestBuild, isStatsBuild }) { | ||||
|     return { | ||||
|       entry: { | ||||
|         service_worker: "./src/entrypoints/service_worker.ts", | ||||
| @@ -132,7 +132,6 @@ module.exports.config = { | ||||
|       isProdBuild, | ||||
|       latestBuild, | ||||
|       isStatsBuild, | ||||
|       isWDS, | ||||
|     }; | ||||
|   }, | ||||
|  | ||||
|   | ||||
| @@ -6,9 +6,6 @@ module.exports = { | ||||
|   useRollup() { | ||||
|     return process.env.ROLLUP === "1"; | ||||
|   }, | ||||
|   useWDS() { | ||||
|     return process.env.WDS === "1"; | ||||
|   }, | ||||
|   isProdBuild() { | ||||
|     return ( | ||||
|       process.env.NODE_ENV === "production" || module.exports.isStatsBuild() | ||||
|   | ||||
| @@ -12,7 +12,6 @@ require("./webpack.js"); | ||||
| require("./service-worker.js"); | ||||
| require("./entry-html.js"); | ||||
| require("./rollup.js"); | ||||
| require("./wds.js"); | ||||
|  | ||||
| gulp.task( | ||||
|   "develop-app", | ||||
| @@ -29,11 +28,7 @@ gulp.task( | ||||
|       "build-translations" | ||||
|     ), | ||||
|     "copy-static-app", | ||||
|     env.useWDS() | ||||
|       ? "wds-watch-app" | ||||
|       : env.useRollup() | ||||
|       ? "rollup-watch-app" | ||||
|       : "webpack-watch-app" | ||||
|     env.useRollup() ? "rollup-watch-app" : "webpack-watch-app" | ||||
|   ) | ||||
| ); | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,6 @@ const renderTemplate = (pth, data = {}, pathFunc = templatePath) => { | ||||
|   return compiled({ | ||||
|     ...data, | ||||
|     useRollup: env.useRollup(), | ||||
|     useWDS: env.useWDS(), | ||||
|     renderTemplate, | ||||
|   }); | ||||
| }; | ||||
| @@ -91,23 +90,10 @@ gulp.task("gen-pages-prod", (done) => { | ||||
| }); | ||||
|  | ||||
| gulp.task("gen-index-app-dev", (done) => { | ||||
|   let latestAppJS, latestCoreJS, latestCustomPanelJS; | ||||
|  | ||||
|   if (env.useWDS()) { | ||||
|     latestAppJS = "http://localhost:8000/src/entrypoints/app.ts"; | ||||
|     latestCoreJS = "http://localhost:8000/src/entrypoints/core.ts"; | ||||
|     latestCustomPanelJS = | ||||
|       "http://localhost:8000/src/entrypoints/custom-panel.ts"; | ||||
|   } else { | ||||
|     latestAppJS = "/frontend_latest/app.js"; | ||||
|     latestCoreJS = "/frontend_latest/core.js"; | ||||
|     latestCustomPanelJS = "/frontend_latest/custom-panel.js"; | ||||
|   } | ||||
|  | ||||
|   const content = renderTemplate("index", { | ||||
|     latestAppJS, | ||||
|     latestCoreJS, | ||||
|     latestCustomPanelJS, | ||||
|     latestAppJS: "/frontend_latest/app.js", | ||||
|     latestCoreJS: "/frontend_latest/core.js", | ||||
|     latestCustomPanelJS: "/frontend_latest/custom-panel.js", | ||||
|  | ||||
|     es5AppJS: "/frontend_es5/app.js", | ||||
|     es5CoreJS: "/frontend_es5/core.js", | ||||
|   | ||||
| @@ -33,10 +33,21 @@ String.prototype.rsplit = function (sep, maxsplit) { | ||||
|     : split; | ||||
| }; | ||||
|  | ||||
| // Panel translations which should be split from the core translations. | ||||
| const TRANSLATION_FRAGMENTS = Object.keys( | ||||
|   require("../../src/translations/en.json").ui.panel | ||||
| ); | ||||
| // Panel translations which should be split from the core translations. These | ||||
| // should mirror the fragment definitions in polymer.json, so that we load | ||||
| // additional resources at equivalent points. | ||||
| const TRANSLATION_FRAGMENTS = [ | ||||
|   "config", | ||||
|   "history", | ||||
|   "logbook", | ||||
|   "mailbox", | ||||
|   "profile", | ||||
|   "shopping-list", | ||||
|   "page-authorize", | ||||
|   "page-demo", | ||||
|   "page-onboarding", | ||||
|   "developer-tools", | ||||
| ]; | ||||
|  | ||||
| function recursiveFlatten(prefix, data) { | ||||
|   let output = {}; | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| // Tasks to run Rollup | ||||
| const gulp = require("gulp"); | ||||
| const { startDevServer } = require("@web/dev-server"); | ||||
|  | ||||
| gulp.task("wds-watch-app", () => { | ||||
|   startDevServer({ | ||||
|     config: { | ||||
|       watch: true, | ||||
|     }, | ||||
|   }); | ||||
| }); | ||||
| @@ -47,7 +47,7 @@ const runDevServer = ({ | ||||
|     ); | ||||
|   }); | ||||
|  | ||||
| const doneHandler = (done) => (err, stats) => { | ||||
| const handler = (done) => (err, stats) => { | ||||
|   if (err) { | ||||
|     log.error(err.stack || err); | ||||
|     if (err.details) { | ||||
| @@ -67,20 +67,11 @@ const doneHandler = (done) => (err, stats) => { | ||||
|   } | ||||
| }; | ||||
|  | ||||
| const prodBuild = (conf) => | ||||
|   new Promise((resolve) => { | ||||
|     webpack( | ||||
|       conf, | ||||
|       // Resolve promise when done. Because we pass a callback, webpack closes itself | ||||
|       doneHandler(resolve) | ||||
|     ); | ||||
|   }); | ||||
|  | ||||
| gulp.task("webpack-watch-app", () => { | ||||
|   // This command will run forever because we don't close compiler | ||||
|   // we are not calling done, so this command will run forever | ||||
|   webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch( | ||||
|     { ignored: /build-translations/ }, | ||||
|     doneHandler() | ||||
|     handler() | ||||
|   ); | ||||
|   gulp.watch( | ||||
|     path.join(paths.translations_src, "en.json"), | ||||
| @@ -88,12 +79,15 @@ gulp.task("webpack-watch-app", () => { | ||||
|   ); | ||||
| }); | ||||
|  | ||||
| gulp.task("webpack-prod-app", () => | ||||
|   prodBuild( | ||||
|     bothBuilds(createAppConfig, { | ||||
|       isProdBuild: true, | ||||
|     }) | ||||
|   ) | ||||
| gulp.task( | ||||
|   "webpack-prod-app", | ||||
|   () => | ||||
|     new Promise((resolve) => | ||||
|       webpack( | ||||
|         bothBuilds(createAppConfig, { isProdBuild: true }), | ||||
|         handler(resolve) | ||||
|       ) | ||||
|     ) | ||||
| ); | ||||
|  | ||||
| gulp.task("webpack-dev-server-demo", () => { | ||||
| @@ -104,12 +98,17 @@ gulp.task("webpack-dev-server-demo", () => { | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| gulp.task("webpack-prod-demo", () => | ||||
|   prodBuild( | ||||
|     bothBuilds(createDemoConfig, { | ||||
|       isProdBuild: true, | ||||
|     }) | ||||
|   ) | ||||
| gulp.task( | ||||
|   "webpack-prod-demo", | ||||
|   () => | ||||
|     new Promise((resolve) => | ||||
|       webpack( | ||||
|         bothBuilds(createDemoConfig, { | ||||
|           isProdBuild: true, | ||||
|         }), | ||||
|         handler(resolve) | ||||
|       ) | ||||
|     ) | ||||
| ); | ||||
|  | ||||
| gulp.task("webpack-dev-server-cast", () => { | ||||
| @@ -122,30 +121,41 @@ gulp.task("webpack-dev-server-cast", () => { | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| gulp.task("webpack-prod-cast", () => | ||||
|   prodBuild( | ||||
|     bothBuilds(createCastConfig, { | ||||
|       isProdBuild: true, | ||||
|     }) | ||||
|   ) | ||||
| gulp.task( | ||||
|   "webpack-prod-cast", | ||||
|   () => | ||||
|     new Promise((resolve) => | ||||
|       webpack( | ||||
|         bothBuilds(createCastConfig, { | ||||
|           isProdBuild: true, | ||||
|         }), | ||||
|  | ||||
|         handler(resolve) | ||||
|       ) | ||||
|     ) | ||||
| ); | ||||
|  | ||||
| gulp.task("webpack-watch-hassio", () => { | ||||
|   // This command will run forever because we don't close compiler | ||||
|   // we are not calling done, so this command will run forever | ||||
|   webpack( | ||||
|     createHassioConfig({ | ||||
|       isProdBuild: false, | ||||
|       latestBuild: true, | ||||
|     }) | ||||
|   ).watch({}, doneHandler()); | ||||
|   ).watch({}, handler()); | ||||
| }); | ||||
|  | ||||
| gulp.task("webpack-prod-hassio", () => | ||||
|   prodBuild( | ||||
|     bothBuilds(createHassioConfig, { | ||||
|       isProdBuild: true, | ||||
|     }) | ||||
|   ) | ||||
| gulp.task( | ||||
|   "webpack-prod-hassio", | ||||
|   () => | ||||
|     new Promise((resolve) => | ||||
|       webpack( | ||||
|         bothBuilds(createHassioConfig, { | ||||
|           isProdBuild: true, | ||||
|         }), | ||||
|         handler(resolve) | ||||
|       ) | ||||
|     ) | ||||
| ); | ||||
|  | ||||
| gulp.task("webpack-dev-server-gallery", () => { | ||||
| @@ -157,11 +167,17 @@ gulp.task("webpack-dev-server-gallery", () => { | ||||
|   }); | ||||
| }); | ||||
|  | ||||
| gulp.task("webpack-prod-gallery", () => | ||||
|   prodBuild( | ||||
|     createGalleryConfig({ | ||||
|       isProdBuild: true, | ||||
|       latestBuild: true, | ||||
|     }) | ||||
|   ) | ||||
| gulp.task( | ||||
|   "webpack-prod-gallery", | ||||
|   () => | ||||
|     new Promise((resolve) => | ||||
|       webpack( | ||||
|         createGalleryConfig({ | ||||
|           isProdBuild: true, | ||||
|           latestBuild: true, | ||||
|         }), | ||||
|  | ||||
|         handler(resolve) | ||||
|       ) | ||||
|     ) | ||||
| ); | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| const path = require("path"); | ||||
|  | ||||
| module.exports = function (userOptions = {}) { | ||||
|   // Files need to be absolute paths. | ||||
|   // This only works if the file has no exports | ||||
|   | ||||
| @@ -3,7 +3,7 @@ const path = require("path"); | ||||
| const commonjs = require("@rollup/plugin-commonjs"); | ||||
| const resolve = require("@rollup/plugin-node-resolve"); | ||||
| const json = require("@rollup/plugin-json"); | ||||
| const babel = require("@rollup/plugin-babel").babel; | ||||
| const babel = require("rollup-plugin-babel"); | ||||
| const replace = require("@rollup/plugin-replace"); | ||||
| const visualizer = require("rollup-plugin-visualizer"); | ||||
| const { string } = require("rollup-plugin-string"); | ||||
| @@ -31,7 +31,6 @@ const createRollupConfig = ({ | ||||
|   isStatsBuild, | ||||
|   publicPath, | ||||
|   dontHash, | ||||
|   isWDS, | ||||
| }) => { | ||||
|   return { | ||||
|     /** | ||||
| @@ -62,7 +61,6 @@ const createRollupConfig = ({ | ||||
|           ...bundle.babelOptions({ latestBuild }), | ||||
|           extensions, | ||||
|           exclude: bundle.babelExclude(), | ||||
|           babelHelpers: isWDS ? "inline" : "bundled", | ||||
|         }), | ||||
|         string({ | ||||
|           // Import certain extensions as strings | ||||
| @@ -71,21 +69,19 @@ const createRollupConfig = ({ | ||||
|         replace( | ||||
|           bundle.definedVars({ isProdBuild, latestBuild, defineOverlay }) | ||||
|         ), | ||||
|         !isWDS && | ||||
|           manifest({ | ||||
|             publicPath, | ||||
|           }), | ||||
|         !isWDS && worker(), | ||||
|         !isWDS && dontHashPlugin({ dontHash }), | ||||
|         !isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)), | ||||
|         !isWDS && | ||||
|           isStatsBuild && | ||||
|         manifest({ | ||||
|           publicPath, | ||||
|         }), | ||||
|         worker(), | ||||
|         dontHashPlugin({ dontHash }), | ||||
|         isProdBuild && terser(bundle.terserOptions(latestBuild)), | ||||
|         isStatsBuild && | ||||
|           visualizer({ | ||||
|             // https://github.com/btd/rollup-plugin-visualizer#options | ||||
|             open: true, | ||||
|             sourcemap: true, | ||||
|           }), | ||||
|       ].filter(Boolean), | ||||
|       ], | ||||
|     }, | ||||
|     /** | ||||
|      * @type { import("rollup").OutputOptions } | ||||
| @@ -112,13 +108,12 @@ const createRollupConfig = ({ | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => { | ||||
| const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { | ||||
|   return createRollupConfig( | ||||
|     bundle.config.app({ | ||||
|       isProdBuild, | ||||
|       latestBuild, | ||||
|       isStatsBuild, | ||||
|       isWDS, | ||||
|     }) | ||||
|   ); | ||||
| }; | ||||
|   | ||||
| @@ -36,7 +36,6 @@ const createWebpackConfig = ({ | ||||
|   const ignorePackages = bundle.ignorePackages({ latestBuild }); | ||||
|   return { | ||||
|     mode: isProdBuild ? "production" : "development", | ||||
|     target: ["web", latestBuild ? "es2017" : "es5"], | ||||
|     devtool: isProdBuild | ||||
|       ? "cheap-module-source-map" | ||||
|       : "eval-cheap-module-source-map", | ||||
| @@ -132,6 +131,22 @@ const createWebpackConfig = ({ | ||||
|         } | ||||
|         return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; | ||||
|       }, | ||||
|       environment: { | ||||
|         // The environment supports arrow functions ('() => { ... }'). | ||||
|         arrowFunction: latestBuild, | ||||
|         // The environment supports BigInt as literal (123n). | ||||
|         bigIntLiteral: false, | ||||
|         // The environment supports const and let for variable declarations. | ||||
|         const: latestBuild, | ||||
|         // The environment supports destructuring ('{ a, b } = obj'). | ||||
|         destructuring: latestBuild, | ||||
|         // The environment supports an async import() function to import EcmaScript modules. | ||||
|         dynamicImport: latestBuild, | ||||
|         // The environment supports 'for of' iteration ('for (const x of array) { ... }'). | ||||
|         forOf: latestBuild, | ||||
|         // The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...'). | ||||
|         module: latestBuild, | ||||
|       }, | ||||
|       chunkFilename: | ||||
|         isProdBuild && !isStatsBuild | ||||
|           ? "chunk.[chunkhash].js" | ||||
|   | ||||
| @@ -48,7 +48,7 @@ class HcCast extends LitElement { | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     if (this.lovelaceConfig === undefined) { | ||||
|       return html`<hass-loading-screen no-toolbar></hass-loading-screen>`; | ||||
|       return html` <hass-loading-screen no-toolbar></hass-loading-screen>> `; | ||||
|     } | ||||
|  | ||||
|     const error = | ||||
|   | ||||
| @@ -98,12 +98,8 @@ class HcLayout extends LitElement { | ||||
|         line-height: 32px; | ||||
|         padding: 24px 16px 16px; | ||||
|         display: block; | ||||
|         margin: 0; | ||||
|       } | ||||
| 	 | ||||
|       .hero { | ||||
|         border-radius: 4px 4px 0 0; | ||||
|       } | ||||
|  | ||||
|       .subtitle { | ||||
|         font-size: 14px; | ||||
|         color: var(--secondary-text-color); | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   internalProperty, | ||||
|   property, | ||||
|   internalProperty, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { mockHistory } from "../../../../demo/src/stubs/history"; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import "web-animations-js/web-animations-next-lite.min"; | ||||
| import "../../../src/resources/ha-style"; | ||||
| import "../../../src/resources/roboto"; | ||||
| import "../../../src/resources/ha-style"; | ||||
| import "./layout/hc-lovelace"; | ||||
|   | ||||
| @@ -54,8 +54,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => | ||||
|       state: "21", | ||||
|       attributes: { | ||||
|         friendly_name: "Living room temperature", | ||||
|         device_class: "temperature", | ||||
|         unit_of_measurement: "°C", | ||||
|       }, | ||||
|     }, | ||||
|     "sensor.study_temp_rounded": { | ||||
| @@ -63,8 +61,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => | ||||
|       state: "23", | ||||
|       attributes: { | ||||
|         friendly_name: "Study temperature", | ||||
|         device_class: "temperature", | ||||
|         unit_of_measurement: "°C", | ||||
|       }, | ||||
|     }, | ||||
|     "sensor.living_room": { | ||||
| @@ -265,7 +261,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => | ||||
|       entity_id: "light.kitchen_lights", | ||||
|       state: "off", | ||||
|       attributes: { | ||||
|         friendly_name: "Kitchen Lights", | ||||
|         friendly_name: "Kitchen lights", | ||||
|         supported_features: 1, | ||||
|       }, | ||||
|     }, | ||||
| @@ -488,7 +484,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => | ||||
|       attributes: { | ||||
|         min_mireds: 111, | ||||
|         max_mireds: 400, | ||||
|         friendly_name: "Garage Lights", | ||||
|         friendly_name: "Garage lights", | ||||
|         supported_features: 55, | ||||
|       }, | ||||
|     }, | ||||
|   | ||||
| @@ -12,7 +12,6 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({ | ||||
|         { | ||||
|           type: "entities", | ||||
|           title: localize("ui.panel.page-demo.config.arsaboo.labels.lights"), | ||||
|           state_color: true, | ||||
|           entities: [ | ||||
|             { | ||||
|               entity: "light.kitchen_lights", | ||||
|   | ||||
| @@ -3,10 +3,22 @@ import { Lovelace } from "../../../src/panels/lovelace/types"; | ||||
| import { DemoConfig } from "./types"; | ||||
|  | ||||
| export const demoConfigs: Array<() => Promise<DemoConfig>> = [ | ||||
|   () => import("./arsaboo").then((mod) => mod.demoArsaboo), | ||||
|   () => import("./teachingbirds").then((mod) => mod.demoTeachingbirds), | ||||
|   () => import("./kernehed").then((mod) => mod.demoKernehed), | ||||
|   () => import("./jimpower").then((mod) => mod.demoJimpower), | ||||
|   () => | ||||
|     import(/* webpackChunkName: "arsaboo" */ "./arsaboo").then( | ||||
|       (mod) => mod.demoArsaboo | ||||
|     ), | ||||
|   () => | ||||
|     import(/* webpackChunkName: "teachingbirds" */ "./teachingbirds").then( | ||||
|       (mod) => mod.demoTeachingbirds | ||||
|     ), | ||||
|   () => | ||||
|     import(/* webpackChunkName: "kernehed" */ "./kernehed").then( | ||||
|       (mod) => mod.demoKernehed | ||||
|     ), | ||||
|   () => | ||||
|     import(/* webpackChunkName: "jimpower" */ "./jimpower").then( | ||||
|       (mod) => mod.demoJimpower | ||||
|     ), | ||||
| ]; | ||||
|  | ||||
| // eslint-disable-next-line import/no-mutable-exports | ||||
|   | ||||
| @@ -653,7 +653,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () => | ||||
|       entity_id: "binary_sensor.smoke_sensor_158d0001b8ddc7", | ||||
|       state: "off", | ||||
|       attributes: { | ||||
|         density: 0, | ||||
|         Density: 0, | ||||
|         battery_level: 59, | ||||
|         friendly_name: "Downstairs Smoke Detector", | ||||
|         device_class: "smoke", | ||||
| @@ -663,7 +663,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () => | ||||
|       entity_id: "binary_sensor.smoke_sensor_158d0001b8deba", | ||||
|       state: "off", | ||||
|       attributes: { | ||||
|         density: 0, | ||||
|         Density: 0, | ||||
|         battery_level: 65, | ||||
|         friendly_name: "Upstairs Smoke Detector", | ||||
|         device_class: "smoke", | ||||
|   | ||||
| @@ -3,7 +3,49 @@ import { DemoConfig } from "../types"; | ||||
|  | ||||
| export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | ||||
|   name: "Kingia Castle", | ||||
|   resources: [], | ||||
|   resources: [ | ||||
|     // { | ||||
|     //   url: "/local/custom_ui/dark-sky-weather-card.js?v=4", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom_ui/mini-media-player-bundle.js?v=0.9.8", | ||||
|     //   type: "module", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom_ui/tracker-card.js?v=0.1.5", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom_ui/surveillance-card.js?v=0.0.1", | ||||
|     //   type: "module", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom_ui/mini-graph-card-bundle.js?v=0.1.0", | ||||
|     //   type: "module", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom_ui/slider-entity-row.js?v=d6da75", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: | ||||
|     //     "/local/custom_ui/compact-custom-header/compact-custom-header.js?v=0.2.7", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom_ui/waze-card.js?v=1.1.1", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom_ui/circle-sensor-card.js?v=1.2.0", | ||||
|     //   type: "module", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom_ui/monster-card.js?v=0.2.3", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|   ], | ||||
|   views: [ | ||||
|     { | ||||
|       cards: [ | ||||
| @@ -561,6 +603,89 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | ||||
|         }, | ||||
|         { | ||||
|           cards: [ | ||||
|             // { | ||||
|             //   style: { | ||||
|             //     "background-image": 'url("/assets/jimpower/cardbackK.png")', | ||||
|             //     "background-size": "100% 400px", | ||||
|             //     "box-shadow": "3px 3px rgba(0,0,0,0.4)", | ||||
|             //     "background-repeat": "no-repeat", | ||||
|             //     color: "#999999", | ||||
|             //     "border-radius": "20px", | ||||
|             //     border: "solid 1px rgba(100,100,100,0.3)", | ||||
|             //     "background-color": "rgba(50,50,50,0.3)", | ||||
|             //   }, | ||||
|             //   type: "custom:card-modder", | ||||
|             //   card: { | ||||
|             //     entity_visibility: "sensor.dark_sky_visibility", | ||||
|             //     entity_sun: "sun.sun", | ||||
|             //     entity_daily_summary: | ||||
|             //       "sensor.bom_gc_forecast_detailed_summary_0", | ||||
|             //     entity_temperature: "sensor.bom_temp", | ||||
|             //     entity_forecast_high_temp_3: | ||||
|             //       "sensor.bom_gc_forecast_max_temp_c_3", | ||||
|             //     entity_forecast_high_temp_2: | ||||
|             //       "sensor.bom_gc_forecast_max_temp_c_2", | ||||
|             //     entity_forecast_high_temp_5: | ||||
|             //       "sensor.bom_gc_forecast_max_temp_c_5", | ||||
|             //     entity_forecast_high_temp_4: | ||||
|             //       "sensor.bom_gc_forecast_max_temp_c_4", | ||||
|             //     entity_wind_speed: "sensor.bom_wind_sp", | ||||
|             //     entity_forecast_icon_4: "sensor.dark_sky_icon_4", | ||||
|             //     entity_forecast_icon_5: "sensor.dark_sky_icon_5", | ||||
|             //     entity_forecast_icon_2: "sensor.dark_sky_icon_2", | ||||
|             //     entity_forecast_icon_3: "sensor.dark_sky_icon_3", | ||||
|             //     entity_forecast_icon_1: "sensor.dark_sky_icon_1", | ||||
|             //     entity_forecast_high_temp_1: | ||||
|             //       "sensor.bom_gc_forecast_max_temp_c_1", | ||||
|             //     entity_wind_bearing: "sensor.bom_wind_bear", | ||||
|             //     entity_forecast_low_temp_2: | ||||
|             //       "sensor.bom_gc_forecast_min_temp_c_2", | ||||
|             //     entity_forecast_low_temp_3: | ||||
|             //       "sensor.bom_gc_forecast_min_temp_c_3", | ||||
|             //     entity_pressure: "sensor.bom_pres", | ||||
|             //     entity_forecast_low_temp_1: | ||||
|             //       "sensor.bom_gc_forecast_min_temp_c_1", | ||||
|             //     entity_forecast_low_temp_4: | ||||
|             //       "sensor.bom_gc_forecast_min_temp_c_4", | ||||
|             //     entity_forecast_low_temp_5: | ||||
|             //       "sensor.bom_gc_forecast_min_temp_c_5", | ||||
|             //     entity_humidity: "sensor.bom_humd", | ||||
|             //     type: "custom:dark-sky-weather-card", | ||||
|             //     entity_current_conditions: "sensor.dark_sky_icon", | ||||
|             //   }, | ||||
|             // }, | ||||
|             // { | ||||
|             //   style: { | ||||
|             //     "background-image": 'url("/assets/jimpower/home/waze_5.png")', | ||||
|             //     "background-size": "100% 400px", | ||||
|             //     "box-shadow": "3px 3px rgba(0,0,0,0.4)", | ||||
|             //     "background-repeat": "no-repeat", | ||||
|             //     "border-radius": "20px", | ||||
|             //     border: "solid 1px rgba(100,100,100,0.3)", | ||||
|             //     "background-color": "rgba(50,50,50,0.3)", | ||||
|             //   }, | ||||
|             //   type: "custom:card-modder", | ||||
|             //   card: { | ||||
|             //     entities: [ | ||||
|             //       { | ||||
|             //         name: "James", | ||||
|             //         zone: "zone.home", | ||||
|             //         entity: "sensor.james_to_home", | ||||
|             //       }, | ||||
|             //       { | ||||
|             //         name: "Tina", | ||||
|             //         zone: "zone.home", | ||||
|             //         entity: "sensor.tina_to_home", | ||||
|             //       }, | ||||
|             //       { | ||||
|             //         name: "Work", | ||||
|             //         zone: "zone.powertec", | ||||
|             //         entity: "sensor.commute_to_work", | ||||
|             //       }, | ||||
|             //     ], | ||||
|             //     type: "custom:waze-card", | ||||
|             //   }, | ||||
|             // }, | ||||
|             { | ||||
|               style: { | ||||
|                 "border-radius": "20px", | ||||
| @@ -597,8 +722,46 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | ||||
|           ], | ||||
|           type: "vertical-stack", | ||||
|         }, | ||||
|         // { | ||||
|         //   cards: [ | ||||
|         //     { | ||||
|         //       style: { | ||||
|         //         "border-radius": "20px", | ||||
|         //         color: "#999999", | ||||
|         //         "box-shadow": "3px 3px rgba(0,0,0,0.4)", | ||||
|         //         border: "solid 1px rgba(100,100,100,0.3)", | ||||
|         //       }, | ||||
|         //       type: "custom:card-modder", | ||||
|         //       card: { | ||||
|         //         type: "picture-entity", | ||||
|         //         entity: "camera.bom_radar", | ||||
|         //       }, | ||||
|         //     }, | ||||
|         //     // { | ||||
|         //     //   style: { | ||||
|         //     //     "background-image": 'url("/assets/jimpower/cardbackK.png")', | ||||
|         //     //     "background-size": "100% 525px", | ||||
|         //     //     "box-shadow": "3px 3px rgba(0,0,0,0.4)", | ||||
|         //     //     "background-repeat": "no-repeat", | ||||
|         //     //     color: "#999999", | ||||
|         //     //     "border-radius": "20px", | ||||
|         //     //     border: "solid 1px rgba(100,100,100,0.3)", | ||||
|         //     //     "background-color": "rgba(50,50,50,0.3)", | ||||
|         //     //   }, | ||||
|         //     //   type: "custom:card-modder", | ||||
|         //     //   card: { | ||||
|         //     //     title: null, | ||||
|         //     //     type: "custom:tracker-card", | ||||
|         //     //     trackers: [ | ||||
|         //     //       "sensor.custom_card_tracker", | ||||
|         //     //       "sensor.custom_component_tracker", | ||||
|         //     //     ], | ||||
|         //     //   }, | ||||
|         //     // }, | ||||
|         //   ], | ||||
|         //   type: "vertical-stack", | ||||
|         // }, | ||||
|       ], | ||||
|       path: "home", | ||||
|       icon: "mdi:castle", | ||||
|       name: "Home", | ||||
|       background: | ||||
| @@ -718,13 +881,26 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | ||||
|               card: { | ||||
|                 image: "/assets/jimpower/security/air_8.jpg", | ||||
|                 elements: [ | ||||
|                   { | ||||
|                     image: | ||||
|                       "https://www.airvisual.com/assets/aqi/ic-face-1-green.svg", | ||||
|                     type: "image", | ||||
|                     style: { | ||||
|                       width: "80px", | ||||
|                       top: "30%", | ||||
|                       left: "12%", | ||||
|                       transform: "none", | ||||
|                       height: "80px", | ||||
|                     }, | ||||
|                     entity: "sensor.us_air_pollution_level_2", | ||||
|                   }, | ||||
|                   { | ||||
|                     style: { | ||||
|                       color: "hsl(120, 41%, 39%)", | ||||
|                       top: "50%", | ||||
|                       "font-weight": 600, | ||||
|                       "font-size": "50px", | ||||
|                       left: "30%", | ||||
|                       "font-size": "20px", | ||||
|                       left: "44%", | ||||
|                     }, | ||||
|                     type: "state-label", | ||||
|                     entity: "sensor.us_air_pollution_level_2", | ||||
| @@ -744,7 +920,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | ||||
|                     style: { | ||||
|                       color: "white", | ||||
|                       top: "80%", | ||||
|                       left: "48%", | ||||
|                       left: "52%", | ||||
|                     }, | ||||
|                     type: "state-icon", | ||||
|                     entity: "sensor.us_main_pollutant_2", | ||||
| @@ -1235,7 +1411,6 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({ | ||||
|           type: "vertical-stack", | ||||
|         }, | ||||
|       ], | ||||
|       path: "security", | ||||
|       icon: "hass:shield-home", | ||||
|       name: "Security", | ||||
|       background: | ||||
|   | ||||
| @@ -101,12 +101,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | ||||
|     "sensor.zwave_battery_front_door": { | ||||
|       entity_id: "sensor.zwave_battery_front_door", | ||||
|       state: "63", | ||||
|       attributes: { | ||||
|         friendly_name: "Battery", | ||||
|         icon: "mdi:battery-60", | ||||
|         unit_of_measurement: "%", | ||||
|         device_class: "battery", | ||||
|       }, | ||||
|       attributes: { friendly_name: "Battery", icon: "mdi:battery-60" }, | ||||
|     }, | ||||
|     "sensor.oskar_devices": { | ||||
|       entity_id: "sensor.oskar_devices", | ||||
| @@ -169,7 +164,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | ||||
|     }, | ||||
|     "input_select.christmas_pattern": { | ||||
|       entity_id: "input_select.christmas_pattern", | ||||
|       state: "Rainbow", | ||||
|       state: "None", | ||||
|       attributes: { | ||||
|         options: [ | ||||
|           "None", | ||||
| @@ -191,7 +186,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | ||||
|     }, | ||||
|     "input_select.christmas_palette": { | ||||
|       entity_id: "input_select.christmas_palette", | ||||
|       state: "Party", | ||||
|       state: "None", | ||||
|       attributes: { | ||||
|         options: [ | ||||
|           "None", | ||||
| @@ -462,7 +457,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | ||||
|       state: "0.0", | ||||
|       attributes: { | ||||
|         unit_of_measurement: "kB/s", | ||||
|         friendly_name: "Downloading", | ||||
|         friendly_name: "Nedladdning", | ||||
|         icon: "mdi:file-download", | ||||
|       }, | ||||
|     }, | ||||
| @@ -476,7 +471,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () => | ||||
|       state: "0.0", | ||||
|       attributes: { | ||||
|         unit_of_measurement: "kB/s", | ||||
|         friendly_name: "Uploading", | ||||
|         friendly_name: "Uppladdning", | ||||
|         icon: "mdi:file-upload", | ||||
|       }, | ||||
|     }, | ||||
|   | ||||
| @@ -2,7 +2,44 @@ import { DemoConfig } from "../types"; | ||||
|  | ||||
| export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|   name: "Hem", | ||||
|   resources: [], | ||||
|   resources: [ | ||||
|     // { | ||||
|     //   url: "/local/custom-lovelace/monster-card.js", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom-lovelace/mini-media-player-bundle.js?v=0.9.8", | ||||
|     //   type: "module", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom-lovelace/slideshow-card.js?=1.1.0", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom-lovelace/fold-entity-row.js?v=3ae2c4", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom-lovelace/swipe-card/swipe-card.js?v=2.0.0", | ||||
|     //   type: "module", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom-lovelace/upcoming-media-card/upcoming-media-card.js", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom-lovelace/tracker-card.js?v=0.1.5", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom-lovelace/card-tools.js?v=6ce5d0", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|     // { | ||||
|     //   url: "/local/custom-lovelace/krisinfo.js?=0.0.1", | ||||
|     //   type: "js", | ||||
|     // }, | ||||
|   ], | ||||
|   views: [ | ||||
|     { | ||||
|       cards: [ | ||||
| @@ -27,7 +64,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|                       style: { | ||||
|                         color: "white", | ||||
|                         top: "93%", | ||||
|                         left: "85%", | ||||
|                         left: "90%", | ||||
|                       }, | ||||
|                       type: "state-label", | ||||
|                       entity: "sensor.battery_oskar", | ||||
| @@ -50,7 +87,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|                     { | ||||
|                       style: { | ||||
|                         color: "white", | ||||
|                         top: "93%", | ||||
|                         top: "92%", | ||||
|                         left: "20%", | ||||
|                       }, | ||||
|                       type: "state-label", | ||||
| @@ -59,8 +96,8 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|                     { | ||||
|                       style: { | ||||
|                         color: "white", | ||||
|                         top: "93%", | ||||
|                         left: "85%", | ||||
|                         top: "92%", | ||||
|                         left: "90%", | ||||
|                       }, | ||||
|                       type: "state-label", | ||||
|                       entity: "sensor.battery_bella", | ||||
| @@ -68,7 +105,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|                     { | ||||
|                       style: { | ||||
|                         color: "white", | ||||
|                         top: "93%", | ||||
|                         top: "92%", | ||||
|                         left: "55%", | ||||
|                       }, | ||||
|                       type: "state-label", | ||||
| @@ -94,6 +131,78 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|           type: "entities", | ||||
|           title: "Lock", | ||||
|         }, | ||||
|         // { | ||||
|         //   filter: { | ||||
|         //     exclude: [ | ||||
|         //       { | ||||
|         //         state: "not_home", | ||||
|         //       }, | ||||
|         //     ], | ||||
|         //     include: [ | ||||
|         //       { | ||||
|         //         entity_id: "device_tracker.annasiphone", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: "device_tracker.iphone_2", | ||||
|         //       }, | ||||
|         //     ], | ||||
|         //   }, | ||||
|         //   type: "custom:monster-card", | ||||
|         //   card: { | ||||
|         //     show_header_toggle: false, | ||||
|         //     type: "entities", | ||||
|         //     title: "G\u00e4ster", | ||||
|         //   }, | ||||
|         //   show_empty: false, | ||||
|         // }, | ||||
|         // { | ||||
|         //   filter: { | ||||
|         //     exclude: [ | ||||
|         //       { | ||||
|         //         state: "Inget", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         state: "i.u.", | ||||
|         //       }, | ||||
|         //     ], | ||||
|         //     include: [ | ||||
|         //       { | ||||
|         //         entity_id: "sensor.pollen_al", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: "sensor.pollen_alm", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: "sensor.pollen_salg_vide", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: "sensor.pollen_bjork", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: "sensor.pollen_bok", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: "sensor.pollen_ek", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: "sensor.pollen_grabo", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: "sensor.pollen_gras", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: "sensor.pollen_hassel", | ||||
|         //       }, | ||||
|         //     ], | ||||
|         //   }, | ||||
|         //   type: "custom:monster-card", | ||||
|         //   card: { | ||||
|         //     show_header_toggle: false, | ||||
|         //     type: "entities", | ||||
|         //     title: "Pollenniv\u00e5er", | ||||
|         //   }, | ||||
|         //   show_empty: false, | ||||
|         // }, | ||||
|         { | ||||
|           cards: [ | ||||
|             { | ||||
| @@ -117,6 +226,10 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|           ], | ||||
|           type: "vertical-stack", | ||||
|         }, | ||||
|         // { | ||||
|         //   url: "https://embed.windy.com/embed2.html", | ||||
|         //   type: "iframe", | ||||
|         // }, | ||||
|         { | ||||
|           entities: [ | ||||
|             { | ||||
| @@ -150,7 +263,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|           ], | ||||
|           type: "glance", | ||||
|           show_state: false, | ||||
|           columns: 4, | ||||
|         }, | ||||
|         { | ||||
|           entities: ["sensor.oskar_bluetooth"], | ||||
| @@ -158,6 +270,32 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|           type: "entities", | ||||
|           title: "Occupancy", | ||||
|         }, | ||||
|         // { | ||||
|         //   filter: { | ||||
|         //     exclude: [ | ||||
|         //       { | ||||
|         //         state: false, | ||||
|         //       }, | ||||
|         //     ], | ||||
|         //     include: [ | ||||
|         //       { | ||||
|         //         entity_id: | ||||
|         //           "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_2", | ||||
|         //       }, | ||||
|         //       { | ||||
|         //         entity_id: | ||||
|         //           "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_3", | ||||
|         //       }, | ||||
|         //     ], | ||||
|         //   }, | ||||
|         //   type: "custom:monster-card", | ||||
|         //   card: { | ||||
|         //     show_header_toggle: false, | ||||
|         //     type: "entities", | ||||
|         //     title: "Brandvarnare", | ||||
|         //   }, | ||||
|         //   show_empty: false, | ||||
|         // }, | ||||
|         { | ||||
|           type: "weather-forecast", | ||||
|           entity: "weather.smhi_vader", | ||||
| @@ -240,9 +378,41 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|             "binary_sensor.windows_server", | ||||
|             "binary_sensor.teamspeak", | ||||
|             "binary_sensor.harmony_hub", | ||||
|             // { | ||||
|             //   style: { | ||||
|             //     height: "1px", | ||||
|             //     width: "85%", | ||||
|             //     "margin-left": "auto", | ||||
|             //     background: "#62717b", | ||||
|             //     "margin-right": "auto", | ||||
|             //   }, | ||||
|             //   type: "divider", | ||||
|             // }, | ||||
|             // { | ||||
|             //   items: ["sensor.uptime_router", "sensor.installerad_routeros"], | ||||
|             //   head: { | ||||
|             //     entity: "binary_sensor.router", | ||||
|             //   }, | ||||
|             //   type: "custom:fold-entity-row", | ||||
|             //   group_config: { | ||||
|             //     icon: "mdi:router", | ||||
|             //   }, | ||||
|             // }, | ||||
|             // { | ||||
|             //   items: [ | ||||
|             //     "sensor.uptime_router_server", | ||||
|             //     "sensor.installerad_routeros_server", | ||||
|             //   ], | ||||
|             //   head: { | ||||
|             //     entity: "binary_sensor.router_server", | ||||
|             //   }, | ||||
|             //   type: "custom:fold-entity-row", | ||||
|             //   group_config: { | ||||
|             //     icon: "mdi:router", | ||||
|             //   }, | ||||
|             // }, | ||||
|           ], | ||||
|           show_header_toggle: false, | ||||
|           state_color: true, | ||||
|           type: "entities", | ||||
|           title: "Network", | ||||
|         }, | ||||
| @@ -252,10 +422,29 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({ | ||||
|             "binary_sensor.ubiquiti_switch", | ||||
|             "binary_sensor.ubiquiti_nvr", | ||||
|             "binary_sensor.entre_kamera", | ||||
|             // { | ||||
|             //   items: ["sensor.uptime_ap_1"], | ||||
|             //   head: { | ||||
|             //     entity: "binary_sensor.accesspunkt_1", | ||||
|             //   }, | ||||
|             //   type: "custom:fold-entity-row", | ||||
|             //   group_config: { | ||||
|             //     icon: "router-wireless", | ||||
|             //   }, | ||||
|             // }, | ||||
|             // { | ||||
|             //   items: ["sensor.uptime_ap_2"], | ||||
|             //   head: { | ||||
|             //     entity: "binary_sensor.accesspunkt_2", | ||||
|             //   }, | ||||
|             //   type: "custom:fold-entity-row", | ||||
|             //   group_config: { | ||||
|             //     icon: "router-wireless", | ||||
|             //   }, | ||||
|             // }, | ||||
|             "sensor.total_clients_wireless", | ||||
|           ], | ||||
|           show_header_toggle: false, | ||||
|           state_color: true, | ||||
|           type: "entities", | ||||
|           title: "Ubiquiti", | ||||
|         }, | ||||
|   | ||||
| @@ -215,7 +215,6 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | ||||
|           card: { | ||||
|             type: "glance", | ||||
|             show_state: false, | ||||
|             columns: 4, | ||||
|           }, | ||||
|           state_filter: ["on"], | ||||
|         }, | ||||
| @@ -809,6 +808,67 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | ||||
|           ], | ||||
|           type: "vertical-stack", | ||||
|         }, | ||||
|         // { | ||||
|         //   cards: [ | ||||
|         //     { | ||||
|         //       entities: [ | ||||
|         //         { | ||||
|         //           hide_when_off: true, | ||||
|         //           toggle: true, | ||||
|         //           type: "custom:slider-entity-row", | ||||
|         //           name: "Bedside", | ||||
|         //           entity: "light.bedside_lamp", | ||||
|         //         }, | ||||
|         //         { | ||||
|         //           hide_when_off: true, | ||||
|         //           toggle: true, | ||||
|         //           type: "custom:slider-entity-row", | ||||
|         //           name: "Bedroom", | ||||
|         //           entity: "light.bedroom_ceiling_light", | ||||
|         //         }, | ||||
|         //         { | ||||
|         //           hide_when_off: true, | ||||
|         //           toggle: true, | ||||
|         //           type: "custom:slider-entity-row", | ||||
|         //           name: "Isa", | ||||
|         //           entity: "light.isa_ceiling_light", | ||||
|         //         }, | ||||
|         //         { | ||||
|         //           hide_when_off: true, | ||||
|         //           toggle: true, | ||||
|         //           type: "custom:slider-entity-row", | ||||
|         //           name: "Upstairs hallway", | ||||
|         //           entity: "light.upstairs_hallway_ceiling_light_level", | ||||
|         //         }, | ||||
|         //         { | ||||
|         //           hide_when_off: true, | ||||
|         //           toggle: true, | ||||
|         //           type: "custom:slider-entity-row", | ||||
|         //           name: "Nightlight", | ||||
|         //           entity: "light.gateway_light_34ce008bfc4b", | ||||
|         //         }, | ||||
|         //         { | ||||
|         //           hide_when_off: true, | ||||
|         //           toggle: true, | ||||
|         //           type: "custom:slider-entity-row", | ||||
|         //           name: "Walk in closet", | ||||
|         //           entity: "light.walk_in_closet_lights", | ||||
|         //         }, | ||||
|         //         { | ||||
|         //           hide_when_off: true, | ||||
|         //           toggle: false, | ||||
|         //           type: "custom:slider-entity-row", | ||||
|         //           name: "Stefan", | ||||
|         //           entity: "light.stefan_lightstrip", | ||||
|         //         }, | ||||
|         //       ], | ||||
|         //       show_header_toggle: false, | ||||
|         //       type: "entities", | ||||
|         //       title: "Upstairs", | ||||
|         //     }, | ||||
|         //   ], | ||||
|         //   type: "vertical-stack", | ||||
|         // }, | ||||
|       ], | ||||
|       path: "lights", | ||||
|       title: "Lights", | ||||
| @@ -858,6 +918,10 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | ||||
|                   name: "Dafang", | ||||
|                   icon: "mdi:webcam", | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "IR Hallway", | ||||
|                   entity: "sensor.system_ir_blaster", | ||||
|                 }, | ||||
|                 { | ||||
|                   name: "IR Bedroom", | ||||
|                   entity: "sensor.system_ir_blaster_bedroom", | ||||
| @@ -876,7 +940,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({ | ||||
|                 "sensor.system_ring_chime", | ||||
|               ], | ||||
|               type: "glance", | ||||
|               columns: 4, | ||||
|               columns: 5, | ||||
|               show_state: false, | ||||
|             }, | ||||
|             { | ||||
|   | ||||
| @@ -9,5 +9,5 @@ export interface DemoConfig { | ||||
|   authorUrl: string; | ||||
|   lovelace: (localize: LocalizeFunc) => LovelaceConfig; | ||||
|   entities: (localize: LocalizeFunc) => Entity[]; | ||||
|   theme: () => Record<string, string> | null; | ||||
|   theme: () => { [key: string]: string } | null; | ||||
| } | ||||
|   | ||||
| @@ -3,8 +3,8 @@ import { | ||||
|   CSSResult, | ||||
|   customElement, | ||||
|   html, | ||||
|   internalProperty, | ||||
|   LitElement, | ||||
|   internalProperty, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { CastManager } from "../../../src/cast/cast_manager"; | ||||
|   | ||||
| @@ -3,9 +3,9 @@ import { | ||||
|   css, | ||||
|   CSSResult, | ||||
|   html, | ||||
|   internalProperty, | ||||
|   LitElement, | ||||
|   property, | ||||
|   internalProperty, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { until } from "lit-html/directives/until"; | ||||
|   | ||||
| @@ -1,11 +1,13 @@ | ||||
| import "../../src/resources/safari-14-attachshadow-patch"; | ||||
| import "@polymer/polymer/lib/elements/dom-if"; | ||||
| import "@polymer/polymer/lib/elements/dom-repeat"; | ||||
| import "../../src/resources/ha-style"; | ||||
| import "../../src/resources/roboto"; | ||||
| import "../../src/resources/safari-14-attachshadow-patch"; | ||||
| import "./ha-demo"; | ||||
|  | ||||
| /* polyfill for paper-dropdown */ | ||||
| setTimeout(() => { | ||||
|   import("web-animations-js/web-animations-next-lite.min"); | ||||
|   import( | ||||
|     /* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min" | ||||
|   ); | ||||
| }, 1000); | ||||
|   | ||||
| @@ -1,4 +1,3 @@ | ||||
| // Compat needs to be first import | ||||
| import "../../src/resources/compatibility"; | ||||
| import { isNavigationClick } from "../../src/common/dom/is-navigation-click"; | ||||
| import { navigate } from "../../src/common/navigate"; | ||||
|   | ||||
| @@ -21,16 +21,15 @@ class DemoCard extends PolymerElement { | ||||
|         } | ||||
|         pre { | ||||
|           width: 400px; | ||||
|           margin: 0 16px; | ||||
|           margin: 16px; | ||||
|           overflow: auto; | ||||
|           color: var(--primary-text-color); | ||||
|         } | ||||
|         @media only screen and (max-width: 800px) { | ||||
|           .root { | ||||
|             flex-direction: column; | ||||
|           } | ||||
|           pre { | ||||
|             margin: 16px 0; | ||||
|             margin-left: 0; | ||||
|           } | ||||
|         } | ||||
|       </style> | ||||
|   | ||||
| @@ -2,10 +2,10 @@ import "@polymer/app-layout/app-toolbar/app-toolbar"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element"; | ||||
| import "../../../src/components/ha-formfield"; | ||||
| import "../../../src/components/ha-switch"; | ||||
| import "../../../src/components/ha-formfield"; | ||||
| import "./demo-card"; | ||||
| import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element"; | ||||
|  | ||||
| class DemoCards extends PolymerElement { | ||||
|   static get template() { | ||||
|   | ||||
| @@ -2,62 +2,58 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import "../../../src/dialogs/more-info/more-info-content"; | ||||
| import "../../../src/state-summary/state-card-content"; | ||||
| import "../../../src/dialogs/more-info/more-info-content"; | ||||
|  | ||||
| class DemoMoreInfo extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <style> | ||||
|         .root { | ||||
|         :host { | ||||
|           display: flex; | ||||
|           align-items: start; | ||||
|         } | ||||
|         #card { | ||||
|           max-width: 400px; | ||||
|           width: 100vw; | ||||
|         } | ||||
|  | ||||
|         ha-card { | ||||
|           width: 352px; | ||||
|           width: 333px; | ||||
|           padding: 20px 24px; | ||||
|         } | ||||
|  | ||||
|         state-card-content { | ||||
|           display: block; | ||||
|           margin-bottom: 16px; | ||||
|         } | ||||
|  | ||||
|         pre { | ||||
|           width: 400px; | ||||
|           margin: 0 16px; | ||||
|           margin: 16px; | ||||
|           overflow: auto; | ||||
|           color: var(--primary-text-color); | ||||
|         } | ||||
|  | ||||
|         @media only screen and (max-width: 800px) { | ||||
|           .root { | ||||
|           :host { | ||||
|             flex-direction: column; | ||||
|           } | ||||
|           pre { | ||||
|             margin: 16px 0; | ||||
|             margin-left: 0; | ||||
|           } | ||||
|         } | ||||
|       </style> | ||||
|       <div class="root"> | ||||
|         <div id="card"> | ||||
|           <ha-card> | ||||
|             <state-card-content | ||||
|               state-obj="[[_stateObj]]" | ||||
|               hass="[[hass]]" | ||||
|               in-dialog | ||||
|             ></state-card-content> | ||||
|       <ha-card> | ||||
|         <state-card-content | ||||
|           state-obj="[[_stateObj]]" | ||||
|           hass="[[hass]]" | ||||
|           in-dialog | ||||
|         ></state-card-content> | ||||
|  | ||||
|             <more-info-content | ||||
|               hass="[[hass]]" | ||||
|               state-obj="[[_stateObj]]" | ||||
|             ></more-info-content> | ||||
|           </ha-card> | ||||
|         </div> | ||||
|         <template is="dom-if" if="[[showConfig]]"> | ||||
|           <pre>[[_jsonEntity(_stateObj)]]</pre> | ||||
|         </template> | ||||
|       </div> | ||||
|         <more-info-content | ||||
|           hass="[[hass]]" | ||||
|           state-obj="[[_stateObj]]" | ||||
|         ></more-info-content> | ||||
|       </ha-card> | ||||
|       <template is="dom-if" if="[[showConfig]]"> | ||||
|         <pre>[[_jsonEntity(_stateObj)]]</pre> | ||||
|       </template> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -2,8 +2,6 @@ import "@polymer/app-layout/app-toolbar/app-toolbar"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element"; | ||||
| import "../../../src/components/ha-formfield"; | ||||
| import "../../../src/components/ha-switch"; | ||||
| import "./demo-more-info"; | ||||
|  | ||||
| @@ -11,10 +9,6 @@ class DemoMoreInfos extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <style> | ||||
|         #container { | ||||
|           min-height: calc(100vh - 128px); | ||||
|           background: var(--primary-background-color); | ||||
|         } | ||||
|         .cards { | ||||
|           display: flex; | ||||
|           flex-wrap: wrap; | ||||
| @@ -29,31 +23,20 @@ class DemoMoreInfos extends PolymerElement { | ||||
|         .filters { | ||||
|           margin-left: 60px; | ||||
|         } | ||||
|         ha-formfield { | ||||
|           margin-right: 16px; | ||||
|         } | ||||
|       </style> | ||||
|       <app-toolbar> | ||||
|         <div class="filters"> | ||||
|           <ha-formfield label="Show entities"> | ||||
|             <ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled"> | ||||
|             </ha-switch> | ||||
|           </ha-formfield> | ||||
|           <ha-formfield label="Dark theme"> | ||||
|             <ha-switch on-change="_darkThemeToggled"> </ha-switch> | ||||
|           </ha-formfield> | ||||
|           <ha-switch checked="{{_showConfig}}">Show entity</ha-switch> | ||||
|         </div> | ||||
|       </app-toolbar> | ||||
|       <div id="container"> | ||||
|         <div class="cards"> | ||||
|           <template is="dom-repeat" items="[[entities]]"> | ||||
|             <demo-more-info | ||||
|               entity-id="[[item]]" | ||||
|               show-config="[[_showConfig]]" | ||||
|               hass="[[hass]]" | ||||
|             ></demo-more-info> | ||||
|           </template> | ||||
|         </div> | ||||
|       <div class="cards"> | ||||
|         <template is="dom-repeat" items="[[entities]]"> | ||||
|           <demo-more-info | ||||
|             entity-id="[[item]]" | ||||
|             show-config="[[_showConfig]]" | ||||
|             hass="[[hass]]" | ||||
|           ></demo-more-info> | ||||
|         </template> | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
| @@ -68,16 +51,6 @@ class DemoMoreInfos extends PolymerElement { | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   _showConfigToggled(ev) { | ||||
|     this._showConfig = ev.target.checked; | ||||
|   } | ||||
|  | ||||
|   _darkThemeToggled(ev) { | ||||
|     applyThemesOnElement(this.$.container, { themes: {} }, "default", { | ||||
|       dark: ev.target.checked, | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-more-infos", DemoMoreInfos); | ||||
|   | ||||
| @@ -7,8 +7,8 @@ export const createMediaPlayerEntities = () => [ | ||||
|     media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)", | ||||
|     media_artist: "Technohead", | ||||
|     // Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media + | ||||
|     // Select Source + Stop + Clear + Play + Shuffle Set | ||||
|     supported_features: 64063, | ||||
|     // Select Source + Stop + Clear + Play + Shuffle Set + Browse Media | ||||
|     supported_features: 195135, | ||||
|     entity_picture: "/images/album_cover_2.jpg", | ||||
|     media_duration: 300, | ||||
|     media_position: 50, | ||||
| @@ -24,8 +24,8 @@ export const createMediaPlayerEntities = () => [ | ||||
|     media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)", | ||||
|     media_artist: "Technohead", | ||||
|     // Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media + | ||||
|     // Select Source + Stop + Clear + Play + Shuffle Set + Browse Media | ||||
|     supported_features: 195135, | ||||
|     // Select Source + Stop + Clear + Play + Shuffle Set | ||||
|     supported_features: 64063, | ||||
|     entity_picture: "/images/album_cover.jpg", | ||||
|     media_duration: 300, | ||||
|     media_position: 0, | ||||
|   | ||||
| @@ -1,72 +0,0 @@ | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
|  | ||||
| export const createPlantEntities = () => [ | ||||
|   getEntity("plant", "lemon_tree", "ok", { | ||||
|     problem: "none", | ||||
|     sensors: { | ||||
|       moisture: "sensor.lemon_tree_moisture", | ||||
|       battery: "sensor.lemon_tree_battery", | ||||
|       temperature: "sensor.lemon_tree_temperature", | ||||
|       conductivity: "sensor.lemon_tree_conductivity", | ||||
|       brightness: "sensor.lemon_tree_brightness", | ||||
|     }, | ||||
|     unit_of_measurement_dict: { | ||||
|       temperature: "°C", | ||||
|       moisture: "%", | ||||
|       brightness: "lx", | ||||
|       battery: "%", | ||||
|       conductivity: "μS/cm", | ||||
|     }, | ||||
|     moisture: 54, | ||||
|     battery: 95, | ||||
|     temperature: 15.6, | ||||
|     conductivity: 1, | ||||
|     brightness: 12, | ||||
|     max_brightness: 20, | ||||
|     friendly_name: "Lemon Tree", | ||||
|   }), | ||||
|   getEntity("plant", "apple_tree", "ok", { | ||||
|     problem: "brightness", | ||||
|     sensors: { | ||||
|       moisture: "sensor.apple_tree_moisture", | ||||
|       battery: "sensor.apple_tree_battery", | ||||
|       temperature: "sensor.apple_tree_temperature", | ||||
|       conductivity: "sensor.apple_tree_conductivity", | ||||
|       brightness: "sensor.apple_tree_brightness", | ||||
|     }, | ||||
|     unit_of_measurement_dict: { | ||||
|       temperature: "°C", | ||||
|       moisture: "%", | ||||
|       brightness: "lx", | ||||
|       battery: "%", | ||||
|       conductivity: "μS/cm", | ||||
|     }, | ||||
|     moisture: 54, | ||||
|     battery: 2, | ||||
|     temperature: 15.6, | ||||
|     conductivity: 1, | ||||
|     brightness: 25, | ||||
|     max_brightness: 20, | ||||
|     friendly_name: "Apple Tree", | ||||
|   }), | ||||
|   getEntity("plant", "sunflowers", "ok", { | ||||
|     problem: "moisture, temperature, conductivity", | ||||
|     sensors: { | ||||
|       moisture: "sensor.sunflowers_moisture", | ||||
|       temperature: "sensor.sunflowers_temperature", | ||||
|       conductivity: "sensor.sunflowers_conductivity", | ||||
|       brightness: "sensor.sunflowers_brightness", | ||||
|     }, | ||||
|     unit_of_measurement_dict: { | ||||
|       temperature: "°C", | ||||
|       moisture: "%", | ||||
|       brightness: "lx", | ||||
|       conductivity: "μS/cm", | ||||
|     }, | ||||
|     moisture: 54, | ||||
|     temperature: 15.6, | ||||
|     conductivity: 1, | ||||
|     brightness: 25, | ||||
|     entity_picture: "/images/sunflowers.jpg", | ||||
|   }), | ||||
| ]; | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -76,19 +71,35 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-alarm-panel-card") | ||||
| class DemoAlarmPanelEntity extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoAlarmPanelEntity extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <demo-cards | ||||
|         id="demos" | ||||
|         hass="[[hass]]" | ||||
|         configs="[[_configs]]" | ||||
|       ></demo-cards> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|       hass: Object, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     this._setupDemo(); | ||||
|   } | ||||
|  | ||||
|   private async _setupDemo() { | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     await hass.updateTranslations(null, "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -58,19 +53,31 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-conditional-card") | ||||
| class DemoConditional extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoConditional extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <demo-cards | ||||
|         id="demos" | ||||
|         hass="[[hass]]" | ||||
|         configs="[[_configs]]" | ||||
|       ></demo-cards> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|       hass: Object, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -222,19 +217,24 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-entities-card") | ||||
| class DemoEntities extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoEntities extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -25,10 +20,10 @@ const CONFIGS = [ | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "With Name (defined in card)", | ||||
|     heading: "With Name", | ||||
|     config: ` | ||||
| - type: button | ||||
|   name: Custom Name | ||||
|   name: Bedroom | ||||
|   entity: light.bed_light | ||||
|     `, | ||||
|   }, | ||||
| @@ -37,7 +32,7 @@ const CONFIGS = [ | ||||
|     config: ` | ||||
| - type: button | ||||
|   entity: light.bed_light | ||||
|   icon: mdi:tools | ||||
|   icon: mdi:hotel | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
| @@ -53,7 +48,7 @@ const CONFIGS = [ | ||||
|     config: ` | ||||
| - type: button | ||||
|   entity: light.bed_light | ||||
|   tap_action: | ||||
|   tap_action:  | ||||
|     action: toggle | ||||
|     `, | ||||
|   }, | ||||
| @@ -74,19 +69,31 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-entity-button-card") | ||||
| class DemoButtonEntity extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoButtonEntity extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <demo-cards | ||||
|         id="demos" | ||||
|         hass="[[hass]]" | ||||
|         configs="[[_configs]]" | ||||
|       ></demo-cards> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|       hass: Object, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -48,7 +43,7 @@ const ENTITIES = [ | ||||
|  | ||||
| const CONFIGS = [ | ||||
|   { | ||||
|     heading: "Unfiltered controller", | ||||
|     heading: "Controller", | ||||
|     config: ` | ||||
| - type: entities | ||||
|   entities: | ||||
| @@ -58,7 +53,7 @@ const CONFIGS = [ | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Filtered entities card", | ||||
|     heading: "Basic", | ||||
|     config: ` | ||||
| - type: entity-filter | ||||
|   entities: | ||||
| @@ -74,27 +69,7 @@ const CONFIGS = [ | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: 'With "entities" card config', | ||||
|     config: ` | ||||
| - type: entity-filter | ||||
|   entities: | ||||
|     - device_tracker.demo_anne_therese | ||||
|     - device_tracker.demo_home_boy | ||||
|     - device_tracker.demo_paulus | ||||
|     - light.bed_light | ||||
|     - light.ceiling_lights | ||||
|     - light.kitchen_lights | ||||
|   state_filter: | ||||
|     - "on" | ||||
|     - not_home | ||||
|   card: | ||||
|     type: entities | ||||
|     title: Custom Title | ||||
|     show_header_toggle: false | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: 'With "glance" card config', | ||||
|     heading: "With card config", | ||||
|     config: ` | ||||
| - type: entity-filter | ||||
|   entities: | ||||
| @@ -109,27 +84,31 @@ const CONFIGS = [ | ||||
|     - not_home | ||||
|   card: | ||||
|     type: glance | ||||
|     show_state: true | ||||
|     title: Custom Title | ||||
|     show_state: false | ||||
|     `, | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-entity-filter-card") | ||||
| class DemoEntityFilter extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoFilter extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-hui-entity-filter-card", DemoEntityFilter); | ||||
| customElements.define("demo-hui-entity-filter-card", DemoFilter); | ||||
|   | ||||
| @@ -1,19 +1,12 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
|  | ||||
| const ENTITIES = [ | ||||
|   getEntity("sensor", "brightness", "12", {}), | ||||
|   getEntity("sensor", "brightness_medium", "53", {}), | ||||
|   getEntity("sensor", "brightness_high", "87", {}), | ||||
|   getEntity("plant", "bonsai", "ok", {}), | ||||
|   getEntity("sensor", "not_working", "unavailable", {}), | ||||
|   getEntity("sensor", "outside_humidity", "54", { | ||||
| @@ -28,10 +21,16 @@ const CONFIGS = [ | ||||
|   { | ||||
|     heading: "Basic example", | ||||
|     config: ` | ||||
| - type: gauge | ||||
|   entity: sensor.brightness | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "With title", | ||||
|     config: ` | ||||
| - type: gauge | ||||
|   title: Humidity | ||||
|   entity: sensor.outside_humidity | ||||
|   name: Outside Humidity | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
| @@ -40,7 +39,6 @@ const CONFIGS = [ | ||||
| - type: gauge | ||||
|   entity: sensor.outside_temperature | ||||
|   unit_of_measurement: C | ||||
|   name: Outside Temperature | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
| @@ -48,45 +46,19 @@ const CONFIGS = [ | ||||
|     config: ` | ||||
| - type: gauge | ||||
|   entity: sensor.brightness | ||||
|   name: Brightness Low | ||||
|   severity: | ||||
|     red: 75 | ||||
|     red: 32 | ||||
|     green: 0 | ||||
|     yellow: 50 | ||||
|     yellow: 23 | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Setting Severity Levels", | ||||
|     config: ` | ||||
| - type: gauge | ||||
|   entity: sensor.brightness_medium | ||||
|   name: Brightness Medium | ||||
|   severity: | ||||
|     red: 75 | ||||
|     green: 0 | ||||
|     yellow: 50 | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Setting Severity Levels", | ||||
|     config: ` | ||||
| - type: gauge | ||||
|   entity: sensor.brightness_high | ||||
|   name: Brightness High | ||||
|   severity: | ||||
|     red: 75 | ||||
|     green: 0 | ||||
|     yellow: 50 | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Setting Min (0) and Max (15) Values", | ||||
|     heading: "Setting Min and Max Values", | ||||
|     config: ` | ||||
| - type: gauge | ||||
|   entity: sensor.brightness | ||||
|   name: Brightness | ||||
|   min: 0 | ||||
|   max: 15 | ||||
|   max: 38 | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
| @@ -112,19 +84,24 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-gauge-card") | ||||
| class DemoGaugeEntity extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoGaugeEntity extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -82,8 +77,7 @@ const CONFIGS = [ | ||||
|     heading: "With title", | ||||
|     config: ` | ||||
| - type: glance | ||||
|   title: Custom title | ||||
|   columns: 4 | ||||
|   title: This is glance | ||||
|   entities: | ||||
|     - device_tracker.demo_paulus | ||||
|     - media_player.living_room | ||||
| @@ -110,10 +104,9 @@ const CONFIGS = [ | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "No entity names", | ||||
|     heading: "No name", | ||||
|     config: ` | ||||
| - type: glance | ||||
|   columns: 4 | ||||
|   show_name: false | ||||
|   entities: | ||||
|     - device_tracker.demo_paulus | ||||
| @@ -126,10 +119,9 @@ const CONFIGS = [ | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "No state labels", | ||||
|     heading: "No state", | ||||
|     config: ` | ||||
| - type: glance | ||||
|   columns: 4 | ||||
|   show_state: false | ||||
|   entities: | ||||
|     - device_tracker.demo_paulus | ||||
| @@ -142,10 +134,9 @@ const CONFIGS = [ | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "No names and no state labels", | ||||
|     heading: "No name and no state", | ||||
|     config: ` | ||||
| - type: glance | ||||
|   columns: 4 | ||||
|   show_name: false | ||||
|   show_state: false | ||||
|   entities: | ||||
| @@ -159,24 +150,47 @@ const CONFIGS = [ | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Custom name + custom icon", | ||||
|     heading: "Custom name, custom icon", | ||||
|     config: ` | ||||
| - type: glance | ||||
|   columns: 4 | ||||
|   entities: | ||||
|     - entity: device_tracker.demo_paulus | ||||
|       name: ¯\\_(ツ)_/¯ | ||||
|       icon: mdi:home-assistant | ||||
|     - entity: media_player.living_room | ||||
|       name: ¯\\_(ツ)_/¯ | ||||
|       icon: mdi:home-assistant | ||||
|     - media_player.living_room | ||||
|     - sun.sun | ||||
|     - cover.kitchen_window | ||||
|     - entity: light.kitchen_lights | ||||
|       icon: mdi:alarm-light | ||||
|     - lock.kitchen_door | ||||
|     - light.ceiling_lights | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Custom tap action", | ||||
|     config: ` | ||||
| - type: glance | ||||
|   entities: | ||||
|     - entity: lock.kitchen_door | ||||
|       tap_action: | ||||
|         type: toggle | ||||
|     - entity: light.ceiling_lights | ||||
|       tap_action: | ||||
|         action: call-service | ||||
|         service: light.turn_on | ||||
|         service_data: | ||||
|           entity_id: light.ceiling_lights | ||||
|     - device_tracker.demo_paulus | ||||
|     - media_player.living_room | ||||
|     - sun.sun | ||||
|     - cover.kitchen_window | ||||
|     - light.kitchen_lights | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Selectively hidden name", | ||||
|     config: ` | ||||
| - type: glance | ||||
|   columns: 4 | ||||
|   entities: | ||||
|     - device_tracker.demo_paulus | ||||
|     - entity: media_player.living_room | ||||
| @@ -185,51 +199,45 @@ const CONFIGS = [ | ||||
|     - entity: cover.kitchen_window | ||||
|       name: | ||||
|     - light.kitchen_lights | ||||
|     - entity: lock.kitchen_door | ||||
|       name:  | ||||
|     - light.ceiling_lights | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Custom tap action", | ||||
|     heading: "Primary theme", | ||||
|     config: ` | ||||
| - type: glance | ||||
|   columns: 4   | ||||
|   theming: primary | ||||
|   entities: | ||||
|     - entity: lock.kitchen_door | ||||
|       name: Custom | ||||
|       tap_action: | ||||
|         type: toggle | ||||
|     - entity: light.ceiling_lights | ||||
|       name: Custom | ||||
|       tap_action: | ||||
|         action: call-service | ||||
|         service: light.turn_on | ||||
|         service_data: | ||||
|           entity_id: light.ceiling_lights | ||||
|     - entity: sun.sun | ||||
|       name: Regular | ||||
|     - entity: light.kitchen_lights | ||||
|       name: Regular | ||||
|     - device_tracker.demo_paulus | ||||
|     - media_player.living_room | ||||
|     - sun.sun | ||||
|     - cover.kitchen_window | ||||
|     - light.kitchen_lights | ||||
|     - lock.kitchen_door | ||||
|     - light.ceiling_lights | ||||
|     `, | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-glance-card") | ||||
| class DemoGlanceEntity extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoPicEntity extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-hui-glance-card", DemoGlanceEntity); | ||||
| customElements.define("demo-hui-glance-card", DemoPicEntity); | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| import { customElement, html, LitElement, TemplateResult } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import "../components/demo-cards"; | ||||
|  | ||||
| const CONFIGS = [ | ||||
| @@ -35,10 +37,18 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-iframe-card") | ||||
| class DemoIframe extends LitElement { | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoIframe extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -13,43 +8,29 @@ import "../components/demo-cards"; | ||||
| const ENTITIES = [ | ||||
|   getEntity("light", "bed_light", "on", { | ||||
|     friendly_name: "Bed Light", | ||||
|     brightness: 255, | ||||
|     brightness: 130, | ||||
|   }), | ||||
|   getEntity("light", "dim_on", "on", { | ||||
|     friendly_name: "Dining Room", | ||||
|     supported_features: 1, | ||||
|     brightness: 100, | ||||
|   }), | ||||
|   getEntity("light", "dim_off", "off", { | ||||
|     friendly_name: "Dining Room", | ||||
|   getEntity("light", "dim", "off", { | ||||
|     supported_features: 1, | ||||
|   }), | ||||
|   getEntity("light", "unavailable", "unavailable", { | ||||
|     friendly_name: "Lost Light", | ||||
|     supported_features: 1, | ||||
|   }), | ||||
| ]; | ||||
|  | ||||
| const CONFIGS = [ | ||||
|   { | ||||
|     heading: "Switchable Light", | ||||
|     heading: "Basic example", | ||||
|     config: ` | ||||
| - type: light | ||||
|   entity: light.bed_light | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Dimmable Light On", | ||||
|     heading: "Dim", | ||||
|     config: ` | ||||
| - type: light | ||||
|   entity: light.dim_on | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Dimmable Light Off", | ||||
|     config: ` | ||||
| - type: light | ||||
|   entity: light.dim_off | ||||
|   entity: light.dim | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
| @@ -68,19 +49,24 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-light-card") | ||||
| class DemoLightEntity extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoLightEntity extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -166,19 +161,31 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-map-card") | ||||
| class DemoMap extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoMap extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <demo-cards | ||||
|         id="demos" | ||||
|         hass="[[hass]]" | ||||
|         configs="[[_configs]]" | ||||
|       ></demo-cards> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|       hass: Object, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { mockTemplate } from "../../../demo/src/stubs/template"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -259,19 +254,23 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-markdown-card") | ||||
| class DemoMarkdown extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoMarkdown extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     mockTemplate(hass); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| import { createMediaPlayerEntities } from "../data/media_players"; | ||||
| @@ -151,33 +146,35 @@ const CONFIGS = [ | ||||
|     entity: media_player.receiver_off | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Grid Full Size", | ||||
|     config: ` | ||||
|   - type: grid | ||||
|     columns: 1 | ||||
|     cards: | ||||
|     - type: media-control | ||||
|       entity: media_player.music_paused | ||||
|     `, | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-media-control-card") | ||||
| class DemoHuiMediaControlCard extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoHuiMediControlCard extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <demo-cards | ||||
|         id="demos" | ||||
|         hass="[[hass]]" | ||||
|         configs="[[_configs]]" | ||||
|       ></demo-cards> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|       hass: Object, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(createMediaPlayerEntities()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-hui-media-control-card", DemoHuiMediaControlCard); | ||||
| customElements.define("demo-hui-media-control-card", DemoHuiMediControlCard); | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| import { createMediaPlayerEntities } from "../data/media_players"; | ||||
| @@ -31,9 +26,9 @@ const CONFIGS = [ | ||||
|     - entity: media_player.android_cast | ||||
|       name: Screen casting | ||||
|     - entity: media_player.image_display | ||||
|       name: Digital Picture Frame | ||||
|       name: Digital Picture Frame   | ||||
|     - entity: media_player.sonos_idle | ||||
|       name: Sonos Idle | ||||
|       name: Sonos Idle   | ||||
|     - entity: media_player.idle_browse_media | ||||
|       name: Idle waiting for Browse Media | ||||
|     - entity: media_player.theater_off | ||||
| @@ -43,7 +38,7 @@ const CONFIGS = [ | ||||
|     - entity: media_player.theater_off_static | ||||
|       name: Player Off (cannot be switched on) | ||||
|     - entity: media_player.theater_on_static | ||||
|       name: Player On (cannot be switched off) | ||||
|       name: Player On (cannot be switched off)   | ||||
|     - entity: media_player.idle | ||||
|       name: Player Idle | ||||
|     - entity: media_player.playing | ||||
| @@ -60,21 +55,33 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-media-player-row") | ||||
| class DemoHuiMediaPlayerRow extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoHuiMediaPlayerRows extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <demo-cards | ||||
|         id="demos" | ||||
|         hass="[[hass]]" | ||||
|         configs="[[_configs]]" | ||||
|       ></demo-cards> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|       hass: Object, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(createMediaPlayerEntities()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-hui-media-player-row", DemoHuiMediaPlayerRow); | ||||
| customElements.define("demo-hui-media-player-rows", DemoHuiMediaPlayerRows); | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -130,21 +125,26 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-picture-elements-card") | ||||
| class DemoPictureElements extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoPicElements extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-hui-picture-elements-card", DemoPictureElements); | ||||
| customElements.define("demo-hui-picture-elements-card", DemoPicElements); | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -85,21 +80,26 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-picture-entity-card") | ||||
| class DemoPictureEntity extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoPicEntity extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-hui-picture-entity-card", DemoPictureEntity); | ||||
| customElements.define("demo-hui-picture-entity-card", DemoPicEntity); | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -126,21 +121,26 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-picture-glance-card") | ||||
| class DemoPictureGlance extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoPicGlance extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-hui-picture-glance-card", DemoPictureGlance); | ||||
| customElements.define("demo-hui-picture-glance-card", DemoPicGlance); | ||||
|   | ||||
| @@ -1,55 +0,0 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| import { createPlantEntities } from "../data/plants"; | ||||
|  | ||||
| const CONFIGS = [ | ||||
|   { | ||||
|     heading: "Basic example", | ||||
|     config: ` | ||||
| - type: plant-status | ||||
|   entity: plant.lemon_tree | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "Problem (too bright) + low battery", | ||||
|     config: ` | ||||
| - type: plant-status | ||||
|   entity: plant.apple_tree | ||||
|     `, | ||||
|   }, | ||||
|   { | ||||
|     heading: "With picture + multiple problems", | ||||
|     config: ` | ||||
| - type: plant-status | ||||
|   entity: plant.sunflowers | ||||
|   name: Sunflowers Name Overwrite | ||||
|     `, | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-plant-card") | ||||
| export class DemoPlantEntity extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(createPlantEntities()); | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-hui-plant-card", DemoPlantEntity); | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
|  | ||||
| @@ -25,19 +20,24 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-shopping-list-card") | ||||
| class DemoShoppingListEntity extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoShoppingListEntity extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|  | ||||
|     hass.mockAPI("shopping_list", () => [ | ||||
|       { name: "list", id: 1, complete: false }, | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { mockHistory } from "../../../demo/src/stubs/history"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| @@ -137,19 +132,24 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-stack-card") | ||||
| class DemoStack extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoStack extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|     mockHistory(hass); | ||||
|   } | ||||
|   | ||||
| @@ -1,11 +1,6 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-cards"; | ||||
| @@ -79,19 +74,24 @@ const CONFIGS = [ | ||||
|   }, | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-hui-thermostat-card") | ||||
| class DemoThermostatEntity extends LitElement { | ||||
|   @query("#demos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html`<demo-cards id="demos" .configs=${CONFIGS}></demo-cards>`; | ||||
| class DemoThermostatEntity extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` <demo-cards id="demos" configs="[[_configs]]"></demo-cards> `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _configs: { | ||||
|         type: Object, | ||||
|         value: CONFIGS, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     const hass = provideHass(this.$.demos); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|     hass.updateTranslations("lovelace", "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,29 +1,12 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   property, | ||||
|   PropertyValues, | ||||
|   query, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { html } from "@polymer/polymer/lib/utils/html-tag"; | ||||
| /* eslint-plugin-disable lit */ | ||||
| import { PolymerElement } from "@polymer/polymer/polymer-element"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import { | ||||
|   SUPPORT_BRIGHTNESS, | ||||
|   SUPPORT_COLOR, | ||||
|   SUPPORT_COLOR_TEMP, | ||||
|   SUPPORT_EFFECT, | ||||
|   SUPPORT_FLASH, | ||||
|   SUPPORT_TRANSITION, | ||||
|   SUPPORT_WHITE_VALUE, | ||||
| } from "../../../src/data/light"; | ||||
| import "../../../src/dialogs/more-info/more-info-content"; | ||||
| import { SUPPORT_BRIGHTNESS } from "../../../src/data/light"; | ||||
| import { getEntity } from "../../../src/fake_data/entity"; | ||||
| import { | ||||
|   MockHomeAssistant, | ||||
|   provideHass, | ||||
| } from "../../../src/fake_data/provide_hass"; | ||||
| import { provideHass } from "../../../src/fake_data/provide_hass"; | ||||
| import "../components/demo-more-infos"; | ||||
| import "../../../src/dialogs/more-info/more-info-content"; | ||||
|  | ||||
| const ENTITIES = [ | ||||
|   getEntity("light", "bed_light", "on", { | ||||
| @@ -31,52 +14,38 @@ const ENTITIES = [ | ||||
|   }), | ||||
|   getEntity("light", "kitchen_light", "on", { | ||||
|     friendly_name: "Brightness Light", | ||||
|     brightness: 200, | ||||
|     brightness: 80, | ||||
|     supported_features: SUPPORT_BRIGHTNESS, | ||||
|   }), | ||||
|   getEntity("light", "color_temperature_light", "on", { | ||||
|     friendly_name: "White Color Temperature Light", | ||||
|     brightness: 128, | ||||
|     color_temp: 75, | ||||
|     min_mireds: 30, | ||||
|     max_mireds: 150, | ||||
|     supported_features: SUPPORT_BRIGHTNESS + SUPPORT_COLOR_TEMP, | ||||
|   }), | ||||
|   getEntity("light", "color_effectslight", "on", { | ||||
|     friendly_name: "Color Effets Light", | ||||
|     brightness: 255, | ||||
|     hs_color: [30, 100], | ||||
|     white_value: 36, | ||||
|     supported_features: | ||||
|       SUPPORT_BRIGHTNESS + | ||||
|       SUPPORT_EFFECT + | ||||
|       SUPPORT_FLASH + | ||||
|       SUPPORT_COLOR + | ||||
|       SUPPORT_TRANSITION + | ||||
|       SUPPORT_WHITE_VALUE, | ||||
|     effect_list: ["random", "colorloop"], | ||||
|   }), | ||||
| ]; | ||||
|  | ||||
| @customElement("demo-more-info-light") | ||||
| class DemoMoreInfoLight extends LitElement { | ||||
|   @property() public hass!: MockHomeAssistant; | ||||
|  | ||||
|   @query("demo-more-infos") private _demoRoot!: HTMLElement; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
| class DemoMoreInfoLight extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <demo-more-infos | ||||
|         .hass=${this.hass} | ||||
|         .entities=${ENTITIES.map((ent) => ent.entityId)} | ||||
|         hass="[[hass]]" | ||||
|         entities="[[_entities]]" | ||||
|       ></demo-more-infos> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProperties: PropertyValues) { | ||||
|     super.firstUpdated(changedProperties); | ||||
|     const hass = provideHass(this._demoRoot); | ||||
|     hass.updateTranslations(null, "en"); | ||||
|   static get properties() { | ||||
|     return { | ||||
|       _entities: { | ||||
|         type: Array, | ||||
|         value: ENTITIES.map((ent) => ent.entityId), | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public ready() { | ||||
|     super.ready(); | ||||
|     this._setupDemo(); | ||||
|   } | ||||
|  | ||||
|   private async _setupDemo() { | ||||
|     const hass = provideHass(this); | ||||
|     await hass.updateTranslations(null, "en"); | ||||
|     hass.addEntities(ENTITIES); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,9 @@ | ||||
| import "@material/mwc-button"; | ||||
| import { customElement, html, LitElement, TemplateResult } from "lit-element"; | ||||
| import { html, LitElement, TemplateResult } from "lit-element"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import { ActionHandlerEvent } from "../../../src/data/lovelace"; | ||||
| import { actionHandler } from "../../../src/panels/lovelace/common/directives/action-handler-directive"; | ||||
|  | ||||
| @customElement("demo-util-long-press") | ||||
| export class DemoUtilLongPress extends LitElement { | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
| @@ -21,7 +20,7 @@ export class DemoUtilLongPress extends LitElement { | ||||
|  | ||||
|             <textarea></textarea> | ||||
|  | ||||
|             <div>Try pressing and scrolling too!</div> | ||||
|             <div>(try pressing and scrolling too!)</div> | ||||
|           </ha-card> | ||||
|         ` | ||||
|       )} | ||||
| @@ -63,3 +62,5 @@ export class DemoUtilLongPress extends LitElement { | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| customElements.define("demo-util-long-press", DemoUtilLongPress); | ||||
|   | ||||
| @@ -14,51 +14,54 @@ import "../../src/styles/polymer-ha-style"; | ||||
| // eslint-disable-next-line import/extensions | ||||
| import { DEMOS } from "../build/import-demos"; | ||||
|  | ||||
| const fixPath = (path) => path.substr(2, path.length - 5); | ||||
|  | ||||
| class HaGallery extends PolymerElement { | ||||
|   static get template() { | ||||
|     return html` | ||||
|       <style include="iron-positioning ha-style"> | ||||
|         :host { | ||||
|           -ms-user-select: initial; | ||||
|           -webkit-user-select: initial; | ||||
|           -moz-user-select: initial; | ||||
|         } | ||||
|         app-header-layout { | ||||
|           min-height: 100vh; | ||||
|         } | ||||
|         ha-icon-button.invisible { | ||||
|           visibility: hidden; | ||||
|         } | ||||
|       :host { | ||||
|         -ms-user-select: initial; | ||||
|         -webkit-user-select: initial; | ||||
|         -moz-user-select: initial; | ||||
|       } | ||||
|       app-header-layout { | ||||
|         min-height: 100vh; | ||||
|       } | ||||
|       ha-icon-button.invisible { | ||||
|         visibility: hidden; | ||||
|       } | ||||
|  | ||||
|         .pickers { | ||||
|           display: flex; | ||||
|           flex-wrap: wrap; | ||||
|           justify-content: center; | ||||
|           align-items: start; | ||||
|         } | ||||
|       .pickers { | ||||
|         display: flex; | ||||
|         flex-wrap: wrap; | ||||
|         justify-content: center; | ||||
|         align-items: start; | ||||
|       } | ||||
|  | ||||
|         .pickers ha-card { | ||||
|           width: 400px; | ||||
|           display: block; | ||||
|           margin: 16px 8px; | ||||
|         } | ||||
|       .pickers ha-card { | ||||
|         width: 400px; | ||||
|         display: block; | ||||
|         margin: 16px 8px; | ||||
|       } | ||||
|  | ||||
|         .pickers ha-card:last-child { | ||||
|           margin-bottom: 16px; | ||||
|         } | ||||
|       .pickers ha-card:last-child { | ||||
|         margin-bottom: 16px; | ||||
|       } | ||||
|  | ||||
|         .intro { | ||||
|           margin: -1em 0; | ||||
|         } | ||||
|       .intro { | ||||
|         margin: -1em 0; | ||||
|       } | ||||
|  | ||||
|         p a { | ||||
|           color: var(--primary-color); | ||||
|         } | ||||
|       p a { | ||||
|         color: var(--primary-color); | ||||
|       } | ||||
|  | ||||
|       a { | ||||
|         color: var(--primary-text-color); | ||||
|         text-decoration: none; | ||||
|       } | ||||
|  | ||||
|         a { | ||||
|           color: var(--primary-text-color); | ||||
|           text-decoration: none; | ||||
|         } | ||||
|       </style> | ||||
|  | ||||
|       <app-header-layout> | ||||
| @@ -67,42 +70,32 @@ class HaGallery extends PolymerElement { | ||||
|             <ha-icon-button | ||||
|               icon="hass:arrow-left" | ||||
|               on-click="_backTapped" | ||||
|               class$="[[_computeHeaderButtonClass(_demo)]]" | ||||
|               class$='[[_computeHeaderButtonClass(_demo)]]' | ||||
|             ></ha-icon-button> | ||||
|             <div main-title> | ||||
|               [[_withDefault(_demo, "Home Assistant Gallery")]] | ||||
|             </div> | ||||
|             <div main-title>[[_withDefault(_demo, "Home Assistant Gallery")]]</div> | ||||
|           </app-toolbar> | ||||
|         </app-header> | ||||
|  | ||||
|         <div class="content"> | ||||
|           <div id="demo"></div> | ||||
|           <template is="dom-if" if="[[!_demo]]"> | ||||
|             <div class="pickers"> | ||||
|               <ha-card header="Lovelace Card Demos"> | ||||
|                 <div class="card-content intro"> | ||||
|         <div class='content'> | ||||
|           <div id='demo'></div> | ||||
|           <template is='dom-if' if='[[!_demo]]'> | ||||
|             <div class='pickers'> | ||||
|               <ha-card header="Lovelace card demos"> | ||||
|                 <div class='card-content intro'> | ||||
|                   <p> | ||||
|                     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 are very customizable, as no household is | ||||
|                     the same. | ||||
|                     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 are very customizable, as no household is the same. | ||||
|                   </p> | ||||
|  | ||||
|                   <p> | ||||
|                     This gallery helps our developers and designers to see all | ||||
|                     the different states that each card can be in. | ||||
|                     This gallery helps our developers and designers to see all the different states that each card can be in. | ||||
|                   </p> | ||||
|  | ||||
|                   <p> | ||||
|                     Check | ||||
|                     <a href="https://www.home-assistant.io/lovelace" | ||||
|                       >the official website</a | ||||
|                     > | ||||
|                     for instructions on how to get started with Lovelace. | ||||
|                     Check <a href='https://www.home-assistant.io/lovelace'>the official website</a> for instructions on how to get started with Lovelace.</a>. | ||||
|                   </p> | ||||
|                 </div> | ||||
|                 <template is="dom-repeat" items="[[_lovelaceDemos]]"> | ||||
|                   <a href="#[[item]]"> | ||||
|                 <template is='dom-repeat' items='[[_lovelaceDemos]]'> | ||||
|                   <a href='#[[item]]'> | ||||
|                     <paper-item> | ||||
|                       <paper-item-body>{{ item }}</paper-item-body> | ||||
|                       <ha-icon icon="hass:chevron-right"></ha-icon> | ||||
| @@ -111,14 +104,14 @@ class HaGallery extends PolymerElement { | ||||
|                 </template> | ||||
|               </ha-card> | ||||
|  | ||||
|               <ha-card header="More Info Demos"> | ||||
|                 <div class="card-content intro"> | ||||
|               <ha-card header="More Info demos"> | ||||
|                 <div class='card-content intro'> | ||||
|                   <p> | ||||
|                     More info screens show up when an entity is clicked. | ||||
|                   </p> | ||||
|                 </div> | ||||
|                 <template is="dom-repeat" items="[[_moreInfoDemos]]"> | ||||
|                   <a href="#[[item]]"> | ||||
|                 <template is='dom-repeat' items='[[_moreInfoDemos]]'> | ||||
|                   <a href='#[[item]]'> | ||||
|                     <paper-item> | ||||
|                       <paper-item-body>{{ item }}</paper-item-body> | ||||
|                       <ha-icon icon="hass:chevron-right"></ha-icon> | ||||
| @@ -127,14 +120,14 @@ class HaGallery extends PolymerElement { | ||||
|                 </template> | ||||
|               </ha-card> | ||||
|  | ||||
|               <ha-card header="Util Demos"> | ||||
|                 <div class="card-content intro"> | ||||
|               <ha-card header="Util demos"> | ||||
|                 <div class='card-content intro'> | ||||
|                   <p> | ||||
|                     Test pages for our utility functions. | ||||
|                   </p> | ||||
|                 </div> | ||||
|                 <template is="dom-repeat" items="[[_utilDemos]]"> | ||||
|                   <a href="#[[item]]"> | ||||
|                 <template is='dom-repeat' items='[[_utilDemos]]'> | ||||
|                   <a href='#[[item]]'> | ||||
|                     <paper-item> | ||||
|                       <paper-item-body>{{ item }}</paper-item-body> | ||||
|                       <ha-icon icon="hass:chevron-right"></ha-icon> | ||||
| @@ -146,10 +139,7 @@ class HaGallery extends PolymerElement { | ||||
|           </template> | ||||
|         </div> | ||||
|       </app-header-layout> | ||||
|       <notification-manager | ||||
|         hass="[[_fakeHass]]" | ||||
|         id="notifications" | ||||
|       ></notification-manager> | ||||
|       <notification-manager hass=[[_fakeHass]] id='notifications'></notification-manager> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -11,18 +11,17 @@ import { | ||||
|   PropertyValues, | ||||
| } from "lit-element"; | ||||
| import { html, TemplateResult } from "lit-html"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { atLeastVersion } from "../../../src/common/config/version"; | ||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||
| import "../../../src/common/search/search-input"; | ||||
| import "../../../src/components/ha-button-menu"; | ||||
| import "../../../src/components/ha-svg-icon"; | ||||
| import { | ||||
|   fetchHassioAddonsInfo, | ||||
|   HassioAddonInfo, | ||||
|   HassioAddonRepository, | ||||
|   reloadHassioAddons, | ||||
| } from "../../../src/data/hassio/addon"; | ||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||
| import { extractApiErrorMessage } from "../../../src/data/hassio/common"; | ||||
| import "../../../src/layouts/hass-loading-screen"; | ||||
| import "../../../src/layouts/hass-tabs-subpage"; | ||||
| import { HomeAssistant, Route } from "../../../src/types"; | ||||
| @@ -50,27 +49,46 @@ const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => { | ||||
| class HassioAddonStore extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|  | ||||
|   @property({ type: Boolean }) public narrow!: boolean; | ||||
|  | ||||
|   @property({ attribute: false }) public route!: Route; | ||||
|  | ||||
|   @property({ attribute: false }) private _addons?: HassioAddonInfo[]; | ||||
|  | ||||
|   @property({ attribute: false }) private _repos?: HassioAddonRepository[]; | ||||
|  | ||||
|   @internalProperty() private _filter?: string; | ||||
|  | ||||
|   public async refreshData() { | ||||
|     this._repos = undefined; | ||||
|     this._addons = undefined; | ||||
|     this._filter = undefined; | ||||
|     await reloadHassioAddons(this.hass); | ||||
|     await this._loadData(); | ||||
|   } | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     let repos: TemplateResult[] = []; | ||||
|     const repos: TemplateResult[] = []; | ||||
|  | ||||
|     if (this.supervisor.addon.repositories) { | ||||
|       repos = this.addonRepositories( | ||||
|         this.supervisor.addon.repositories, | ||||
|         this.supervisor.addon.addons | ||||
|       ); | ||||
|     if (this._repos) { | ||||
|       for (const repo of this._repos) { | ||||
|         const addons = this._addons!.filter( | ||||
|           (addon) => addon.repository === repo.slug | ||||
|         ); | ||||
|  | ||||
|         if (addons.length === 0) { | ||||
|           continue; | ||||
|         } | ||||
|  | ||||
|         repos.push(html` | ||||
|           <hassio-addon-repository | ||||
|             .hass=${this.hass} | ||||
|             .repo=${repo} | ||||
|             .addons=${addons} | ||||
|             .filter=${this._filter!} | ||||
|           ></hassio-addon-repository> | ||||
|         `); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     return html` | ||||
| @@ -139,27 +157,6 @@ class HassioAddonStore extends LitElement { | ||||
|     this._loadData(); | ||||
|   } | ||||
|  | ||||
|   private addonRepositories = memoizeOne( | ||||
|     (repositories: HassioAddonRepository[], addons: HassioAddonInfo[]) => { | ||||
|       return repositories.sort(sortRepos).map((repo) => { | ||||
|         const filteredAddons = addons.filter( | ||||
|           (addon) => addon.repository === repo.slug | ||||
|         ); | ||||
|  | ||||
|         return filteredAddons.length !== 0 | ||||
|           ? html` | ||||
|               <hassio-addon-repository | ||||
|                 .hass=${this.hass} | ||||
|                 .repo=${repo} | ||||
|                 .addons=${filteredAddons} | ||||
|                 .filter=${this._filter!} | ||||
|               ></hassio-addon-repository> | ||||
|             ` | ||||
|           : html``; | ||||
|       }); | ||||
|     } | ||||
|   ); | ||||
|  | ||||
|   private _handleAction(ev: CustomEvent<ActionDetail>) { | ||||
|     switch (ev.detail.index) { | ||||
|       case 0: | ||||
| @@ -182,7 +179,7 @@ class HassioAddonStore extends LitElement { | ||||
|  | ||||
|   private async _manageRepositories() { | ||||
|     showRepositoriesDialog(this, { | ||||
|       repos: this.supervisor.addon.repositories, | ||||
|       repos: this._repos!, | ||||
|       loadData: () => this._loadData(), | ||||
|     }); | ||||
|   } | ||||
| @@ -192,8 +189,14 @@ class HassioAddonStore extends LitElement { | ||||
|   } | ||||
|  | ||||
|   private async _loadData() { | ||||
|     fireEvent(this, "supervisor-store-refresh", { store: "addon" }); | ||||
|     fireEvent(this, "supervisor-store-refresh", { store: "supervisor" }); | ||||
|     try { | ||||
|       const addonsInfo = await fetchHassioAddonsInfo(this.hass); | ||||
|       this._repos = addonsInfo.repositories; | ||||
|       this._repos.sort(sortRepos); | ||||
|       this._addons = addonsInfo.addons; | ||||
|     } catch (err) { | ||||
|       alert(extractApiErrorMessage(err)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private async _filterChanged(e) { | ||||
|   | ||||
| @@ -7,14 +7,13 @@ import { | ||||
|   CSSResult, | ||||
|   customElement, | ||||
|   html, | ||||
|   internalProperty, | ||||
|   LitElement, | ||||
|   property, | ||||
|   internalProperty, | ||||
|   PropertyValues, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import "web-animations-js/web-animations-next-lite.min"; | ||||
| import "../../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import { | ||||
|   HassioAddonDetails, | ||||
| @@ -29,6 +28,7 @@ import { haStyle } from "../../../../src/resources/styles"; | ||||
| import { HomeAssistant } from "../../../../src/types"; | ||||
| import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; | ||||
| import { hassioStyle } from "../../resources/hassio-style"; | ||||
| import "../../../../src/components/buttons/ha-progress-button"; | ||||
|  | ||||
| @customElement("hassio-addon-audio") | ||||
| class HassioAddonAudio extends LitElement { | ||||
|   | ||||
| @@ -7,11 +7,11 @@ import { | ||||
|   property, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import "../../../../src/components/ha-circular-progress"; | ||||
| import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; | ||||
| import { haStyle } from "../../../../src/resources/styles"; | ||||
| import { HomeAssistant } from "../../../../src/types"; | ||||
| import { hassioStyle } from "../../resources/hassio-style"; | ||||
| import "../../../../src/components/ha-circular-progress"; | ||||
| import "./hassio-addon-audio"; | ||||
| import "./hassio-addon-config"; | ||||
| import "./hassio-addon-network"; | ||||
| @@ -26,41 +26,28 @@ class HassioAddonConfigDashboard extends LitElement { | ||||
|     if (!this.addon) { | ||||
|       return html`<ha-circular-progress active></ha-circular-progress>`; | ||||
|     } | ||||
|     const hasOptions = | ||||
|       this.addon.options && Object.keys(this.addon.options).length; | ||||
|     const hasSchema = | ||||
|       hasOptions && this.addon.schema && Object.keys(this.addon.schema).length; | ||||
|  | ||||
|     return html` | ||||
|       <div class="content"> | ||||
|         ${hasOptions || hasSchema || this.addon.network || this.addon.audio | ||||
|         <hassio-addon-config | ||||
|           .hass=${this.hass} | ||||
|           .addon=${this.addon} | ||||
|         ></hassio-addon-config> | ||||
|         ${this.addon.network | ||||
|           ? html` | ||||
|               ${hasOptions || hasSchema | ||||
|                 ? html` | ||||
|                     <hassio-addon-config | ||||
|                       .hass=${this.hass} | ||||
|                       .addon=${this.addon} | ||||
|                     ></hassio-addon-config> | ||||
|                   ` | ||||
|                 : ""} | ||||
|               ${this.addon.network | ||||
|                 ? html` | ||||
|                     <hassio-addon-network | ||||
|                       .hass=${this.hass} | ||||
|                       .addon=${this.addon} | ||||
|                     ></hassio-addon-network> | ||||
|                   ` | ||||
|                 : ""} | ||||
|               ${this.addon.audio | ||||
|                 ? html` | ||||
|                     <hassio-addon-audio | ||||
|                       .hass=${this.hass} | ||||
|                       .addon=${this.addon} | ||||
|                     ></hassio-addon-audio> | ||||
|                   ` | ||||
|                 : ""} | ||||
|               <hassio-addon-network | ||||
|                 .hass=${this.hass} | ||||
|                 .addon=${this.addon} | ||||
|               ></hassio-addon-network> | ||||
|             ` | ||||
|           : "This add-on does not expose configuration for you to mess with.... 👋"} | ||||
|           : ""} | ||||
|         ${this.addon.audio | ||||
|           ? html` | ||||
|               <hassio-addon-audio | ||||
|                 .hass=${this.hass} | ||||
|                 .addon=${this.addon} | ||||
|               ></hassio-addon-audio> | ||||
|             ` | ||||
|           : ""} | ||||
|       </div> | ||||
|     `; | ||||
|   } | ||||
|   | ||||
| @@ -1,7 +1,4 @@ | ||||
| import "@material/mwc-button"; | ||||
| import { ActionDetail } from "@material/mwc-list"; | ||||
| import "@material/mwc-list/mwc-list-item"; | ||||
| import { mdiDotsVertical } from "@mdi/js"; | ||||
| import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea"; | ||||
| import { | ||||
|   css, | ||||
| @@ -17,9 +14,7 @@ import { | ||||
| } from "lit-element"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import "../../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../../src/components/ha-button-menu"; | ||||
| import "../../../../src/components/ha-card"; | ||||
| import "../../../../src/components/ha-form/ha-form"; | ||||
| import "../../../../src/components/ha-yaml-editor"; | ||||
| import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor"; | ||||
| import { | ||||
| @@ -34,67 +29,35 @@ import type { HomeAssistant } from "../../../../src/types"; | ||||
| import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; | ||||
| import { hassioStyle } from "../../resources/hassio-style"; | ||||
|  | ||||
| const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"]; | ||||
|  | ||||
| @customElement("hassio-addon-config") | ||||
| class HassioAddonConfig extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public addon!: HassioAddonDetails; | ||||
|  | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|   @internalProperty() private _error?: string; | ||||
|  | ||||
|   @property({ type: Boolean }) private _configHasChanged = false; | ||||
|  | ||||
|   @property({ type: Boolean }) private _valid = true; | ||||
|  | ||||
|   @internalProperty() private _canShowSchema = false; | ||||
|  | ||||
|   @internalProperty() private _error?: string; | ||||
|  | ||||
|   @internalProperty() private _options?: Record<string, unknown>; | ||||
|  | ||||
|   @internalProperty() private _yamlMode = false; | ||||
|  | ||||
|   @query("ha-yaml-editor") private _editor?: HaYamlEditor; | ||||
|   @query("ha-yaml-editor", true) private _editor!: HaYamlEditor; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       <h1>${this.addon.name}</h1> | ||||
|       <ha-card> | ||||
|         <div class="header"> | ||||
|           <h2>Configuration</h2> | ||||
|           <div class="card-menu"> | ||||
|             <ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}> | ||||
|               <mwc-icon-button slot="trigger"> | ||||
|                 <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon> | ||||
|               </mwc-icon-button> | ||||
|               <mwc-list-item .disabled=${!this._canShowSchema}> | ||||
|                 ${this._yamlMode ? "Edit in UI" : "Edit in YAML"} | ||||
|               </mwc-list-item> | ||||
|               <mwc-list-item class="warning"> | ||||
|                 Reset to defaults | ||||
|               </mwc-list-item> | ||||
|             </ha-button-menu> | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
|       <ha-card header="Configuration"> | ||||
|         <div class="card-content"> | ||||
|           ${!this._yamlMode && this._canShowSchema && this.addon.schema | ||||
|             ? html`<ha-form | ||||
|                 .data=${this._options!} | ||||
|                 @value-changed=${this._configChanged} | ||||
|                 .schema=${this.addon.schema} | ||||
|               ></ha-form>` | ||||
|             : html` <ha-yaml-editor | ||||
|                 @value-changed=${this._configChanged} | ||||
|               ></ha-yaml-editor>`} | ||||
|           <ha-yaml-editor | ||||
|             @value-changed=${this._configChanged} | ||||
|           ></ha-yaml-editor> | ||||
|           ${this._error ? html` <div class="errors">${this._error}</div> ` : ""} | ||||
|           ${!this._yamlMode || | ||||
|           (this._canShowSchema && this.addon.schema) || | ||||
|           this._valid | ||||
|             ? "" | ||||
|             : html` <div class="errors">Invalid YAML</div> `} | ||||
|           ${this._valid ? "" : html` <div class="errors">Invalid YAML</div> `} | ||||
|         </div> | ||||
|         <div class="card-actions right"> | ||||
|         <div class="card-actions"> | ||||
|           <ha-progress-button class="warning" @click=${this._resetTapped}> | ||||
|             Reset to defaults | ||||
|           </ha-progress-button> | ||||
|           <ha-progress-button | ||||
|             @click=${this._saveTapped} | ||||
|             .disabled=${!this._configHasChanged || !this._valid} | ||||
| @@ -106,55 +69,16 @@ class HassioAddonConfig extends LitElement { | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected firstUpdated(changedProps) { | ||||
|     super.firstUpdated(changedProps); | ||||
|     this._canShowSchema = | ||||
|       Object.keys(this.addon.options).length !== 0 && | ||||
|       !this.addon.schema!.find( | ||||
|         // @ts-ignore | ||||
|         (entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple | ||||
|       ); | ||||
|     this._yamlMode = !this._canShowSchema; | ||||
|   } | ||||
|  | ||||
|   protected updated(changedProperties: PropertyValues): void { | ||||
|     if (changedProperties.has("addon")) { | ||||
|       this._options = { ...this.addon.options }; | ||||
|     } | ||||
|     super.updated(changedProperties); | ||||
|     if ( | ||||
|       changedProperties.has("_yamlMode") || | ||||
|       changedProperties.has("_options") | ||||
|     ) { | ||||
|       if (this._yamlMode) { | ||||
|         const editor = this._editor; | ||||
|         if (editor) { | ||||
|           editor.setValue(this._options!); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private _handleAction(ev: CustomEvent<ActionDetail>) { | ||||
|     switch (ev.detail.index) { | ||||
|       case 0: | ||||
|         this._yamlMode = !this._yamlMode; | ||||
|         break; | ||||
|       case 1: | ||||
|         this._resetTapped(ev); | ||||
|         break; | ||||
|     if (changedProperties.has("addon")) { | ||||
|       this._editor.setValue(this.addon.options); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private _configChanged(ev): void { | ||||
|     if (this.addon.schema && this._canShowSchema && !this._yamlMode) { | ||||
|       this._valid = true; | ||||
|       this._configHasChanged = true; | ||||
|       this._options! = ev.detail.value; | ||||
|     } else { | ||||
|       this._configHasChanged = true; | ||||
|       this._valid = ev.detail.isValid; | ||||
|     } | ||||
|     this._configHasChanged = true; | ||||
|     this._valid = ev.detail.isValid; | ||||
|   } | ||||
|  | ||||
|   private async _resetTapped(ev: CustomEvent): Promise<void> { | ||||
| @@ -198,13 +122,18 @@ class HassioAddonConfig extends LitElement { | ||||
|     const button = ev.currentTarget as any; | ||||
|     button.progress = true; | ||||
|  | ||||
|     let data: HassioAddonSetOptionParams; | ||||
|     this._error = undefined; | ||||
|  | ||||
|     try { | ||||
|       await setHassioAddonOption(this.hass, this.addon.slug, { | ||||
|         options: this._yamlMode ? this._editor?.value : this._options, | ||||
|       }); | ||||
|  | ||||
|       data = { | ||||
|         options: this._editor.value, | ||||
|       }; | ||||
|     } catch (err) { | ||||
|       this._error = err; | ||||
|       return; | ||||
|     } | ||||
|     try { | ||||
|       await setHassioAddonOption(this.hass, this.addon.slug, data); | ||||
|       this._configHasChanged = false; | ||||
|       const eventdata = { | ||||
|         success: true, | ||||
| @@ -249,32 +178,6 @@ class HassioAddonConfig extends LitElement { | ||||
|         .syntaxerror { | ||||
|           color: var(--error-color); | ||||
|         } | ||||
|         .card-menu { | ||||
|           float: right; | ||||
|           z-index: 3; | ||||
|           --mdc-theme-text-primary-on-background: var(--primary-text-color); | ||||
|         } | ||||
|         mwc-list-item[disabled] { | ||||
|           --mdc-theme-text-primary-on-background: var(--disabled-text-color); | ||||
|         } | ||||
|         .header { | ||||
|           display: flex; | ||||
|           justify-content: space-between; | ||||
|         } | ||||
|         .header h2 { | ||||
|           color: var(--ha-card-header-color, --primary-text-color); | ||||
|           font-family: var(--ha-card-header-font-family, inherit); | ||||
|           font-size: var(--ha-card-header-font-size, 24px); | ||||
|           letter-spacing: -0.012em; | ||||
|           line-height: 48px; | ||||
|           padding: 12px 16px 16px; | ||||
|           display: block; | ||||
|           margin-block: 0px; | ||||
|           font-weight: normal; | ||||
|         } | ||||
|         .card-actions.right { | ||||
|           justify-content: flex-end; | ||||
|         } | ||||
|       `, | ||||
|     ]; | ||||
|   } | ||||
|   | ||||
| @@ -9,25 +9,17 @@ import { | ||||
|   CSSResult, | ||||
|   customElement, | ||||
|   html, | ||||
|   internalProperty, | ||||
|   LitElement, | ||||
|   property, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||
| import { navigate } from "../../../src/common/navigate"; | ||||
| import { extractSearchParam } from "../../../src/common/url/search-params"; | ||||
| import "../../../src/components/ha-circular-progress"; | ||||
| import { | ||||
|   fetchHassioAddonInfo, | ||||
|   HassioAddonDetails, | ||||
| } from "../../../src/data/hassio/addon"; | ||||
| import { extractApiErrorMessage } from "../../../src/data/hassio/common"; | ||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||
| import "../../../src/layouts/hass-error-screen"; | ||||
| import "../../../src/layouts/hass-loading-screen"; | ||||
| import "../../../src/layouts/hass-tabs-subpage"; | ||||
| import "../../../src/components/ha-circular-progress"; | ||||
| import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage"; | ||||
| import { haStyle } from "../../../src/resources/styles"; | ||||
| import { HomeAssistant, Route } from "../../../src/types"; | ||||
| @@ -43,16 +35,12 @@ import "./log/hassio-addon-logs"; | ||||
| class HassioAddonDashboard extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|  | ||||
|   @property({ attribute: false }) public route!: Route; | ||||
|  | ||||
|   @property({ attribute: false }) public addon?: HassioAddonDetails; | ||||
|  | ||||
|   @property({ type: Boolean }) public narrow!: boolean; | ||||
|  | ||||
|   @internalProperty() _error?: string; | ||||
|  | ||||
|   private _computeTail = memoizeOne((route: Route) => { | ||||
|     const dividerPos = route.path.indexOf("/", 1); | ||||
|     return dividerPos === -1 | ||||
| @@ -67,14 +55,8 @@ class HassioAddonDashboard extends LitElement { | ||||
|   }); | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     if (this._error) { | ||||
|       return html`<hass-error-screen | ||||
|         .error=${this._error} | ||||
|       ></hass-error-screen>`; | ||||
|     } | ||||
|  | ||||
|     if (!this.addon) { | ||||
|       return html`<hass-loading-screen></hass-loading-screen>`; | ||||
|       return html`<ha-circular-progress active></ha-circular-progress>`; | ||||
|     } | ||||
|  | ||||
|     const addonTabs: PageNavigation[] = [ | ||||
| @@ -124,7 +106,6 @@ class HassioAddonDashboard extends LitElement { | ||||
|           .route=${route} | ||||
|           .narrow=${this.narrow} | ||||
|           .hass=${this.hass} | ||||
|           .supervisor=${this.supervisor} | ||||
|           .addon=${this.addon} | ||||
|         ></hassio-addon-router> | ||||
|       </hass-tabs-subpage> | ||||
| @@ -171,51 +152,30 @@ class HassioAddonDashboard extends LitElement { | ||||
|   } | ||||
|  | ||||
|   protected async firstUpdated(): Promise<void> { | ||||
|     if (this.route.path === "") { | ||||
|       const addon = extractSearchParam("addon"); | ||||
|       if (addon) { | ||||
|         navigate(this, `/hassio/addon/${addon}`, true); | ||||
|       } | ||||
|     } | ||||
|     await this._routeDataChanged(this.route); | ||||
|     this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev)); | ||||
|   } | ||||
|  | ||||
|   private async _apiCalled(ev): Promise<void> { | ||||
|     const pathSplit: string[] = ev.detail.path?.split("/"); | ||||
|     const path: string = ev.detail.path; | ||||
|  | ||||
|     if (!pathSplit || pathSplit.length === 0) { | ||||
|     if (!path) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const path: string = pathSplit[pathSplit.length - 1]; | ||||
|  | ||||
|     if (["uninstall", "install", "update", "start", "stop"].includes(path)) { | ||||
|       fireEvent(this, "supervisor-store-refresh", { store: "supervisor" }); | ||||
|     } | ||||
|  | ||||
|     if (path === "uninstall") { | ||||
|       window.history.back(); | ||||
|       history.back(); | ||||
|     } else { | ||||
|       await this._routeDataChanged(); | ||||
|       await this._routeDataChanged(this.route); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   protected updated(changedProperties) { | ||||
|     if (changedProperties.has("route") && !this.addon) { | ||||
|       this._routeDataChanged(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private async _routeDataChanged(): Promise<void> { | ||||
|     const addon = this.route.path.split("/")[1]; | ||||
|     if (!addon) { | ||||
|       return; | ||||
|     } | ||||
|   private async _routeDataChanged(routeData: Route): Promise<void> { | ||||
|     const addon = routeData.path.split("/")[1]; | ||||
|     try { | ||||
|       const addoninfo = await fetchHassioAddonInfo(this.hass, addon); | ||||
|       this.addon = addoninfo; | ||||
|     } catch (err) { | ||||
|       this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`; | ||||
|     } catch { | ||||
|       this.addon = undefined; | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { customElement, property } from "lit-element"; | ||||
| import { HassioAddonDetails } from "../../../src/data/hassio/addon"; | ||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||
| import { | ||||
|   HassRouterPage, | ||||
|   RouterOptions, | ||||
| @@ -18,8 +17,6 @@ class HassioAddonRouter extends HassRouterPage { | ||||
|  | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|  | ||||
|   @property({ attribute: false }) public addon!: HassioAddonDetails; | ||||
|  | ||||
|   protected routerOptions: RouterOptions = { | ||||
| @@ -44,7 +41,6 @@ class HassioAddonRouter extends HassRouterPage { | ||||
|   protected updatePageEl(el) { | ||||
|     el.route = this.routeTail; | ||||
|     el.hass = this.hass; | ||||
|     el.supervisor = this.supervisor; | ||||
|     el.addon = this.addon; | ||||
|     el.narrow = this.narrow; | ||||
|   } | ||||
|   | ||||
| @@ -7,9 +7,8 @@ import { | ||||
|   property, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import "../../../../src/components/ha-circular-progress"; | ||||
| import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; | ||||
| import { Supervisor } from "../../../../src/data/supervisor/supervisor"; | ||||
| import "../../../../src/components/ha-circular-progress"; | ||||
| import { haStyle } from "../../../../src/resources/styles"; | ||||
| import { HomeAssistant } from "../../../../src/types"; | ||||
| import { hassioStyle } from "../../resources/hassio-style"; | ||||
| @@ -21,8 +20,6 @@ class HassioAddonInfoDashboard extends LitElement { | ||||
|  | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|  | ||||
|   @property({ attribute: false }) public addon?: HassioAddonDetails; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
| @@ -35,7 +32,6 @@ class HassioAddonInfoDashboard extends LitElement { | ||||
|         <hassio-addon-info | ||||
|           .narrow=${this.narrow} | ||||
|           .hass=${this.hass} | ||||
|           .supervisor=${this.supervisor} | ||||
|           .addon=${this.addon} | ||||
|         ></hassio-addon-info> | ||||
|       </div> | ||||
|   | ||||
| @@ -43,33 +43,22 @@ import { | ||||
|   HassioAddonSetOptionParams, | ||||
|   HassioAddonSetSecurityParams, | ||||
|   installHassioAddon, | ||||
|   restartHassioAddon, | ||||
|   setHassioAddonOption, | ||||
|   setHassioAddonSecurity, | ||||
|   startHassioAddon, | ||||
|   stopHassioAddon, | ||||
|   uninstallHassioAddon, | ||||
|   updateHassioAddon, | ||||
|   validateHassioAddonOption, | ||||
| } from "../../../../src/data/hassio/addon"; | ||||
| import { | ||||
|   extractApiErrorMessage, | ||||
|   fetchHassioStats, | ||||
|   HassioStats, | ||||
| } from "../../../../src/data/hassio/common"; | ||||
| import { Supervisor } from "../../../../src/data/supervisor/supervisor"; | ||||
| import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | ||||
| import { | ||||
|   showAlertDialog, | ||||
|   showConfirmationDialog, | ||||
| } from "../../../../src/dialogs/generic/show-dialog-box"; | ||||
| import { haStyle } from "../../../../src/resources/styles"; | ||||
| import { HomeAssistant } from "../../../../src/types"; | ||||
| import { bytesToString } from "../../../../src/util/bytes-to-string"; | ||||
| import "../../components/hassio-card-content"; | ||||
| import "../../components/supervisor-metric"; | ||||
| import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown"; | ||||
| import { hassioStyle } from "../../resources/hassio-style"; | ||||
| import { addonArchIsSupported } from "../../util/addon"; | ||||
|  | ||||
| const STAGE_ICON = { | ||||
|   stable: mdiCheckCircle, | ||||
| @@ -80,7 +69,7 @@ const STAGE_ICON = { | ||||
| const PERMIS_DESC = { | ||||
|   stage: { | ||||
|     title: "Add-on Stage", | ||||
|     description: `Add-ons can have one of three stages:\n\n<ha-svg-icon path="${STAGE_ICON.stable}"></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon path="${STAGE_ICON.experimental}"></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon path="${STAGE_ICON.deprecated}"></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`, | ||||
|     description: `Add-ons can have one of three stages:\n\n<ha-svg-icon .path='${STAGE_ICON.stable}'></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon .path='${STAGE_ICON.experimental}'></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon .path='${STAGE_ICON.deprecated}'></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`, | ||||
|   }, | ||||
|   rating: { | ||||
|     title: "Add-on Security Rating", | ||||
| @@ -142,26 +131,9 @@ class HassioAddonInfo extends LitElement { | ||||
|  | ||||
|   @property({ attribute: false }) public addon!: HassioAddonDetails; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|  | ||||
|   @internalProperty() private _metrics?: HassioStats; | ||||
|  | ||||
|   @internalProperty() private _error?: string; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     const metrics = [ | ||||
|       { | ||||
|         description: "Add-on CPU Usage", | ||||
|         value: this._metrics?.cpu_percent, | ||||
|       }, | ||||
|       { | ||||
|         description: "Add-on RAM Usage", | ||||
|         value: this._metrics?.memory_percent, | ||||
|         tooltip: `${bytesToString(this._metrics?.memory_usage)}/${bytesToString( | ||||
|           this._metrics?.memory_limit | ||||
|         )}`, | ||||
|       }, | ||||
|     ]; | ||||
|     return html` | ||||
|       ${this.addon.update_available | ||||
|         ? html` | ||||
| @@ -177,31 +149,21 @@ class HassioAddonInfo extends LitElement { | ||||
|                   iconClass="update" | ||||
|                 ></hassio-card-content> | ||||
|                 ${!this.addon.available | ||||
|                   ? !addonArchIsSupported( | ||||
|                       this.supervisor.info.supported_arch, | ||||
|                       this.addon.arch | ||||
|                     ) | ||||
|                     ? html` | ||||
|                         <p> | ||||
|                           This add-on is not compatible with the processor of | ||||
|                           your device or the operating system you have installed | ||||
|                           on your device. | ||||
|                         </p> | ||||
|                       ` | ||||
|                     : html` | ||||
|                         <p> | ||||
|                           You are running Home Assistant | ||||
|                           ${this.supervisor.core.version}, to update to this | ||||
|                           version of the add-on you need at least version | ||||
|                           ${this.addon.homeassistant} of Home Assistant | ||||
|                         </p> | ||||
|                       ` | ||||
|                   ? html` | ||||
|                       <p> | ||||
|                         This update is no longer compatible with your system. | ||||
|                       </p> | ||||
|                     ` | ||||
|                   : ""} | ||||
|               </div> | ||||
|               <div class="card-actions"> | ||||
|                 <ha-progress-button @click=${this._updateClicked}> | ||||
|                 <ha-call-api-button | ||||
|                   .hass=${this.hass} | ||||
|                   .disabled=${!this.addon.available} | ||||
|                   path="hassio/addons/${this.addon.slug}/update" | ||||
|                 > | ||||
|                   Update | ||||
|                 </ha-progress-button> | ||||
|                 </ha-call-api-button> | ||||
|                 ${this.addon.changelog | ||||
|                   ? html` | ||||
|                       <mwc-button @click=${this._openChangelog}> | ||||
| @@ -275,378 +237,331 @@ class HassioAddonInfo extends LitElement { | ||||
|             > | ||||
|             for details. | ||||
|           </div> | ||||
|           <div class="addon-container"> | ||||
|             <div> | ||||
|               ${this.addon.logo | ||||
|                 ? html` | ||||
|                     <img | ||||
|                       class="logo" | ||||
|                       src="/api/hassio/addons/${this.addon.slug}/logo" | ||||
|                     /> | ||||
|                   ` | ||||
|                 : ""} | ||||
|               <div class="security"> | ||||
|                 ${this.addon.stage !== "stable" | ||||
|                   ? html` <ha-label-badge | ||||
|                       class=${classMap({ | ||||
|                         yellow: this.addon.stage === "experimental", | ||||
|                         red: this.addon.stage === "deprecated", | ||||
|                       })} | ||||
|                       @click=${this._showMoreInfo} | ||||
|                       id="stage" | ||||
|                       label="stage" | ||||
|                       description="" | ||||
|                     > | ||||
|                       <ha-svg-icon | ||||
|                         .path=${STAGE_ICON[this.addon.stage]} | ||||
|                       ></ha-svg-icon> | ||||
|                     </ha-label-badge>` | ||||
|                   : ""} | ||||
|  | ||||
|                 <ha-label-badge | ||||
|           ${this.addon.logo | ||||
|             ? html` | ||||
|                 <img | ||||
|                   class="logo" | ||||
|                   src="/api/hassio/addons/${this.addon.slug}/logo" | ||||
|                 /> | ||||
|               ` | ||||
|             : ""} | ||||
|           <div class="security"> | ||||
|             ${this.addon.stage !== "stable" | ||||
|               ? html` <ha-label-badge | ||||
|                   class=${classMap({ | ||||
|                     green: [5, 6].includes(Number(this.addon.rating)), | ||||
|                     yellow: [3, 4].includes(Number(this.addon.rating)), | ||||
|                     red: [1, 2].includes(Number(this.addon.rating)), | ||||
|                     yellow: this.addon.stage === "experimental", | ||||
|                     red: this.addon.stage === "deprecated", | ||||
|                   })} | ||||
|                   @click=${this._showMoreInfo} | ||||
|                   id="rating" | ||||
|                   .value=${this.addon.rating} | ||||
|                   label="rating" | ||||
|                   id="stage" | ||||
|                   label="stage" | ||||
|                   description="" | ||||
|                 ></ha-label-badge> | ||||
|                 ${this.addon.host_network | ||||
|                   ? html` | ||||
|                       <ha-label-badge | ||||
|                         @click=${this._showMoreInfo} | ||||
|                         id="host_network" | ||||
|                         label="host" | ||||
|                         description="" | ||||
|                       > | ||||
|                         <ha-svg-icon .path=${mdiNetwork}></ha-svg-icon> | ||||
|                       </ha-label-badge> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this.addon.full_access | ||||
|                   ? html` | ||||
|                       <ha-label-badge | ||||
|                         @click=${this._showMoreInfo} | ||||
|                         id="full_access" | ||||
|                         label="hardware" | ||||
|                         description="" | ||||
|                       > | ||||
|                         <ha-svg-icon .path=${mdiChip}></ha-svg-icon> | ||||
|                       </ha-label-badge> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this.addon.homeassistant_api | ||||
|                   ? html` | ||||
|                       <ha-label-badge | ||||
|                         @click=${this._showMoreInfo} | ||||
|                         id="homeassistant_api" | ||||
|                         label="hass" | ||||
|                         description="" | ||||
|                       > | ||||
|                         <ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon> | ||||
|                       </ha-label-badge> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this._computeHassioApi | ||||
|                   ? html` | ||||
|                       <ha-label-badge | ||||
|                         @click=${this._showMoreInfo} | ||||
|                         id="hassio_api" | ||||
|                         label="hassio" | ||||
|                         .description=${this.addon.hassio_role} | ||||
|                       > | ||||
|                         <ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon> | ||||
|                       </ha-label-badge> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this.addon.docker_api | ||||
|                   ? html` | ||||
|                       <ha-label-badge | ||||
|                         @click=${this._showMoreInfo} | ||||
|                         id="docker_api" | ||||
|                         label="docker" | ||||
|                         description="" | ||||
|                       > | ||||
|                         <ha-svg-icon .path=${mdiDocker}></ha-svg-icon> | ||||
|                       </ha-label-badge> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this.addon.host_pid | ||||
|                   ? html` | ||||
|                       <ha-label-badge | ||||
|                         @click=${this._showMoreInfo} | ||||
|                         id="host_pid" | ||||
|                         label="host pid" | ||||
|                         description="" | ||||
|                       > | ||||
|                         <ha-svg-icon .path=${mdiPound}></ha-svg-icon> | ||||
|                       </ha-label-badge> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this.addon.apparmor | ||||
|                   ? html` | ||||
|                       <ha-label-badge | ||||
|                         @click=${this._showMoreInfo} | ||||
|                         class=${this._computeApparmorClassName} | ||||
|                         id="apparmor" | ||||
|                         label="apparmor" | ||||
|                         description="" | ||||
|                       > | ||||
|                         <ha-svg-icon .path=${mdiShield}></ha-svg-icon> | ||||
|                       </ha-label-badge> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this.addon.auth_api | ||||
|                   ? html` | ||||
|                       <ha-label-badge | ||||
|                         @click=${this._showMoreInfo} | ||||
|                         id="auth_api" | ||||
|                         label="auth" | ||||
|                         description="" | ||||
|                       > | ||||
|                         <ha-svg-icon .path=${mdiKey}></ha-svg-icon> | ||||
|                       </ha-label-badge> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this.addon.ingress | ||||
|                   ? html` | ||||
|                       <ha-label-badge | ||||
|                         @click=${this._showMoreInfo} | ||||
|                         id="ingress" | ||||
|                         label="ingress" | ||||
|                         description="" | ||||
|                       > | ||||
|                         <ha-svg-icon | ||||
|                           .path=${mdiCursorDefaultClickOutline} | ||||
|                         ></ha-svg-icon> | ||||
|                       </ha-label-badge> | ||||
|                     ` | ||||
|                   : ""} | ||||
|               </div> | ||||
|                 > | ||||
|                   <ha-svg-icon | ||||
|                     .path=${STAGE_ICON[this.addon.stage]} | ||||
|                   ></ha-svg-icon> | ||||
|                 </ha-label-badge>` | ||||
|               : ""} | ||||
|  | ||||
|               ${this.addon.version | ||||
|                 ? html` | ||||
|                     <div | ||||
|                       class="${classMap({ | ||||
|                         "addon-options": true, | ||||
|                         started: this.addon.state === "started", | ||||
|                       })}" | ||||
|                     > | ||||
|                       <ha-settings-row ?three-line=${this.narrow}> | ||||
|                         <span slot="heading"> | ||||
|                           Start on boot | ||||
|                         </span> | ||||
|                         <span slot="description"> | ||||
|                           Make the add-on start during a system boot | ||||
|                         </span> | ||||
|                         <ha-switch | ||||
|                           @change=${this._startOnBootToggled} | ||||
|                           .checked=${this.addon.boot === "auto"} | ||||
|                           haptic | ||||
|                         ></ha-switch> | ||||
|                       </ha-settings-row> | ||||
|  | ||||
|                       ${this.addon.startup !== "once" | ||||
|                         ? html` | ||||
|                             <ha-settings-row ?three-line=${this.narrow}> | ||||
|                               <span slot="heading"> | ||||
|                                 Watchdog | ||||
|                               </span> | ||||
|                               <span slot="description"> | ||||
|                                 This will start the add-on if it crashes | ||||
|                               </span> | ||||
|                               <ha-switch | ||||
|                                 @change=${this._watchdogToggled} | ||||
|                                 .checked=${this.addon.watchdog} | ||||
|                                 haptic | ||||
|                               ></ha-switch> | ||||
|                             </ha-settings-row> | ||||
|                           ` | ||||
|                         : ""} | ||||
|                       ${this.addon.auto_update || | ||||
|                       this.hass.userData?.showAdvanced | ||||
|                         ? html` | ||||
|                             <ha-settings-row ?three-line=${this.narrow}> | ||||
|                               <span slot="heading"> | ||||
|                                 Auto update | ||||
|                               </span> | ||||
|                               <span slot="description"> | ||||
|                                 Auto update the add-on when there is a new | ||||
|                                 version available | ||||
|                               </span> | ||||
|                               <ha-switch | ||||
|                                 @change=${this._autoUpdateToggled} | ||||
|                                 .checked=${this.addon.auto_update} | ||||
|                                 haptic | ||||
|                               ></ha-switch> | ||||
|                             </ha-settings-row> | ||||
|                           ` | ||||
|                         : ""} | ||||
|                       ${this.addon.ingress | ||||
|                         ? html` | ||||
|                             <ha-settings-row ?three-line=${this.narrow}> | ||||
|                               <span slot="heading"> | ||||
|                                 Show in sidebar | ||||
|                               </span> | ||||
|                               <span slot="description"> | ||||
|                                 ${this._computeCannotIngressSidebar | ||||
|                                   ? "This option requires Home Assistant 0.92 or later." | ||||
|                                   : "Add this add-on to your sidebar"} | ||||
|                               </span> | ||||
|                               <ha-switch | ||||
|                                 @change=${this._panelToggled} | ||||
|                                 .checked=${this.addon.ingress_panel} | ||||
|                                 .disabled=${this._computeCannotIngressSidebar} | ||||
|                                 haptic | ||||
|                               ></ha-switch> | ||||
|                             </ha-settings-row> | ||||
|                           ` | ||||
|                         : ""} | ||||
|                       ${this._computeUsesProtectedOptions | ||||
|                         ? html` | ||||
|                             <ha-settings-row ?three-line=${this.narrow}> | ||||
|                               <span slot="heading"> | ||||
|                                 Protection mode | ||||
|                               </span> | ||||
|                               <span slot="description"> | ||||
|                                 Blocks elevated system access from the add-on | ||||
|                               </span> | ||||
|                               <ha-switch | ||||
|                                 @change=${this._protectionToggled} | ||||
|                                 .checked=${this.addon.protected} | ||||
|                                 haptic | ||||
|                               ></ha-switch> | ||||
|                             </ha-settings-row> | ||||
|                           ` | ||||
|                         : ""} | ||||
|                     </div> | ||||
|                   ` | ||||
|                 : ""} | ||||
|             </div> | ||||
|             <div> | ||||
|               ${this.addon.state === "started" | ||||
|                 ? html`<ha-settings-row ?three-line=${this.narrow}> | ||||
|                       <span slot="heading"> | ||||
|                         Hostname | ||||
|                       </span> | ||||
|                       <code slot="description"> | ||||
|                         ${this.addon.hostname} | ||||
|                       </code> | ||||
|                     </ha-settings-row> | ||||
|                     ${metrics.map( | ||||
|                       (metric) => | ||||
|                         html` | ||||
|                           <supervisor-metric | ||||
|                             .description=${metric.description} | ||||
|                             .value=${metric.value ?? 0} | ||||
|                             .tooltip=${metric.tooltip} | ||||
|                           ></supervisor-metric> | ||||
|                         ` | ||||
|                     )}` | ||||
|                 : ""} | ||||
|             </div> | ||||
|           </div> | ||||
|           ${this._error ? html` <div class="errors">${this._error}</div> ` : ""} | ||||
|           ${!this.addon.available | ||||
|             ? !addonArchIsSupported( | ||||
|                 this.supervisor.info.supported_arch, | ||||
|                 this.addon.arch | ||||
|               ) | ||||
|             <ha-label-badge | ||||
|               class=${classMap({ | ||||
|                 green: [5, 6].includes(Number(this.addon.rating)), | ||||
|                 yellow: [3, 4].includes(Number(this.addon.rating)), | ||||
|                 red: [1, 2].includes(Number(this.addon.rating)), | ||||
|               })} | ||||
|               @click=${this._showMoreInfo} | ||||
|               id="rating" | ||||
|               .value=${this.addon.rating} | ||||
|               label="rating" | ||||
|               description="" | ||||
|             ></ha-label-badge> | ||||
|             ${this.addon.host_network | ||||
|               ? html` | ||||
|                   <p class="warning"> | ||||
|                     This add-on is not compatible with the processor of your | ||||
|                     device or the operating system you have installed on your | ||||
|                     device. | ||||
|                   </p> | ||||
|                 ` | ||||
|               : html` | ||||
|                   <p class="warning"> | ||||
|                     You are running Home Assistant | ||||
|                     ${this.supervisor.core.version}, to install this add-on you | ||||
|                     need at least version ${this.addon.homeassistant} of Home | ||||
|                     Assistant | ||||
|                   </p> | ||||
|                 ` | ||||
|             : ""} | ||||
|         </div> | ||||
|         <div class="card-actions"> | ||||
|           <div> | ||||
|             ${this.addon.version | ||||
|               ? this._computeIsRunning | ||||
|                 ? html` | ||||
|                     <ha-progress-button | ||||
|                       class="warning" | ||||
|                       @click=${this._stopClicked} | ||||
|                     > | ||||
|                       Stop | ||||
|                     </ha-progress-button> | ||||
|                     <ha-progress-button | ||||
|                       class="warning" | ||||
|                       @click=${this._restartClicked} | ||||
|                     > | ||||
|                       Restart | ||||
|                     </ha-progress-button> | ||||
|                   ` | ||||
|                 : html` | ||||
|                     <ha-progress-button @click=${this._startClicked}> | ||||
|                       Start | ||||
|                     </ha-progress-button> | ||||
|                   ` | ||||
|               : html` | ||||
|                   <ha-progress-button | ||||
|                     .disabled=${!this.addon.available} | ||||
|                     @click=${this._installClicked} | ||||
|                   <ha-label-badge | ||||
|                     @click=${this._showMoreInfo} | ||||
|                     id="host_network" | ||||
|                     label="host" | ||||
|                     description="" | ||||
|                   > | ||||
|                     Install | ||||
|                   </ha-progress-button> | ||||
|                 `} | ||||
|           </div> | ||||
|           <div> | ||||
|             ${this.addon.version | ||||
|               ? html` ${this._computeShowWebUI | ||||
|                     ? html` | ||||
|                         <a | ||||
|                           href=${this._pathWebui!} | ||||
|                           tabindex="-1" | ||||
|                           target="_blank" | ||||
|                           rel="noopener" | ||||
|                         > | ||||
|                           <mwc-button> | ||||
|                             Open web UI | ||||
|                           </mwc-button> | ||||
|                         </a> | ||||
|                       ` | ||||
|                     : ""} | ||||
|                   ${this._computeShowIngressUI | ||||
|                     ? html` | ||||
|                         <mwc-button @click=${this._openIngress}> | ||||
|                           Open web UI | ||||
|                         </mwc-button> | ||||
|                       ` | ||||
|                     : ""} | ||||
|                   <ha-progress-button | ||||
|                     class="warning" | ||||
|                     @click=${this._uninstallClicked} | ||||
|                     <ha-svg-icon .path=${mdiNetwork}></ha-svg-icon> | ||||
|                   </ha-label-badge> | ||||
|                 ` | ||||
|               : ""} | ||||
|             ${this.addon.full_access | ||||
|               ? html` | ||||
|                   <ha-label-badge | ||||
|                     @click=${this._showMoreInfo} | ||||
|                     id="full_access" | ||||
|                     label="hardware" | ||||
|                     description="" | ||||
|                   > | ||||
|                     Uninstall | ||||
|                   </ha-progress-button> | ||||
|                   ${this.addon.build | ||||
|                     ? html` | ||||
|                         <ha-call-api-button | ||||
|                           class="warning" | ||||
|                           .hass=${this.hass} | ||||
|                           .path="hassio/addons/${this.addon.slug}/rebuild" | ||||
|                         > | ||||
|                           Rebuild | ||||
|                         </ha-call-api-button> | ||||
|                       ` | ||||
|                     : ""}` | ||||
|                     <ha-svg-icon .path=${mdiChip}></ha-svg-icon> | ||||
|                   </ha-label-badge> | ||||
|                 ` | ||||
|               : ""} | ||||
|             ${this.addon.homeassistant_api | ||||
|               ? html` | ||||
|                   <ha-label-badge | ||||
|                     @click=${this._showMoreInfo} | ||||
|                     id="homeassistant_api" | ||||
|                     label="hass" | ||||
|                     description="" | ||||
|                   > | ||||
|                     <ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon> | ||||
|                   </ha-label-badge> | ||||
|                 ` | ||||
|               : ""} | ||||
|             ${this._computeHassioApi | ||||
|               ? html` | ||||
|                   <ha-label-badge | ||||
|                     @click=${this._showMoreInfo} | ||||
|                     id="hassio_api" | ||||
|                     label="hassio" | ||||
|                     .description=${this.addon.hassio_role} | ||||
|                   > | ||||
|                     <ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon> | ||||
|                   </ha-label-badge> | ||||
|                 ` | ||||
|               : ""} | ||||
|             ${this.addon.docker_api | ||||
|               ? html` | ||||
|                   <ha-label-badge | ||||
|                     @click=${this._showMoreInfo} | ||||
|                     id="docker_api" | ||||
|                     label="docker" | ||||
|                     description="" | ||||
|                   > | ||||
|                     <ha-svg-icon .path=${mdiDocker}></ha-svg-icon> | ||||
|                   </ha-label-badge> | ||||
|                 ` | ||||
|               : ""} | ||||
|             ${this.addon.host_pid | ||||
|               ? html` | ||||
|                   <ha-label-badge | ||||
|                     @click=${this._showMoreInfo} | ||||
|                     id="host_pid" | ||||
|                     label="host pid" | ||||
|                     description="" | ||||
|                   > | ||||
|                     <ha-svg-icon .path=${mdiPound}></ha-svg-icon> | ||||
|                   </ha-label-badge> | ||||
|                 ` | ||||
|               : ""} | ||||
|             ${this.addon.apparmor | ||||
|               ? html` | ||||
|                   <ha-label-badge | ||||
|                     @click=${this._showMoreInfo} | ||||
|                     class=${this._computeApparmorClassName} | ||||
|                     id="apparmor" | ||||
|                     label="apparmor" | ||||
|                     description="" | ||||
|                   > | ||||
|                     <ha-svg-icon .path=${mdiShield}></ha-svg-icon> | ||||
|                   </ha-label-badge> | ||||
|                 ` | ||||
|               : ""} | ||||
|             ${this.addon.auth_api | ||||
|               ? html` | ||||
|                   <ha-label-badge | ||||
|                     @click=${this._showMoreInfo} | ||||
|                     id="auth_api" | ||||
|                     label="auth" | ||||
|                     description="" | ||||
|                   > | ||||
|                     <ha-svg-icon .path=${mdiKey}></ha-svg-icon> | ||||
|                   </ha-label-badge> | ||||
|                 ` | ||||
|               : ""} | ||||
|             ${this.addon.ingress | ||||
|               ? html` | ||||
|                   <ha-label-badge | ||||
|                     @click=${this._showMoreInfo} | ||||
|                     id="ingress" | ||||
|                     label="ingress" | ||||
|                     description="" | ||||
|                   > | ||||
|                     <ha-svg-icon | ||||
|                       .path=${mdiCursorDefaultClickOutline} | ||||
|                     ></ha-svg-icon> | ||||
|                   </ha-label-badge> | ||||
|                 ` | ||||
|               : ""} | ||||
|           </div> | ||||
|  | ||||
|           ${this.addon.version | ||||
|             ? html` | ||||
|                 <div class="addon-options"> | ||||
|                   <ha-settings-row ?three-line=${this.narrow}> | ||||
|                     <span slot="heading"> | ||||
|                       Start on boot | ||||
|                     </span> | ||||
|                     <span slot="description"> | ||||
|                       Make the add-on start during a system boot | ||||
|                     </span> | ||||
|                     <ha-switch | ||||
|                       @change=${this._startOnBootToggled} | ||||
|                       .checked=${this.addon.boot === "auto"} | ||||
|                       haptic | ||||
|                     ></ha-switch> | ||||
|                   </ha-settings-row> | ||||
|  | ||||
|                   ${this.addon.startup !== "once" | ||||
|                     ? html` | ||||
|                         <ha-settings-row ?three-line=${this.narrow}> | ||||
|                           <span slot="heading"> | ||||
|                             Watchdog | ||||
|                           </span> | ||||
|                           <span slot="description"> | ||||
|                             This will start the add-on if it crashes | ||||
|                           </span> | ||||
|                           <ha-switch | ||||
|                             @change=${this._watchdogToggled} | ||||
|                             .checked=${this.addon.watchdog} | ||||
|                             haptic | ||||
|                           ></ha-switch> | ||||
|                         </ha-settings-row> | ||||
|                       ` | ||||
|                     : ""} | ||||
|                   ${this.addon.auto_update || this.hass.userData?.showAdvanced | ||||
|                     ? html` | ||||
|                         <ha-settings-row ?three-line=${this.narrow}> | ||||
|                           <span slot="heading"> | ||||
|                             Auto update | ||||
|                           </span> | ||||
|                           <span slot="description"> | ||||
|                             Auto update the add-on when there is a new version | ||||
|                             available | ||||
|                           </span> | ||||
|                           <ha-switch | ||||
|                             @change=${this._autoUpdateToggled} | ||||
|                             .checked=${this.addon.auto_update} | ||||
|                             haptic | ||||
|                           ></ha-switch> | ||||
|                         </ha-settings-row> | ||||
|                       ` | ||||
|                     : ""} | ||||
|                   ${this.addon.ingress | ||||
|                     ? html` | ||||
|                         <ha-settings-row ?three-line=${this.narrow}> | ||||
|                           <span slot="heading"> | ||||
|                             Show in sidebar | ||||
|                           </span> | ||||
|                           <span slot="description"> | ||||
|                             ${this._computeCannotIngressSidebar | ||||
|                               ? "This option requires Home Assistant 0.92 or later." | ||||
|                               : "Add this add-on to your sidebar"} | ||||
|                           </span> | ||||
|                           <ha-switch | ||||
|                             @change=${this._panelToggled} | ||||
|                             .checked=${this.addon.ingress_panel} | ||||
|                             .disabled=${this._computeCannotIngressSidebar} | ||||
|                             haptic | ||||
|                           ></ha-switch> | ||||
|                         </ha-settings-row> | ||||
|                       ` | ||||
|                     : ""} | ||||
|                   ${this._computeUsesProtectedOptions | ||||
|                     ? html` | ||||
|                         <ha-settings-row ?three-line=${this.narrow}> | ||||
|                           <span slot="heading"> | ||||
|                             Protection mode | ||||
|                           </span> | ||||
|                           <span slot="description"> | ||||
|                             Blocks elevated system access from the add-on | ||||
|                           </span> | ||||
|                           <ha-switch | ||||
|                             @change=${this._protectionToggled} | ||||
|                             .checked=${this.addon.protected} | ||||
|                             haptic | ||||
|                           ></ha-switch> | ||||
|                         </ha-settings-row> | ||||
|                       ` | ||||
|                     : ""} | ||||
|                 </div> | ||||
|               ` | ||||
|             : ""} | ||||
|           ${this._error ? html` <div class="errors">${this._error}</div> ` : ""} | ||||
|         </div> | ||||
|         <div class="card-actions"> | ||||
|           ${this.addon.version | ||||
|             ? html` | ||||
|                 ${this._computeIsRunning | ||||
|                   ? html` | ||||
|                       <ha-call-api-button | ||||
|                         class="warning" | ||||
|                         .hass=${this.hass} | ||||
|                         .path="hassio/addons/${this.addon.slug}/stop" | ||||
|                       > | ||||
|                         Stop | ||||
|                       </ha-call-api-button> | ||||
|                       <ha-call-api-button | ||||
|                         class="warning" | ||||
|                         .hass=${this.hass} | ||||
|                         .path="hassio/addons/${this.addon.slug}/restart" | ||||
|                       > | ||||
|                         Restart | ||||
|                       </ha-call-api-button> | ||||
|                     ` | ||||
|                   : html` | ||||
|                       <ha-progress-button @click=${this._startClicked}> | ||||
|                         Start | ||||
|                       </ha-progress-button> | ||||
|                     `} | ||||
|                 ${this._computeShowWebUI | ||||
|                   ? html` | ||||
|                       <a | ||||
|                         href=${this._pathWebui!} | ||||
|                         tabindex="-1" | ||||
|                         target="_blank" | ||||
|                         class="right" | ||||
|                         rel="noopener" | ||||
|                       > | ||||
|                         <mwc-button> | ||||
|                           Open web UI | ||||
|                         </mwc-button> | ||||
|                       </a> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this._computeShowIngressUI | ||||
|                   ? html` | ||||
|                       <mwc-button class="right" @click=${this._openIngress}> | ||||
|                         Open web UI | ||||
|                       </mwc-button> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 <ha-progress-button | ||||
|                   class=" right warning" | ||||
|                   @click=${this._uninstallClicked} | ||||
|                 > | ||||
|                   Uninstall | ||||
|                 </ha-progress-button> | ||||
|                 ${this.addon.build | ||||
|                   ? html` | ||||
|                       <ha-call-api-button | ||||
|                         class="warning right" | ||||
|                         .hass=${this.hass} | ||||
|                         .path="hassio/addons/${this.addon.slug}/rebuild" | ||||
|                       > | ||||
|                         Rebuild | ||||
|                       </ha-call-api-button> | ||||
|                     ` | ||||
|                   : ""} | ||||
|               ` | ||||
|             : html` | ||||
|                 ${!this.addon.available | ||||
|                   ? html` | ||||
|                       <p class="warning"> | ||||
|                         This add-on is not available on your system. | ||||
|                       </p> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 <ha-progress-button | ||||
|                   .disabled=${!this.addon.available} | ||||
|                   @click=${this._installClicked} | ||||
|                 > | ||||
|                   Install | ||||
|                 </ha-progress-button> | ||||
|               `} | ||||
|         </div> | ||||
|       </ha-card> | ||||
|  | ||||
| @@ -664,22 +579,6 @@ class HassioAddonInfo extends LitElement { | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   protected updated(changedProps) { | ||||
|     super.updated(changedProps); | ||||
|     if (changedProps.has("addon")) { | ||||
|       this._loadData(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private async _loadData(): Promise<void> { | ||||
|     if (this.addon.state === "started") { | ||||
|       this._metrics = await fetchHassioStats( | ||||
|         this.hass, | ||||
|         `addons/${this.addon.slug}` | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private get _computeHassioApi(): boolean { | ||||
|     return ( | ||||
|       this.addon.hassio_api && | ||||
| @@ -880,82 +779,6 @@ class HassioAddonInfo extends LitElement { | ||||
|     button.progress = false; | ||||
|   } | ||||
|  | ||||
|   private async _stopClicked(ev: CustomEvent): Promise<void> { | ||||
|     const button = ev.currentTarget as any; | ||||
|     button.progress = true; | ||||
|  | ||||
|     try { | ||||
|       await stopHassioAddon(this.hass, this.addon.slug); | ||||
|       const eventdata = { | ||||
|         success: true, | ||||
|         response: undefined, | ||||
|         path: "stop", | ||||
|       }; | ||||
|       fireEvent(this, "hass-api-called", eventdata); | ||||
|     } catch (err) { | ||||
|       showAlertDialog(this, { | ||||
|         title: "Failed to stop addon", | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|     } | ||||
|     button.progress = false; | ||||
|   } | ||||
|  | ||||
|   private async _restartClicked(ev: CustomEvent): Promise<void> { | ||||
|     const button = ev.currentTarget as any; | ||||
|     button.progress = true; | ||||
|  | ||||
|     try { | ||||
|       await restartHassioAddon(this.hass, this.addon.slug); | ||||
|       const eventdata = { | ||||
|         success: true, | ||||
|         response: undefined, | ||||
|         path: "stop", | ||||
|       }; | ||||
|       fireEvent(this, "hass-api-called", eventdata); | ||||
|     } catch (err) { | ||||
|       showAlertDialog(this, { | ||||
|         title: "Failed to restart addon", | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|     } | ||||
|     button.progress = false; | ||||
|   } | ||||
|  | ||||
|   private async _updateClicked(ev: CustomEvent): Promise<void> { | ||||
|     const button = ev.currentTarget as any; | ||||
|     button.progress = true; | ||||
|  | ||||
|     const confirmed = await showConfirmationDialog(this, { | ||||
|       title: this.addon.name, | ||||
|       text: "Are you sure you want to update this add-on?", | ||||
|       confirmText: "update add-on", | ||||
|       dismissText: "no", | ||||
|     }); | ||||
|  | ||||
|     if (!confirmed) { | ||||
|       button.progress = false; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this._error = undefined; | ||||
|     try { | ||||
|       await updateHassioAddon(this.hass, this.addon.slug); | ||||
|       const eventdata = { | ||||
|         success: true, | ||||
|         response: undefined, | ||||
|         path: "update", | ||||
|       }; | ||||
|       fireEvent(this, "hass-api-called", eventdata); | ||||
|     } catch (err) { | ||||
|       showAlertDialog(this, { | ||||
|         title: "Failed to update addon", | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|     } | ||||
|     button.progress = false; | ||||
|   } | ||||
|  | ||||
|   private async _startClicked(ev: CustomEvent): Promise<void> { | ||||
|     const button = ev.currentTarget as any; | ||||
|     button.progress = true; | ||||
| @@ -964,10 +787,10 @@ class HassioAddonInfo extends LitElement { | ||||
|         this.hass, | ||||
|         this.addon.slug | ||||
|       ); | ||||
|       if (!validate.valid) { | ||||
|       if (!validate.data.valid) { | ||||
|         await showConfirmationDialog(this, { | ||||
|           title: "Failed to start addon - configuration validation failed!", | ||||
|           text: validate.message.split(" Got ")[0], | ||||
|           text: validate.data.message.split(" Got ")[0], | ||||
|           confirm: () => this._openConfiguration(), | ||||
|           confirmText: "Go to configuration", | ||||
|           dismissText: "Cancel", | ||||
| @@ -987,12 +810,6 @@ class HassioAddonInfo extends LitElement { | ||||
|     try { | ||||
|       await startHassioAddon(this.hass, this.addon.slug); | ||||
|       this.addon = await fetchHassioAddonInfo(this.hass, this.addon.slug); | ||||
|       const eventdata = { | ||||
|         success: true, | ||||
|         response: undefined, | ||||
|         path: "start", | ||||
|       }; | ||||
|       fireEvent(this, "hass-api-called", eventdata); | ||||
|     } catch (err) { | ||||
|       showAlertDialog(this, { | ||||
|         title: "Failed to start addon", | ||||
| @@ -1108,6 +925,9 @@ class HassioAddonInfo extends LitElement { | ||||
|           font-weight: 500; | ||||
|           color: var(--primary-color); | ||||
|         } | ||||
|         .right { | ||||
|           float: right; | ||||
|         } | ||||
|         protection-enable mwc-button { | ||||
|           --mdc-theme-primary: white; | ||||
|         } | ||||
| @@ -1130,8 +950,7 @@ class HassioAddonInfo extends LitElement { | ||||
|           margin-bottom: 16px; | ||||
|         } | ||||
|         .card-actions { | ||||
|           justify-content: space-between; | ||||
|           display: flex; | ||||
|           display: flow-root; | ||||
|         } | ||||
|         .security h3 { | ||||
|           margin-bottom: 8px; | ||||
| @@ -1167,26 +986,12 @@ class HassioAddonInfo extends LitElement { | ||||
|         } | ||||
|  | ||||
|         .addon-options { | ||||
|           max-width: 90%; | ||||
|           max-width: 50%; | ||||
|         } | ||||
|  | ||||
|         .addon-container { | ||||
|           display: grid; | ||||
|           grid-auto-flow: column; | ||||
|           grid-template-columns: 60% 40%; | ||||
|         } | ||||
|  | ||||
|         .addon-container > div:last-of-type { | ||||
|           align-self: end; | ||||
|         } | ||||
|  | ||||
|         @media (max-width: 720px) { | ||||
|           .addon-options { | ||||
|             max-width: 100%; | ||||
|           } | ||||
|           .addon-container { | ||||
|             display: block; | ||||
|           } | ||||
|         } | ||||
|       `, | ||||
|     ]; | ||||
|   | ||||
| @@ -7,8 +7,8 @@ import { | ||||
|   property, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import "../../../../src/components/ha-circular-progress"; | ||||
| import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; | ||||
| import "../../../../src/components/ha-circular-progress"; | ||||
| import { haStyle } from "../../../../src/resources/styles"; | ||||
| import { HomeAssistant } from "../../../../src/types"; | ||||
| import { hassioStyle } from "../../resources/hassio-style"; | ||||
|   | ||||
| @@ -27,8 +27,6 @@ declare global { | ||||
|   } | ||||
| } | ||||
|  | ||||
| const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB | ||||
|  | ||||
| @customElement("hassio-upload-snapshot") | ||||
| export class HassioUploadSnapshot extends LitElement { | ||||
|   public hass!: HomeAssistant; | ||||
| @@ -53,20 +51,6 @@ export class HassioUploadSnapshot extends LitElement { | ||||
|   private async _uploadFile(ev) { | ||||
|     const file = ev.detail.files[0]; | ||||
|  | ||||
|     if (file.size > MAX_FILE_SIZE) { | ||||
|       showAlertDialog(this, { | ||||
|         title: "Snapshot file is too big", | ||||
|         text: html`The maximum allowed filesize is 1GB.<br /> | ||||
|           <a | ||||
|             href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-snapshot-on-a-new-install" | ||||
|             target="_blank" | ||||
|             >Have a look here on how to restore it.</a | ||||
|           >`, | ||||
|         confirmText: "ok", | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (!["application/x-tar"].includes(file.type)) { | ||||
|       showAlertDialog(this, { | ||||
|         title: "Unsupported file format", | ||||
|   | ||||
| @@ -1,87 +0,0 @@ | ||||
| import { | ||||
|   css, | ||||
|   CSSResult, | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   property, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { classMap } from "lit-html/directives/class-map"; | ||||
| import "../../../src/components/ha-bar"; | ||||
| import "../../../src/components/ha-settings-row"; | ||||
| import { roundWithOneDecimal } from "../../../src/util/calculate"; | ||||
|  | ||||
| @customElement("supervisor-metric") | ||||
| class SupervisorMetric extends LitElement { | ||||
|   @property({ type: Number }) public value!: number; | ||||
|  | ||||
|   @property({ type: String }) public description!: string; | ||||
|  | ||||
|   @property({ type: String }) public tooltip?: string; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     const roundedValue = roundWithOneDecimal(this.value); | ||||
|     return html`<ha-settings-row> | ||||
|       <span slot="heading"> | ||||
|         ${this.description} | ||||
|       </span> | ||||
|       <div slot="description" title="${this.tooltip ?? ""}"> | ||||
|         <span class="value"> | ||||
|           ${roundedValue}% | ||||
|         </span> | ||||
|         <ha-bar | ||||
|           class="${classMap({ | ||||
|             "target-warning": roundedValue > 50, | ||||
|             "target-critical": roundedValue > 85, | ||||
|           })}" | ||||
|           .value=${this.value} | ||||
|         ></ha-bar> | ||||
|       </div> | ||||
|     </ha-settings-row>`; | ||||
|   } | ||||
|  | ||||
|   static get styles(): CSSResult { | ||||
|     return css` | ||||
|       ha-settings-row { | ||||
|         padding: 0; | ||||
|         height: 54px; | ||||
|         width: 100%; | ||||
|       } | ||||
|       ha-settings-row > div[slot="description"] { | ||||
|         white-space: normal; | ||||
|         color: var(--secondary-text-color); | ||||
|         display: flex; | ||||
|         justify-content: space-between; | ||||
|       } | ||||
|       ha-bar { | ||||
|         --ha-bar-primary-color: var( | ||||
|           --hassio-bar-ok-color, | ||||
|           var(--success-color) | ||||
|         ); | ||||
|       } | ||||
|       .target-warning { | ||||
|         --ha-bar-primary-color: var( | ||||
|           --hassio-bar-warning-color, | ||||
|           var(--warning-color) | ||||
|         ); | ||||
|       } | ||||
|       .target-critical { | ||||
|         --ha-bar-primary-color: var( | ||||
|           --hassio-bar-critical-color, | ||||
|           var(--error-color) | ||||
|         ); | ||||
|       } | ||||
|       .value { | ||||
|         width: 42px; | ||||
|         padding-right: 4px; | ||||
|       } | ||||
|     `; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "supervisor-metric": SupervisorMetric; | ||||
|   } | ||||
| } | ||||
| @@ -12,7 +12,7 @@ import { atLeastVersion } from "../../../src/common/config/version"; | ||||
| import { navigate } from "../../../src/common/navigate"; | ||||
| import { compare } from "../../../src/common/string/compare"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||
| import { HassioAddonInfo } from "../../../src/data/hassio/addon"; | ||||
| import { haStyle } from "../../../src/resources/styles"; | ||||
| import { HomeAssistant } from "../../../src/types"; | ||||
| import "../components/hassio-card-content"; | ||||
| @@ -22,14 +22,14 @@ import { hassioStyle } from "../resources/hassio-style"; | ||||
| class HassioAddons extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|   @property({ attribute: false }) public addons?: HassioAddonInfo[]; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       <div class="content"> | ||||
|         <h1>Add-ons</h1> | ||||
|         <div class="card-group"> | ||||
|           ${!this.supervisor.supervisor.addons?.length | ||||
|           ${!this.addons?.length | ||||
|             ? html` | ||||
|                 <ha-card> | ||||
|                   <div class="card-content"> | ||||
| @@ -41,7 +41,7 @@ class HassioAddons extends LitElement { | ||||
|                   </div> | ||||
|                 </ha-card> | ||||
|               ` | ||||
|             : this.supervisor.supervisor.addons | ||||
|             : this.addons | ||||
|                 .sort((a, b) => compare(a.name, b.name)) | ||||
|                 .map( | ||||
|                   (addon) => html` | ||||
|   | ||||
| @@ -7,7 +7,11 @@ import { | ||||
|   property, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||
| import { HassioHassOSInfo } from "../../../src/data/hassio/host"; | ||||
| import { | ||||
|   HassioHomeAssistantInfo, | ||||
|   HassioSupervisorInfo, | ||||
| } from "../../../src/data/hassio/supervisor"; | ||||
| import "../../../src/layouts/hass-tabs-subpage"; | ||||
| import { haStyle } from "../../../src/resources/styles"; | ||||
| import { HomeAssistant, Route } from "../../../src/types"; | ||||
| @@ -19,12 +23,16 @@ import "./hassio-update"; | ||||
| class HassioDashboard extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|  | ||||
|   @property({ type: Boolean }) public narrow!: boolean; | ||||
|  | ||||
|   @property({ attribute: false }) public route!: Route; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     return html` | ||||
|       <hass-tabs-subpage | ||||
| @@ -39,11 +47,13 @@ class HassioDashboard extends LitElement { | ||||
|         <div class="content"> | ||||
|           <hassio-update | ||||
|             .hass=${this.hass} | ||||
|             .supervisor=${this.supervisor} | ||||
|             .hassInfo=${this.hassInfo} | ||||
|             .supervisorInfo=${this.supervisorInfo} | ||||
|             .hassOsInfo=${this.hassOsInfo} | ||||
|           ></hassio-update> | ||||
|           <hassio-addons | ||||
|             .hass=${this.hass} | ||||
|             .supervisor=${this.supervisor} | ||||
|             .addons=${this.supervisorInfo.addons} | ||||
|           ></hassio-addons> | ||||
|         </div> | ||||
|       </hass-tabs-subpage> | ||||
|   | ||||
| @@ -10,7 +10,6 @@ import { | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import memoizeOne from "memoize-one"; | ||||
| import { fireEvent } from "../../../src/common/dom/fire_event"; | ||||
| import "../../../src/components/buttons/ha-progress-button"; | ||||
| import "../../../src/components/ha-card"; | ||||
| import "../../../src/components/ha-svg-icon"; | ||||
| @@ -24,7 +23,6 @@ import { | ||||
|   HassioHomeAssistantInfo, | ||||
|   HassioSupervisorInfo, | ||||
| } from "../../../src/data/hassio/supervisor"; | ||||
| import { Supervisor } from "../../../src/data/supervisor/supervisor"; | ||||
| import { | ||||
|   showAlertDialog, | ||||
|   showConfirmationDialog, | ||||
| @@ -37,20 +35,31 @@ import { hassioStyle } from "../resources/hassio-style"; | ||||
| export class HassioUpdate extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|   @property({ attribute: false }) public hassInfo?: HassioHomeAssistantInfo; | ||||
|  | ||||
|   private _pendingUpdates = memoizeOne((supervisor: Supervisor): number => { | ||||
|     return Object.keys(supervisor).filter( | ||||
|       (value) => supervisor[value].update_available | ||||
|     ).length; | ||||
|   }); | ||||
|   @property({ attribute: false }) public hassOsInfo?: HassioHassOSInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisorInfo?: HassioSupervisorInfo; | ||||
|  | ||||
|   private _pendingUpdates = memoizeOne( | ||||
|     ( | ||||
|       core?: HassioHomeAssistantInfo, | ||||
|       supervisor?: HassioSupervisorInfo, | ||||
|       os?: HassioHassOSInfo | ||||
|     ): number => { | ||||
|       return [core, supervisor, os].filter( | ||||
|         (value) => !!value && value?.update_available | ||||
|       ).length; | ||||
|     } | ||||
|   ); | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     if (!this.supervisor) { | ||||
|       return html``; | ||||
|     } | ||||
|     const updatesAvailable = this._pendingUpdates( | ||||
|       this.hassInfo, | ||||
|       this.supervisorInfo, | ||||
|       this.hassOsInfo | ||||
|     ); | ||||
|  | ||||
|     const updatesAvailable = this._pendingUpdates(this.supervisor); | ||||
|     if (!updatesAvailable) { | ||||
|       return html``; | ||||
|     } | ||||
| @@ -65,27 +74,26 @@ export class HassioUpdate extends LitElement { | ||||
|         <div class="card-group"> | ||||
|           ${this._renderUpdateCard( | ||||
|             "Home Assistant Core", | ||||
|             "core", | ||||
|             this.supervisor.core, | ||||
|             this.hassInfo!, | ||||
|             "hassio/homeassistant/update", | ||||
|             `https://${ | ||||
|               this.supervisor.core.version_latest.includes("b") ? "rc" : "www" | ||||
|               this.hassInfo?.version_latest.includes("b") ? "rc" : "www" | ||||
|             }.home-assistant.io/latest-release-notes/` | ||||
|           )} | ||||
|           ${this._renderUpdateCard( | ||||
|             "Supervisor", | ||||
|             "supervisor", | ||||
|             this.supervisor.supervisor, | ||||
|             this.supervisorInfo!, | ||||
|             "hassio/supervisor/update", | ||||
|             `https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}` | ||||
|             `https://github.com//home-assistant/hassio/releases/tag/${ | ||||
|               this.supervisorInfo!.version_latest | ||||
|             }` | ||||
|           )} | ||||
|           ${this.supervisor.host.features.includes("hassos") | ||||
|           ${this.hassOsInfo | ||||
|             ? this._renderUpdateCard( | ||||
|                 "Operating System", | ||||
|                 "os", | ||||
|                 this.supervisor.os, | ||||
|                 this.hassOsInfo, | ||||
|                 "hassio/os/update", | ||||
|                 `https://github.com//home-assistant/hassos/releases/tag/${this.supervisor.os.version_latest}` | ||||
|                 `https://github.com//home-assistant/hassos/releases/tag/${this.hassOsInfo.version_latest}` | ||||
|               ) | ||||
|             : ""} | ||||
|         </div> | ||||
| @@ -95,7 +103,6 @@ export class HassioUpdate extends LitElement { | ||||
|  | ||||
|   private _renderUpdateCard( | ||||
|     name: string, | ||||
|     key: string, | ||||
|     object: HassioHomeAssistantInfo | HassioSupervisorInfo | HassioHassOSInfo, | ||||
|     apiPath: string, | ||||
|     releaseNotesUrl: string | ||||
| @@ -121,7 +128,6 @@ export class HassioUpdate extends LitElement { | ||||
|           <ha-progress-button | ||||
|             .apiPath=${apiPath} | ||||
|             .name=${name} | ||||
|             .key=${key} | ||||
|             .version=${object.version_latest} | ||||
|             @click=${this._confirmUpdate} | ||||
|           > | ||||
| @@ -148,7 +154,6 @@ export class HassioUpdate extends LitElement { | ||||
|     } | ||||
|     try { | ||||
|       await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath); | ||||
|       fireEvent(this, "supervisor-store-refresh", { store: item.key }); | ||||
|     } catch (err) { | ||||
|       // Only show an error if the status code was not expected (user behind proxy) | ||||
|       // or no status at all(connection terminated) | ||||
|   | ||||
| @@ -3,9 +3,9 @@ import { | ||||
|   CSSResult, | ||||
|   customElement, | ||||
|   html, | ||||
|   internalProperty, | ||||
|   LitElement, | ||||
|   property, | ||||
|   internalProperty, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { createCloseHeading } from "../../../../src/components/ha-dialog"; | ||||
|   | ||||
| @@ -11,7 +11,10 @@ export const showHassioMarkdownDialog = ( | ||||
| ): void => { | ||||
|   fireEvent(element, "show-dialog", { | ||||
|     dialogTag: "dialog-hassio-markdown", | ||||
|     dialogImport: () => import("./dialog-hassio-markdown"), | ||||
|     dialogImport: () => | ||||
|       import( | ||||
|         /* webpackChunkName: "dialog-hassio-markdown" */ "./dialog-hassio-markdown" | ||||
|       ), | ||||
|     dialogParams, | ||||
|   }); | ||||
| }; | ||||
|   | ||||
| @@ -1,7 +1,5 @@ | ||||
| import "@material/mwc-button/mwc-button"; | ||||
| import "@material/mwc-icon-button"; | ||||
| import "@material/mwc-list/mwc-list"; | ||||
| import "@material/mwc-list/mwc-list-item"; | ||||
| import "@material/mwc-tab"; | ||||
| import "@material/mwc-tab-bar"; | ||||
| import { mdiClose } from "@mdi/js"; | ||||
| @@ -18,22 +16,18 @@ import { | ||||
| } from "lit-element"; | ||||
| import { cache } from "lit-html/directives/cache"; | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import "../../../../src/components/ha-chips"; | ||||
| import "../../../../src/components/ha-circular-progress"; | ||||
| import "../../../../src/components/ha-dialog"; | ||||
| import "../../../../src/components/ha-expansion-panel"; | ||||
| import "../../../../src/components/ha-formfield"; | ||||
| import "../../../../src/components/ha-header-bar"; | ||||
| import "../../../../src/components/ha-radio"; | ||||
| import type { HaRadio } from "../../../../src/components/ha-radio"; | ||||
| import "../../../../src/components/ha-related-items"; | ||||
| import "../../../../src/components/ha-svg-icon"; | ||||
| import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; | ||||
| import { | ||||
|   AccessPoints, | ||||
|   accesspointScan, | ||||
|   NetworkInterface, | ||||
|   updateNetworkInterface, | ||||
|   WifiConfiguration, | ||||
| } from "../../../../src/data/hassio/network"; | ||||
| import { | ||||
|   showAlertDialog, | ||||
| @@ -44,51 +38,54 @@ import { haStyleDialog } from "../../../../src/resources/styles"; | ||||
| import type { HomeAssistant } from "../../../../src/types"; | ||||
| import { HassioNetworkDialogParams } from "./show-dialog-network"; | ||||
|  | ||||
| const IP_VERSIONS = ["ipv4", "ipv6"]; | ||||
|  | ||||
| @customElement("dialog-hassio-network") | ||||
| export class DialogHassioNetwork extends LitElement | ||||
|   implements HassDialog<HassioNetworkDialogParams> { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @internalProperty() private _accessPoints?: AccessPoints; | ||||
|  | ||||
|   @internalProperty() private _curTabIndex = 0; | ||||
|  | ||||
|   @internalProperty() private _dirty = false; | ||||
|  | ||||
|   @internalProperty() private _interface?: NetworkInterface; | ||||
|  | ||||
|   @internalProperty() private _interfaces!: NetworkInterface[]; | ||||
|   @internalProperty() private _prosessing = false; | ||||
|  | ||||
|   @internalProperty() private _params?: HassioNetworkDialogParams; | ||||
|  | ||||
|   @internalProperty() private _processing = false; | ||||
|   @internalProperty() private _network!: { | ||||
|     interface: string; | ||||
|     data: NetworkInterface; | ||||
|   }[]; | ||||
|  | ||||
|   @internalProperty() private _scanning = false; | ||||
|   @internalProperty() private _curTabIndex = 0; | ||||
|  | ||||
|   @internalProperty() private _wifiConfiguration?: WifiConfiguration; | ||||
|   @internalProperty() private _device?: { | ||||
|     interface: string; | ||||
|     data: NetworkInterface; | ||||
|   }; | ||||
|  | ||||
|   @internalProperty() private _dirty = false; | ||||
|  | ||||
|   public async showDialog(params: HassioNetworkDialogParams): Promise<void> { | ||||
|     this._params = params; | ||||
|     this._dirty = false; | ||||
|     this._curTabIndex = 0; | ||||
|     this._interfaces = params.network.interfaces.sort((a, b) => { | ||||
|       return a.primary > b.primary ? -1 : 1; | ||||
|     }); | ||||
|     this._interface = { ...this._interfaces[this._curTabIndex] }; | ||||
|  | ||||
|     this._network = Object.keys(params.network?.interfaces) | ||||
|       .map((device) => ({ | ||||
|         interface: device, | ||||
|         data: params.network.interfaces[device], | ||||
|       })) | ||||
|       .sort((a, b) => { | ||||
|         return a.data.primary > b.data.primary ? -1 : 1; | ||||
|       }); | ||||
|     this._device = this._network[this._curTabIndex]; | ||||
|     this._device.data.nameservers = String(this._device.data.nameservers); | ||||
|     await this.updateComplete; | ||||
|   } | ||||
|  | ||||
|   public closeDialog(): void { | ||||
|     this._params = undefined; | ||||
|     this._processing = false; | ||||
|     this._prosessing = false; | ||||
|     fireEvent(this, "dialog-closed", { dialog: this.localName }); | ||||
|   } | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     if (!this._params || !this._interface) { | ||||
|     if (!this._params || !this._network) { | ||||
|       return html``; | ||||
|     } | ||||
|  | ||||
| @@ -110,11 +107,11 @@ export class DialogHassioNetwork extends LitElement | ||||
|               <ha-svg-icon .path=${mdiClose}></ha-svg-icon> | ||||
|             </mwc-icon-button> | ||||
|           </ha-header-bar> | ||||
|           ${this._interfaces.length > 1 | ||||
|           ${this._network.length > 1 | ||||
|             ? html` <mwc-tab-bar | ||||
|                 .activeIndex=${this._curTabIndex} | ||||
|                 @MDCTabBar:activated=${this._handleTabActivated} | ||||
|                 >${this._interfaces.map( | ||||
|                 >${this._network.map( | ||||
|                   (device) => | ||||
|                     html`<mwc-tab | ||||
|                       .id=${device.interface} | ||||
| @@ -132,302 +129,81 @@ export class DialogHassioNetwork extends LitElement | ||||
|  | ||||
|   private _renderTab() { | ||||
|     return html` <div class="form container"> | ||||
|         ${IP_VERSIONS.map((version) => | ||||
|           this._interface![version] ? this._renderIPConfiguration(version) : "" | ||||
|         )} | ||||
|         ${this._interface?.type === "wireless" | ||||
|           ? html` | ||||
|               <ha-expansion-panel header="Wi-Fi" outlined> | ||||
|                 ${this._interface?.wifi?.ssid | ||||
|                   ? html`<p>Connected to: ${this._interface?.wifi?.ssid}</p>` | ||||
|                   : ""} | ||||
|                 <mwc-button | ||||
|                   class="scan" | ||||
|                   @click=${this._scanForAP} | ||||
|                   .disabled=${this._scanning} | ||||
|                 > | ||||
|                   ${this._scanning | ||||
|                     ? html`<ha-circular-progress active size="small"> | ||||
|                       </ha-circular-progress>` | ||||
|                     : "Scan for accesspoints"} | ||||
|                 </mwc-button> | ||||
|                 ${this._accessPoints && | ||||
|                 this._accessPoints.accesspoints && | ||||
|                 this._accessPoints.accesspoints.length !== 0 | ||||
|                   ? html` | ||||
|                       <mwc-list> | ||||
|                         ${this._accessPoints.accesspoints | ||||
|                           .filter((ap) => ap.ssid) | ||||
|                           .map( | ||||
|                             (ap) => | ||||
|                               html` | ||||
|                                 <mwc-list-item | ||||
|                                   twoline | ||||
|                                   @click=${this._selectAP} | ||||
|                                   .activated=${ap.ssid === | ||||
|                                   this._wifiConfiguration?.ssid} | ||||
|                                   .ap=${ap} | ||||
|                                 > | ||||
|                                   <span>${ap.ssid}</span> | ||||
|                                   <span slot="secondary"> | ||||
|                                     ${ap.mac} - Strength: ${ap.signal} | ||||
|                                   </span> | ||||
|                                 </mwc-list-item> | ||||
|                               ` | ||||
|                           )} | ||||
|                       </mwc-list> | ||||
|                     ` | ||||
|                   : ""} | ||||
|                 ${this._wifiConfiguration | ||||
|                   ? html` | ||||
|                       <div class="radio-row"> | ||||
|                         <ha-formfield label="open"> | ||||
|                           <ha-radio | ||||
|                             @change=${this._handleRadioValueChangedAp} | ||||
|                             .ap=${this._wifiConfiguration} | ||||
|                             value="open" | ||||
|                             name="auth" | ||||
|                             .checked=${this._wifiConfiguration.auth === | ||||
|                               undefined || | ||||
|                             this._wifiConfiguration.auth === "open"} | ||||
|                           > | ||||
|                           </ha-radio> | ||||
|                         </ha-formfield> | ||||
|                         <ha-formfield label="wep"> | ||||
|                           <ha-radio | ||||
|                             @change=${this._handleRadioValueChangedAp} | ||||
|                             .ap=${this._wifiConfiguration} | ||||
|                             value="wep" | ||||
|                             name="auth" | ||||
|                             .checked=${this._wifiConfiguration.auth === "wep"} | ||||
|                           > | ||||
|                           </ha-radio> | ||||
|                         </ha-formfield> | ||||
|                         <ha-formfield label="wpa-psk"> | ||||
|                           <ha-radio | ||||
|                             @change=${this._handleRadioValueChangedAp} | ||||
|                             .ap=${this._wifiConfiguration} | ||||
|                             value="wpa-psk" | ||||
|                             name="auth" | ||||
|                             .checked=${this._wifiConfiguration.auth === | ||||
|                             "wpa-psk"} | ||||
|                           > | ||||
|                           </ha-radio> | ||||
|                         </ha-formfield> | ||||
|                       </div> | ||||
|                       ${this._wifiConfiguration.auth === "wpa-psk" || | ||||
|                       this._wifiConfiguration.auth === "wep" | ||||
|                         ? html` | ||||
|                             <paper-input | ||||
|                               class="flex-auto" | ||||
|                               type="password" | ||||
|                               id="psk" | ||||
|                               label="Password" | ||||
|                               version="wifi" | ||||
|                               @value-changed=${this | ||||
|                                 ._handleInputValueChangedWifi} | ||||
|                             > | ||||
|                             </paper-input> | ||||
|                           ` | ||||
|                         : ""} | ||||
|                     ` | ||||
|                   : ""} | ||||
|               </ha-expansion-panel> | ||||
|             ` | ||||
|           : ""} | ||||
|         ${this._dirty | ||||
|           ? html`<div class="warning"> | ||||
|               If you are changing the Wi-Fi, IP or gateway addresses, you might | ||||
|               lose the connection! | ||||
|             </div>` | ||||
|           : ""} | ||||
|       </div> | ||||
|       <div class="buttons"> | ||||
|         <mwc-button label="close" @click=${this.closeDialog}> </mwc-button> | ||||
|         <mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}> | ||||
|           ${this._processing | ||||
|             ? html`<ha-circular-progress active size="small"> | ||||
|               </ha-circular-progress>` | ||||
|             : "Save"} | ||||
|         </mwc-button> | ||||
|       </div>`; | ||||
|   } | ||||
|  | ||||
|   private _selectAP(event) { | ||||
|     this._wifiConfiguration = event.currentTarget.ap; | ||||
|     this._dirty = true; | ||||
|   } | ||||
|  | ||||
|   private async _scanForAP() { | ||||
|     if (!this._interface) { | ||||
|       return; | ||||
|     } | ||||
|     this._scanning = true; | ||||
|     try { | ||||
|       this._accessPoints = await accesspointScan( | ||||
|         this.hass, | ||||
|         this._interface.interface | ||||
|       ); | ||||
|     } catch (err) { | ||||
|       showAlertDialog(this, { | ||||
|         title: "Failed to scan for accesspoints", | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|     } finally { | ||||
|       this._scanning = false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private _renderIPConfiguration(version: string) { | ||||
|     return html` | ||||
|       <ha-expansion-panel | ||||
|         .header=${`IPv${version.charAt(version.length - 1)}`} | ||||
|         outlined | ||||
|       > | ||||
|         <div class="radio-row"> | ||||
|           <ha-formfield label="DHCP"> | ||||
|             <ha-radio | ||||
|               @change=${this._handleRadioValueChanged} | ||||
|               .version=${version} | ||||
|               value="auto" | ||||
|               name="${version}method" | ||||
|               .checked=${this._interface![version]?.method === "auto"} | ||||
|             > | ||||
|             </ha-radio> | ||||
|           </ha-formfield> | ||||
|           <ha-formfield label="Static"> | ||||
|             <ha-radio | ||||
|               @change=${this._handleRadioValueChanged} | ||||
|               .version=${version} | ||||
|               value="static" | ||||
|               name="${version}method" | ||||
|               .checked=${this._interface![version]?.method === "static"} | ||||
|             > | ||||
|             </ha-radio> | ||||
|           </ha-formfield> | ||||
|           <ha-formfield label="Disabled" class="warning"> | ||||
|             <ha-radio | ||||
|               @change=${this._handleRadioValueChanged} | ||||
|               .version=${version} | ||||
|               value="disabled" | ||||
|               name="${version}method" | ||||
|               .checked=${this._interface![version]?.method === "disabled"} | ||||
|             > | ||||
|             </ha-radio> | ||||
|           </ha-formfield> | ||||
|         </div> | ||||
|         ${this._interface![version].method === "static" | ||||
|           ? html` | ||||
|               <paper-input | ||||
|         <ha-formfield label="DHCP"> | ||||
|           <ha-radio | ||||
|             @change=${this._handleRadioValueChanged} | ||||
|             value="dhcp" | ||||
|             name="method" | ||||
|             ?checked=${this._device!.data.method === "dhcp"} | ||||
|           > | ||||
|           </ha-radio> | ||||
|         </ha-formfield> | ||||
|         <ha-formfield label="Static"> | ||||
|           <ha-radio | ||||
|             @change=${this._handleRadioValueChanged} | ||||
|             value="static" | ||||
|             name="method" | ||||
|             ?checked=${this._device!.data.method === "static"} | ||||
|           > | ||||
|           </ha-radio> | ||||
|         </ha-formfield> | ||||
|         ${this._device!.data.method !== "dhcp" | ||||
|           ? html` <paper-input | ||||
|                 class="flex-auto" | ||||
|                 id="address" | ||||
|                 id="ip_address" | ||||
|                 label="IP address/Netmask" | ||||
|                 .version=${version} | ||||
|                 .value=${this._toString(this._interface![version].address)} | ||||
|                 .value="${this._device!.data.ip_address}" | ||||
|                 @value-changed=${this._handleInputValueChanged} | ||||
|               > | ||||
|               </paper-input> | ||||
|               ></paper-input> | ||||
|               <paper-input | ||||
|                 class="flex-auto" | ||||
|                 id="gateway" | ||||
|                 label="Gateway address" | ||||
|                 .version=${version} | ||||
|                 .value=${this._interface![version].gateway} | ||||
|                 .value="${this._device!.data.gateway}" | ||||
|                 @value-changed=${this._handleInputValueChanged} | ||||
|               > | ||||
|               </paper-input> | ||||
|               ></paper-input> | ||||
|               <paper-input | ||||
|                 class="flex-auto" | ||||
|                 id="nameservers" | ||||
|                 label="DNS servers" | ||||
|                 .version=${version} | ||||
|                 .value=${this._toString(this._interface![version].nameservers)} | ||||
|                 .value="${this._device!.data.nameservers as string}" | ||||
|                 @value-changed=${this._handleInputValueChanged} | ||||
|               > | ||||
|               </paper-input> | ||||
|             ` | ||||
|               ></paper-input> | ||||
|               NB!: If you are changing IP or gateway addresses, you might lose | ||||
|               the connection.` | ||||
|           : ""} | ||||
|       </ha-expansion-panel> | ||||
|     `; | ||||
|   } | ||||
|  | ||||
|   _toArray(data: string | string[]): string[] { | ||||
|     if (Array.isArray(data)) { | ||||
|       if (data && typeof data[0] === "string") { | ||||
|         data = data[0]; | ||||
|       } | ||||
|     } | ||||
|     if (!data) { | ||||
|       return []; | ||||
|     } | ||||
|     if (typeof data === "string") { | ||||
|       return data.replace(/ /g, "").split(","); | ||||
|     } | ||||
|     return data; | ||||
|   } | ||||
|  | ||||
|   _toString(data: string | string[]): string { | ||||
|     if (!data) { | ||||
|       return ""; | ||||
|     } | ||||
|     if (Array.isArray(data)) { | ||||
|       return data.join(", "); | ||||
|     } | ||||
|     return data; | ||||
|       </div> | ||||
|       <div class="buttons"> | ||||
|         <mwc-button label="close" @click=${this.closeDialog}> </mwc-button> | ||||
|         <mwc-button @click=${this._updateNetwork} ?disabled=${!this._dirty}> | ||||
|           ${this._prosessing | ||||
|             ? html`<ha-circular-progress active></ha-circular-progress>` | ||||
|             : "Update"} | ||||
|         </mwc-button> | ||||
|       </div>`; | ||||
|   } | ||||
|  | ||||
|   private async _updateNetwork() { | ||||
|     this._processing = true; | ||||
|     let interfaceOptions: Partial<NetworkInterface> = {}; | ||||
|  | ||||
|     IP_VERSIONS.forEach((version) => { | ||||
|       interfaceOptions[version] = { | ||||
|         method: this._interface![version]?.method || "auto", | ||||
|     this._prosessing = true; | ||||
|     let options: Partial<NetworkInterface> = { | ||||
|       method: this._device!.data.method, | ||||
|     }; | ||||
|     if (options.method !== "dhcp") { | ||||
|       options = { | ||||
|         ...options, | ||||
|         address: this._device!.data.ip_address, | ||||
|         gateway: this._device!.data.gateway, | ||||
|         dns: String(this._device!.data.nameservers).split(","), | ||||
|       }; | ||||
|       if (this._interface![version]?.method === "static") { | ||||
|         interfaceOptions[version] = { | ||||
|           ...interfaceOptions[version], | ||||
|           address: this._toArray(this._interface![version]?.address), | ||||
|           gateway: this._interface![version]?.gateway, | ||||
|           nameservers: this._toArray(this._interface![version]?.nameservers), | ||||
|         }; | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     if (this._wifiConfiguration) { | ||||
|       interfaceOptions = { | ||||
|         ...interfaceOptions, | ||||
|         wifi: { | ||||
|           ssid: this._wifiConfiguration.ssid, | ||||
|           mode: this._wifiConfiguration.mode, | ||||
|           auth: this._wifiConfiguration.auth || "open", | ||||
|         }, | ||||
|       }; | ||||
|       if (interfaceOptions.wifi!.auth !== "open") { | ||||
|         interfaceOptions.wifi = { | ||||
|           ...interfaceOptions.wifi, | ||||
|           psk: this._wifiConfiguration.psk, | ||||
|         }; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     interfaceOptions.enabled = | ||||
|       this._wifiConfiguration !== undefined || | ||||
|       interfaceOptions.ipv4?.method !== "disabled" || | ||||
|       interfaceOptions.ipv6?.method !== "disabled"; | ||||
|  | ||||
|     try { | ||||
|       await updateNetworkInterface( | ||||
|         this.hass, | ||||
|         this._interface!.interface, | ||||
|         interfaceOptions | ||||
|       ); | ||||
|       await updateNetworkInterface(this.hass, this._device!.interface, options); | ||||
|     } catch (err) { | ||||
|       showAlertDialog(this, { | ||||
|         title: "Failed to change network settings", | ||||
|         text: extractApiErrorMessage(err), | ||||
|       }); | ||||
|       this._processing = false; | ||||
|       this._prosessing = false; | ||||
|       return; | ||||
|     } | ||||
|     this._params?.loadData(); | ||||
| @@ -443,73 +219,40 @@ export class DialogHassioNetwork extends LitElement | ||||
|         dismissText: "no", | ||||
|       }); | ||||
|       if (!confirm) { | ||||
|         this.requestUpdate("_interface"); | ||||
|         this.requestUpdate("_device"); | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|     this._curTabIndex = ev.detail.index; | ||||
|     this._interface = { ...this._interfaces[ev.detail.index] }; | ||||
|     this._device = this._network[ev.detail.index]; | ||||
|     this._device.data.nameservers = String(this._device.data.nameservers); | ||||
|   } | ||||
|  | ||||
|   private _handleRadioValueChanged(ev: CustomEvent): void { | ||||
|     const value = (ev.target as any).value as "disabled" | "auto" | "static"; | ||||
|     const version = (ev.target as any).version as "ipv4" | "ipv6"; | ||||
|     const value = (ev.target as HaRadio).value as "dhcp" | "static"; | ||||
|  | ||||
|     if ( | ||||
|       !value || | ||||
|       !this._interface || | ||||
|       this._interface[version]!.method === value | ||||
|     ) { | ||||
|     if (!value || !this._device || this._device!.data.method === value) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this._dirty = true; | ||||
|  | ||||
|     this._interface[version]!.method = value; | ||||
|     this.requestUpdate("_interface"); | ||||
|   } | ||||
|  | ||||
|   private _handleRadioValueChangedAp(ev: CustomEvent): void { | ||||
|     const value = ((ev.target as any).value as string) as | ||||
|       | "open" | ||||
|       | "wep" | ||||
|       | "wpa-psk"; | ||||
|     this._wifiConfiguration!.auth = value; | ||||
|     this._dirty = true; | ||||
|     this.requestUpdate("_wifiConfiguration"); | ||||
|     this._device!.data.method = value; | ||||
|     this.requestUpdate("_device"); | ||||
|   } | ||||
|  | ||||
|   private _handleInputValueChanged(ev: CustomEvent): void { | ||||
|     const value: string | null | undefined = (ev.target as PaperInputElement) | ||||
|       .value; | ||||
|     const version = (ev.target as any).version as "ipv4" | "ipv6"; | ||||
|     const id = (ev.target as PaperInputElement).id; | ||||
|  | ||||
|     if ( | ||||
|       !value || | ||||
|       !this._interface || | ||||
|       this._toString(this._interface[version]![id]) === this._toString(value) | ||||
|     ) { | ||||
|     if (!value || !this._device || this._device.data[id] === value) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this._dirty = true; | ||||
|     this._interface[version]![id] = value; | ||||
|   } | ||||
|  | ||||
|   private _handleInputValueChangedWifi(ev: CustomEvent): void { | ||||
|     const value: string | null | undefined = (ev.target as PaperInputElement) | ||||
|       .value; | ||||
|     const id = (ev.target as PaperInputElement).id; | ||||
|  | ||||
|     if ( | ||||
|       !value || | ||||
|       !this._wifiConfiguration || | ||||
|       this._wifiConfiguration![id] === value | ||||
|     ) { | ||||
|       return; | ||||
|     } | ||||
|     this._dirty = true; | ||||
|     this._wifiConfiguration![id] = value; | ||||
|     this._device.data[id] = value; | ||||
|   } | ||||
|  | ||||
|   static get styles(): CSSResult[] { | ||||
| @@ -556,16 +299,12 @@ export class DialogHassioNetwork extends LitElement | ||||
|           --mdc-theme-primary: var(--error-color); | ||||
|         } | ||||
|  | ||||
|         mwc-button.scan { | ||||
|           margin-left: 8px; | ||||
|         } | ||||
|  | ||||
|         :host([rtl]) app-toolbar { | ||||
|           direction: rtl; | ||||
|           text-align: right; | ||||
|         } | ||||
|         .container { | ||||
|           padding: 0 8px 4px; | ||||
|           padding: 20px 24px; | ||||
|         } | ||||
|         .form { | ||||
|           margin-bottom: 53px; | ||||
| @@ -583,24 +322,6 @@ export class DialogHassioNetwork extends LitElement | ||||
|           padding-bottom: max(env(safe-area-inset-bottom), 8px); | ||||
|           background-color: var(--mdc-theme-surface, #fff); | ||||
|         } | ||||
|         .warning { | ||||
|           color: var(--error-color); | ||||
|           --primary-color: var(--error-color); | ||||
|         } | ||||
|         div.warning { | ||||
|           margin: 12px 4px -12px; | ||||
|         } | ||||
|  | ||||
|         ha-expansion-panel { | ||||
|           --expansion-panel-summary-padding: 0 16px; | ||||
|           margin: 4px 0; | ||||
|         } | ||||
|         paper-input { | ||||
|           padding: 0 14px; | ||||
|         } | ||||
|         mwc-list-item { | ||||
|           --mdc-list-side-padding: 10px; | ||||
|         } | ||||
|       `, | ||||
|     ]; | ||||
|   } | ||||
|   | ||||
| @@ -13,7 +13,10 @@ export const showNetworkDialog = ( | ||||
| ): void => { | ||||
|   fireEvent(element, "show-dialog", { | ||||
|     dialogTag: "dialog-hassio-network", | ||||
|     dialogImport: () => import("./dialog-hassio-network"), | ||||
|     dialogImport: () => | ||||
|       import( | ||||
|         /* webpackChunkName: "dialog-hassio-network" */ "./dialog-hassio-network" | ||||
|       ), | ||||
|     dialogParams, | ||||
|   }); | ||||
| }; | ||||
|   | ||||
| @@ -4,7 +4,10 @@ import "./dialog-hassio-registries"; | ||||
| export const showRegistriesDialog = (element: HTMLElement): void => { | ||||
|   fireEvent(element, "show-dialog", { | ||||
|     dialogTag: "dialog-hassio-registries", | ||||
|     dialogImport: () => import("./dialog-hassio-registries"), | ||||
|     dialogImport: () => | ||||
|       import( | ||||
|         /* webpackChunkName: "dialog-hassio-registries" */ "./dialog-hassio-registries" | ||||
|       ), | ||||
|     dialogParams: {}, | ||||
|   }); | ||||
| }; | ||||
|   | ||||
| @@ -13,7 +13,10 @@ export const showRepositoriesDialog = ( | ||||
| ): void => { | ||||
|   fireEvent(element, "show-dialog", { | ||||
|     dialogTag: "dialog-hassio-repositories", | ||||
|     dialogImport: () => import("./dialog-hassio-repositories"), | ||||
|     dialogImport: () => | ||||
|       import( | ||||
|         /* webpackChunkName: "dialog-hassio-repositories" */ "./dialog-hassio-repositories" | ||||
|       ), | ||||
|     dialogParams, | ||||
|   }); | ||||
| }; | ||||
|   | ||||
| @@ -22,11 +22,7 @@ import { | ||||
|   fetchHassioSnapshotInfo, | ||||
|   HassioSnapshotDetail, | ||||
| } from "../../../../src/data/hassio/snapshot"; | ||||
| import { Supervisor } from "../../../../src/data/supervisor/supervisor"; | ||||
| import { | ||||
|   showAlertDialog, | ||||
|   showConfirmationDialog, | ||||
| } from "../../../../src/dialogs/generic/show-dialog-box"; | ||||
| import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box"; | ||||
| import { PolymerChangedEvent } from "../../../../src/polymer-types"; | ||||
| import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; | ||||
| import { HomeAssistant } from "../../../../src/types"; | ||||
| @@ -79,8 +75,6 @@ interface FolderItem { | ||||
| class HassioSnapshotDialog extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor?: Supervisor; | ||||
|  | ||||
|   @internalProperty() private _error?: string; | ||||
|  | ||||
|   @internalProperty() private _onboarding = false; | ||||
| @@ -108,7 +102,6 @@ class HassioSnapshotDialog extends LitElement { | ||||
|  | ||||
|     this._dialogParams = params; | ||||
|     this._onboarding = params.onboarding ?? false; | ||||
|     this.supervisor = params.supervisor; | ||||
|   } | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
| @@ -116,7 +109,7 @@ class HassioSnapshotDialog extends LitElement { | ||||
|       return html``; | ||||
|     } | ||||
|     return html` | ||||
|       <ha-dialog open @closing=${this._closeDialog} .heading=${true}> | ||||
|       <ha-dialog open stacked @closing=${this._closeDialog} .heading=${true}> | ||||
|         <div slot="heading"> | ||||
|           <ha-header-bar> | ||||
|             <span slot="title"> | ||||
| @@ -198,37 +191,47 @@ class HassioSnapshotDialog extends LitElement { | ||||
|           : ""} | ||||
|         ${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""} | ||||
|  | ||||
|         <div class="button-row" slot="primaryAction"> | ||||
|           <mwc-button @click=${this._partialRestoreClicked}> | ||||
|             <ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon> | ||||
|             Restore Selected | ||||
|           </mwc-button> | ||||
|           ${!this._onboarding | ||||
|             ? html` | ||||
|                 <mwc-button @click=${this._deleteClicked}> | ||||
|                   <ha-svg-icon .path=${mdiDelete} class="icon warning"> | ||||
|                   </ha-svg-icon> | ||||
|                   <span class="warning">Delete Snapshot</span> | ||||
|                 </mwc-button> | ||||
|               ` | ||||
|             : ""} | ||||
|         </div> | ||||
|         <div class="button-row" slot="secondaryAction"> | ||||
|           ${this._snapshot.type === "full" | ||||
|             ? html` | ||||
|                 <mwc-button @click=${this._fullRestoreClicked}> | ||||
|                   <ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon> | ||||
|                   Restore Everything | ||||
|                 </mwc-button> | ||||
|               ` | ||||
|             : ""} | ||||
|           ${!this._onboarding | ||||
|             ? html`<mwc-button @click=${this._downloadClicked}> | ||||
|                 <ha-svg-icon .path=${mdiDownload} class="icon"></ha-svg-icon> | ||||
|                 Download Snapshot | ||||
|               </mwc-button>` | ||||
|             : ""} | ||||
|         </div> | ||||
|         <div>Actions:</div> | ||||
|         ${!this._onboarding | ||||
|           ? html`<mwc-button | ||||
|               @click=${this._downloadClicked} | ||||
|               slot="primaryAction" | ||||
|             > | ||||
|               <ha-svg-icon .path=${mdiDownload} class="icon"></ha-svg-icon> | ||||
|               Download Snapshot | ||||
|             </mwc-button>` | ||||
|           : ""} | ||||
|  | ||||
|         <mwc-button | ||||
|           @click=${this._partialRestoreClicked} | ||||
|           slot="secondaryAction" | ||||
|         > | ||||
|           <ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon> | ||||
|           Restore Selected | ||||
|         </mwc-button> | ||||
|         ${this._snapshot.type === "full" | ||||
|           ? html` | ||||
|               <mwc-button | ||||
|                 @click=${this._fullRestoreClicked} | ||||
|                 slot="secondaryAction" | ||||
|               > | ||||
|                 <ha-svg-icon .path=${mdiHistory} class="icon"></ha-svg-icon> | ||||
|                 Wipe & restore | ||||
|               </mwc-button> | ||||
|             ` | ||||
|           : ""} | ||||
|         ${!this._onboarding | ||||
|           ? html`<mwc-button | ||||
|               @click=${this._deleteClicked} | ||||
|               slot="secondaryAction" | ||||
|             > | ||||
|               <ha-svg-icon | ||||
|                 .path=${mdiDelete} | ||||
|                 class="icon warning" | ||||
|               ></ha-svg-icon> | ||||
|               <span class="warning">Delete Snapshot</span> | ||||
|             </mwc-button>` | ||||
|           : ""} | ||||
|       </ha-dialog> | ||||
|     `; | ||||
|   } | ||||
| @@ -242,14 +245,6 @@ class HassioSnapshotDialog extends LitElement { | ||||
|           display: block; | ||||
|           margin: 4px; | ||||
|         } | ||||
|         mwc-button ha-svg-icon { | ||||
|           margin-right: 4px; | ||||
|         } | ||||
|         .button-row { | ||||
|           display: grid; | ||||
|           gap: 8px; | ||||
|           margin-right: 8px; | ||||
|         } | ||||
|         .details { | ||||
|           color: var(--secondary-text-color); | ||||
|         } | ||||
| @@ -257,6 +252,10 @@ class HassioSnapshotDialog extends LitElement { | ||||
|         .error { | ||||
|           color: var(--error-color); | ||||
|         } | ||||
|         .buttons { | ||||
|           display: flex; | ||||
|           flex-direction: column; | ||||
|         } | ||||
|         .buttons li { | ||||
|           list-style-type: none; | ||||
|         } | ||||
| @@ -305,16 +304,6 @@ class HassioSnapshotDialog extends LitElement { | ||||
|   } | ||||
|  | ||||
|   private async _partialRestoreClicked() { | ||||
|     if ( | ||||
|       this.supervisor !== undefined && | ||||
|       this.supervisor.info.state !== "running" | ||||
|     ) { | ||||
|       await showAlertDialog(this, { | ||||
|         title: "Could not restore snapshot", | ||||
|         text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`, | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     if ( | ||||
|       !(await showConfirmationDialog(this, { | ||||
|         title: "Are you sure you want partially to restore this snapshot?", | ||||
| @@ -376,16 +365,6 @@ class HassioSnapshotDialog extends LitElement { | ||||
|   } | ||||
|  | ||||
|   private async _fullRestoreClicked() { | ||||
|     if ( | ||||
|       this.supervisor !== undefined && | ||||
|       this.supervisor.info.state !== "running" | ||||
|     ) { | ||||
|       await showAlertDialog(this, { | ||||
|         title: "Could not restore snapshot", | ||||
|         text: `Restoring a snapshot is not possible right now because the system is in ${this.supervisor.info.state} state.`, | ||||
|       }); | ||||
|       return; | ||||
|     } | ||||
|     if ( | ||||
|       !(await showConfirmationDialog(this, { | ||||
|         title: | ||||
|   | ||||
| @@ -1,11 +1,9 @@ | ||||
| import { fireEvent } from "../../../../src/common/dom/fire_event"; | ||||
| import { Supervisor } from "../../../../src/data/supervisor/supervisor"; | ||||
|  | ||||
| export interface HassioSnapshotDialogParams { | ||||
|   slug: string; | ||||
|   onDelete?: () => void; | ||||
|   onboarding?: boolean; | ||||
|   supervisor?: Supervisor; | ||||
| } | ||||
|  | ||||
| export const showHassioSnapshotDialog = ( | ||||
| @@ -14,7 +12,10 @@ export const showHassioSnapshotDialog = ( | ||||
| ): void => { | ||||
|   fireEvent(element, "show-dialog", { | ||||
|     dialogTag: "dialog-hassio-snapshot", | ||||
|     dialogImport: () => import("./dialog-hassio-snapshot"), | ||||
|     dialogImport: () => | ||||
|       import( | ||||
|         /* webpackChunkName: "dialog-hassio-snapshot" */ "./dialog-hassio-snapshot" | ||||
|       ), | ||||
|     dialogParams, | ||||
|   }); | ||||
| }; | ||||
|   | ||||
| @@ -13,7 +13,10 @@ export const showSnapshotUploadDialog = ( | ||||
| ): void => { | ||||
|   fireEvent(element, "show-dialog", { | ||||
|     dialogTag: "dialog-hassio-snapshot-upload", | ||||
|     dialogImport: () => import("./dialog-hassio-snapshot-upload"), | ||||
|     dialogImport: () => | ||||
|       import( | ||||
|         /* webpackChunkName: "dialog-hassio-snapshot-upload" */ "./dialog-hassio-snapshot-upload" | ||||
|       ), | ||||
|     dialogParams, | ||||
|   }); | ||||
| }; | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| // Compat needs to be first import | ||||
| import "../../src/resources/compatibility"; | ||||
| import "../../src/resources/roboto"; | ||||
| import "../../src/resources/safari-14-attachshadow-patch"; | ||||
| import "../../src/resources/roboto"; | ||||
| import "./hassio-main"; | ||||
|  | ||||
| const styleEl = document.createElement("style"); | ||||
|   | ||||
| @@ -1,24 +1,29 @@ | ||||
| import { customElement, html, property, PropertyValues } from "lit-element"; | ||||
| import { atLeastVersion } from "../../src/common/config/version"; | ||||
| import { | ||||
|   html, | ||||
|   PropertyValues, | ||||
|   customElement, | ||||
|   LitElement, | ||||
|   property, | ||||
| } from "lit-element"; | ||||
| import "./hassio-router"; | ||||
| import { urlSyncMixin } from "../../src/state/url-sync-mixin"; | ||||
| import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin"; | ||||
| import { HomeAssistant, Route } from "../../src/types"; | ||||
| import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; | ||||
| import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element"; | ||||
| import { fireEvent } from "../../src/common/dom/fire_event"; | ||||
| import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; | ||||
| import { supervisorStore } from "../../src/data/supervisor/supervisor"; | ||||
| import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; | ||||
| import "../../src/layouts/hass-loading-screen"; | ||||
| import { HomeAssistant, Route } from "../../src/types"; | ||||
| import "./hassio-router"; | ||||
| import { SupervisorBaseElement } from "./supervisor-base-element"; | ||||
| import { atLeastVersion } from "../../src/common/config/version"; | ||||
|  | ||||
| @customElement("hassio-main") | ||||
| export class HassioMain extends SupervisorBaseElement { | ||||
| export class HassioMain extends urlSyncMixin(ProvideHassLitMixin(LitElement)) { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public panel!: HassioPanelInfo; | ||||
|   @property() public panel!: HassioPanelInfo; | ||||
|  | ||||
|   @property({ type: Boolean }) public narrow!: boolean; | ||||
|   @property() public narrow!: boolean; | ||||
|  | ||||
|   @property({ attribute: false }) public route?: Route; | ||||
|   @property() public route?: Route; | ||||
|  | ||||
|   protected firstUpdated(changedProps: PropertyValues) { | ||||
|     super.firstUpdated(changedProps); | ||||
| @@ -72,20 +77,9 @@ export class HassioMain extends SupervisorBaseElement { | ||||
|   } | ||||
|  | ||||
|   protected render() { | ||||
|     if (!this.supervisor || !this.hass) { | ||||
|       return html`<hass-loading-screen></hass-loading-screen>`; | ||||
|     } | ||||
|  | ||||
|     if ( | ||||
|       Object.keys(supervisorStore).some((store) => !this.supervisor![store]) | ||||
|     ) { | ||||
|       return html`<hass-loading-screen></hass-loading-screen>`; | ||||
|     } | ||||
|  | ||||
|     return html` | ||||
|       <hassio-router | ||||
|         .hass=${this.hass} | ||||
|         .supervisor=${this.supervisor} | ||||
|         .route=${this.route} | ||||
|         .panel=${this.panel} | ||||
|         .narrow=${this.narrow} | ||||
|   | ||||
| @@ -1,128 +0,0 @@ | ||||
| import { | ||||
|   customElement, | ||||
|   html, | ||||
|   internalProperty, | ||||
|   LitElement, | ||||
|   property, | ||||
|   TemplateResult, | ||||
| } from "lit-element"; | ||||
| import { sanitizeUrl } from "@braintree/sanitize-url"; | ||||
| import { | ||||
|   createSearchParam, | ||||
|   extractSearchParamsObject, | ||||
| } from "../../src/common/url/search-params"; | ||||
| import "../../src/layouts/hass-error-screen"; | ||||
| import { | ||||
|   ParamType, | ||||
|   Redirect, | ||||
|   Redirects, | ||||
| } from "../../src/panels/my/ha-panel-my"; | ||||
| import { navigate } from "../../src/common/navigate"; | ||||
| import { HomeAssistant, Route } from "../../src/types"; | ||||
|  | ||||
| const REDIRECTS: Redirects = { | ||||
|   supervisor_logs: { | ||||
|     redirect: "/hassio/system", | ||||
|   }, | ||||
|   supervisor_info: { | ||||
|     redirect: "/hassio/system", | ||||
|   }, | ||||
|   supervisor_snapshots: { | ||||
|     redirect: "/hassio/snapshots", | ||||
|   }, | ||||
|   supervisor_store: { | ||||
|     redirect: "/hassio/store", | ||||
|   }, | ||||
|   supervisor: { | ||||
|     redirect: "/hassio/dashboard", | ||||
|   }, | ||||
|   supervisor_addon: { | ||||
|     redirect: "/hassio/addon", | ||||
|     params: { | ||||
|       addon: "string", | ||||
|     }, | ||||
|   }, | ||||
| }; | ||||
|  | ||||
| @customElement("hassio-my-redirect") | ||||
| class HassioMyRedirect extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property() public route!: Route; | ||||
|  | ||||
|   @internalProperty() public _error?: TemplateResult | string; | ||||
|  | ||||
|   connectedCallback() { | ||||
|     super.connectedCallback(); | ||||
|     const path = this.route.path.substr(1); | ||||
|     const redirect = REDIRECTS[path]; | ||||
|  | ||||
|     if (!redirect) { | ||||
|       this._error = html`This redirect is not supported by your Home Assistant | ||||
|         instance. Check the | ||||
|         <a | ||||
|           target="_blank" | ||||
|           rel="noreferrer noopener" | ||||
|           href="https://my.home-assistant.io/faq.html#supported-pages" | ||||
|           >My Home Assistant FAQ</a | ||||
|         > | ||||
|         for the supported redirects and the version they where introduced.`; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let url: string; | ||||
|     try { | ||||
|       url = this._createRedirectUrl(redirect); | ||||
|     } catch (err) { | ||||
|       this._error = "An unknown error occured"; | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     navigate(this, url, true); | ||||
|   } | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     if (this._error) { | ||||
|       return html`<hass-error-screen | ||||
|         .error=${this._error} | ||||
|       ></hass-error-screen>`; | ||||
|     } | ||||
|     return html``; | ||||
|   } | ||||
|  | ||||
|   private _createRedirectUrl(redirect: Redirect): string { | ||||
|     const params = this._createRedirectParams(redirect); | ||||
|     return `${redirect.redirect}${params}`; | ||||
|   } | ||||
|  | ||||
|   private _createRedirectParams(redirect: Redirect): string { | ||||
|     const params = extractSearchParamsObject(); | ||||
|     if (!redirect.params && !Object.keys(params).length) { | ||||
|       return ""; | ||||
|     } | ||||
|     const resultParams = {}; | ||||
|     Object.entries(redirect.params || {}).forEach(([key, type]) => { | ||||
|       if (!params[key] || !this._checkParamType(type, params[key])) { | ||||
|         throw Error(); | ||||
|       } | ||||
|       resultParams[key] = params[key]; | ||||
|     }); | ||||
|     return `?${createSearchParam(resultParams)}`; | ||||
|   } | ||||
|  | ||||
|   private _checkParamType(type: ParamType, value: string) { | ||||
|     if (type === "string") { | ||||
|       return true; | ||||
|     } | ||||
|     if (type === "url") { | ||||
|       return value && value === sanitizeUrl(value); | ||||
|     } | ||||
|     return false; | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   interface HTMLElementTagNameMap { | ||||
|     "hassio-my-redirect": HassioMyRedirect; | ||||
|   } | ||||
| } | ||||
| @@ -1,5 +1,10 @@ | ||||
| import { customElement, property } from "lit-element"; | ||||
| import { Supervisor } from "../../src/data/supervisor/supervisor"; | ||||
| import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host"; | ||||
| import { | ||||
|   HassioHomeAssistantInfo, | ||||
|   HassioSupervisorInfo, | ||||
|   HassioInfo, | ||||
| } from "../../src/data/hassio/supervisor"; | ||||
| import { | ||||
|   HassRouterPage, | ||||
|   RouterOptions, | ||||
| @@ -16,12 +21,20 @@ import "./system/hassio-system"; | ||||
| class HassioPanelRouter extends HassRouterPage { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|  | ||||
|   @property({ attribute: false }) public route!: Route; | ||||
|  | ||||
|   @property({ type: Boolean }) public narrow!: boolean; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisorInfo?: HassioSupervisorInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hassioInfo!: HassioInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hostInfo?: HassioHostInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hassInfo?: HassioHomeAssistantInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo; | ||||
|  | ||||
|   protected routerOptions: RouterOptions = { | ||||
|     routes: { | ||||
|       dashboard: { | ||||
| @@ -41,9 +54,13 @@ class HassioPanelRouter extends HassRouterPage { | ||||
|  | ||||
|   protected updatePageEl(el) { | ||||
|     el.hass = this.hass; | ||||
|     el.supervisor = this.supervisor; | ||||
|     el.route = this.route; | ||||
|     el.narrow = this.narrow; | ||||
|     el.supervisorInfo = this.supervisorInfo; | ||||
|     el.hassioInfo = this.hassioInfo; | ||||
|     el.hostInfo = this.hostInfo; | ||||
|     el.hassInfo = this.hassInfo; | ||||
|     el.hassOsInfo = this.hassOsInfo; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,13 +1,18 @@ | ||||
| import { | ||||
|   css, | ||||
|   CSSResult, | ||||
|   customElement, | ||||
|   html, | ||||
|   LitElement, | ||||
|   property, | ||||
|   TemplateResult, | ||||
|   css, | ||||
|   CSSResult, | ||||
| } from "lit-element"; | ||||
| import { Supervisor } from "../../src/data/supervisor/supervisor"; | ||||
| import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host"; | ||||
| import { | ||||
|   HassioHomeAssistantInfo, | ||||
|   HassioSupervisorInfo, | ||||
|   HassioInfo, | ||||
| } from "../../src/data/hassio/supervisor"; | ||||
| import { HomeAssistant, Route } from "../../src/types"; | ||||
| import "./hassio-panel-router"; | ||||
|  | ||||
| @@ -15,19 +20,34 @@ import "./hassio-panel-router"; | ||||
| class HassioPanel extends LitElement { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|  | ||||
|   @property({ type: Boolean }) public narrow!: boolean; | ||||
|  | ||||
|   @property({ attribute: false }) public route!: Route; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hassioInfo!: HassioInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hostInfo!: HassioHostInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo; | ||||
|  | ||||
|   protected render(): TemplateResult { | ||||
|     if (!this.supervisorInfo) { | ||||
|       return html``; | ||||
|     } | ||||
|     return html` | ||||
|       <hassio-panel-router | ||||
|         .hass=${this.hass} | ||||
|         .supervisor=${this.supervisor} | ||||
|         .route=${this.route} | ||||
|         .hass=${this.hass} | ||||
|         .narrow=${this.narrow} | ||||
|         .supervisorInfo=${this.supervisorInfo} | ||||
|         .hassioInfo=${this.hassioInfo} | ||||
|         .hostInfo=${this.hostInfo} | ||||
|         .hassInfo=${this.hassInfo} | ||||
|         .hassOsInfo=${this.hassOsInfo} | ||||
|       ></hassio-panel-router> | ||||
|     `; | ||||
|   } | ||||
|   | ||||
| @@ -1,6 +1,24 @@ | ||||
| import { customElement, property } from "lit-element"; | ||||
| import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; | ||||
| import { Supervisor } from "../../src/data/supervisor/supervisor"; | ||||
| import { | ||||
|   customElement, | ||||
|   property, | ||||
|   internalProperty, | ||||
|   PropertyValues, | ||||
| } from "lit-element"; | ||||
| import { | ||||
|   fetchHassioHassOsInfo, | ||||
|   fetchHassioHostInfo, | ||||
|   HassioHassOSInfo, | ||||
|   HassioHostInfo, | ||||
| } from "../../src/data/hassio/host"; | ||||
| import { | ||||
|   fetchHassioHomeAssistantInfo, | ||||
|   fetchHassioSupervisorInfo, | ||||
|   fetchHassioInfo, | ||||
|   HassioHomeAssistantInfo, | ||||
|   HassioInfo, | ||||
|   HassioPanelInfo, | ||||
|   HassioSupervisorInfo, | ||||
| } from "../../src/data/hassio/supervisor"; | ||||
| import { | ||||
|   HassRouterPage, | ||||
|   RouterOptions, | ||||
| @@ -14,11 +32,9 @@ import "./hassio-panel"; | ||||
| class HassioRouter extends HassRouterPage { | ||||
|   @property({ attribute: false }) public hass!: HomeAssistant; | ||||
|  | ||||
|   @property({ attribute: false }) public supervisor!: Supervisor; | ||||
|   @property() public panel!: HassioPanelInfo; | ||||
|  | ||||
|   @property({ attribute: false }) public panel!: HassioPanelInfo; | ||||
|  | ||||
|   @property({ type: Boolean }) public narrow!: boolean; | ||||
|   @property() public narrow!: boolean; | ||||
|  | ||||
|   protected routerOptions: RouterOptions = { | ||||
|     // Hass.io has a page with tabs, so we route all non-matching routes to it. | ||||
| @@ -35,43 +51,96 @@ class HassioRouter extends HassRouterPage { | ||||
|       system: "dashboard", | ||||
|       addon: { | ||||
|         tag: "hassio-addon-dashboard", | ||||
|         load: () => import("./addon-view/hassio-addon-dashboard"), | ||||
|         load: () => | ||||
|           import( | ||||
|             /* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard" | ||||
|           ), | ||||
|       }, | ||||
|       ingress: { | ||||
|         tag: "hassio-ingress-view", | ||||
|         load: () => import("./ingress-view/hassio-ingress-view"), | ||||
|       }, | ||||
|       _my_redirect: { | ||||
|         tag: "hassio-my-redirect", | ||||
|         load: () => import("./hassio-my-redirect"), | ||||
|         load: () => | ||||
|           import( | ||||
|             /* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view" | ||||
|           ), | ||||
|       }, | ||||
|     }, | ||||
|   }; | ||||
|  | ||||
|   @internalProperty() private _supervisorInfo?: HassioSupervisorInfo; | ||||
|  | ||||
|   @internalProperty() private _hostInfo?: HassioHostInfo; | ||||
|  | ||||
|   @internalProperty() private _hassioInfo?: HassioInfo; | ||||
|  | ||||
|   @internalProperty() private _hassOsInfo?: HassioHassOSInfo; | ||||
|  | ||||
|   @internalProperty() private _hassInfo?: HassioHomeAssistantInfo; | ||||
|  | ||||
|   protected firstUpdated(changedProps: PropertyValues) { | ||||
|     super.firstUpdated(changedProps); | ||||
|     this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev)); | ||||
|   } | ||||
|  | ||||
|   protected updatePageEl(el) { | ||||
|     // the tabs page does its own routing so needs full route. | ||||
|     const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail; | ||||
|  | ||||
|     el.hass = this.hass; | ||||
|     el.narrow = this.narrow; | ||||
|     el.supervisorInfo = this._supervisorInfo; | ||||
|     el.hassioInfo = this._hassioInfo; | ||||
|     el.hostInfo = this._hostInfo; | ||||
|     el.hassInfo = this._hassInfo; | ||||
|     el.hassOsInfo = this._hassOsInfo; | ||||
|     el.route = route; | ||||
|  | ||||
|     if (el.localName === "hassio-ingress-view") { | ||||
|       el.ingressPanel = this.panel.config && this.panel.config.ingress; | ||||
|     } else { | ||||
|       el.supervisor = this.supervisor; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private async _fetchData() { | ||||
|     if (this.panel.config && this.panel.config.ingress) { | ||||
|       this._redirectIngress(this.panel.config.ingress); | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([ | ||||
|       fetchHassioSupervisorInfo(this.hass), | ||||
|       fetchHassioHostInfo(this.hass), | ||||
|       fetchHassioHomeAssistantInfo(this.hass), | ||||
|       fetchHassioInfo(this.hass), | ||||
|     ]); | ||||
|     this._supervisorInfo = supervisorInfo; | ||||
|     this._hassioInfo = hassioInfo; | ||||
|     this._hostInfo = hostInfo; | ||||
|     this._hassInfo = hassInfo; | ||||
|  | ||||
|     if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) { | ||||
|       this._hassOsInfo = await fetchHassioHassOsInfo(this.hass); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private _redirectIngress(addonSlug: string) { | ||||
|     this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` }; | ||||
|   } | ||||
|  | ||||
|   private _apiCalled(ev) { | ||||
|     if (!ev.detail.success) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let tries = 1; | ||||
|  | ||||
|     const tryUpdate = () => { | ||||
|       this._fetchData().catch(() => { | ||||
|         tries += 1; | ||||
|         setTimeout(tryUpdate, Math.min(tries, 5) * 1000); | ||||
|       }); | ||||
|     }; | ||||
|  | ||||
|     tryUpdate(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| declare global { | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user