mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-10-27 12:39:54 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			20240906.0
			...
			dashboard_
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | ebe5207b6e | 
| @@ -1,25 +1,28 @@ | |||||||
| [modern] | [modern] | ||||||
| # Modern builds target recent browsers supporting the latest features to minimize transpilation, polyfills, etc. | # Support for dynamic import is the main litmus test for serving modern builds. | ||||||
| # It is served to browsers meeting the following requirements: | # Although officially a ES2020 feature, browsers implemented it early, so this | ||||||
| # - released in the last year + current alpha/beta versions | # enables all of ES2017 and some features in ES2018. | ||||||
| # - Firefox extended support release (ESR) | supports es6-module-dynamic-import | ||||||
| # - with global utilization at or above 0.5% |  | ||||||
| # - must support dynamic import of ES modules | # Exclude Safari 11-12 because of a bug in tagged template literals | ||||||
| # - exclude browsers no longer being maintained | # https://bugs.webkit.org/show_bug.cgi?id=190756 | ||||||
| # - exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data | # Note: Dropping version 11 also enables several more ES2018 features | ||||||
| unreleased versions | not Safari < 13 | ||||||
| last 1 year | not iOS < 13 | ||||||
| Firefox ESR |  | ||||||
| >= 0.5% and supports es6-module-dynamic-import | # Exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data | ||||||
| not dead | # Babel ignores these automatically, but we need here for Webpack to output ESM with dynamic imports | ||||||
| not KaiOS > 0 | not KaiOS > 0 | ||||||
| not QQAndroid > 0 | not QQAndroid > 0 | ||||||
| not UCAndroid > 0 | not UCAndroid > 0 | ||||||
|  |  | ||||||
|  | # Exclude unsupported browsers | ||||||
|  | not dead | ||||||
|  |  | ||||||
| [legacy] | [legacy] | ||||||
| # Legacy builds are served when modern requirements are not met and support browsers: | # Legacy builds are served when modern requirements are not met and support browsers: | ||||||
| # - released in the last 7 years + current alpha/beta versionss | # - released in the last 7 years + current alpha/beta versionss | ||||||
| # - with global utilization at or above 0.05% | # - with global utilization above 0.05% | ||||||
| # The lattermost query ensures that support for popular old browsers is not dropped too early | # The lattermost query ensures that support for popular old browsers is not dropped too early | ||||||
| # (e.g. IE 11, Android 4.4, or Samsung 4). | # (e.g. IE 11, Android 4.4, or Samsung 4). | ||||||
| # | # | ||||||
| @@ -33,10 +36,4 @@ not UCAndroid > 0 | |||||||
| # As of May 2023, only web sockets must be added to the query. | # As of May 2023, only web sockets must be added to the query. | ||||||
| unreleased versions | unreleased versions | ||||||
| last 7 years | last 7 years | ||||||
| >= 0.05% and supports websockets | > 0.05% and supports websockets | ||||||
|  |  | ||||||
| [legacy-sw] |  | ||||||
| # Same as legacy plus supports service workers |  | ||||||
| unreleased versions |  | ||||||
| last 7 years |  | ||||||
| >= 0.05% and supports websockets and supports serviceworkers |  | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ | |||||||
|   "postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev", |   "postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev", | ||||||
|   "postStartCommand": "script/bootstrap", |   "postStartCommand": "script/bootstrap", | ||||||
|   "containerEnv": { |   "containerEnv": { | ||||||
|     "DEV_CONTAINER": "1", |  | ||||||
|     "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" |     "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" | ||||||
|   }, |   }, | ||||||
|   "customizations": { |   "customizations": { | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -26,7 +26,7 @@ jobs: | |||||||
|           ref: dev |           ref: dev | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -62,7 +62,7 @@ jobs: | |||||||
|           ref: master |           ref: master | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -26,7 +26,7 @@ jobs: | |||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.1.7 |         uses: actions/checkout@v4.1.7 | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -60,7 +60,7 @@ jobs: | |||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.1.7 |         uses: actions/checkout@v4.1.7 | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -78,7 +78,7 @@ jobs: | |||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.1.7 |         uses: actions/checkout@v4.1.7 | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -89,7 +89,7 @@ jobs: | |||||||
|         env: |         env: | ||||||
|           IS_TEST: "true" |           IS_TEST: "true" | ||||||
|       - name: Upload bundle stats |       - name: Upload bundle stats | ||||||
|         uses: actions/upload-artifact@v4.4.0 |         uses: actions/upload-artifact@v4.3.3 | ||||||
|         with: |         with: | ||||||
|           name: frontend-bundle-stats |           name: frontend-bundle-stats | ||||||
|           path: build/stats/*.json |           path: build/stats/*.json | ||||||
| @@ -102,7 +102,7 @@ jobs: | |||||||
|       - name: Check out files from GitHub |       - name: Check out files from GitHub | ||||||
|         uses: actions/checkout@v4.1.7 |         uses: actions/checkout@v4.1.7 | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -113,7 +113,7 @@ jobs: | |||||||
|         env: |         env: | ||||||
|           IS_TEST: "true" |           IS_TEST: "true" | ||||||
|       - name: Upload bundle stats |       - name: Upload bundle stats | ||||||
|         uses: actions/upload-artifact@v4.4.0 |         uses: actions/upload-artifact@v4.3.3 | ||||||
|         with: |         with: | ||||||
|           name: supervisor-bundle-stats |           name: supervisor-bundle-stats | ||||||
|           path: build/stats/*.json |           path: build/stats/*.json | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -27,7 +27,7 @@ jobs: | |||||||
|           ref: dev |           ref: dev | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -63,7 +63,7 @@ jobs: | |||||||
|           ref: master |           ref: master | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -19,7 +19,7 @@ jobs: | |||||||
|         uses: actions/checkout@v4.1.7 |         uses: actions/checkout@v4.1.7 | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -24,7 +24,7 @@ jobs: | |||||||
|         uses: actions/checkout@v4.1.7 |         uses: actions/checkout@v4.1.7 | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -28,7 +28,7 @@ jobs: | |||||||
|           python-version: ${{ env.PYTHON_VERSION }} |           python-version: ${{ env.PYTHON_VERSION }} | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -57,14 +57,14 @@ jobs: | |||||||
|         run: tar -czvf translations.tar.gz translations |         run: tar -czvf translations.tar.gz translations | ||||||
|  |  | ||||||
|       - name: Upload build artifacts |       - name: Upload build artifacts | ||||||
|         uses: actions/upload-artifact@v4.4.0 |         uses: actions/upload-artifact@v4.3.3 | ||||||
|         with: |         with: | ||||||
|           name: wheels |           name: wheels | ||||||
|           path: dist/home_assistant_frontend*.whl |           path: dist/home_assistant_frontend*.whl | ||||||
|           if-no-files-found: error |           if-no-files-found: error | ||||||
|  |  | ||||||
|       - name: Upload translations |       - name: Upload translations | ||||||
|         uses: actions/upload-artifact@v4.4.0 |         uses: actions/upload-artifact@v4.3.3 | ||||||
|         with: |         with: | ||||||
|           name: translations |           name: translations | ||||||
|           path: translations.tar.gz |           path: translations.tar.gz | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Send bundle stats and build information to RelativeCI |       - name: Send bundle stats and build information to RelativeCI | ||||||
|         uses: relative-ci/agent-action@v2.1.12 |         uses: relative-ci/agent-action@v2.1.11 | ||||||
|         with: |         with: | ||||||
|           key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }} |           key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }} | ||||||
|           token: ${{ github.token }} |           token: ${{ github.token }} | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -34,7 +34,7 @@ jobs: | |||||||
|           python-version: ${{ env.PYTHON_VERSION }} |           python-version: ${{ env.PYTHON_VERSION }} | ||||||
|  |  | ||||||
|       - name: Setup Node |       - name: Setup Node | ||||||
|         uses: actions/setup-node@v4.0.3 |         uses: actions/setup-node@v4.0.2 | ||||||
|         with: |         with: | ||||||
|           node-version-file: ".nvmrc" |           node-version-file: ".nvmrc" | ||||||
|           cache: yarn |           cache: yarn | ||||||
| @@ -55,7 +55,7 @@ jobs: | |||||||
|           script/release |           script/release | ||||||
|  |  | ||||||
|       - name: Upload release assets |       - name: Upload release assets | ||||||
|         uses: softprops/action-gh-release@v2.0.8 |         uses: softprops/action-gh-release@v2.0.6 | ||||||
|         with: |         with: | ||||||
|           files: | |           files: | | ||||||
|             dist/*.whl |             dist/*.whl | ||||||
| @@ -74,9 +74,9 @@ jobs: | |||||||
|           echo "home-assistant-frontend==$version" > ./requirements.txt |           echo "home-assistant-frontend==$version" > ./requirements.txt | ||||||
|  |  | ||||||
|       - name: Build wheels |       - name: Build wheels | ||||||
|         uses: home-assistant/wheels@2024.07.1 |         uses: home-assistant/wheels@2024.01.0 | ||||||
|         with: |         with: | ||||||
|           abi: cp312 |           abi: cp311 | ||||||
|           tag: musllinux_1_2 |           tag: musllinux_1_2 | ||||||
|           arch: amd64 |           arch: amd64 | ||||||
|           wheels-key: ${{ secrets.WHEELS_KEY }} |           wheels-key: ${{ secrets.WHEELS_KEY }} | ||||||
|   | |||||||
| @@ -1 +1,4 @@ | |||||||
|  | #!/usr/bin/env sh | ||||||
|  | . "$(dirname -- "$0")/_/husky.sh" | ||||||
|  |  | ||||||
| yarn run lint-staged --relative --shell "/bin/bash" | yarn run lint-staged --relative --shell "/bin/bash" | ||||||
|   | |||||||
| @@ -1,55 +0,0 @@ | |||||||
| diff --git a/build/inject-manifest.js b/build/inject-manifest.js |  | ||||||
| index 60e3d2bb51c11a19fbbedbad65e101082ec41c36..fed6026630f43f86e25446383982cf6fb694313b 100644 |  | ||||||
| --- a/build/inject-manifest.js |  | ||||||
| +++ b/build/inject-manifest.js |  | ||||||
| @@ -104,7 +104,7 @@ async function injectManifest(config) { |  | ||||||
|              replaceString: manifestString, |  | ||||||
|              searchString: options.injectionPoint, |  | ||||||
|          }); |  | ||||||
| -        filesToWrite[options.swDest] = source; |  | ||||||
| +        filesToWrite[options.swDest] = source.replace(url, encodeURI(upath_1.default.basename(destPath))); |  | ||||||
|          filesToWrite[destPath] = map; |  | ||||||
|      } |  | ||||||
|      else { |  | ||||||
| diff --git a/build/lib/translate-url-to-sourcemap-paths.js b/build/lib/translate-url-to-sourcemap-paths.js |  | ||||||
| index 3220c5474eeac6e8a56ca9b2ac2bd9be48529e43..5f003879a904d4840529a42dd056d288fd213771 100644 |  | ||||||
| --- a/build/lib/translate-url-to-sourcemap-paths.js |  | ||||||
| +++ b/build/lib/translate-url-to-sourcemap-paths.js |  | ||||||
| @@ -22,7 +22,7 @@ function translateURLToSourcemapPaths(url, swSrc, swDest) { |  | ||||||
|          const possibleSrcPath = upath_1.default.resolve(upath_1.default.dirname(swSrc), url); |  | ||||||
|          if (fs_extra_1.default.existsSync(possibleSrcPath)) { |  | ||||||
|              srcPath = possibleSrcPath; |  | ||||||
| -            destPath = upath_1.default.resolve(upath_1.default.dirname(swDest), url); |  | ||||||
| +            destPath = `${swDest}.map`; |  | ||||||
|          } |  | ||||||
|          else { |  | ||||||
|              warning = `${errors_1.errors['cant-find-sourcemap']} ${possibleSrcPath}`; |  | ||||||
| diff --git a/src/inject-manifest.ts b/src/inject-manifest.ts |  | ||||||
| index 8795ddcaa77aea7b0356417e4bc4b19e2b3f860c..fcdc68342d9ac53936c9ed40a9ccfc2f5070cad3 100644 |  | ||||||
| --- a/src/inject-manifest.ts |  | ||||||
| +++ b/src/inject-manifest.ts |  | ||||||
| @@ -129,7 +129,10 @@ export async function injectManifest( |  | ||||||
|        searchString: options.injectionPoint!, |  | ||||||
|      }); |  | ||||||
|   |  | ||||||
| -    filesToWrite[options.swDest] = source; |  | ||||||
| +    filesToWrite[options.swDest] = source.replace( |  | ||||||
| +      url!, |  | ||||||
| +      encodeURI(upath.basename(destPath)), |  | ||||||
| +    ); |  | ||||||
|      filesToWrite[destPath] = map; |  | ||||||
|    } else { |  | ||||||
|      // If there's no sourcemap associated with swSrc, a simple string |  | ||||||
| diff --git a/src/lib/translate-url-to-sourcemap-paths.ts b/src/lib/translate-url-to-sourcemap-paths.ts |  | ||||||
| index 072eac40d4ef5d095a01cb7f7e392a9e034853bd..f0bbe69e88ef3a415de18a7e9cb264daea273d71 100644 |  | ||||||
| --- a/src/lib/translate-url-to-sourcemap-paths.ts |  | ||||||
| +++ b/src/lib/translate-url-to-sourcemap-paths.ts |  | ||||||
| @@ -28,7 +28,7 @@ export function translateURLToSourcemapPaths( |  | ||||||
|      const possibleSrcPath = upath.resolve(upath.dirname(swSrc), url); |  | ||||||
|      if (fse.existsSync(possibleSrcPath)) { |  | ||||||
|        srcPath = possibleSrcPath; |  | ||||||
| -      destPath = upath.resolve(upath.dirname(swDest), url); |  | ||||||
| +      destPath = `${swDest}.map`; |  | ||||||
|      } else { |  | ||||||
|        warning = `${errors['cant-find-sourcemap']} ${possibleSrcPath}`; |  | ||||||
|      } |  | ||||||
							
								
								
									
										894
									
								
								.yarn/releases/yarn-4.3.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										894
									
								
								.yarn/releases/yarn-4.3.1.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										925
									
								
								.yarn/releases/yarn-4.4.1.cjs
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										925
									
								
								.yarn/releases/yarn-4.4.1.cjs
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -6,4 +6,4 @@ enableGlobalCache: false | |||||||
|  |  | ||||||
| nodeLinker: node-modules | nodeLinker: node-modules | ||||||
|  |  | ||||||
| yarnPath: .yarn/releases/yarn-4.4.1.cjs | yarnPath: .yarn/releases/yarn-4.3.1.cjs | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) => | |||||||
|  |  | ||||||
| module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ | module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ | ||||||
|   __DEV__: !isProdBuild, |   __DEV__: !isProdBuild, | ||||||
|   __BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"), |   __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), | ||||||
|   __VERSION__: JSON.stringify(env.version()), |   __VERSION__: JSON.stringify(env.version()), | ||||||
|   __DEMO__: false, |   __DEMO__: false, | ||||||
|   __SUPERVISOR__: false, |   __SUPERVISOR__: false, | ||||||
| @@ -79,12 +79,7 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({ | |||||||
|   sourceMap: !isTestBuild, |   sourceMap: !isTestBuild, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| module.exports.babelOptions = ({ | module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ | ||||||
|   latestBuild, |  | ||||||
|   isProdBuild, |  | ||||||
|   isTestBuild, |  | ||||||
|   sw, |  | ||||||
| }) => ({ |  | ||||||
|   babelrc: false, |   babelrc: false, | ||||||
|   compact: false, |   compact: false, | ||||||
|   assumptions: { |   assumptions: { | ||||||
| @@ -92,7 +87,7 @@ module.exports.babelOptions = ({ | |||||||
|     setPublicClassFields: true, |     setPublicClassFields: true, | ||||||
|     setSpreadProperties: true, |     setSpreadProperties: true, | ||||||
|   }, |   }, | ||||||
|   browserslistEnv: latestBuild ? "modern" : `legacy${sw ? "-sw" : ""}`, |   browserslistEnv: latestBuild ? "modern" : "legacy", | ||||||
|   presets: [ |   presets: [ | ||||||
|     [ |     [ | ||||||
|       "@babel/preset-env", |       "@babel/preset-env", | ||||||
| @@ -140,14 +135,8 @@ module.exports.babelOptions = ({ | |||||||
|       "@babel/plugin-transform-runtime", |       "@babel/plugin-transform-runtime", | ||||||
|       { version: dependencies["@babel/runtime"] }, |       { version: dependencies["@babel/runtime"] }, | ||||||
|     ], |     ], | ||||||
|     // Transpile decorators (still in TC39 process) |     // Support  some proposals still in TC39 process | ||||||
|     // Modern browsers support class fields and private methods, but transform is required with the older decorator version dictated by Lit |     ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], | ||||||
|     [ |  | ||||||
|       "@babel/plugin-proposal-decorators", |  | ||||||
|       { version: "2018-09", decoratorsBeforeExport: true }, |  | ||||||
|     ], |  | ||||||
|     "@babel/plugin-transform-class-properties", |  | ||||||
|     "@babel/plugin-transform-private-methods", |  | ||||||
|   ].filter(Boolean), |   ].filter(Boolean), | ||||||
|   exclude: [ |   exclude: [ | ||||||
|     // \\ for Windows, / for Mac OS and Linux |     // \\ for Windows, / for Mac OS and Linux | ||||||
| @@ -226,13 +215,7 @@ module.exports.config = { | |||||||
|     return { |     return { | ||||||
|       name: "frontend" + nameSuffix(latestBuild), |       name: "frontend" + nameSuffix(latestBuild), | ||||||
|       entry: { |       entry: { | ||||||
|         "service-worker": |         service_worker: "./src/entrypoints/service_worker.ts", | ||||||
|           !env.useRollup() && !latestBuild |  | ||||||
|             ? { |  | ||||||
|                 import: "./src/entrypoints/service-worker.ts", |  | ||||||
|                 layer: "sw", |  | ||||||
|               } |  | ||||||
|             : "./src/entrypoints/service-worker.ts", |  | ||||||
|         app: "./src/entrypoints/app.ts", |         app: "./src/entrypoints/app.ts", | ||||||
|         authorize: "./src/entrypoints/authorize.ts", |         authorize: "./src/entrypoints/authorize.ts", | ||||||
|         onboarding: "./src/entrypoints/onboarding.ts", |         onboarding: "./src/entrypoints/onboarding.ts", | ||||||
|   | |||||||
| @@ -32,7 +32,4 @@ module.exports = { | |||||||
|     } |     } | ||||||
|     return version[1]; |     return version[1]; | ||||||
|   }, |   }, | ||||||
|   isDevContainer() { |  | ||||||
|     return process.env.DEV_CONTAINER === "1"; |  | ||||||
|   }, |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,68 +1,19 @@ | |||||||
| // Tasks to compress | // Tasks to compress | ||||||
|  |  | ||||||
| import { constants } from "node:zlib"; |  | ||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
| import brotli from "gulp-brotli"; |  | ||||||
| import zopfli from "gulp-zopfli-green"; | import zopfli from "gulp-zopfli-green"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
|  |  | ||||||
| const filesGlob = "*.{js,json,css,svg,xml}"; |  | ||||||
| const brotliOptions = { |  | ||||||
|   skipLarger: true, |  | ||||||
|   params: { |  | ||||||
|     [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY, |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
| const zopfliOptions = { threshold: 150 }; | const zopfliOptions = { threshold: 150 }; | ||||||
|  |  | ||||||
| const compressDistBrotli = (rootDir, modernDir, compressServiceWorker = true) => | const compressDist = (rootDir) => | ||||||
|   gulp |   gulp | ||||||
|     .src( |     .src([ | ||||||
|       [ |       `${rootDir}/**/*.{js,json,css,svg,xml}`, | ||||||
|         `${modernDir}/**/${filesGlob}`, |  | ||||||
|         compressServiceWorker ? `${rootDir}/sw-modern.js` : undefined, |  | ||||||
|       ].filter(Boolean), |  | ||||||
|       { |  | ||||||
|         base: rootDir, |  | ||||||
|       } |  | ||||||
|     ) |  | ||||||
|     .pipe(brotli(brotliOptions)) |  | ||||||
|     .pipe(gulp.dest(rootDir)); |  | ||||||
|  |  | ||||||
| const compressDistZopfli = (rootDir, modernDir, compressModern = false) => |  | ||||||
|   gulp |  | ||||||
|     .src( |  | ||||||
|       [ |  | ||||||
|         `${rootDir}/**/${filesGlob}`, |  | ||||||
|         compressModern ? undefined : `!${modernDir}/**/${filesGlob}`, |  | ||||||
|         `!${rootDir}/{sw-modern,service_worker}.js`, |  | ||||||
|       `${rootDir}/{authorize,onboarding}.html`, |       `${rootDir}/{authorize,onboarding}.html`, | ||||||
|       ].filter(Boolean), |     ]) | ||||||
|       { base: rootDir } |  | ||||||
|     ) |  | ||||||
|     .pipe(zopfli(zopfliOptions)) |     .pipe(zopfli(zopfliOptions)) | ||||||
|     .pipe(gulp.dest(rootDir)); |     .pipe(gulp.dest(rootDir)); | ||||||
|  |  | ||||||
| const compressAppBrotli = () => | gulp.task("compress-app", () => compressDist(paths.app_output_root)); | ||||||
|   compressDistBrotli(paths.app_output_root, paths.app_output_latest); | gulp.task("compress-hassio", () => compressDist(paths.hassio_output_root)); | ||||||
| const compressHassioBrotli = () => |  | ||||||
|   compressDistBrotli( |  | ||||||
|     paths.hassio_output_root, |  | ||||||
|     paths.hassio_output_latest, |  | ||||||
|     false |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
| const compressAppZopfli = () => |  | ||||||
|   compressDistZopfli(paths.app_output_root, paths.app_output_latest); |  | ||||||
| const compressHassioZopfli = () => |  | ||||||
|   compressDistZopfli( |  | ||||||
|     paths.hassio_output_root, |  | ||||||
|     paths.hassio_output_latest, |  | ||||||
|     true |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
| gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli)); |  | ||||||
| gulp.task( |  | ||||||
|   "compress-hassio", |  | ||||||
|   gulp.parallel(compressHassioBrotli, compressHassioZopfli) |  | ||||||
| ); |  | ||||||
|   | |||||||
| @@ -1,76 +1,28 @@ | |||||||
| // Tasks to generate entry HTML | // Tasks to generate entry HTML | ||||||
|  |  | ||||||
| import { |  | ||||||
|   applyVersionsToRegexes, |  | ||||||
|   compileRegex, |  | ||||||
|   getPreUserAgentRegexes, |  | ||||||
| } from "browserslist-useragent-regexp"; |  | ||||||
| import fs from "fs-extra"; | import fs from "fs-extra"; | ||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
| import { minify } from "html-minifier-terser"; | import { minify } from "html-minifier-terser"; | ||||||
| import template from "lodash.template"; | import template from "lodash.template"; | ||||||
| import { dirname, extname, resolve } from "node:path"; | import path from "path"; | ||||||
| import { htmlMinifierOptions, terserOptions } from "../bundle.cjs"; | import { htmlMinifierOptions, terserOptions } from "../bundle.cjs"; | ||||||
| import env from "../env.cjs"; | import env from "../env.cjs"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
|  |  | ||||||
| // macOS companion app has no way to obtain the Safari version used by WKWebView, |  | ||||||
| // and it is not in the default user agent string. So we add an additional regex |  | ||||||
| // to serve modern based on a minimum macOS version. We take the minimum Safari |  | ||||||
| // major version from browserslist and manually map that to a supported macOS |  | ||||||
| // version. Note this assumes the user has kept Safari updated. |  | ||||||
| const HA_MACOS_REGEX = |  | ||||||
|   /Home Assistant\/[\d.]+ \(.+; macOS (\d+)\.(\d+)(?:\.(\d+))?\)/; |  | ||||||
| const SAFARI_TO_MACOS = { |  | ||||||
|   15: [10, 15, 0], |  | ||||||
|   16: [11, 0, 0], |  | ||||||
|   17: [12, 0, 0], |  | ||||||
|   18: [13, 0, 0], |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const getCommonTemplateVars = () => { |  | ||||||
|   const browserRegexes = getPreUserAgentRegexes({ |  | ||||||
|     env: "modern", |  | ||||||
|     allowHigherVersions: true, |  | ||||||
|     mobileToDesktop: true, |  | ||||||
|     throwOnMissing: true, |  | ||||||
|   }); |  | ||||||
|   const minSafariVersion = browserRegexes.find( |  | ||||||
|     (regex) => regex.family === "safari" |  | ||||||
|   )?.matchedVersions[0][0]; |  | ||||||
|   const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion]; |  | ||||||
|   if (!minMacOSVersion) { |  | ||||||
|     throw Error( |  | ||||||
|       `Could not find minimum MacOS version for Safari ${minSafariVersion}.` |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|   const haMacOSRegex = applyVersionsToRegexes( |  | ||||||
|     [ |  | ||||||
|       { |  | ||||||
|         family: "ha_macos", |  | ||||||
|         regex: HA_MACOS_REGEX, |  | ||||||
|         matchedVersions: [minMacOSVersion], |  | ||||||
|         requestVersions: [minMacOSVersion], |  | ||||||
|       }, |  | ||||||
|     ], |  | ||||||
|     { ignorePatch: true, allowHigherVersions: true } |  | ||||||
|   ); |  | ||||||
|   return { |  | ||||||
|     useRollup: env.useRollup(), |  | ||||||
|     useWDS: env.useWDS(), |  | ||||||
|     modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(), |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const renderTemplate = (templateFile, data = {}) => { | const renderTemplate = (templateFile, data = {}) => { | ||||||
|   const compiled = template( |   const compiled = template( | ||||||
|     fs.readFileSync(templateFile, { encoding: "utf-8" }) |     fs.readFileSync(templateFile, { encoding: "utf-8" }) | ||||||
|   ); |   ); | ||||||
|   return compiled({ |   return compiled({ | ||||||
|     ...data, |     ...data, | ||||||
|  |     useRollup: env.useRollup(), | ||||||
|  |     useWDS: env.useWDS(), | ||||||
|     // Resolve any child/nested templates relative to the parent and pass the same data |     // Resolve any child/nested templates relative to the parent and pass the same data | ||||||
|     renderTemplate: (childTemplate) => |     renderTemplate: (childTemplate) => | ||||||
|       renderTemplate(resolve(dirname(templateFile), childTemplate), data), |       renderTemplate( | ||||||
|  |         path.resolve(path.dirname(templateFile), childTemplate), | ||||||
|  |         data | ||||||
|  |       ), | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -104,12 +56,10 @@ const genPagesDevTask = | |||||||
|     publicRoot = "" |     publicRoot = "" | ||||||
|   ) => |   ) => | ||||||
|   async () => { |   async () => { | ||||||
|     const commonVars = getCommonTemplateVars(); |  | ||||||
|     for (const [page, entries] of Object.entries(pageEntries)) { |     for (const [page, entries] of Object.entries(pageEntries)) { | ||||||
|       const content = renderTemplate( |       const content = renderTemplate( | ||||||
|         resolve(inputRoot, inputSub, `${page}.template`), |         path.resolve(inputRoot, inputSub, `${page}.template`), | ||||||
|         { |         { | ||||||
|           ...commonVars, |  | ||||||
|           latestEntryJS: entries.map((entry) => |           latestEntryJS: entries.map((entry) => | ||||||
|             useWDS |             useWDS | ||||||
|               ? `http://localhost:8000/src/entrypoints/${entry}.ts` |               ? `http://localhost:8000/src/entrypoints/${entry}.ts` | ||||||
| @@ -124,7 +74,7 @@ const genPagesDevTask = | |||||||
|           es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`, |           es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`, | ||||||
|         } |         } | ||||||
|       ); |       ); | ||||||
|       fs.outputFileSync(resolve(outputRoot, page), content); |       fs.outputFileSync(path.resolve(outputRoot, page), content); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
| @@ -141,18 +91,16 @@ const genPagesProdTask = | |||||||
|   ) => |   ) => | ||||||
|   async () => { |   async () => { | ||||||
|     const latestManifest = fs.readJsonSync( |     const latestManifest = fs.readJsonSync( | ||||||
|       resolve(outputLatest, "manifest.json") |       path.resolve(outputLatest, "manifest.json") | ||||||
|     ); |     ); | ||||||
|     const es5Manifest = outputES5 |     const es5Manifest = outputES5 | ||||||
|       ? fs.readJsonSync(resolve(outputES5, "manifest.json")) |       ? fs.readJsonSync(path.resolve(outputES5, "manifest.json")) | ||||||
|       : {}; |       : {}; | ||||||
|     const commonVars = getCommonTemplateVars(); |  | ||||||
|     const minifiedHTML = []; |     const minifiedHTML = []; | ||||||
|     for (const [page, entries] of Object.entries(pageEntries)) { |     for (const [page, entries] of Object.entries(pageEntries)) { | ||||||
|       const content = renderTemplate( |       const content = renderTemplate( | ||||||
|         resolve(inputRoot, inputSub, `${page}.template`), |         path.resolve(inputRoot, inputSub, `${page}.template`), | ||||||
|         { |         { | ||||||
|           ...commonVars, |  | ||||||
|           latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]), |           latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]), | ||||||
|           es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]), |           es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]), | ||||||
|           latestCustomPanelJS: latestManifest["custom-panel.js"], |           latestCustomPanelJS: latestManifest["custom-panel.js"], | ||||||
| @@ -160,8 +108,8 @@ const genPagesProdTask = | |||||||
|         } |         } | ||||||
|       ); |       ); | ||||||
|       minifiedHTML.push( |       minifiedHTML.push( | ||||||
|         minifyHtml(content, extname(page)).then((minified) => |         minifyHtml(content, path.extname(page)).then((minified) => | ||||||
|           fs.outputFileSync(resolve(outputRoot, page), minified) |           fs.outputFileSync(path.resolve(outputRoot, page), minified) | ||||||
|         ) |         ) | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,18 +1,19 @@ | |||||||
| // Generate service workers | // Generate service worker. | ||||||
|  | // Based on manifest, create a file with the content as service_worker.js | ||||||
|  |  | ||||||
| import { deleteAsync } from "del"; | import fs from "fs-extra"; | ||||||
| import gulp from "gulp"; | import gulp from "gulp"; | ||||||
| import { mkdir, readFile, symlink, writeFile } from "node:fs/promises"; | import path from "path"; | ||||||
| import { basename, join, relative } from "node:path"; | import sourceMapUrl from "source-map-url"; | ||||||
| import { injectManifest } from "workbox-build"; | import workboxBuild from "workbox-build"; | ||||||
| import paths from "../paths.cjs"; | import paths from "../paths.cjs"; | ||||||
|  |  | ||||||
| const SW_MAP = { | const swDest = path.resolve(paths.app_output_root, "service_worker.js"); | ||||||
|   [paths.app_output_latest]: "modern", |  | ||||||
|   [paths.app_output_es5]: "legacy", |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| const SW_DEV = | const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n"); | ||||||
|  |  | ||||||
|  | gulp.task("gen-service-worker-app-dev", (done) => { | ||||||
|  |   writeSW( | ||||||
|     ` |     ` | ||||||
| console.debug('Service worker disabled in development'); | console.debug('Service worker disabled in development'); | ||||||
|  |  | ||||||
| @@ -21,40 +22,46 @@ self.addEventListener('install', (event) => { | |||||||
|   // removing any prod service worker the dev might have running |   // removing any prod service worker the dev might have running | ||||||
|   self.skipWaiting(); |   self.skipWaiting(); | ||||||
| }); | }); | ||||||
|   `.trim() + "\n"; |   ` | ||||||
|  |  | ||||||
| gulp.task("gen-service-worker-app-dev", async () => { |  | ||||||
|   await mkdir(paths.app_output_root, { recursive: true }); |  | ||||||
|   await Promise.all( |  | ||||||
|     Object.values(SW_MAP).map((build) => |  | ||||||
|       writeFile(join(paths.app_output_root, `sw-${build}.js`), SW_DEV, { |  | ||||||
|         encoding: "utf-8", |  | ||||||
|       }) |  | ||||||
|     ) |  | ||||||
|   ); |   ); | ||||||
|  |   done(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| gulp.task("gen-service-worker-app-prod", () => | gulp.task("gen-service-worker-app-prod", async () => { | ||||||
|   Promise.all( |   // Read bundled source file | ||||||
|     Object.entries(SW_MAP).map(async ([outPath, build]) => { |   const bundleManifestLatest = fs.readJsonSync( | ||||||
|       const manifest = JSON.parse( |     path.resolve(paths.app_output_latest, "manifest.json") | ||||||
|         await readFile(join(outPath, "manifest.json"), "utf-8") |  | ||||||
|   ); |   ); | ||||||
|       const swSrc = join(paths.app_output_root, manifest["service-worker.js"]); |   let serviceWorkerContent = fs.readFileSync( | ||||||
|       const swDest = join(paths.app_output_root, `sw-${build}.js`); |     paths.app_output_root + bundleManifestLatest["service_worker.js"], | ||||||
|       const buildDir = relative(paths.app_output_root, outPath); |     "utf-8" | ||||||
|       const { warnings } = await injectManifest({ |   ); | ||||||
|         swSrc, |  | ||||||
|         swDest, |   // Delete old file from frontend_latest so manifest won't pick it up | ||||||
|         injectionPoint: "__WB_MANIFEST__", |   fs.removeSync( | ||||||
|  |     paths.app_output_root + bundleManifestLatest["service_worker.js"] | ||||||
|  |   ); | ||||||
|  |   fs.removeSync( | ||||||
|  |     paths.app_output_root + bundleManifestLatest["service_worker.js.map"] | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   // Remove ES5 | ||||||
|  |   const bundleManifestES5 = fs.readJsonSync( | ||||||
|  |     path.resolve(paths.app_output_es5, "manifest.json") | ||||||
|  |   ); | ||||||
|  |   fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]); | ||||||
|  |   fs.removeSync( | ||||||
|  |     paths.app_output_root + bundleManifestES5["service_worker.js.map"] | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   const workboxManifest = await workboxBuild.getManifest({ | ||||||
|     // Files that mach this pattern will be considered unique and skip revision check |     // Files that mach this pattern will be considered unique and skip revision check | ||||||
|     // ignore JS files + translation files |     // ignore JS files + translation files | ||||||
|         dontCacheBustURLsMatching: new RegExp( |     dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/, | ||||||
|           `(?:${buildDir}/.+|static/translations/.+)` |  | ||||||
|         ), |  | ||||||
|     globDirectory: paths.app_output_root, |     globDirectory: paths.app_output_root, | ||||||
|     globPatterns: [ |     globPatterns: [ | ||||||
|           `${buildDir}/*.js`, |       "frontend_latest/*.js", | ||||||
|       // Cache all English translations because we catch them as fallback |       // Cache all English translations because we catch them as fallback | ||||||
|       // Using pattern to match hash instead of * to avoid caching en-GB |       // Using pattern to match hash instead of * to avoid caching en-GB | ||||||
|       // 'v' added as valid hash letter because in dev we hash with 'dev' |       // 'v' added as valid hash letter because in dev we hash with 'dev' | ||||||
| @@ -68,20 +75,19 @@ gulp.task("gen-service-worker-app-prod", () => | |||||||
|       "static/fonts/roboto/Roboto-Regular.woff2", |       "static/fonts/roboto/Roboto-Regular.woff2", | ||||||
|       "static/fonts/roboto/Roboto-Bold.woff2", |       "static/fonts/roboto/Roboto-Bold.woff2", | ||||||
|     ], |     ], | ||||||
|         globIgnores: [`${buildDir}/service-worker*`], |  | ||||||
|   }); |   }); | ||||||
|       if (warnings.length > 0) { |  | ||||||
|         console.warn( |   for (const warning of workboxManifest.warnings) { | ||||||
|           `Problems while injecting ${build} service worker:\n`, |     console.warn(warning); | ||||||
|           warnings.join("\n") |  | ||||||
|         ); |  | ||||||
|   } |   } | ||||||
|       await deleteAsync(`${swSrc}?(.map)`); |  | ||||||
|       // Needed to install new SW from a cached HTML |   // remove source map and add WB manifest | ||||||
|       if (build === "modern") { |   serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent); | ||||||
|         const swOld = join(paths.app_output_root, "service_worker.js"); |   serviceWorkerContent = serviceWorkerContent.replace( | ||||||
|         await symlink(basename(swDest), swOld); |     "WB_MANIFEST", | ||||||
|       } |     JSON.stringify(workboxManifest.manifestEntries) | ||||||
|     }) |  | ||||||
|   ) |  | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   // Write new file to root | ||||||
|  |   fs.writeFileSync(swDest, serviceWorkerContent); | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -244,11 +244,11 @@ const createTranslations = async () => { | |||||||
|   // TODO: This is a naive interpretation of BCP47 that should be improved. |   // TODO: This is a naive interpretation of BCP47 that should be improved. | ||||||
|   //       Will be OK for now as long as we don't have anything more complicated |   //       Will be OK for now as long as we don't have anything more complicated | ||||||
|   // than a base translation + region. |   // than a base translation + region. | ||||||
|   const masterStream = gulp |   gulp | ||||||
|     .src(`${workDir}/en.json`) |     .src(`${workDir}/en.json`) | ||||||
|     .pipe(new PassThrough({ objectMode: true })); |     .pipe(new PassThrough({ objectMode: true })) | ||||||
|   masterStream.pipe(hashStream, { end: false }); |     .pipe(hashStream, { end: false }); | ||||||
|   const mergesFinished = [finished(masterStream)]; |   const mergesFinished = []; | ||||||
|   for (const translationFile of translationFiles) { |   for (const translationFile of translationFiles) { | ||||||
|     const locale = basename(translationFile, ".json"); |     const locale = basename(translationFile, ".json"); | ||||||
|     const subtags = locale.split("-"); |     const subtags = locale.split("-"); | ||||||
|   | |||||||
| @@ -40,12 +40,8 @@ const runDevServer = async ({ | |||||||
|   compiler, |   compiler, | ||||||
|   contentBase, |   contentBase, | ||||||
|   port, |   port, | ||||||
|   listenHost = undefined, |   listenHost = "localhost", | ||||||
| }) => { | }) => { | ||||||
|   if (listenHost === undefined) { |  | ||||||
|     // For dev container, we need to listen on all hosts |  | ||||||
|     listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost"; |  | ||||||
|   } |  | ||||||
|   const server = new WebpackDevServer( |   const server = new WebpackDevServer( | ||||||
|     { |     { | ||||||
|       hot: false, |       hot: false, | ||||||
|   | |||||||
| @@ -63,25 +63,17 @@ const createWebpackConfig = ({ | |||||||
|       rules: [ |       rules: [ | ||||||
|         { |         { | ||||||
|           test: /\.m?js$|\.ts$/, |           test: /\.m?js$|\.ts$/, | ||||||
|           use: (info) => ({ |           use: { | ||||||
|             loader: "babel-loader", |             loader: "babel-loader", | ||||||
|             options: { |             options: { | ||||||
|               ...bundle.babelOptions({ |               ...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }), | ||||||
|                 latestBuild, |  | ||||||
|                 isProdBuild, |  | ||||||
|                 isTestBuild, |  | ||||||
|                 sw: info.issuerLayer === "sw", |  | ||||||
|               }), |  | ||||||
|               cacheDirectory: !isProdBuild, |               cacheDirectory: !isProdBuild, | ||||||
|               cacheCompression: false, |               cacheCompression: false, | ||||||
|             }, |             }, | ||||||
|           }), |           }, | ||||||
|           resolve: { |           resolve: { | ||||||
|             fullySpecified: false, |             fullySpecified: false, | ||||||
|           }, |           }, | ||||||
|           parser: { |  | ||||||
|             worker: ["*context.audioWorklet.addModule()", "..."], |  | ||||||
|           }, |  | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           test: /\.css$/, |           test: /\.css$/, | ||||||
| @@ -100,15 +92,11 @@ const createWebpackConfig = ({ | |||||||
|       moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", |       moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", | ||||||
|       chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", |       chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named", | ||||||
|       splitChunks: { |       splitChunks: { | ||||||
|         // Disable splitting for web workers and worklets because imports of |         // Disable splitting for web workers with ESM output | ||||||
|         // external chunks are broken for: |         // Imports of external chunks are broken | ||||||
|         // - ESM output: https://github.com/webpack/webpack/issues/17014 |         chunks: latestBuild | ||||||
|         // - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543 |           ? (chunk) => !chunk.canBeInitial() && !/^.+-worker$/.test(chunk.name) | ||||||
|         chunks: (chunk) => |           : undefined, | ||||||
|           !chunk.canBeInitial() && |  | ||||||
|           !new RegExp(`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`).test( |  | ||||||
|             chunk.name |  | ||||||
|           ), |  | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|     plugins: [ |     plugins: [ | ||||||
| @@ -240,7 +228,6 @@ const createWebpackConfig = ({ | |||||||
|       ), |       ), | ||||||
|     }, |     }, | ||||||
|     experiments: { |     experiments: { | ||||||
|       layers: true, |  | ||||||
|       outputModule: true, |       outputModule: true, | ||||||
|     }, |     }, | ||||||
|   }; |   }; | ||||||
|   | |||||||
| @@ -1,5 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
|  |  | ||||||
| self.addEventListener("fetch", (event) => { |  | ||||||
|   event.respondWith(fetch(event.request)); |  | ||||||
| }); |  | ||||||
| @@ -36,7 +36,13 @@ | |||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> |     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||||
|     <%= renderTemplate("../../../src/html/_script_loader.html.template") %> |     <script> | ||||||
|  |       <% for (const entry of latestEntryJS) { %> | ||||||
|  |         import("<%= entry %>"); | ||||||
|  |       <% } %> | ||||||
|  |       window.latestJS = true; | ||||||
|  |     </script> | ||||||
|  |     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||||
|     <hc-layout subtitle="FAQ"> |     <hc-layout subtitle="FAQ"> | ||||||
|       <style> |       <style> | ||||||
|         a { |         a { | ||||||
| @@ -226,5 +232,17 @@ http: | |||||||
|         </p> |         </p> | ||||||
|       </div> |       </div> | ||||||
|     </hc-layout> |     </hc-layout> | ||||||
|  |  | ||||||
|  |     <script> | ||||||
|  |       var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]]; | ||||||
|  |       (function (d, t) { | ||||||
|  |         var g = d.createElement(t), | ||||||
|  |           s = d.getElementsByTagName(t)[0]; | ||||||
|  |         g.src = | ||||||
|  |           ("https:" == location.protocol ? "//ssl" : "//www") + | ||||||
|  |           ".google-analytics.com/ga.js"; | ||||||
|  |         s.parentNode.insertBefore(g, s); | ||||||
|  |       })(document, "script"); | ||||||
|  |     </script> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -13,9 +13,15 @@ | |||||||
|     <%= renderTemplate("_social_meta.html.template") %> |     <%= renderTemplate("_social_meta.html.template") %> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <hc-connect></hc-connect> |  | ||||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> |     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||||
|     <%= renderTemplate("../../../src/html/_script_loader.html.template") %> |     <hc-connect></hc-connect> | ||||||
|  |     <script> | ||||||
|  |       <% for (const entry of latestEntryJS) { %> | ||||||
|  |         import("<%= entry %>"); | ||||||
|  |       <% } %> | ||||||
|  |       window.latestJS = true; | ||||||
|  |     </script> | ||||||
|  |     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||||
|     <script> |     <script> | ||||||
|     (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |     (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||||||
|     (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |     (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||||||
|   | |||||||
| @@ -14,10 +14,22 @@ | |||||||
|         --background-color: #41bdf5; |         --background-color: #41bdf5; | ||||||
|       } |       } | ||||||
|     </style> |     </style> | ||||||
|  |     <script> | ||||||
|  |       var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']]; | ||||||
|  |       (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0]; | ||||||
|  |       g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js'; | ||||||
|  |       s.parentNode.insertBefore(g,s)}(document,'script')); | ||||||
|  |     </script> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <cast-media-player></cast-media-player> |  | ||||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> |     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||||
|     <%= renderTemplate("../../../src/html/_script_loader.html.template") %> |     <cast-media-player></cast-media-player> | ||||||
|  |     <script> | ||||||
|  |       <% for (const entry of latestEntryJS) { %> | ||||||
|  |         import("<%= entry %>"); | ||||||
|  |       <% } %> | ||||||
|  |       window.latestJS = true; | ||||||
|  |     </script> | ||||||
|  |     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -11,4 +11,10 @@ | |||||||
|       font-size: initial; |       font-size: initial; | ||||||
|     } |     } | ||||||
|   </style> |   </style> | ||||||
|  |   <script> | ||||||
|  |   var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']]; | ||||||
|  |   (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0]; | ||||||
|  |   g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js'; | ||||||
|  |   s.parentNode.insertBefore(g,s)}(document,'script')); | ||||||
|  |   </script> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import "../../../src/resources/safari-14-attachshadow-patch"; | ||||||
| import "./layout/hc-connect"; | import "./layout/hc-connect"; | ||||||
|  |  | ||||||
| import("../../../src/resources/ha-style"); | import("../../../src/resources/ha-style"); | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 30 KiB | 
| @@ -1,5 +0,0 @@ | |||||||
| "use strict"; |  | ||||||
|  |  | ||||||
| self.addEventListener("fetch", (event) => { |  | ||||||
|   event.respondWith(fetch(event.request)); |  | ||||||
| }); |  | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { convertEntities } from "../../../../src/fake_data/entity"; | import { convertEntities } from "../../../../src/fake_data/entity"; | ||||||
| import { DemoConfig } from "../types"; | import { DemoConfig } from "../types"; | ||||||
|  |  | ||||||
| export const demoEntitiesSections: DemoConfig["entities"] = (localize) => | export const demoEntitiesSections: DemoConfig["entities"] = () => | ||||||
|   convertEntities({ |   convertEntities({ | ||||||
|     "cover.living_room_garden_shutter": { |     "cover.living_room_garden_shutter": { | ||||||
|       entity_id: "cover.living_room_garden_shutter", |       entity_id: "cover.living_room_garden_shutter", | ||||||
| @@ -113,30 +113,11 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) => | |||||||
|     }, |     }, | ||||||
|     "media_player.living_room_nest_mini": { |     "media_player.living_room_nest_mini": { | ||||||
|       entity_id: "media_player.living_room_nest_mini", |       entity_id: "media_player.living_room_nest_mini", | ||||||
|       state: "on", |       state: "off", | ||||||
|       attributes: { |       attributes: { | ||||||
|         device_class: "speaker", |         device_class: "speaker", | ||||||
|         volume_level: 0.18, |         friendly_name: "Living room Nest Mini", | ||||||
|         is_volume_muted: false, |         supported_features: 152461, | ||||||
|         media_content_type: "music", |  | ||||||
|         media_duration: 300, |  | ||||||
|         media_position: 0, |  | ||||||
|         media_position_updated_at: new Date( |  | ||||||
|           // 23 seconds in |  | ||||||
|           new Date().getTime() - 23000 |  | ||||||
|         ).toISOString(), |  | ||||||
|         media_title: "I Wasn't Born To Follow", |  | ||||||
|         media_artist: "The Byrds", |  | ||||||
|         media_album_name: "The Notorious Byrd Brothers", |  | ||||||
|         source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"], |  | ||||||
|         shuffle: false, |  | ||||||
|         night_sound: false, |  | ||||||
|         speech_enhance: false, |  | ||||||
|         friendly_name: localize( |  | ||||||
|           "ui.panel.page-demo.config.sections.entities.media_player.living_room_nest_mini" |  | ||||||
|         ), |  | ||||||
|         entity_picture: "/assets/sections/images/media_player_family_room.jpg", |  | ||||||
|         supported_features: 64063, |  | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|     "cover.kitchen_shutter": { |     "cover.kitchen_shutter": { | ||||||
| @@ -187,27 +168,8 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) => | |||||||
|       state: "on", |       state: "on", | ||||||
|       attributes: { |       attributes: { | ||||||
|         device_class: "speaker", |         device_class: "speaker", | ||||||
|         volume_level: 0.18, |         friendly_name: "Kitchen Nest Audio", | ||||||
|         is_volume_muted: false, |         supported_features: 152461, | ||||||
|         media_content_type: "music", |  | ||||||
|         media_duration: 300, |  | ||||||
|         media_position: 0, |  | ||||||
|         media_position_updated_at: new Date( |  | ||||||
|           // 23 seconds in |  | ||||||
|           new Date().getTime() - 23000 |  | ||||||
|         ).toISOString(), |  | ||||||
|         media_title: "I Wasn't Born To Follow", |  | ||||||
|         media_artist: "The Byrds", |  | ||||||
|         media_album_name: "The Notorious Byrd Brothers", |  | ||||||
|         source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"], |  | ||||||
|         shuffle: false, |  | ||||||
|         night_sound: false, |  | ||||||
|         speech_enhance: false, |  | ||||||
|         friendly_name: localize( |  | ||||||
|           "ui.panel.page-demo.config.sections.entities.media_player.kitchen_nest_audio" |  | ||||||
|         ), |  | ||||||
|         entity_picture: "/assets/sections/images/media_player_family_room.jpg", |  | ||||||
|         supported_features: 64063, |  | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|     "binary_sensor.tesla_wall_connector_vehicle_connected": { |     "binary_sensor.tesla_wall_connector_vehicle_connected": { | ||||||
| @@ -371,28 +333,8 @@ export const demoEntitiesSections: DemoConfig["entities"] = (localize) => | |||||||
|       entity_id: "media_player.study_nest_hub", |       entity_id: "media_player.study_nest_hub", | ||||||
|       state: "off", |       state: "off", | ||||||
|       attributes: { |       attributes: { | ||||||
|         device_class: "speaker", |         friendly_name: "Study Nest Hub", | ||||||
|         volume_level: 0.18, |         supported_features: 152461, | ||||||
|         is_volume_muted: false, |  | ||||||
|         media_content_type: "music", |  | ||||||
|         media_duration: 300, |  | ||||||
|         media_position: 0, |  | ||||||
|         media_position_updated_at: new Date( |  | ||||||
|           // 23 seconds in |  | ||||||
|           new Date().getTime() - 23000 |  | ||||||
|         ).toISOString(), |  | ||||||
|         media_title: "I Wasn't Born To Follow", |  | ||||||
|         media_artist: "The Byrds", |  | ||||||
|         media_album_name: "The Notorious Byrd Brothers", |  | ||||||
|         source_list: ["It's A Party", "Radio HSL", "Retro 70s and 80s"], |  | ||||||
|         shuffle: false, |  | ||||||
|         night_sound: false, |  | ||||||
|         speech_enhance: false, |  | ||||||
|         friendly_name: localize( |  | ||||||
|           "ui.panel.page-demo.config.sections.entities.media_player.study_nest_hub" |  | ||||||
|         ), |  | ||||||
|         entity_picture: "/assets/sections/images/media_player_family_room.jpg", |  | ||||||
|         supported_features: 64063, |  | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|     "sensor.standing_desk_height": { |     "sensor.standing_desk_height": { | ||||||
|   | |||||||
| @@ -1,25 +1,40 @@ | |||||||
| import { isFrontpageEmbed } from "../../util/is_frontpage"; |  | ||||||
| import { DemoConfig } from "../types"; | import { DemoConfig } from "../types"; | ||||||
|  |  | ||||||
| export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ | export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({ | ||||||
|   title: "Home Assistant Demo", |   title: "Home Assistant Demo", | ||||||
|   views: [ |   views: [ | ||||||
|     { |     { | ||||||
|       type: "sections", |       type: "sections", | ||||||
|       title: isFrontpageEmbed ? "Home Assistant" : "Demo", |       title: "Demo", | ||||||
|       path: "home", |       path: "home", | ||||||
|       icon: "mdi:home-assistant", |       icon: "mdi:home-assistant", | ||||||
|       sections: [ |       sections: [ | ||||||
|         ...(isFrontpageEmbed |  | ||||||
|           ? [] |  | ||||||
|           : [ |  | ||||||
|         { |         { | ||||||
|                 title: `${localize("ui.panel.page-demo.config.sections.titles.welcome")} 👋`, |           title: "Welcome 👋", | ||||||
|           cards: [{ type: "custom:ha-demo-card" }], |           cards: [{ type: "custom:ha-demo-card" }], | ||||||
|         }, |         }, | ||||||
|             ]), |  | ||||||
|         { |         { | ||||||
|           cards: [ |           cards: [ | ||||||
|  |             { | ||||||
|  |               type: "tile", | ||||||
|  |               entity: "cover.living_room_garden_shutter", | ||||||
|  |               name: "Garden", | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               type: "tile", | ||||||
|  |               entity: "cover.living_room_graveyard_shutter", | ||||||
|  |               name: "Rear", | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               type: "tile", | ||||||
|  |               entity: "cover.living_room_left_shutter", | ||||||
|  |               name: "Left", | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               type: "tile", | ||||||
|  |               entity: "cover.living_room_right_shutter", | ||||||
|  |               name: "Right", | ||||||
|  |             }, | ||||||
|             { |             { | ||||||
|               type: "tile", |               type: "tile", | ||||||
|               entity: "light.floor_lamp", |               entity: "light.floor_lamp", | ||||||
| @@ -45,17 +60,13 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ | |||||||
|               detail: 1, |               detail: 1, | ||||||
|               name: "Temperature", |               name: "Temperature", | ||||||
|             }, |             }, | ||||||
|             { |  | ||||||
|               type: "tile", |  | ||||||
|               entity: "cover.living_room_garden_shutter", |  | ||||||
|               name: "Blinds", |  | ||||||
|             }, |  | ||||||
|             { |             { | ||||||
|               type: "tile", |               type: "tile", | ||||||
|               entity: "media_player.living_room_nest_mini", |               entity: "media_player.living_room_nest_mini", | ||||||
|  |               name: "Nest Mini", | ||||||
|             }, |             }, | ||||||
|           ], |           ], | ||||||
|           title: `🛋️ ${localize("ui.panel.page-demo.config.sections.titles.living_room")} `, |           title: "🛋️ Living room ", | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           type: "grid", |           type: "grid", | ||||||
| @@ -88,9 +99,10 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ | |||||||
|             { |             { | ||||||
|               type: "tile", |               type: "tile", | ||||||
|               entity: "media_player.kitchen_nest_audio", |               entity: "media_player.kitchen_nest_audio", | ||||||
|  |               name: "Nest Audio", | ||||||
|             }, |             }, | ||||||
|           ], |           ], | ||||||
|           title: `👩🍳 ${localize("ui.panel.page-demo.config.sections.titles.kitchen")}`, |           title: "👩🍳 Kitchen", | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           type: "grid", |           type: "grid", | ||||||
| @@ -132,7 +144,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ | |||||||
|               color: "dark-grey", |               color: "dark-grey", | ||||||
|             }, |             }, | ||||||
|           ], |           ], | ||||||
|           title: `⚡️ ${localize("ui.panel.page-demo.config.sections.titles.energy")}`, |           title: "⚡️ Energy", | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           type: "grid", |           type: "grid", | ||||||
| @@ -169,7 +181,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ | |||||||
|               state_content: ["preset_mode", "current_temperature"], |               state_content: ["preset_mode", "current_temperature"], | ||||||
|             }, |             }, | ||||||
|           ], |           ], | ||||||
|           title: `🌤️ ${localize("ui.panel.page-demo.config.sections.titles.climate")}`, |           title: "🌤️ Climate", | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           type: "grid", |           type: "grid", | ||||||
| @@ -187,6 +199,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ | |||||||
|             { |             { | ||||||
|               type: "tile", |               type: "tile", | ||||||
|               entity: "media_player.study_nest_hub", |               entity: "media_player.study_nest_hub", | ||||||
|  |               name: "Nest Hub", | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|               type: "tile", |               type: "tile", | ||||||
| @@ -196,7 +209,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ | |||||||
|               icon: "mdi:desk", |               icon: "mdi:desk", | ||||||
|             }, |             }, | ||||||
|           ], |           ], | ||||||
|           title: `🧑💻 ${localize("ui.panel.page-demo.config.sections.titles.study")}`, |           title: "🧑💻 Study", | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           type: "grid", |           type: "grid", | ||||||
| @@ -230,7 +243,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ | |||||||
|               name: "Illuminance", |               name: "Illuminance", | ||||||
|             }, |             }, | ||||||
|           ], |           ], | ||||||
|           title: `🌳 ${localize("ui.panel.page-demo.config.sections.titles.outdoor")}`, |           title: "🌳 Outdoor", | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           type: "grid", |           type: "grid", | ||||||
| @@ -260,7 +273,7 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = (localize) => ({ | |||||||
|               icon: "mdi:home-assistant", |               icon: "mdi:home-assistant", | ||||||
|             }, |             }, | ||||||
|           ], |           ], | ||||||
|           title: `🎉 ${localize("ui.panel.page-demo.config.sections.titles.updates")}`, |           title: "🎉 Updates", | ||||||
|         }, |         }, | ||||||
|       ], |       ], | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import "./util/is_frontpage"; | import "../../src/resources/safari-14-attachshadow-patch"; | ||||||
| import "./ha-demo"; | import "./ha-demo"; | ||||||
|  |  | ||||||
| import("../../src/resources/ha-style"); | import("../../src/resources/ha-style"); | ||||||
|   | |||||||
| @@ -82,8 +82,6 @@ export class HaDemo extends HomeAssistantAppEl { | |||||||
|         has_entity_name: false, |         has_entity_name: false, | ||||||
|         unique_id: "co2_intensity", |         unique_id: "co2_intensity", | ||||||
|         options: null, |         options: null, | ||||||
|         created_at: 0, |  | ||||||
|         modified_at: 0, |  | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         config_entry_id: "co2signal", |         config_entry_id: "co2signal", | ||||||
| @@ -102,8 +100,6 @@ export class HaDemo extends HomeAssistantAppEl { | |||||||
|         has_entity_name: false, |         has_entity_name: false, | ||||||
|         unique_id: "grid_fossil_fuel_percentage", |         unique_id: "grid_fossil_fuel_percentage", | ||||||
|         options: null, |         options: null, | ||||||
|         created_at: 0, |  | ||||||
|         modified_at: 0, |  | ||||||
|       }, |       }, | ||||||
|     ]); |     ]); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -63,47 +63,46 @@ | |||||||
|         align-items: center; |         align-items: center; | ||||||
|       } |       } | ||||||
|       #ha-launch-screen svg { |       #ha-launch-screen svg { | ||||||
|         width: 112px; |         width: 170px; | ||||||
|         flex-shrink: 0; |         flex-shrink: 0; | ||||||
|       } |       } | ||||||
|       #ha-launch-screen .ha-launch-screen-spacer-top { |       #ha-launch-screen .ha-launch-screen-spacer { | ||||||
|         flex: 1; |         flex: 1; | ||||||
|         margin-top: calc( 2 * max(env(safe-area-inset-bottom), 48px) + 46px ); |  | ||||||
|         padding-top: 48px; |  | ||||||
|       } |  | ||||||
|       #ha-launch-screen .ha-launch-screen-spacer-bottom { |  | ||||||
|         flex: 1; |  | ||||||
|         padding-top: 48px; |  | ||||||
|       } |  | ||||||
|       .ohf-logo { |  | ||||||
|         margin: max(env(safe-area-inset-bottom), 48px) 0; |  | ||||||
|         display: flex; |  | ||||||
|         flex-direction: column; |  | ||||||
|         align-items: center; |  | ||||||
|         opacity: .66; |  | ||||||
|       } |  | ||||||
|       @media (prefers-color-scheme: dark) { |  | ||||||
|         .ohf-logo { |  | ||||||
|           filter: invert(1); |  | ||||||
|         } |  | ||||||
|       } |       } | ||||||
|     </style> |     </style> | ||||||
|   </head> |   </head> | ||||||
|   <body> |   <body> | ||||||
|     <div id="ha-launch-screen"> |     <div id="ha-launch-screen"> | ||||||
|       <div class="ha-launch-screen-spacer-top"></div> |       <div class="ha-launch-screen-spacer"></div> | ||||||
|       <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240"> |       <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240"> | ||||||
|         <path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/> |         <path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/> | ||||||
|         <path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/> |         <path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/> | ||||||
|       </svg> |       </svg> | ||||||
|       <div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer-bottom"></div> |       <div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div> | ||||||
|       <div class="ohf-logo"> |  | ||||||
|         <img src="/static/images/ohf-badge.svg" alt="Home Assistant is a project by the Open Home Foundation" height="46"> |  | ||||||
|       </div> |  | ||||||
|     </div> |     </div> | ||||||
|     <ha-demo></ha-demo> |     <ha-demo></ha-demo> | ||||||
|     <%= renderTemplate("../../../src/html/_js_base.html.template") %> |     <%= renderTemplate("../../../src/html/_js_base.html.template") %> | ||||||
|     <%= renderTemplate("../../../src/html/_preload_roboto.html.template") %> |     <%= renderTemplate("../../../src/html/_preload_roboto.html.template") %> | ||||||
|     <%= renderTemplate("../../../src/html/_script_loader.html.template") %> |     <script> | ||||||
|  |       // Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5 | ||||||
|  |       if (!isS11_12) { | ||||||
|  |         <% for (const entry of latestEntryJS) { %> | ||||||
|  |           import("<%= entry %>"); | ||||||
|  |         <% } %> | ||||||
|  |         window.latestJS = true; | ||||||
|  |       } | ||||||
|  |     </script> | ||||||
|  |     <%= renderTemplate("../../../src/html/_script_load_es5.html.template") %> | ||||||
|  |     <script> | ||||||
|  |       var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]]; | ||||||
|  |       (function (d, t) { | ||||||
|  |         var g = d.createElement(t), | ||||||
|  |           s = d.getElementsByTagName(t)[0]; | ||||||
|  |         g.src = | ||||||
|  |           ("https:" == location.protocol ? "//ssl" : "//www") + | ||||||
|  |           ".google-analytics.com/ga.js"; | ||||||
|  |         s.parentNode.insertBefore(g, s); | ||||||
|  |       })(document, "script"); | ||||||
|  |     </script> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|   | |||||||
| @@ -1,55 +1,5 @@ | |||||||
| import { convertEntities } from "../../../src/fake_data/entity"; | import { convertEntities } from "../../../src/fake_data/entity"; | ||||||
|  |  | ||||||
| export const mapEntities = () => |  | ||||||
|   convertEntities({ |  | ||||||
|     "zone.home": { |  | ||||||
|       entity_id: "zone.home", |  | ||||||
|       state: "zoning", |  | ||||||
|       attributes: { |  | ||||||
|         hidden: true, |  | ||||||
|         latitude: 52.3631339, |  | ||||||
|         longitude: 4.8903147, |  | ||||||
|         radius: 200, |  | ||||||
|         friendly_name: "Home", |  | ||||||
|         icon: "hademo:home", |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     "zone.uva": { |  | ||||||
|       entity_id: "zone.buckhead", |  | ||||||
|       state: "zoning", |  | ||||||
|       attributes: { |  | ||||||
|         hidden: true, |  | ||||||
|         radius: 400, |  | ||||||
|         friendly_name: "UvA", |  | ||||||
|         icon: "hademo:school", |  | ||||||
|         latitude: 52.3558182, |  | ||||||
|         longitude: 4.9535376, |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     "person.arsaboo": { |  | ||||||
|       entity_id: "person.arsaboo", |  | ||||||
|       state: "not_home", |  | ||||||
|       attributes: { |  | ||||||
|         radius: 50, |  | ||||||
|         friendly_name: "Arsaboo", |  | ||||||
|         latitude: 52.3579946, |  | ||||||
|         longitude: 4.8664597, |  | ||||||
|         entity_picture: "/assets/arsaboo/images/arsaboo.jpg", |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     "person.melody": { |  | ||||||
|       entity_id: "person.melody", |  | ||||||
|       state: "not_home", |  | ||||||
|       attributes: { |  | ||||||
|         radius: 50, |  | ||||||
|         friendly_name: "Melody", |  | ||||||
|         latitude: 52.3408927, |  | ||||||
|         longitude: 4.8711073, |  | ||||||
|         entity_picture: "/assets/arsaboo/images/melody.jpg", |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
| export const energyEntities = () => | export const energyEntities = () => | ||||||
|   convertEntities({ |   convertEntities({ | ||||||
|     "sensor.grid_fossil_fuel_percentage": { |     "sensor.grid_fossil_fuel_percentage": { | ||||||
|   | |||||||
| @@ -7,25 +7,16 @@ import { | |||||||
| } from "../configs/demo-configs"; | } from "../configs/demo-configs"; | ||||||
| import "../custom-cards/cast-demo-row"; | import "../custom-cards/cast-demo-row"; | ||||||
| import "../custom-cards/ha-demo-card"; | import "../custom-cards/ha-demo-card"; | ||||||
| import { mapEntities } from "./entities"; |  | ||||||
|  |  | ||||||
| export const mockLovelace = ( | export const mockLovelace = ( | ||||||
|   hass: MockHomeAssistant, |   hass: MockHomeAssistant, | ||||||
|   localizePromise: Promise<LocalizeFunc> |   localizePromise: Promise<LocalizeFunc> | ||||||
| ) => { | ) => { | ||||||
|   hass.mockWS("lovelace/config", ({ url_path }) => { |   hass.mockWS("lovelace/config", () => | ||||||
|     if (url_path === "map") { |     Promise.all([selectedDemoConfig, localizePromise]).then( | ||||||
|       hass.addEntities(mapEntities()); |  | ||||||
|       return { |  | ||||||
|         strategy: { |  | ||||||
|           type: "map", |  | ||||||
|         }, |  | ||||||
|       }; |  | ||||||
|     } |  | ||||||
|     return Promise.all([selectedDemoConfig, localizePromise]).then( |  | ||||||
|       ([config, localize]) => config.lovelace(localize) |       ([config, localize]) => config.lovelace(localize) | ||||||
|  |     ) | ||||||
|   ); |   ); | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   hass.mockWS("lovelace/config/save", () => Promise.resolve()); |   hass.mockWS("lovelace/config/save", () => Promise.resolve()); | ||||||
|   hass.mockWS("lovelace/resources", () => Promise.resolve([])); |   hass.mockWS("lovelace/resources", () => Promise.resolve([])); | ||||||
|   | |||||||
| @@ -1 +0,0 @@ | |||||||
| export const isFrontpageEmbed = document.location.search === "?frontpage"; |  | ||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 110 KiB | 
| @@ -532,6 +532,15 @@ export default { | |||||||
|     last_changed: "2018-07-19T10:44:46.200946+00:00", |     last_changed: "2018-07-19T10:44:46.200946+00:00", | ||||||
|     last_updated: "2018-07-19T10:44:46.200946+00:00", |     last_updated: "2018-07-19T10:44:46.200946+00:00", | ||||||
|   }, |   }, | ||||||
|  |   "mailbox.demomailbox": { | ||||||
|  |     entity_id: "mailbox.demomailbox", | ||||||
|  |     state: "10", | ||||||
|  |     attributes: { | ||||||
|  |       friendly_name: "DemoMailbox", | ||||||
|  |     }, | ||||||
|  |     last_changed: "2018-07-19T10:45:16.555210+00:00", | ||||||
|  |     last_updated: "2018-07-19T10:45:16.555210+00:00", | ||||||
|  |   }, | ||||||
|   "input_select.living_room_preset": { |   "input_select.living_room_preset": { | ||||||
|     entity_id: "input_select.living_room_preset", |     entity_id: "input_select.living_room_preset", | ||||||
|     state: "Visitors", |     state: "Visitors", | ||||||
|   | |||||||
| @@ -3,16 +3,13 @@ title: When to use remove, delete, add and create | |||||||
| subtitle: The difference between remove/delete and add/create. | subtitle: The difference between remove/delete and add/create. | ||||||
| --- | --- | ||||||
|  |  | ||||||
| # Removing or deleting content | # Remove vs Delete | ||||||
|  |  | ||||||
| _Remove_ and _Delete_ are quite similar, but can be frustrating if used inconsistently. | Remove and Delete are quite similar, but can be frustrating if used inconsistently. | ||||||
|  |  | ||||||
| - Remove refers to an action that can be restored or reapplied. |  | ||||||
| - Delete refers to a permanent, non-recoverable action. |  | ||||||
|  |  | ||||||
| ## Remove | ## Remove | ||||||
|  |  | ||||||
| The term _Remove_ should always be used when an item/setting or content is to be removed or disassociated, but the action can be reversed or reapplied. | Take away and set aside, but kept in existence. | ||||||
|  |  | ||||||
| For example: | For example: | ||||||
|  |  | ||||||
| @@ -25,7 +22,7 @@ For example: | |||||||
|  |  | ||||||
| ## Delete | ## Delete | ||||||
|  |  | ||||||
| The term _Delete_ should always be used to refer to any action that will cause the permanent deletion of an item/setting or content. | Erase, rendered nonexistent or nonrecoverable. | ||||||
|  |  | ||||||
| For example: | For example: | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervis | |||||||
| import type { ConditionWithShorthand } from "../../../../src/data/automation"; | import type { ConditionWithShorthand } from "../../../../src/data/automation"; | ||||||
| import "../../../../src/panels/config/automation/condition/ha-automation-condition"; | import "../../../../src/panels/config/automation/condition/ha-automation-condition"; | ||||||
| import { HaDeviceCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-device"; | import { HaDeviceCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-device"; | ||||||
|  | import { HaLogicalCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-logical"; | ||||||
| import HaNumericStateCondition from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-numeric_state"; | import HaNumericStateCondition from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-numeric_state"; | ||||||
| import { HaStateCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-state"; | import { HaStateCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-state"; | ||||||
| import { HaSunCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-sun"; | import { HaSunCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-sun"; | ||||||
| @@ -18,67 +19,62 @@ import { HaTemplateCondition } from "../../../../src/panels/config/automation/co | |||||||
| import { HaTimeCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-time"; | import { HaTimeCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-time"; | ||||||
| import { HaTriggerCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger"; | import { HaTriggerCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger"; | ||||||
| import { HaZoneCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-zone"; | import { HaZoneCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-zone"; | ||||||
| import { HaAndCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-and"; |  | ||||||
| import { HaOrCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-or"; |  | ||||||
| import { HaNotCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-not"; |  | ||||||
|  |  | ||||||
| const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [ | const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [ | ||||||
|   { |   { | ||||||
|     name: "State", |     name: "State", | ||||||
|     conditions: [{ ...HaStateCondition.defaultConfig }], |     conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Numeric State", |     name: "Numeric State", | ||||||
|     conditions: [{ ...HaNumericStateCondition.defaultConfig }], |     conditions: [ | ||||||
|  |       { condition: "numeric_state", ...HaNumericStateCondition.defaultConfig }, | ||||||
|  |     ], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Sun", |     name: "Sun", | ||||||
|     conditions: [{ ...HaSunCondition.defaultConfig }], |     conditions: [{ condition: "sun", ...HaSunCondition.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Zone", |     name: "Zone", | ||||||
|     conditions: [{ ...HaZoneCondition.defaultConfig }], |     conditions: [{ condition: "zone", ...HaZoneCondition.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Time", |     name: "Time", | ||||||
|     conditions: [{ ...HaTimeCondition.defaultConfig }], |     conditions: [{ condition: "time", ...HaTimeCondition.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Template", |     name: "Template", | ||||||
|     conditions: [{ ...HaTemplateCondition.defaultConfig }], |     conditions: [ | ||||||
|  |       { condition: "template", ...HaTemplateCondition.defaultConfig }, | ||||||
|  |     ], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Device", |     name: "Device", | ||||||
|     conditions: [{ ...HaDeviceCondition.defaultConfig }], |     conditions: [{ condition: "device", ...HaDeviceCondition.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "And", |     name: "And", | ||||||
|     conditions: [{ ...HaAndCondition.defaultConfig }], |     conditions: [{ condition: "and", ...HaLogicalCondition.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Or", |     name: "Or", | ||||||
|     conditions: [{ ...HaOrCondition.defaultConfig }], |     conditions: [{ condition: "or", ...HaLogicalCondition.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Not", |     name: "Not", | ||||||
|     conditions: [{ ...HaNotCondition.defaultConfig }], |     conditions: [{ condition: "not", ...HaLogicalCondition.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Trigger", |     name: "Trigger", | ||||||
|     conditions: [{ ...HaTriggerCondition.defaultConfig }], |     conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Shorthand", |     name: "Shorthand", | ||||||
|     conditions: [ |     conditions: [ | ||||||
|       { |       { and: HaLogicalCondition.defaultConfig.conditions }, | ||||||
|         ...HaAndCondition.defaultConfig, |       { or: HaLogicalCondition.defaultConfig.conditions }, | ||||||
|       }, |       { not: HaLogicalCondition.defaultConfig.conditions }, | ||||||
|       { |  | ||||||
|         ...HaOrCondition.defaultConfig, |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         ...HaNotCondition.defaultConfig, |  | ||||||
|       }, |  | ||||||
|     ], |     ], | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|   | |||||||
| @@ -30,48 +30,55 @@ import { HaConversationTrigger } from "../../../../src/panels/config/automation/ | |||||||
| const SCHEMAS: { name: string; triggers: Trigger[] }[] = [ | const SCHEMAS: { name: string; triggers: Trigger[] }[] = [ | ||||||
|   { |   { | ||||||
|     name: "State", |     name: "State", | ||||||
|     triggers: [{ ...HaStateTrigger.defaultConfig }], |     triggers: [{ platform: "state", ...HaStateTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "MQTT", |     name: "MQTT", | ||||||
|     triggers: [{ ...HaMQTTTrigger.defaultConfig }], |     triggers: [{ platform: "mqtt", ...HaMQTTTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "GeoLocation", |     name: "GeoLocation", | ||||||
|     triggers: [{ ...HaGeolocationTrigger.defaultConfig }], |     triggers: [ | ||||||
|  |       { platform: "geo_location", ...HaGeolocationTrigger.defaultConfig }, | ||||||
|  |     ], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Home Assistant", |     name: "Home Assistant", | ||||||
|     triggers: [{ ...HaHassTrigger.defaultConfig }], |     triggers: [{ platform: "homeassistant", ...HaHassTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Numeric State", |     name: "Numeric State", | ||||||
|     triggers: [{ ...HaNumericStateTrigger.defaultConfig }], |     triggers: [ | ||||||
|  |       { platform: "numeric_state", ...HaNumericStateTrigger.defaultConfig }, | ||||||
|  |     ], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Sun", |     name: "Sun", | ||||||
|     triggers: [{ ...HaSunTrigger.defaultConfig }], |     triggers: [{ platform: "sun", ...HaSunTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Time Pattern", |     name: "Time Pattern", | ||||||
|     triggers: [{ ...HaTimePatternTrigger.defaultConfig }], |     triggers: [ | ||||||
|  |       { platform: "time_pattern", ...HaTimePatternTrigger.defaultConfig }, | ||||||
|  |     ], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Webhook", |     name: "Webhook", | ||||||
|     triggers: [{ ...HaWebhookTrigger.defaultConfig }], |     triggers: [{ platform: "webhook", ...HaWebhookTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Persistent Notification", |     name: "Persistent Notification", | ||||||
|     triggers: [ |     triggers: [ | ||||||
|       { |       { | ||||||
|  |         platform: "persistent_notification", | ||||||
|         ...HaPersistentNotificationTrigger.defaultConfig, |         ...HaPersistentNotificationTrigger.defaultConfig, | ||||||
|       }, |       }, | ||||||
|     ], |     ], | ||||||
| @@ -79,37 +86,37 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [ | |||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Zone", |     name: "Zone", | ||||||
|     triggers: [{ ...HaZoneTrigger.defaultConfig }], |     triggers: [{ platform: "zone", ...HaZoneTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Tag", |     name: "Tag", | ||||||
|     triggers: [{ ...HaTagTrigger.defaultConfig }], |     triggers: [{ platform: "tag", ...HaTagTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Time", |     name: "Time", | ||||||
|     triggers: [{ ...HaTimeTrigger.defaultConfig }], |     triggers: [{ platform: "time", ...HaTimeTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Template", |     name: "Template", | ||||||
|     triggers: [{ ...HaTemplateTrigger.defaultConfig }], |     triggers: [{ platform: "template", ...HaTemplateTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Event", |     name: "Event", | ||||||
|     triggers: [{ ...HaEventTrigger.defaultConfig }], |     triggers: [{ platform: "event", ...HaEventTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   { |   { | ||||||
|     name: "Device Trigger", |     name: "Device Trigger", | ||||||
|     triggers: [{ ...HaDeviceTrigger.defaultConfig }], |     triggers: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: "Sentence", |     name: "Sentence", | ||||||
|     triggers: [ |     triggers: [ | ||||||
|       { ...HaConversationTrigger.defaultConfig }, |       { platform: "conversation", ...HaConversationTrigger.defaultConfig }, | ||||||
|       { |       { | ||||||
|         platform: "conversation", |         platform: "conversation", | ||||||
|         command: ["Turn on the lights", "Turn the lights on"], |         command: ["Turn on the lights", "Turn the lights on"], | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ import { getEntity } from "../../../../src/fake_data/entity"; | |||||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; | import { provideHass } from "../../../../src/fake_data/provide_hass"; | ||||||
| import { HomeAssistant } from "../../../../src/types"; | import { HomeAssistant } from "../../../../src/types"; | ||||||
| import "../../components/demo-black-white-row"; | import "../../components/demo-black-white-row"; | ||||||
| import { DeviceRegistryEntry } from "../../../../src/data/device_registry"; |  | ||||||
|  |  | ||||||
| const ENTITIES = [ | const ENTITIES = [ | ||||||
|   getEntity("alarm_control_panel", "alarm", "disarmed", { |   getEntity("alarm_control_panel", "alarm", "disarmed", { | ||||||
| @@ -42,7 +41,7 @@ const ENTITIES = [ | |||||||
|   }), |   }), | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const DEVICES: DeviceRegistryEntry[] = [ | const DEVICES = [ | ||||||
|   { |   { | ||||||
|     area_id: "bedroom", |     area_id: "bedroom", | ||||||
|     configuration_url: null, |     configuration_url: null, | ||||||
| @@ -54,7 +53,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     identifiers: [["demo", "volume1"] as [string, string]], |     identifiers: [["demo", "volume1"] as [string, string]], | ||||||
|     manufacturer: null, |     manufacturer: null, | ||||||
|     model: null, |     model: null, | ||||||
|     model_id: null, |  | ||||||
|     name_by_user: null, |     name_by_user: null, | ||||||
|     name: "Dishwasher", |     name: "Dishwasher", | ||||||
|     sw_version: null, |     sw_version: null, | ||||||
| @@ -62,9 +60,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     via_device_id: null, |     via_device_id: null, | ||||||
|     serial_number: null, |     serial_number: null, | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|     primary_config_entry: null, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     area_id: "backyard", |     area_id: "backyard", | ||||||
| @@ -77,7 +72,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     identifiers: [["demo", "pwm1"] as [string, string]], |     identifiers: [["demo", "pwm1"] as [string, string]], | ||||||
|     manufacturer: null, |     manufacturer: null, | ||||||
|     model: null, |     model: null, | ||||||
|     model_id: null, |  | ||||||
|     name_by_user: null, |     name_by_user: null, | ||||||
|     name: "Lamp", |     name: "Lamp", | ||||||
|     sw_version: null, |     sw_version: null, | ||||||
| @@ -85,9 +79,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     via_device_id: null, |     via_device_id: null, | ||||||
|     serial_number: null, |     serial_number: null, | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|     primary_config_entry: null, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     area_id: null, |     area_id: null, | ||||||
| @@ -100,7 +91,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     identifiers: [["demo", "pwm1"] as [string, string]], |     identifiers: [["demo", "pwm1"] as [string, string]], | ||||||
|     manufacturer: null, |     manufacturer: null, | ||||||
|     model: null, |     model: null, | ||||||
|     model_id: null, |  | ||||||
|     name_by_user: "User name", |     name_by_user: "User name", | ||||||
|     name: "Technical name", |     name: "Technical name", | ||||||
|     sw_version: null, |     sw_version: null, | ||||||
| @@ -108,9 +98,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     via_device_id: null, |     via_device_id: null, | ||||||
|     serial_number: null, |     serial_number: null, | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|     primary_config_entry: null, |  | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @@ -123,8 +110,6 @@ const AREAS: AreaRegistryEntry[] = [ | |||||||
|     picture: null, |     picture: null, | ||||||
|     aliases: [], |     aliases: [], | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     area_id: "bedroom", |     area_id: "bedroom", | ||||||
| @@ -134,8 +119,6 @@ const AREAS: AreaRegistryEntry[] = [ | |||||||
|     picture: null, |     picture: null, | ||||||
|     aliases: [], |     aliases: [], | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     area_id: "livingroom", |     area_id: "livingroom", | ||||||
| @@ -145,8 +128,6 @@ const AREAS: AreaRegistryEntry[] = [ | |||||||
|     picture: null, |     picture: null, | ||||||
|     aliases: [], |     aliases: [], | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,7 +21,6 @@ import { FloorRegistryEntry } from "../../../../src/data/floor_registry"; | |||||||
| import { LabelRegistryEntry } from "../../../../src/data/label_registry"; | import { LabelRegistryEntry } from "../../../../src/data/label_registry"; | ||||||
| import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry"; | import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry"; | ||||||
| import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry"; | import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry"; | ||||||
| import { DeviceRegistryEntry } from "../../../../src/data/device_registry"; |  | ||||||
|  |  | ||||||
| const ENTITIES = [ | const ENTITIES = [ | ||||||
|   getEntity("alarm_control_panel", "alarm", "disarmed", { |   getEntity("alarm_control_panel", "alarm", "disarmed", { | ||||||
| @@ -42,7 +41,7 @@ const ENTITIES = [ | |||||||
|   }), |   }), | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const DEVICES: DeviceRegistryEntry[] = [ | const DEVICES = [ | ||||||
|   { |   { | ||||||
|     area_id: "bedroom", |     area_id: "bedroom", | ||||||
|     configuration_url: null, |     configuration_url: null, | ||||||
| @@ -54,7 +53,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     identifiers: [["demo", "volume1"] as [string, string]], |     identifiers: [["demo", "volume1"] as [string, string]], | ||||||
|     manufacturer: null, |     manufacturer: null, | ||||||
|     model: null, |     model: null, | ||||||
|     model_id: null, |  | ||||||
|     name_by_user: null, |     name_by_user: null, | ||||||
|     name: "Dishwasher", |     name: "Dishwasher", | ||||||
|     sw_version: null, |     sw_version: null, | ||||||
| @@ -62,9 +60,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     via_device_id: null, |     via_device_id: null, | ||||||
|     serial_number: null, |     serial_number: null, | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|     primary_config_entry: null, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     area_id: "backyard", |     area_id: "backyard", | ||||||
| @@ -77,7 +72,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     identifiers: [["demo", "pwm1"] as [string, string]], |     identifiers: [["demo", "pwm1"] as [string, string]], | ||||||
|     manufacturer: null, |     manufacturer: null, | ||||||
|     model: null, |     model: null, | ||||||
|     model_id: null, |  | ||||||
|     name_by_user: null, |     name_by_user: null, | ||||||
|     name: "Lamp", |     name: "Lamp", | ||||||
|     sw_version: null, |     sw_version: null, | ||||||
| @@ -85,9 +79,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     via_device_id: null, |     via_device_id: null, | ||||||
|     serial_number: null, |     serial_number: null, | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|     primary_config_entry: null, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     area_id: null, |     area_id: null, | ||||||
| @@ -100,7 +91,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     identifiers: [["demo", "pwm1"] as [string, string]], |     identifiers: [["demo", "pwm1"] as [string, string]], | ||||||
|     manufacturer: null, |     manufacturer: null, | ||||||
|     model: null, |     model: null, | ||||||
|     model_id: null, |  | ||||||
|     name_by_user: "User name", |     name_by_user: "User name", | ||||||
|     name: "Technical name", |     name: "Technical name", | ||||||
|     sw_version: null, |     sw_version: null, | ||||||
| @@ -108,9 +98,6 @@ const DEVICES: DeviceRegistryEntry[] = [ | |||||||
|     via_device_id: null, |     via_device_id: null, | ||||||
|     serial_number: null, |     serial_number: null, | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|     primary_config_entry: null, |  | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @@ -123,8 +110,6 @@ const AREAS: AreaRegistryEntry[] = [ | |||||||
|     picture: null, |     picture: null, | ||||||
|     aliases: [], |     aliases: [], | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     area_id: "bedroom", |     area_id: "bedroom", | ||||||
| @@ -134,8 +119,6 @@ const AREAS: AreaRegistryEntry[] = [ | |||||||
|     picture: null, |     picture: null, | ||||||
|     aliases: [], |     aliases: [], | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     area_id: "livingroom", |     area_id: "livingroom", | ||||||
| @@ -145,8 +128,6 @@ const AREAS: AreaRegistryEntry[] = [ | |||||||
|     picture: null, |     picture: null, | ||||||
|     aliases: [], |     aliases: [], | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @@ -157,8 +138,6 @@ const FLOORS: FloorRegistryEntry[] = [ | |||||||
|     level: 0, |     level: 0, | ||||||
|     icon: null, |     icon: null, | ||||||
|     aliases: [], |     aliases: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     floor_id: "first", |     floor_id: "first", | ||||||
| @@ -166,8 +145,6 @@ const FLOORS: FloorRegistryEntry[] = [ | |||||||
|     level: 1, |     level: 1, | ||||||
|     icon: "mdi:numeric-1", |     icon: "mdi:numeric-1", | ||||||
|     aliases: [], |     aliases: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     floor_id: "second", |     floor_id: "second", | ||||||
| @@ -175,8 +152,6 @@ const FLOORS: FloorRegistryEntry[] = [ | |||||||
|     level: 2, |     level: 2, | ||||||
|     icon: "mdi:numeric-2", |     icon: "mdi:numeric-2", | ||||||
|     aliases: [], |     aliases: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @@ -187,8 +162,6 @@ const LABELS: LabelRegistryEntry[] = [ | |||||||
|     icon: null, |     icon: null, | ||||||
|     color: "yellow", |     color: "yellow", | ||||||
|     description: null, |     description: null, | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     label_id: "entertainment", |     label_id: "entertainment", | ||||||
| @@ -196,8 +169,6 @@ const LABELS: LabelRegistryEntry[] = [ | |||||||
|     icon: "mdi:popcorn", |     icon: "mdi:popcorn", | ||||||
|     color: "blue", |     color: "blue", | ||||||
|     description: null, |     description: null, | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -287,11 +287,11 @@ const CONFIGS = [ | |||||||
|     config: ` |     config: ` | ||||||
| - type: entities | - type: entities | ||||||
|   entities: |   entities: | ||||||
|     - type: perform-action |     - type: call-service | ||||||
|       icon: mdi:power |       icon: mdi:power | ||||||
|       name: Bed light |       name: Bed light | ||||||
|       action_name: Toggle light |       action_name: Toggle light | ||||||
|       action: light.toggle |       service: light.toggle | ||||||
|       data: |       data: | ||||||
|         entity_id: light.bed_light |         entity_id: light.bed_light | ||||||
|     - type: section |     - type: section | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| --- |  | ||||||
| title: Picture Card |  | ||||||
| --- |  | ||||||
| @@ -1,61 +0,0 @@ | |||||||
| import { html, LitElement, PropertyValues, TemplateResult } from "lit"; |  | ||||||
| import { customElement, query } from "lit/decorators"; |  | ||||||
| import { getEntity } from "../../../../src/fake_data/entity"; |  | ||||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; |  | ||||||
| import "../../components/demo-cards"; |  | ||||||
| import { mockIcons } from "../../../../demo/src/stubs/icons"; |  | ||||||
|  |  | ||||||
| const ENTITIES = [ |  | ||||||
|   getEntity("person", "paulus", "home", { |  | ||||||
|     friendly_name: "Paulus", |  | ||||||
|     entity_picture: "/images/paulus.jpg", |  | ||||||
|   }), |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| const CONFIGS = [ |  | ||||||
|   { |  | ||||||
|     heading: "Image URL", |  | ||||||
|     config: ` |  | ||||||
| - type: picture |  | ||||||
|   image: /images/living_room.png |  | ||||||
|     `, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     heading: "Person entity", |  | ||||||
|     config: ` |  | ||||||
| - type: picture |  | ||||||
|   image_entity: person.paulus |  | ||||||
|     `, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     heading: "Error: Image required", |  | ||||||
|     config: ` |  | ||||||
| - type: picture |  | ||||||
|   entity: person.paulus |  | ||||||
|     `, |  | ||||||
|   }, |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| @customElement("demo-lovelace-picture-card") |  | ||||||
| class DemoPicture 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(ENTITIES); |  | ||||||
|     mockIcons(hass); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| declare global { |  | ||||||
|   interface HTMLElementTagNameMap { |  | ||||||
|     "demo-lovelace-picture-card": DemoPicture; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -25,15 +25,6 @@ const ENTITIES = [ | |||||||
|     friendly_name: "Movement Backyard", |     friendly_name: "Movement Backyard", | ||||||
|     device_class: "motion", |     device_class: "motion", | ||||||
|   }), |   }), | ||||||
|   getEntity("person", "paulus", "home", { |  | ||||||
|     friendly_name: "Paulus", |  | ||||||
|     entity_picture: "/images/paulus.jpg", |  | ||||||
|   }), |  | ||||||
|   getEntity("sensor", "battery", 35, { |  | ||||||
|     device_class: "battery", |  | ||||||
|     friendly_name: "Battery", |  | ||||||
|     unit_of_measurement: "%", |  | ||||||
|   }), |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const CONFIGS = [ | const CONFIGS = [ | ||||||
| @@ -132,19 +123,6 @@ const CONFIGS = [ | |||||||
|         left: 35% |         left: 35% | ||||||
|     `, |     `, | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     heading: "Person entity", |  | ||||||
|     config: ` |  | ||||||
| - type: picture-elements |  | ||||||
|   image_entity: person.paulus |  | ||||||
|   elements: |  | ||||||
|   - type: state-icon |  | ||||||
|     entity: sensor.battery |  | ||||||
|     style: |  | ||||||
|       top: 8% |  | ||||||
|       left: 8% |  | ||||||
|     `, |  | ||||||
|   }, |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @customElement("demo-lovelace-picture-elements-card") | @customElement("demo-lovelace-picture-elements-card") | ||||||
|   | |||||||
| @@ -12,10 +12,6 @@ const ENTITIES = [ | |||||||
|   getEntity("light", "bed_light", "off", { |   getEntity("light", "bed_light", "off", { | ||||||
|     friendly_name: "Bed Light", |     friendly_name: "Bed Light", | ||||||
|   }), |   }), | ||||||
|   getEntity("person", "paulus", "home", { |  | ||||||
|     friendly_name: "Paulus", |  | ||||||
|     entity_picture: "/images/paulus.jpg", |  | ||||||
|   }), |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const CONFIGS = [ | const CONFIGS = [ | ||||||
| @@ -54,13 +50,6 @@ const CONFIGS = [ | |||||||
|   entity: camera.demo_camera |   entity: camera.demo_camera | ||||||
|     `, |     `, | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     heading: "Person entity", |  | ||||||
|     config: ` |  | ||||||
| - type: picture-entity |  | ||||||
|   entity: person.paulus |  | ||||||
|     `, |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|     heading: "Hidden name", |     heading: "Hidden name", | ||||||
|     config: ` |     config: ` | ||||||
|   | |||||||
| @@ -20,15 +20,6 @@ const ENTITIES = [ | |||||||
|     friendly_name: "Basement Floor Wet", |     friendly_name: "Basement Floor Wet", | ||||||
|     device_class: "moisture", |     device_class: "moisture", | ||||||
|   }), |   }), | ||||||
|   getEntity("person", "paulus", "home", { |  | ||||||
|     friendly_name: "Paulus", |  | ||||||
|     entity_picture: "/images/paulus.jpg", |  | ||||||
|   }), |  | ||||||
|   getEntity("sensor", "battery", 35, { |  | ||||||
|     device_class: "battery", |  | ||||||
|     friendly_name: "Battery", |  | ||||||
|     unit_of_measurement: "%", |  | ||||||
|   }), |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const CONFIGS = [ | const CONFIGS = [ | ||||||
| @@ -99,15 +90,6 @@ const CONFIGS = [ | |||||||
|     - light.ceiling_lights |     - light.ceiling_lights | ||||||
|     `, |     `, | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     heading: "Person entity", |  | ||||||
|     config: ` |  | ||||||
| - type: picture-glance |  | ||||||
|   image_entity: person.paulus |  | ||||||
|   entities: |  | ||||||
|     - sensor.battery |  | ||||||
|     `, |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|     heading: "Custom icon", |     heading: "Custom icon", | ||||||
|     config: ` |     config: ` | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ import { getEntity } from "../../../../src/fake_data/entity"; | |||||||
| import { provideHass } from "../../../../src/fake_data/provide_hass"; | import { provideHass } from "../../../../src/fake_data/provide_hass"; | ||||||
| import "../../components/demo-cards"; | import "../../components/demo-cards"; | ||||||
| import { mockIcons } from "../../../../demo/src/stubs/icons"; | import { mockIcons } from "../../../../demo/src/stubs/icons"; | ||||||
| import { ClimateEntityFeature } from "../../../../src/data/climate"; |  | ||||||
|  |  | ||||||
| const ENTITIES = [ | const ENTITIES = [ | ||||||
|   getEntity("switch", "tv_outlet", "on", { |   getEntity("switch", "tv_outlet", "on", { | ||||||
| @@ -61,36 +60,6 @@ const ENTITIES = [ | |||||||
|       CoverEntityFeature.OPEN_TILT + |       CoverEntityFeature.OPEN_TILT + | ||||||
|       CoverEntityFeature.STOP_TILT, |       CoverEntityFeature.STOP_TILT, | ||||||
|   }), |   }), | ||||||
|   getEntity("input_number", "counter", "1.0", { |  | ||||||
|     friendly_name: "Counter", |  | ||||||
|     initial: 0, |  | ||||||
|     min: 0, |  | ||||||
|     max: 100, |  | ||||||
|     step: 1, |  | ||||||
|     mode: "slider", |  | ||||||
|   }), |  | ||||||
|   getEntity("climate", "dual_thermostat", "heat/cool", { |  | ||||||
|     friendly_name: "Dual thermostat", |  | ||||||
|     hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"], |  | ||||||
|     min_temp: 7, |  | ||||||
|     max_temp: 35, |  | ||||||
|     fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"], |  | ||||||
|     preset_modes: ["home", "eco", "away"], |  | ||||||
|     swing_modes: ["auto", "1", "2", "3", "off"], |  | ||||||
|     current_temperature: 23, |  | ||||||
|     target_temp_high: 24, |  | ||||||
|     target_temp_low: 21, |  | ||||||
|     fan_mode: "auto_low", |  | ||||||
|     preset_mode: "home", |  | ||||||
|     swing_mode: "auto", |  | ||||||
|     supported_features: |  | ||||||
|       ClimateEntityFeature.TURN_ON + |  | ||||||
|       ClimateEntityFeature.TURN_OFF + |  | ||||||
|       ClimateEntityFeature.SWING_MODE + |  | ||||||
|       ClimateEntityFeature.PRESET_MODE + |  | ||||||
|       ClimateEntityFeature.FAN_MODE + |  | ||||||
|       ClimateEntityFeature.TARGET_TEMPERATURE_RANGE, |  | ||||||
|   }), |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| const CONFIGS = [ | const CONFIGS = [ | ||||||
| @@ -224,25 +193,6 @@ const CONFIGS = [ | |||||||
|   - type: "cover-tilt" |   - type: "cover-tilt" | ||||||
|     `, |     `, | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     heading: "Number buttons feature", |  | ||||||
|     config: ` |  | ||||||
| - type: tile |  | ||||||
|   entity: input_number.counter |  | ||||||
|   features: |  | ||||||
|   - type: numeric-input |  | ||||||
|     style: buttons |  | ||||||
|     `, |  | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     heading: "Dual thermostat feature", |  | ||||||
|     config: ` |  | ||||||
| - type: tile |  | ||||||
|   entity: climate.dual_thermostat |  | ||||||
|   features: |  | ||||||
|   - type: target-temperature |  | ||||||
|     `, |  | ||||||
|   }, |  | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @customElement("demo-lovelace-tile-card") | @customElement("demo-lovelace-tile-card") | ||||||
|   | |||||||
| @@ -140,9 +140,6 @@ const ENTITIES: HassEntity[] = [ | |||||||
|   createEntity("climate.auto_preheating", "auto", undefined, { |   createEntity("climate.auto_preheating", "auto", undefined, { | ||||||
|     hvac_action: "preheating", |     hvac_action: "preheating", | ||||||
|   }), |   }), | ||||||
|   createEntity("climate.auto_defrosting", "auto", undefined, { |  | ||||||
|     hvac_action: "defrosting", |  | ||||||
|   }), |  | ||||||
|   createEntity("climate.auto_heating", "auto", undefined, { |   createEntity("climate.auto_heating", "auto", undefined, { | ||||||
|     hvac_action: "heating", |     hvac_action: "heating", | ||||||
|   }), |   }), | ||||||
| @@ -358,11 +355,13 @@ export class DemoEntityState extends LitElement { | |||||||
|         }, |         }, | ||||||
|         entity_id: { |         entity_id: { | ||||||
|           title: "Entity ID", |           title: "Entity ID", | ||||||
|  |           width: "30%", | ||||||
|           filterable: true, |           filterable: true, | ||||||
|           sortable: true, |           sortable: true, | ||||||
|         }, |         }, | ||||||
|         state: { |         state: { | ||||||
|           title: "State", |           title: "State", | ||||||
|  |           width: "20%", | ||||||
|           sortable: true, |           sortable: true, | ||||||
|           template: (entry) => |           template: (entry) => | ||||||
|             html`${computeStateDisplay( |             html`${computeStateDisplay( | ||||||
| @@ -377,12 +376,14 @@ export class DemoEntityState extends LitElement { | |||||||
|         device_class: { |         device_class: { | ||||||
|           title: "Device class", |           title: "Device class", | ||||||
|           template: (entry) => html`${entry.device_class ?? "-"}`, |           template: (entry) => html`${entry.device_class ?? "-"}`, | ||||||
|  |           width: "20%", | ||||||
|           filterable: true, |           filterable: true, | ||||||
|           sortable: true, |           sortable: true, | ||||||
|         }, |         }, | ||||||
|         domain: { |         domain: { | ||||||
|           title: "Domain", |           title: "Domain", | ||||||
|           template: (entry) => html`${computeDomain(entry.entity_id)}`, |           template: (entry) => html`${computeDomain(entry.entity_id)}`, | ||||||
|  |           width: "20%", | ||||||
|           filterable: true, |           filterable: true, | ||||||
|           sortable: true, |           sortable: true, | ||||||
|         }, |         }, | ||||||
|   | |||||||
| @@ -1,3 +0,0 @@ | |||||||
| --- |  | ||||||
| title: Markdown |  | ||||||
| --- |  | ||||||
| @@ -1,93 +0,0 @@ | |||||||
| import { css, html, LitElement } from "lit"; |  | ||||||
| import "../../../../src/components/ha-card"; |  | ||||||
| import "../../../../src/components/ha-markdown"; |  | ||||||
|  |  | ||||||
| import { customElement } from "lit/decorators"; |  | ||||||
|  |  | ||||||
| interface MarkdownContent { |  | ||||||
|   content: string; |  | ||||||
|   breaks: boolean; |  | ||||||
|   allowSvg: boolean; |  | ||||||
|   lazyImages: boolean; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const mdContentwithDefaults = (md: Partial<MarkdownContent>) => |  | ||||||
|   ({ |  | ||||||
|     breaks: false, |  | ||||||
|     allowSvg: false, |  | ||||||
|     lazyImages: false, |  | ||||||
|     ...md, |  | ||||||
|   }) as MarkdownContent; |  | ||||||
|  |  | ||||||
| const generateContent = (md) => ` |  | ||||||
| \`\`\`json |  | ||||||
| ${JSON.stringify({ ...md, content: undefined })} |  | ||||||
| \`\`\` |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ${md.content} |  | ||||||
| `; |  | ||||||
|  |  | ||||||
| const markdownContents: MarkdownContent[] = [ |  | ||||||
|   mdContentwithDefaults({ |  | ||||||
|     content: "_Hello_ **there** 👋, ~~nice~~ of you ||to|| show up.", |  | ||||||
|   }), |  | ||||||
|   ...[true, false].map((breaks) => |  | ||||||
|     mdContentwithDefaults({ |  | ||||||
|       breaks, |  | ||||||
|       content: ` |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| > [!TIP] |  | ||||||
| > Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer dictum quis ante eu eleifend. Integer sed [consectetur est, nec elementum magna](#). Fusce lobortis lectus ac rutrum tincidunt. Quisque suscipit gravida ante, in convallis risus vulputate non. |  | ||||||
|  |  | ||||||
| key | description |  | ||||||
| --  | -- |  | ||||||
| lorem | ipsum |  | ||||||
|  |  | ||||||
| - list item 1 |  | ||||||
| - list item 2 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     `, |  | ||||||
|     }) |  | ||||||
|   ), |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| @customElement("demo-misc-ha-markdown") |  | ||||||
| export class DemoMiscMarkdown extends LitElement { |  | ||||||
|   protected render() { |  | ||||||
|     return html` |  | ||||||
|       <div class="container"> |  | ||||||
|         ${markdownContents.map( |  | ||||||
|           (md) => |  | ||||||
|             html`<ha-card> |  | ||||||
|               <ha-markdown |  | ||||||
|                 .content=${generateContent(md)} |  | ||||||
|                 .breaks=${md.breaks} |  | ||||||
|                 .allowSvg=${md.allowSvg} |  | ||||||
|                 .lazyImages=${md.lazyImages} |  | ||||||
|               ></ha-markdown> |  | ||||||
|             </ha-card>` |  | ||||||
|         )} |  | ||||||
|       </div> |  | ||||||
|     `; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static get styles() { |  | ||||||
|     return css` |  | ||||||
|       ha-card { |  | ||||||
|         margin: 12px; |  | ||||||
|         padding: 12px; |  | ||||||
|       } |  | ||||||
|     `; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| declare global { |  | ||||||
|   interface HTMLElementTagNameMap { |  | ||||||
|     "demo-misc-ha-markdown": DemoMiscMarkdown; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -203,8 +203,6 @@ const createEntityRegistryEntries = ( | |||||||
|     options: null, |     options: null, | ||||||
|     labels: [], |     labels: [], | ||||||
|     categories: {}, |     categories: {}, | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| @@ -217,7 +215,6 @@ const createDeviceRegistryEntries = ( | |||||||
|     connections: [], |     connections: [], | ||||||
|     manufacturer: "ESPHome", |     manufacturer: "ESPHome", | ||||||
|     model: "Mock Device", |     model: "Mock Device", | ||||||
|     model_id: "ABC-001", |  | ||||||
|     name: "Tag Reader", |     name: "Tag Reader", | ||||||
|     sw_version: null, |     sw_version: null, | ||||||
|     hw_version: "1.0.0", |     hw_version: "1.0.0", | ||||||
| @@ -230,9 +227,6 @@ const createDeviceRegistryEntries = ( | |||||||
|     disabled_by: null, |     disabled_by: null, | ||||||
|     configuration_url: null, |     configuration_url: null, | ||||||
|     labels: [], |     labels: [], | ||||||
|     created_at: 0, |  | ||||||
|     modified_at: 0, |  | ||||||
|     primary_config_entry: null, |  | ||||||
|   }, |   }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -127,13 +127,14 @@ export class HassioBackups extends LitElement { | |||||||
|         main: true, |         main: true, | ||||||
|         sortable: true, |         sortable: true, | ||||||
|         filterable: true, |         filterable: true, | ||||||
|         flex: 2, |         grows: true, | ||||||
|         template: (backup) => |         template: (backup) => | ||||||
|           html`${backup.name || backup.slug} |           html`${backup.name || backup.slug} | ||||||
|             <div class="secondary">${backup.secondary}</div>`, |             <div class="secondary">${backup.secondary}</div>`, | ||||||
|       }, |       }, | ||||||
|       size: { |       size: { | ||||||
|         title: this.supervisor.localize("backup.size"), |         title: this.supervisor.localize("backup.size"), | ||||||
|  |         width: "15%", | ||||||
|         hidden: narrow, |         hidden: narrow, | ||||||
|         filterable: true, |         filterable: true, | ||||||
|         sortable: true, |         sortable: true, | ||||||
| @@ -141,6 +142,7 @@ export class HassioBackups extends LitElement { | |||||||
|       }, |       }, | ||||||
|       location: { |       location: { | ||||||
|         title: this.supervisor.localize("backup.location"), |         title: this.supervisor.localize("backup.location"), | ||||||
|  |         width: "15%", | ||||||
|         hidden: narrow, |         hidden: narrow, | ||||||
|         filterable: true, |         filterable: true, | ||||||
|         sortable: true, |         sortable: true, | ||||||
| @@ -149,6 +151,7 @@ export class HassioBackups extends LitElement { | |||||||
|       }, |       }, | ||||||
|       date: { |       date: { | ||||||
|         title: this.supervisor.localize("backup.created"), |         title: this.supervisor.localize("backup.created"), | ||||||
|  |         width: "15%", | ||||||
|         direction: "desc", |         direction: "desc", | ||||||
|         hidden: narrow, |         hidden: narrow, | ||||||
|         filterable: true, |         filterable: true, | ||||||
|   | |||||||
| @@ -1,8 +1,6 @@ | |||||||
| import type { IFuseOptions } from "fuse.js"; |  | ||||||
| import Fuse from "fuse.js"; | import Fuse from "fuse.js"; | ||||||
| import { stripDiacritics } from "../../../src/common/string/strip-diacritics"; | import type { IFuseOptions } from "fuse.js"; | ||||||
| import { StoreAddon } from "../../../src/data/supervisor/store"; | import { StoreAddon } from "../../../src/data/supervisor/store"; | ||||||
| import { getStripDiacriticsFn } from "../../../src/util/fuse"; |  | ||||||
|  |  | ||||||
| export function filterAndSort(addons: StoreAddon[], filter: string) { | export function filterAndSort(addons: StoreAddon[], filter: string) { | ||||||
|   const options: IFuseOptions<StoreAddon> = { |   const options: IFuseOptions<StoreAddon> = { | ||||||
| @@ -10,8 +8,7 @@ export function filterAndSort(addons: StoreAddon[], filter: string) { | |||||||
|     isCaseSensitive: false, |     isCaseSensitive: false, | ||||||
|     minMatchCharLength: Math.min(filter.length, 2), |     minMatchCharLength: Math.min(filter.length, 2), | ||||||
|     threshold: 0.2, |     threshold: 0.2, | ||||||
|     getFn: getStripDiacriticsFn, |  | ||||||
|   }; |   }; | ||||||
|   const fuse = new Fuse(addons, options); |   const fuse = new Fuse(addons, options); | ||||||
|   return fuse.search(stripDiacritics(filter)).map((result) => result.item); |   return fuse.search(filter).map((result) => result.item); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -66,8 +66,7 @@ class HassioRepositoriesDialog extends LitElement { | |||||||
|           repo.slug !== "core" && // The core add-ons repository |           repo.slug !== "core" && // The core add-ons repository | ||||||
|           repo.slug !== "local" && // Locally managed add-ons |           repo.slug !== "local" && // Locally managed add-ons | ||||||
|           repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons |           repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons | ||||||
|           repo.slug !== "5c53de3b" && // The ESPHome repository |           repo.slug !== "5c53de3b" // The ESPHome repository | ||||||
|           repo.slug !== "d5369777" // Music Assistant repository |  | ||||||
|       ) |       ) | ||||||
|       .sort((a, b) => |       .sort((a, b) => | ||||||
|         caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language) |         caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language) | ||||||
|   | |||||||
| @@ -4,7 +4,11 @@ | |||||||
|     el.src = src; |     el.src = src; | ||||||
|     document.body.appendChild(el); |     document.body.appendChild(el); | ||||||
|   } |   } | ||||||
|   if (<%= modernRegex %>.test(navigator.userAgent)) { |   if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) { | ||||||
|  |     <% for (const entry of es5EntryJS) { %> | ||||||
|  |       loadES5("<%= entry %>"); | ||||||
|  |     <% } %> | ||||||
|  |   } else { | ||||||
|     try { |     try { | ||||||
|         <% for (const entry of latestEntryJS) { %> |         <% for (const entry of latestEntryJS) { %> | ||||||
|           new Function("import('<%= entry %>')")(); |           new Function("import('<%= entry %>')")(); | ||||||
| @@ -13,10 +17,6 @@ | |||||||
|       <% for (const entry of es5EntryJS) { %> |       <% for (const entry of es5EntryJS) { %> | ||||||
|         loadES5("<%= entry %>"); |         loadES5("<%= entry %>"); | ||||||
|       <% } %> |       <% } %> | ||||||
|   } else { |  | ||||||
|     <% for (const entry of es5EntryJS) { %> |  | ||||||
|       loadES5("<%= entry %>"); |  | ||||||
|     <% } %> |  | ||||||
|   } |   } | ||||||
|   } |   } | ||||||
| })(); | })(); | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| // Compat needs to be first import | // Compat needs to be first import | ||||||
| import "../../src/resources/compatibility"; | import "../../src/resources/compatibility"; | ||||||
|  | import "../../src/resources/safari-14-attachshadow-patch"; | ||||||
| import "./hassio-main"; | import "./hassio-main"; | ||||||
|  |  | ||||||
| import("../../src/resources/ha-style"); | import("../../src/resources/ha-style"); | ||||||
|   | |||||||
							
								
								
									
										105
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								package.json
									
									
									
									
									
								
							| @@ -16,7 +16,7 @@ | |||||||
|     "lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"", |     "lint:lit": "lit-analyzer \"{.,*}/src/**/*.ts\"", | ||||||
|     "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types && yarn run lint:lit", |     "lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types && yarn run lint:lit", | ||||||
|     "format": "yarn run format:eslint && yarn run format:prettier", |     "format": "yarn run format:eslint && yarn run format:prettier", | ||||||
|     "postinstall": "husky", |     "postinstall": "husky install", | ||||||
|     "prepack": "pinst --disable", |     "prepack": "pinst --disable", | ||||||
|     "postpack": "pinst --enable", |     "postpack": "pinst --enable", | ||||||
|     "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\"" |     "test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.cjs \"test/**/*.ts\"" | ||||||
| @@ -25,15 +25,15 @@ | |||||||
|   "license": "Apache-2.0", |   "license": "Apache-2.0", | ||||||
|   "type": "module", |   "type": "module", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@babel/runtime": "7.25.6", |     "@babel/runtime": "7.24.7", | ||||||
|     "@braintree/sanitize-url": "7.1.0", |     "@braintree/sanitize-url": "7.0.3", | ||||||
|     "@codemirror/autocomplete": "6.18.0", |     "@codemirror/autocomplete": "6.16.3", | ||||||
|     "@codemirror/commands": "6.6.1", |     "@codemirror/commands": "6.6.0", | ||||||
|     "@codemirror/language": "6.10.2", |     "@codemirror/language": "6.10.2", | ||||||
|     "@codemirror/legacy-modes": "6.4.1", |     "@codemirror/legacy-modes": "6.4.0", | ||||||
|     "@codemirror/search": "6.5.6", |     "@codemirror/search": "6.5.6", | ||||||
|     "@codemirror/state": "6.4.1", |     "@codemirror/state": "6.4.1", | ||||||
|     "@codemirror/view": "6.33.0", |     "@codemirror/view": "6.28.2", | ||||||
|     "@egjs/hammerjs": "2.0.17", |     "@egjs/hammerjs": "2.0.17", | ||||||
|     "@formatjs/intl-datetimeformat": "6.12.5", |     "@formatjs/intl-datetimeformat": "6.12.5", | ||||||
|     "@formatjs/intl-displaynames": "6.6.8", |     "@formatjs/intl-displaynames": "6.6.8", | ||||||
| @@ -43,17 +43,17 @@ | |||||||
|     "@formatjs/intl-numberformat": "8.10.3", |     "@formatjs/intl-numberformat": "8.10.3", | ||||||
|     "@formatjs/intl-pluralrules": "5.2.14", |     "@formatjs/intl-pluralrules": "5.2.14", | ||||||
|     "@formatjs/intl-relativetimeformat": "11.2.14", |     "@formatjs/intl-relativetimeformat": "11.2.14", | ||||||
|     "@fullcalendar/core": "6.1.15", |     "@fullcalendar/core": "6.1.11", | ||||||
|     "@fullcalendar/daygrid": "6.1.15", |     "@fullcalendar/daygrid": "6.1.11", | ||||||
|     "@fullcalendar/interaction": "6.1.15", |     "@fullcalendar/interaction": "6.1.11", | ||||||
|     "@fullcalendar/list": "6.1.15", |     "@fullcalendar/list": "6.1.11", | ||||||
|     "@fullcalendar/luxon3": "6.1.15", |     "@fullcalendar/luxon3": "6.1.11", | ||||||
|     "@fullcalendar/timegrid": "6.1.15", |     "@fullcalendar/timegrid": "6.1.11", | ||||||
|     "@lezer/highlight": "1.2.1", |     "@lezer/highlight": "1.2.0", | ||||||
|     "@lit-labs/context": "0.4.1", |     "@lit-labs/context": "0.4.1", | ||||||
|     "@lit-labs/motion": "1.0.7", |     "@lit-labs/motion": "1.0.7", | ||||||
|     "@lit-labs/observers": "2.0.2", |     "@lit-labs/observers": "2.0.2", | ||||||
|     "@lit-labs/virtualizer": "2.0.14", |     "@lit-labs/virtualizer": "2.0.13", | ||||||
|     "@lrnwebcomponents/simple-tooltip": "8.0.2", |     "@lrnwebcomponents/simple-tooltip": "8.0.2", | ||||||
|     "@material/chips": "=14.0.0-canary.53b3cad2f.0", |     "@material/chips": "=14.0.0-canary.53b3cad2f.0", | ||||||
|     "@material/data-table": "=14.0.0-canary.53b3cad2f.0", |     "@material/data-table": "=14.0.0-canary.53b3cad2f.0", | ||||||
| @@ -80,7 +80,7 @@ | |||||||
|     "@material/mwc-top-app-bar": "0.27.0", |     "@material/mwc-top-app-bar": "0.27.0", | ||||||
|     "@material/mwc-top-app-bar-fixed": "0.27.0", |     "@material/mwc-top-app-bar-fixed": "0.27.0", | ||||||
|     "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0", |     "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0", | ||||||
|     "@material/web": "2.1.0", |     "@material/web": "1.5.0", | ||||||
|     "@mdi/js": "7.4.47", |     "@mdi/js": "7.4.47", | ||||||
|     "@mdi/svg": "7.4.47", |     "@mdi/svg": "7.4.47", | ||||||
|     "@polymer/paper-item": "3.0.1", |     "@polymer/paper-item": "3.0.1", | ||||||
| @@ -88,8 +88,8 @@ | |||||||
|     "@polymer/paper-tabs": "3.1.0", |     "@polymer/paper-tabs": "3.1.0", | ||||||
|     "@polymer/polymer": "3.5.1", |     "@polymer/polymer": "3.5.1", | ||||||
|     "@thomasloven/round-slider": "0.6.0", |     "@thomasloven/round-slider": "0.6.0", | ||||||
|     "@vaadin/combo-box": "24.4.7", |     "@vaadin/combo-box": "24.4.0", | ||||||
|     "@vaadin/vaadin-themable-mixin": "24.4.7", |     "@vaadin/vaadin-themable-mixin": "24.4.0", | ||||||
|     "@vibrant/color": "3.2.1-alpha.1", |     "@vibrant/color": "3.2.1-alpha.1", | ||||||
|     "@vibrant/core": "3.2.1-alpha.1", |     "@vibrant/core": "3.2.1-alpha.1", | ||||||
|     "@vibrant/quantizer-mmcq": "3.2.1-alpha.1", |     "@vibrant/quantizer-mmcq": "3.2.1-alpha.1", | ||||||
| @@ -97,10 +97,10 @@ | |||||||
|     "@webcomponents/scoped-custom-element-registry": "0.0.9", |     "@webcomponents/scoped-custom-element-registry": "0.0.9", | ||||||
|     "@webcomponents/webcomponentsjs": "2.8.0", |     "@webcomponents/webcomponentsjs": "2.8.0", | ||||||
|     "app-datepicker": "5.1.1", |     "app-datepicker": "5.1.1", | ||||||
|     "chart.js": "4.4.4", |     "chart.js": "4.4.3", | ||||||
|     "color-name": "2.0.0", |     "color-name": "2.0.0", | ||||||
|     "comlink": "4.4.1", |     "comlink": "4.4.1", | ||||||
|     "core-js": "3.38.1", |     "core-js": "3.37.1", | ||||||
|     "cropperjs": "1.6.2", |     "cropperjs": "1.6.2", | ||||||
|     "date-fns": "3.6.0", |     "date-fns": "3.6.0", | ||||||
|     "date-fns-tz": "3.1.3", |     "date-fns-tz": "3.1.3", | ||||||
| @@ -117,20 +117,20 @@ | |||||||
|     "leaflet": "1.9.4", |     "leaflet": "1.9.4", | ||||||
|     "leaflet-draw": "1.0.4", |     "leaflet-draw": "1.0.4", | ||||||
|     "lit": "2.8.0", |     "lit": "2.8.0", | ||||||
|     "luxon": "3.5.0", |     "luxon": "3.4.4", | ||||||
|     "marked": "14.1.0", |     "marked": "12.0.2", | ||||||
|     "memoize-one": "6.0.0", |     "memoize-one": "6.0.0", | ||||||
|     "node-vibrant": "3.2.1-alpha.1", |     "node-vibrant": "3.2.1-alpha.1", | ||||||
|     "proxy-polyfill": "0.3.2", |     "proxy-polyfill": "0.3.2", | ||||||
|     "punycode": "2.3.1", |     "punycode": "2.3.1", | ||||||
|     "qr-scanner": "1.4.2", |     "qr-scanner": "1.4.2", | ||||||
|     "qrcode": "1.5.4", |     "qrcode": "1.5.3", | ||||||
|     "roboto-fontface": "0.10.0", |     "roboto-fontface": "0.10.0", | ||||||
|     "rrule": "2.8.1", |     "rrule": "2.8.1", | ||||||
|     "sortablejs": "1.15.2", |     "sortablejs": "1.15.2", | ||||||
|     "stacktrace-js": "2.0.2", |     "stacktrace-js": "2.0.2", | ||||||
|     "superstruct": "2.0.2", |     "superstruct": "1.0.4", | ||||||
|     "tinykeys": "3.0.0", |     "tinykeys": "2.1.0", | ||||||
|     "tsparticles-engine": "2.12.0", |     "tsparticles-engine": "2.12.0", | ||||||
|     "tsparticles-preset-links": "2.12.0", |     "tsparticles-preset-links": "2.12.0", | ||||||
|     "ua-parser-js": "1.0.38", |     "ua-parser-js": "1.0.38", | ||||||
| @@ -149,18 +149,18 @@ | |||||||
|     "xss": "1.0.15" |     "xss": "1.0.15" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@babel/core": "7.25.2", |     "@babel/core": "7.24.7", | ||||||
|     "@babel/helper-define-polyfill-provider": "0.6.2", |     "@babel/helper-define-polyfill-provider": "0.6.2", | ||||||
|     "@babel/plugin-proposal-decorators": "7.24.7", |     "@babel/plugin-proposal-decorators": "7.24.7", | ||||||
|     "@babel/plugin-transform-runtime": "7.25.4", |     "@babel/plugin-transform-runtime": "7.24.7", | ||||||
|     "@babel/preset-env": "7.25.4", |     "@babel/preset-env": "7.24.7", | ||||||
|     "@babel/preset-typescript": "7.24.7", |     "@babel/preset-typescript": "7.24.7", | ||||||
|     "@bundle-stats/plugin-webpack-filter": "4.15.0", |     "@bundle-stats/plugin-webpack-filter": "4.13.2", | ||||||
|     "@koa/cors": "5.0.0", |     "@koa/cors": "5.0.0", | ||||||
|     "@lokalise/node-api": "12.7.0", |     "@lokalise/node-api": "12.5.0", | ||||||
|     "@octokit/auth-oauth-device": "7.1.1", |     "@octokit/auth-oauth-device": "7.1.1", | ||||||
|     "@octokit/plugin-retry": "7.1.1", |     "@octokit/plugin-retry": "7.1.1", | ||||||
|     "@octokit/rest": "21.0.2", |     "@octokit/rest": "21.0.0", | ||||||
|     "@open-wc/dev-server-hmr": "0.1.4", |     "@open-wc/dev-server-hmr": "0.1.4", | ||||||
|     "@rollup/plugin-babel": "6.0.4", |     "@rollup/plugin-babel": "6.0.4", | ||||||
|     "@rollup/plugin-commonjs": "26.0.1", |     "@rollup/plugin-commonjs": "26.0.1", | ||||||
| @@ -168,7 +168,7 @@ | |||||||
|     "@rollup/plugin-node-resolve": "15.2.3", |     "@rollup/plugin-node-resolve": "15.2.3", | ||||||
|     "@rollup/plugin-replace": "5.0.7", |     "@rollup/plugin-replace": "5.0.7", | ||||||
|     "@types/babel__plugin-transform-runtime": "7.9.5", |     "@types/babel__plugin-transform-runtime": "7.9.5", | ||||||
|     "@types/chromecast-caf-receiver": "6.0.17", |     "@types/chromecast-caf-receiver": "6.0.15", | ||||||
|     "@types/chromecast-caf-sender": "1.0.10", |     "@types/chromecast-caf-sender": "1.0.10", | ||||||
|     "@types/color-name": "1.1.4", |     "@types/color-name": "1.1.4", | ||||||
|     "@types/glob": "8.1.0", |     "@types/glob": "8.1.0", | ||||||
| @@ -178,20 +178,19 @@ | |||||||
|     "@types/leaflet-draw": "1.0.11", |     "@types/leaflet-draw": "1.0.11", | ||||||
|     "@types/lodash.merge": "4.6.9", |     "@types/lodash.merge": "4.6.9", | ||||||
|     "@types/luxon": "3.4.2", |     "@types/luxon": "3.4.2", | ||||||
|     "@types/mocha": "10.0.7", |     "@types/mocha": "10.0.6", | ||||||
|     "@types/qrcode": "1.5.5", |     "@types/qrcode": "1.5.5", | ||||||
|     "@types/serve-handler": "6.1.4", |     "@types/serve-handler": "6.1.4", | ||||||
|     "@types/sortablejs": "1.15.8", |     "@types/sortablejs": "1.15.8", | ||||||
|     "@types/tar": "6.1.13", |     "@types/tar": "6.1.13", | ||||||
|     "@types/ua-parser-js": "0.7.39", |     "@types/ua-parser-js": "0.7.39", | ||||||
|     "@types/webspeechapi": "0.0.29", |     "@types/webspeechapi": "0.0.29", | ||||||
|     "@typescript-eslint/eslint-plugin": "7.18.0", |     "@typescript-eslint/eslint-plugin": "7.13.1", | ||||||
|     "@typescript-eslint/parser": "7.18.0", |     "@typescript-eslint/parser": "7.13.1", | ||||||
|     "@web/dev-server": "0.1.38", |     "@web/dev-server": "0.1.38", | ||||||
|     "@web/dev-server-rollup": "0.4.1", |     "@web/dev-server-rollup": "0.4.1", | ||||||
|     "babel-loader": "9.1.3", |     "babel-loader": "9.1.3", | ||||||
|     "babel-plugin-template-html-minifier": "4.1.0", |     "babel-plugin-template-html-minifier": "4.1.0", | ||||||
|     "browserslist-useragent-regexp": "4.1.3", |  | ||||||
|     "chai": "5.1.1", |     "chai": "5.1.1", | ||||||
|     "del": "7.1.0", |     "del": "7.1.0", | ||||||
|     "eslint": "8.57.0", |     "eslint": "8.57.0", | ||||||
| @@ -201,51 +200,51 @@ | |||||||
|     "eslint-import-resolver-webpack": "0.13.8", |     "eslint-import-resolver-webpack": "0.13.8", | ||||||
|     "eslint-plugin-import": "2.29.1", |     "eslint-plugin-import": "2.29.1", | ||||||
|     "eslint-plugin-lit": "1.14.0", |     "eslint-plugin-lit": "1.14.0", | ||||||
|     "eslint-plugin-lit-a11y": "4.1.4", |     "eslint-plugin-lit-a11y": "4.1.2", | ||||||
|     "eslint-plugin-unused-imports": "4.1.3", |     "eslint-plugin-unused-imports": "4.0.0", | ||||||
|     "eslint-plugin-wc": "2.1.1", |     "eslint-plugin-wc": "2.1.0", | ||||||
|     "fancy-log": "2.0.0", |     "fancy-log": "2.0.0", | ||||||
|     "fs-extra": "11.2.0", |     "fs-extra": "11.2.0", | ||||||
|     "glob": "11.0.0", |     "glob": "10.4.2", | ||||||
|     "gulp": "5.0.0", |     "gulp": "5.0.0", | ||||||
|     "gulp-brotli": "3.0.0", |  | ||||||
|     "gulp-json-transform": "0.5.0", |     "gulp-json-transform": "0.5.0", | ||||||
|     "gulp-rename": "2.0.0", |     "gulp-rename": "2.0.0", | ||||||
|     "gulp-zopfli-green": "6.0.2", |     "gulp-zopfli-green": "6.0.1", | ||||||
|     "html-minifier-terser": "7.2.0", |     "html-minifier-terser": "7.2.0", | ||||||
|     "husky": "9.1.5", |     "husky": "9.0.11", | ||||||
|     "instant-mocha": "1.5.2", |     "instant-mocha": "1.5.2", | ||||||
|     "jszip": "3.10.1", |     "jszip": "3.10.1", | ||||||
|     "lint-staged": "15.2.10", |     "lint-staged": "15.2.7", | ||||||
|     "lit-analyzer": "2.0.3", |     "lit-analyzer": "2.0.3", | ||||||
|     "lodash.merge": "4.6.2", |     "lodash.merge": "4.6.2", | ||||||
|     "lodash.template": "4.5.0", |     "lodash.template": "4.5.0", | ||||||
|     "magic-string": "0.30.11", |     "magic-string": "0.30.10", | ||||||
|     "map-stream": "0.0.7", |     "map-stream": "0.0.7", | ||||||
|     "mocha": "10.5.0", |     "mocha": "10.4.0", | ||||||
|     "object-hash": "3.0.0", |     "object-hash": "3.0.0", | ||||||
|     "open": "10.1.0", |     "open": "10.1.0", | ||||||
|     "pinst": "3.0.0", |     "pinst": "3.0.0", | ||||||
|     "prettier": "3.3.3", |     "prettier": "3.3.2", | ||||||
|     "rollup": "2.79.1", |     "rollup": "2.79.1", | ||||||
|     "rollup-plugin-string": "3.0.0", |     "rollup-plugin-string": "3.0.0", | ||||||
|     "rollup-plugin-terser": "7.0.2", |     "rollup-plugin-terser": "7.0.2", | ||||||
|     "rollup-plugin-visualizer": "5.12.0", |     "rollup-plugin-visualizer": "5.12.0", | ||||||
|     "serve-handler": "6.1.5", |     "serve-handler": "6.1.5", | ||||||
|     "sinon": "18.0.0", |     "sinon": "18.0.0", | ||||||
|  |     "source-map-url": "0.4.1", | ||||||
|     "systemjs": "6.15.1", |     "systemjs": "6.15.1", | ||||||
|     "tar": "7.4.3", |     "tar": "7.4.0", | ||||||
|     "terser-webpack-plugin": "5.3.10", |     "terser-webpack-plugin": "5.3.10", | ||||||
|     "transform-async-modules-webpack-plugin": "1.1.1", |     "transform-async-modules-webpack-plugin": "1.1.1", | ||||||
|     "ts-lit-plugin": "2.0.2", |     "ts-lit-plugin": "2.0.2", | ||||||
|     "typescript": "5.5.4", |     "typescript": "5.4.5", | ||||||
|     "webpack": "5.94.0", |     "webpack": "5.92.1", | ||||||
|     "webpack-cli": "5.1.4", |     "webpack-cli": "5.1.4", | ||||||
|     "webpack-dev-server": "5.0.4", |     "webpack-dev-server": "5.0.4", | ||||||
|     "webpack-manifest-plugin": "5.0.0", |     "webpack-manifest-plugin": "5.0.0", | ||||||
|     "webpack-stats-plugin": "1.1.3", |     "webpack-stats-plugin": "1.1.3", | ||||||
|     "webpackbar": "6.0.1", |     "webpackbar": "6.0.1", | ||||||
|     "workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch" |     "workbox-build": "7.1.1" | ||||||
|   }, |   }, | ||||||
|   "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch", |   "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch", | ||||||
|   "resolutions": { |   "resolutions": { | ||||||
| @@ -254,9 +253,9 @@ | |||||||
|     "lit": "2.8.0", |     "lit": "2.8.0", | ||||||
|     "clean-css": "5.3.3", |     "clean-css": "5.3.3", | ||||||
|     "@lit/reactive-element": "1.6.3", |     "@lit/reactive-element": "1.6.3", | ||||||
|     "@fullcalendar/daygrid": "6.1.15", |     "@fullcalendar/daygrid": "6.1.11", | ||||||
|     "sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch", |     "sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch", | ||||||
|     "leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch" |     "leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch" | ||||||
|   }, |   }, | ||||||
|   "packageManager": "yarn@4.4.1" |   "packageManager": "yarn@4.3.1" | ||||||
| } | } | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| Before Width: | Height: | Size: 5.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/static/images/logo_twitter.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/static/images/logo_twitter.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 1.2 KiB | 
| @@ -1,3 +0,0 @@ | |||||||
| <svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg"> |  | ||||||
| <path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/> |  | ||||||
| </svg> |  | ||||||
| Before Width: | Height: | Size: 430 B | 
| @@ -1,66 +0,0 @@ | |||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <svg id="b" data-name="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760.69 138.69"> |  | ||||||
|   <g id="c" data-name="Layer_1"> |  | ||||||
|     <g> |  | ||||||
|       <g> |  | ||||||
|         <path d="M136.22,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM136.27,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/> |  | ||||||
|         <path d="M184.16,80.53c0,3.47-1.06,6.27-3.18,8.41s-4.98,3.21-8.59,3.21h-7.45v12h-6.56v-35.18h14.06c3.64,0,6.5,1.04,8.59,3.11s3.13,4.89,3.13,8.45ZM177.25,80.39c0-1.64-.52-2.98-1.56-4.03s-2.52-1.57-4.44-1.57h-6.3v11.65h6.26c1.95,0,3.45-.55,4.49-1.65s1.56-2.57,1.56-4.39Z"/> |  | ||||||
|         <path d="M210.82,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/> |  | ||||||
|         <path d="M246.95,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/> |  | ||||||
|         <path d="M266.45,68.98h6.56v14.44l14.7.05v-14.48h6.63v35.18h-6.63v-14.84l-14.7-.09v14.93h-6.56v-35.18Z"/> |  | ||||||
|         <path d="M316.41,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM316.46,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/> |  | ||||||
|         <path d="M373.66,68.98v35.18h-6.45v-20.55l-8.11,20.55h-6.23l-8.02-20.39v20.39h-6.28v-35.18h6.28l11.13,27.54,11.23-27.54h6.45Z"/> |  | ||||||
|         <path d="M402.87,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/> |  | ||||||
|         <path d="M427.83,75.12v8.93h13.01l-.05,5.91h-12.96v14.2h-6.52v-35.18h21.98l-.05,6.14h-15.42Z"/> |  | ||||||
|         <path d="M463.16,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM463.21,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/> |  | ||||||
|         <path d="M485,68.98h6.56v22.12c0,2.31.72,4.12,2.16,5.43s3.3,1.96,5.58,1.96,4.08-.67,5.58-2.02,2.25-3.13,2.25-5.37v-22.12h6.52v22.31c0,2.08-.38,3.98-1.14,5.7s-1.79,3.14-3.09,4.25-2.82,1.98-4.56,2.59-3.59.91-5.55.91c-2.59,0-4.96-.52-7.1-1.55s-3.88-2.58-5.2-4.65-1.99-4.49-1.99-7.25v-22.31Z"/> |  | ||||||
|         <path d="M549.63,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/> |  | ||||||
|         <path d="M586.9,86.58c.05,3.34-.71,6.37-2.27,9.08s-3.7,4.82-6.42,6.32-5.73,2.23-9.02,2.18h-12.42v-35.18h12.42c2.45-.03,4.78.39,6.98,1.28s4.1,2.1,5.68,3.66,2.84,3.43,3.75,5.64,1.35,4.55,1.3,7.03ZM579.99,86.58c0-3.39-1-6.16-3.01-8.3s-4.62-3.21-7.84-3.21h-5.81v23.04h5.81c3.27,0,5.89-1.06,7.88-3.19s2.98-4.91,2.98-8.34Z"/> |  | ||||||
|         <path d="M609.16,96.19h-12.73l-2.79,7.97h-6.82l12.68-35.18h6.63l12.66,35.18h-6.96l-2.67-7.97ZM607.24,90.73l-4.43-12.87-4.45,12.87h8.88Z"/> |  | ||||||
|         <path d="M642.87,75.17h-9.89v28.99h-6.56v-28.99h-9.94v-6.19h26.39v6.19Z"/> |  | ||||||
|         <path d="M647.06,104.16v-35.18h6.56v35.18h-6.56Z"/> |  | ||||||
|         <path d="M675.71,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM675.76,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/> |  | ||||||
|         <path d="M726.96,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/> |  | ||||||
|       </g> |  | ||||||
|       <g> |  | ||||||
|         <path d="M94.34,79.34c0,2.75-2.25,5-5,5h-50c-2.75,0-5-2.25-5-5v-20c0-2.75,1.59-6.59,3.54-8.54l22.93-22.93c1.94-1.94,5.13-1.94,7.07,0l22.93,22.93c1.94,1.94,3.54,5.79,3.54,8.54v20Z"/> |  | ||||||
|         <g> |  | ||||||
|           <rect x="34.34" y="94.34" width="60" height="10" rx="2.5" ry="2.5"/> |  | ||||||
|           <rect x="34.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/> |  | ||||||
|           <rect x="84.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/> |  | ||||||
|         </g> |  | ||||||
|       </g> |  | ||||||
|       <path d="M735.34,3c12.32,0,22.34,10.02,22.34,22.34v88c0,12.32-10.02,22.34-22.34,22.34H25.34c-12.32,0-22.34-10.02-22.34-22.34V25.34C3,13.02,13.02,3,25.34,3h710M735.34,0H25.34C11.37,0,0,11.37,0,25.34v88c0,13.98,11.37,25.34,25.34,25.34h710c13.97,0,25.34-11.37,25.34-25.34V25.34c0-13.98-11.37-25.34-25.34-25.34h0Z"/> |  | ||||||
|       <g> |  | ||||||
|         <path d="M120.98,36.79h2.95v7.26l7.66.02v-7.29h2.97v17.37h-2.97v-7.47l-7.66-.02v7.49h-2.95v-17.37Z"/> |  | ||||||
|         <path d="M146.97,36.47c1.63,0,3.09.39,4.37,1.16s2.28,1.84,2.99,3.2,1.06,2.9,1.06,4.61c.02,1.7-.32,3.24-1.04,4.62s-1.72,2.47-3.02,3.25-2.75,1.16-4.36,1.14c-1.62.02-3.08-.36-4.37-1.14s-2.29-1.86-3-3.24-1.05-2.91-1.03-4.61c0-1.27.2-2.47.61-3.58s.99-2.08,1.72-2.88,1.63-1.42,2.68-1.88,2.18-.67,3.39-.66ZM146.99,51.57c1.6,0,2.89-.56,3.85-1.67s1.45-2.6,1.45-4.45-.48-3.32-1.45-4.43-2.25-1.66-3.85-1.66-2.89.55-3.86,1.66-1.45,2.58-1.45,4.43.48,3.34,1.44,4.46,2.25,1.67,3.88,1.67Z"/> |  | ||||||
|         <path d="M176.51,36.79v17.37h-2.89v-10.78l-4.29,10.78h-2.81l-4.25-10.71v10.71h-2.84v-17.37h2.84l5.66,13.92,5.69-13.92h2.89Z"/> |  | ||||||
|         <path d="M192.41,51.37v2.79h-10.78v-17.37h10.78v2.81h-7.83v4.5h7v2.61h-7v4.66h7.83Z"/> |  | ||||||
|         <path d="M213.93,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM213.07,47.62l-2.43-6.95-2.45,6.95h4.88Z"/> |  | ||||||
|         <path d="M226.96,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/> |  | ||||||
|         <path d="M242.38,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/> |  | ||||||
|         <path d="M252.68,54.16v-17.37h2.95v17.37h-2.95Z"/> |  | ||||||
|         <path d="M265.82,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/> |  | ||||||
|         <path d="M287.47,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/> |  | ||||||
|         <path d="M298.87,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM298.01,47.62l-2.43-6.95-2.45,6.95h4.88Z"/> |  | ||||||
|         <path d="M320.89,36.79v17.37h-2.93l-8.25-12.67v12.67h-2.93v-17.37h2.93l8.25,12.65v-12.65h2.93Z"/> |  | ||||||
|         <path d="M337.31,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/> |  | ||||||
|         <path d="M348.75,54.16v-17.14h2.05v17.14h-2.05Z"/> |  | ||||||
|         <path d="M360.95,36.72c1.55,0,2.82.38,3.81,1.14,1,.74,1.61,1.72,1.82,2.95l-1.95.52c-.16-.87-.56-1.54-1.23-2.02s-1.5-.71-2.5-.71c-1.08,0-1.95.27-2.6.8s-.97,1.24-.97,2.13c0,1.36.84,2.26,2.52,2.71l2.9.73c1.37.34,2.41.9,3.11,1.68s1.05,1.74,1.05,2.9c0,1.46-.53,2.64-1.6,3.54s-2.49,1.36-4.28,1.36c-1.61,0-2.95-.37-4.03-1.12s-1.72-1.76-1.95-3.06l1.98-.55c.13.88.55,1.56,1.25,2.06s1.63.75,2.77.75,2.12-.26,2.79-.77,1.02-1.22,1.02-2.14c0-1.44-.84-2.36-2.52-2.77l-2.88-.71c-1.38-.34-2.42-.9-3.12-1.69s-1.05-1.75-1.05-2.9c0-1.44.52-2.61,1.56-3.51s2.4-1.35,4.09-1.35Z"/> |  | ||||||
|         <path d="M388.35,49.75h-7.54l-1.59,4.4h-2.07l6.25-17.14h2.36l6.31,17.14h-2.15l-1.57-4.4ZM387.73,48.05l-3.09-8.71-3.2,8.71h6.29Z"/> |  | ||||||
|         <path d="M415.46,42.47c0,1.6-.5,2.91-1.5,3.95s-2.32,1.56-3.97,1.56h-4.53v6.18h-2.05v-17.14h6.6c1.67,0,3,.49,3.98,1.47s1.47,2.31,1.47,3.98ZM413.31,42.42c0-1.07-.32-1.92-.95-2.56s-1.51-.96-2.64-.96h-4.26v7.24h4.17c1.15,0,2.06-.34,2.71-1.02s.98-1.58.98-2.7Z"/> |  | ||||||
|         <path d="M428.37,46.9l3.43,7.26h-2.31l-3.18-6.95h-4.76v6.95h-2.05v-17.14h6.54c1.81,0,3.22.45,4.24,1.35s1.53,2.14,1.53,3.72c0,1.22-.3,2.26-.9,3.1s-1.44,1.41-2.53,1.7ZM429.64,42.12c0-1.01-.32-1.81-.95-2.38s-1.52-.86-2.66-.86h-4.5v6.47h4.53c1.15,0,2.03-.28,2.64-.85s.92-1.36.92-2.38Z"/> |  | ||||||
|         <path d="M443.34,36.74c1.18-.02,2.28.2,3.31.65s1.9,1.07,2.62,1.85,1.28,1.73,1.69,2.83.6,2.27.59,3.52c.02,1.67-.33,3.19-1.03,4.54s-1.68,2.42-2.95,3.19-2.68,1.14-4.25,1.12c-1.59,0-3-.38-4.25-1.13s-2.21-1.81-2.89-3.15-1.03-2.87-1.03-4.57c-.02-1.67.32-3.18,1.02-4.53s1.67-2.42,2.93-3.2,2.68-1.15,4.24-1.13ZM443.34,52.45c1.8,0,3.26-.64,4.38-1.91s1.68-2.92,1.68-4.95-.56-3.71-1.68-4.98-2.58-1.9-4.38-1.9-3.28.63-4.4,1.9-1.68,2.93-1.68,4.98.56,3.69,1.68,4.96,2.59,1.9,4.39,1.9Z"/> |  | ||||||
|         <path d="M464.3,37.02v12.42c0,1.49-.47,2.71-1.41,3.64s-2.17,1.39-3.71,1.39c-1.56,0-2.76-.46-3.61-1.37s-1.27-2.13-1.27-3.69v-.55h1.98v.55c0,1.18.29,1.99.86,2.45.59.45,1.26.67,2.02.67.93,0,1.68-.26,2.24-.78.57-.52.85-1.32.85-2.39v-12.35h2.05Z"/> |  | ||||||
|         <path d="M479.86,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/> |  | ||||||
|         <path d="M496.97,42.42c-.36-1.15-1.01-2.06-1.93-2.71s-2.02-.98-3.3-.98c-1.79,0-3.24.63-4.35,1.89s-1.66,2.92-1.66,4.96.55,3.72,1.66,4.96,2.55,1.86,4.33,1.86c1.27,0,2.39-.31,3.35-.94.95-.63,1.61-1.44,1.98-2.45l1.93.83c-.55,1.41-1.48,2.53-2.78,3.36-1.31.81-2.81,1.22-4.51,1.22-2.4,0-4.35-.81-5.84-2.43s-2.24-3.75-2.24-6.4c0-1.74.34-3.28,1.02-4.62s1.64-2.39,2.88-3.13,2.65-1.11,4.25-1.11c1.8,0,3.33.46,4.59,1.38,1.26.91,2.12,2.1,2.57,3.59l-1.93.71Z"/> |  | ||||||
|         <path d="M512.92,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/> |  | ||||||
|         <path d="M536.4,49.66c0,1.39-.49,2.48-1.46,3.29s-2.27,1.21-3.9,1.21h-6.72v-17.12h6.58c1.65,0,2.95.41,3.89,1.24s1.41,1.93,1.41,3.32c0,.92-.21,1.72-.64,2.4s-1.02,1.2-1.79,1.57c.81.37,1.45.91,1.92,1.62s.7,1.53.7,2.47ZM526.3,38.81v5.97h4.54c1.03,0,1.85-.27,2.46-.81s.92-1.24.92-2.09c0-.91-.31-1.65-.93-2.22s-1.45-.85-2.5-.85h-4.5ZM534.45,49.39c0-.91-.32-1.64-.95-2.17s-1.44-.8-2.46-.8h-4.74v5.95h4.74c1.04,0,1.86-.27,2.48-.81s.92-1.26.92-2.16Z"/> |  | ||||||
|         <path d="M541.07,37.02l4.54,8.1,4.54-8.1h2.24l-5.76,10.05v7.09h-2.05v-7.09l-5.83-10.05h2.31Z"/> |  | ||||||
|         <path d="M574.27,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/> |  | ||||||
|         <path d="M577.95,37.02h2.05v7.55l8.74.02v-7.58h2.05v17.14h-2.05v-7.76l-8.74-.02v7.79h-2.05v-17.14Z"/> |  | ||||||
|         <path d="M606.55,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/> |  | ||||||
|       </g> |  | ||||||
|     </g> |  | ||||||
|   </g> |  | ||||||
| </svg> |  | ||||||
| Before Width: | Height: | Size: 12 KiB | 
| @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" | |||||||
|  |  | ||||||
| [project] | [project] | ||||||
| name         = "home-assistant-frontend" | name         = "home-assistant-frontend" | ||||||
| version      = "20240906.0" | version      = "20240610.0" | ||||||
| license      = {text = "Apache-2.0"} | license      = {text = "Apache-2.0"} | ||||||
| description  = "The Home Assistant frontend" | description  = "The Home Assistant frontend" | ||||||
| readme       = "README.md" | readme       = "README.md" | ||||||
|   | |||||||
| @@ -37,7 +37,8 @@ | |||||||
|     { |     { | ||||||
|       "description": "Group tsparticles engine and presets", |       "description": "Group tsparticles engine and presets", | ||||||
|       "groupName": "tsparticles", |       "groupName": "tsparticles", | ||||||
|       "matchPackageNames": ["tsparticles-engine", "tsparticles-preset-{/,}**"] |       "matchPackageNames": ["tsparticles-engine"], | ||||||
|  |       "matchPackagePrefixes": ["tsparticles-preset-"] | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "description": "Group date-fns with dependent timezone package", |       "description": "Group date-fns with dependent timezone package", | ||||||
| @@ -47,8 +48,8 @@ | |||||||
|     { |     { | ||||||
|       "description": "Group and temporarily disable WDS packages", |       "description": "Group and temporarily disable WDS packages", | ||||||
|       "groupName": "Web Dev Server", |       "groupName": "Web Dev Server", | ||||||
|       "enabled": false, |       "matchPackagePrefixes": ["@web/dev-server"], | ||||||
|       "matchPackageNames": ["@web/dev-server{/,}**"] |       "enabled": false | ||||||
|     } |     } | ||||||
|   ] |   ] | ||||||
| } | } | ||||||
|   | |||||||
| @@ -40,6 +40,7 @@ import { | |||||||
|   mdiImageFilterFrames, |   mdiImageFilterFrames, | ||||||
|   mdiLightbulb, |   mdiLightbulb, | ||||||
|   mdiLightningBolt, |   mdiLightningBolt, | ||||||
|  |   mdiMailbox, | ||||||
|   mdiMapMarkerRadius, |   mdiMapMarkerRadius, | ||||||
|   mdiMeterGas, |   mdiMeterGas, | ||||||
|   mdiMicrophoneMessage, |   mdiMicrophoneMessage, | ||||||
| @@ -118,6 +119,7 @@ export const FIXED_DOMAIN_ICONS = { | |||||||
|   input_text: mdiFormTextbox, |   input_text: mdiFormTextbox, | ||||||
|   lawn_mower: mdiRobotMower, |   lawn_mower: mdiRobotMower, | ||||||
|   light: mdiLightbulb, |   light: mdiLightbulb, | ||||||
|  |   mailbox: mdiMailbox, | ||||||
|   notify: mdiCommentAlert, |   notify: mdiCommentAlert, | ||||||
|   number: mdiRayVertex, |   number: mdiRayVertex, | ||||||
|   persistent_notification: mdiBell, |   persistent_notification: mdiBell, | ||||||
|   | |||||||
| @@ -71,7 +71,8 @@ export const computeStateDisplayFromEntityAttributes = ( | |||||||
|     if ( |     if ( | ||||||
|       attributes.device_class === "duration" && |       attributes.device_class === "duration" && | ||||||
|       attributes.unit_of_measurement && |       attributes.unit_of_measurement && | ||||||
|       UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement] |       UNIT_TO_MILLISECOND_CONVERT[attributes.unit_of_measurement] && | ||||||
|  |       entity?.display_precision === undefined | ||||||
|     ) { |     ) { | ||||||
|       try { |       try { | ||||||
|         return formatDuration(state, attributes.unit_of_measurement); |         return formatDuration(state, attributes.unit_of_measurement); | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ export const FIXED_DOMAIN_STATES = { | |||||||
|   humidifier: ["on", "off"], |   humidifier: ["on", "off"], | ||||||
|   input_boolean: ["on", "off"], |   input_boolean: ["on", "off"], | ||||||
|   input_button: [], |   input_button: [], | ||||||
|   lawn_mower: ["error", "paused", "mowing", "returning", "docked"], |   lawn_mower: ["error", "paused", "mowing", "docked"], | ||||||
|   light: ["on", "off"], |   light: ["on", "off"], | ||||||
|   lock: [ |   lock: [ | ||||||
|     "jammed", |     "jammed", | ||||||
| @@ -125,7 +125,6 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = { | |||||||
|       "off", |       "off", | ||||||
|       "idle", |       "idle", | ||||||
|       "preheating", |       "preheating", | ||||||
|       "defrosting", |  | ||||||
|       "heating", |       "heating", | ||||||
|       "cooling", |       "cooling", | ||||||
|       "drying", |       "drying", | ||||||
|   | |||||||
| @@ -25,9 +25,7 @@ export const navigate = (path: string, options?: NavigateOptions) => { | |||||||
|   if (__DEMO__) { |   if (__DEMO__) { | ||||||
|     if (replace) { |     if (replace) { | ||||||
|       mainWindow.history.replaceState( |       mainWindow.history.replaceState( | ||||||
|         mainWindow.history.state?.root |         mainWindow.history.state?.root ? { root: true } : options?.data ?? null, | ||||||
|           ? { root: true } |  | ||||||
|           : (options?.data ?? null), |  | ||||||
|         "", |         "", | ||||||
|         `${mainWindow.location.pathname}#${path}` |         `${mainWindow.location.pathname}#${path}` | ||||||
|       ); |       ); | ||||||
| @@ -36,7 +34,7 @@ export const navigate = (path: string, options?: NavigateOptions) => { | |||||||
|     } |     } | ||||||
|   } else if (replace) { |   } else if (replace) { | ||||||
|     mainWindow.history.replaceState( |     mainWindow.history.replaceState( | ||||||
|       mainWindow.history.state?.root ? { root: true } : (options?.data ?? null), |       mainWindow.history.state?.root ? { root: true } : options?.data ?? null, | ||||||
|       "", |       "", | ||||||
|       path |       path | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -1,4 +1,3 @@ | |||||||
| import { stripDiacritics } from "../strip-diacritics"; |  | ||||||
| import { fuzzyScore } from "./filter"; | import { fuzzyScore } from "./filter"; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -20,10 +19,10 @@ export const fuzzySequentialMatch = ( | |||||||
|   for (const word of item.strings) { |   for (const word of item.strings) { | ||||||
|     const scores = fuzzyScore( |     const scores = fuzzyScore( | ||||||
|       filter, |       filter, | ||||||
|       stripDiacritics(filter.toLowerCase()), |       filter.toLowerCase(), | ||||||
|       0, |       0, | ||||||
|       word, |       word, | ||||||
|       stripDiacritics(word.toLowerCase()), |       word.toLowerCase(), | ||||||
|       0, |       0, | ||||||
|       true |       true | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -1,2 +0,0 @@ | |||||||
| export const stripDiacritics = (str: string) => |  | ||||||
|   str.normalize("NFD").replace(/[\u0300-\u036F]/g, ""); |  | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| import { LitElement, TemplateResult, html } from "lit"; | import { LitElement, TemplateResult, html } from "lit"; | ||||||
| import { customElement, property } from "lit/decorators"; | import { customElement, property } from "lit/decorators"; | ||||||
| import { HassServiceTarget } from "home-assistant-js-websocket"; |  | ||||||
| import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box"; | import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box"; | ||||||
| import "./ha-progress-button"; | import "./ha-progress-button"; | ||||||
| import { HomeAssistant } from "../../types"; | import { HomeAssistant } from "../../types"; | ||||||
| @@ -18,9 +17,7 @@ class HaCallServiceButton extends LitElement { | |||||||
|  |  | ||||||
|   @property() public service!: string; |   @property() public service!: string; | ||||||
|  |  | ||||||
|   @property({ type: Object }) public target!: HassServiceTarget; |   @property({ type: Object }) public serviceData = {}; | ||||||
|  |  | ||||||
|   @property({ type: Object }) public data = {}; |  | ||||||
|  |  | ||||||
|   @property() public confirmation?; |   @property() public confirmation?; | ||||||
|  |  | ||||||
| @@ -42,8 +39,7 @@ class HaCallServiceButton extends LitElement { | |||||||
|     const eventData = { |     const eventData = { | ||||||
|       domain: this.domain, |       domain: this.domain, | ||||||
|       service: this.service, |       service: this.service, | ||||||
|       data: this.data, |       serviceData: this.serviceData, | ||||||
|       target: this.target, |  | ||||||
|       success: false, |       success: false, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
| @@ -51,12 +47,7 @@ class HaCallServiceButton extends LitElement { | |||||||
|       this.shadowRoot!.querySelector("ha-progress-button")!; |       this.shadowRoot!.querySelector("ha-progress-button")!; | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       await this.hass.callService( |       await this.hass.callService(this.domain, this.service, this.serviceData); | ||||||
|         this.domain, |  | ||||||
|         this.service, |  | ||||||
|         this.data, |  | ||||||
|         this.target |  | ||||||
|       ); |  | ||||||
|       this.progress = false; |       this.progress = false; | ||||||
|       progressElement.actionSuccess(); |       progressElement.actionSuccess(); | ||||||
|       eventData.success = true; |       eventData.success = true; | ||||||
| @@ -94,8 +85,7 @@ declare global { | |||||||
|     "hass-service-called": { |     "hass-service-called": { | ||||||
|       domain: string; |       domain: string; | ||||||
|       service: string; |       service: string; | ||||||
|       target: HassServiceTarget; |       serviceData: object; | ||||||
|       data: object; |  | ||||||
|       success: boolean; |       success: boolean; | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,6 +0,0 @@ | |||||||
| import type { ChartEvent } from "chart.js"; |  | ||||||
|  |  | ||||||
| export const clickIsTouch = (event: ChartEvent): boolean => |  | ||||||
|   !(event.native instanceof MouseEvent) || |  | ||||||
|   (event.native instanceof PointerEvent && |  | ||||||
|     event.native.pointerType !== "mouse"); |  | ||||||
| @@ -16,7 +16,6 @@ import { | |||||||
|   HaChartBase, |   HaChartBase, | ||||||
|   MIN_TIME_BETWEEN_UPDATES, |   MIN_TIME_BETWEEN_UPDATES, | ||||||
| } from "./ha-chart-base"; | } from "./ha-chart-base"; | ||||||
| import { clickIsTouch } from "./click_is_touch"; |  | ||||||
|  |  | ||||||
| const safeParseFloat = (value) => { | const safeParseFloat = (value) => { | ||||||
|   const parsed = parseFloat(value); |   const parsed = parseFloat(value); | ||||||
| @@ -221,7 +220,12 @@ export class StateHistoryChartLine extends LitElement { | |||||||
|         // @ts-expect-error |         // @ts-expect-error | ||||||
|         locale: numberFormatToLocale(this.hass.locale), |         locale: numberFormatToLocale(this.hass.locale), | ||||||
|         onClick: (e: any) => { |         onClick: (e: any) => { | ||||||
|           if (!this.clickForMoreInfo || clickIsTouch(e)) { |           if ( | ||||||
|  |             !this.clickForMoreInfo || | ||||||
|  |             !(e.native instanceof MouseEvent) || | ||||||
|  |             (e.native instanceof PointerEvent && | ||||||
|  |               e.native.pointerType !== "mouse") | ||||||
|  |           ) { | ||||||
|             return; |             return; | ||||||
|           } |           } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,7 +16,6 @@ import { | |||||||
| } from "./ha-chart-base"; | } from "./ha-chart-base"; | ||||||
| import type { TimeLineData } from "./timeline-chart/const"; | import type { TimeLineData } from "./timeline-chart/const"; | ||||||
| import { computeTimelineColor } from "./timeline-chart/timeline-color"; | import { computeTimelineColor } from "./timeline-chart/timeline-color"; | ||||||
| import { clickIsTouch } from "./click_is_touch"; |  | ||||||
|  |  | ||||||
| @customElement("state-history-chart-timeline") | @customElement("state-history-chart-timeline") | ||||||
| export class StateHistoryChartTimeline extends LitElement { | export class StateHistoryChartTimeline extends LitElement { | ||||||
| @@ -160,10 +159,10 @@ export class StateHistoryChartTimeline extends LitElement { | |||||||
|           }, |           }, | ||||||
|           afterUpdate: (y) => { |           afterUpdate: (y) => { | ||||||
|             const yWidth = this.showNames |             const yWidth = this.showNames | ||||||
|               ? (y.width ?? 0) |               ? y.width ?? 0 | ||||||
|               : computeRTL(this.hass) |               : computeRTL(this.hass) | ||||||
|                 ? 0 |                 ? 0 | ||||||
|                 : (y.left ?? 0); |                 : y.left ?? 0; | ||||||
|             if ( |             if ( | ||||||
|               this._yWidth !== Math.floor(yWidth) && |               this._yWidth !== Math.floor(yWidth) && | ||||||
|               y.ticks.length === this.data.length |               y.ticks.length === this.data.length | ||||||
| @@ -225,7 +224,11 @@ export class StateHistoryChartTimeline extends LitElement { | |||||||
|       // @ts-expect-error |       // @ts-expect-error | ||||||
|       locale: numberFormatToLocale(this.hass.locale), |       locale: numberFormatToLocale(this.hass.locale), | ||||||
|       onClick: (e: any) => { |       onClick: (e: any) => { | ||||||
|         if (!this.clickForMoreInfo || clickIsTouch(e)) { |         if ( | ||||||
|  |           !this.clickForMoreInfo || | ||||||
|  |           !(e.native instanceof MouseEvent) || | ||||||
|  |           (e.native instanceof PointerEvent && e.native.pointerType !== "mouse") | ||||||
|  |         ) { | ||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -39,7 +39,6 @@ import type { | |||||||
|   ChartDatasetExtra, |   ChartDatasetExtra, | ||||||
|   HaChartBase, |   HaChartBase, | ||||||
| } from "./ha-chart-base"; | } from "./ha-chart-base"; | ||||||
| import { clickIsTouch } from "./click_is_touch"; |  | ||||||
|  |  | ||||||
| export const supportedStatTypeMap: Record<StatisticType, StatisticType> = { | export const supportedStatTypeMap: Record<StatisticType, StatisticType> = { | ||||||
|   mean: "mean", |   mean: "mean", | ||||||
| @@ -279,7 +278,11 @@ export class StatisticsChart extends LitElement { | |||||||
|       // @ts-expect-error |       // @ts-expect-error | ||||||
|       locale: numberFormatToLocale(this.hass.locale), |       locale: numberFormatToLocale(this.hass.locale), | ||||||
|       onClick: (e: any) => { |       onClick: (e: any) => { | ||||||
|         if (!this.clickForMoreInfo || clickIsTouch(e)) { |         if ( | ||||||
|  |           !this.clickForMoreInfo || | ||||||
|  |           !(e.native instanceof MouseEvent) || | ||||||
|  |           (e.native instanceof PointerEvent && e.native.pointerType !== "mouse") | ||||||
|  |         ) { | ||||||
|           return; |           return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,319 +0,0 @@ | |||||||
| import "@material/mwc-list"; |  | ||||||
| import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js"; |  | ||||||
| import { CSSResultGroup, LitElement, css, html, nothing } from "lit"; |  | ||||||
| import { customElement, property, state } from "lit/decorators"; |  | ||||||
| import { classMap } from "lit/directives/class-map"; |  | ||||||
| import { repeat } from "lit/directives/repeat"; |  | ||||||
| import memoizeOne from "memoize-one"; |  | ||||||
| import { haStyleDialog } from "../../resources/styles"; |  | ||||||
| import { HomeAssistant } from "../../types"; |  | ||||||
| import { createCloseHeading } from "../ha-dialog"; |  | ||||||
| import "../ha-list-item"; |  | ||||||
| import "../ha-sortable"; |  | ||||||
| import "../ha-button"; |  | ||||||
| import { DataTableColumnContainer, DataTableColumnData } from "./ha-data-table"; |  | ||||||
| import { DataTableSettingsDialogParams } from "./show-dialog-data-table-settings"; |  | ||||||
| import { fireEvent } from "../../common/dom/fire_event"; |  | ||||||
|  |  | ||||||
| @customElement("dialog-data-table-settings") |  | ||||||
| export class DialogDataTableSettings extends LitElement { |  | ||||||
|   @property({ attribute: false }) public hass!: HomeAssistant; |  | ||||||
|  |  | ||||||
|   @state() private _params?: DataTableSettingsDialogParams; |  | ||||||
|  |  | ||||||
|   @state() private _columnOrder?: string[]; |  | ||||||
|  |  | ||||||
|   @state() private _hiddenColumns?: string[]; |  | ||||||
|  |  | ||||||
|   public showDialog(params: DataTableSettingsDialogParams) { |  | ||||||
|     this._params = params; |  | ||||||
|     this._columnOrder = params.columnOrder; |  | ||||||
|     this._hiddenColumns = params.hiddenColumns; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public closeDialog() { |  | ||||||
|     this._params = undefined; |  | ||||||
|     fireEvent(this, "dialog-closed", { dialog: this.localName }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _sortedColumns = memoizeOne( |  | ||||||
|     ( |  | ||||||
|       columns: DataTableColumnContainer, |  | ||||||
|       columnOrder: string[] | undefined, |  | ||||||
|       hiddenColumns: string[] | undefined |  | ||||||
|     ) => |  | ||||||
|       Object.keys(columns) |  | ||||||
|         .filter((col) => !columns[col].hidden) |  | ||||||
|         .sort((a, b) => { |  | ||||||
|           const orderA = columnOrder?.indexOf(a) ?? -1; |  | ||||||
|           const orderB = columnOrder?.indexOf(b) ?? -1; |  | ||||||
|           const hiddenA = |  | ||||||
|             hiddenColumns?.includes(a) ?? Boolean(columns[a].defaultHidden); |  | ||||||
|           const hiddenB = |  | ||||||
|             hiddenColumns?.includes(b) ?? Boolean(columns[b].defaultHidden); |  | ||||||
|           if (hiddenA !== hiddenB) { |  | ||||||
|             return hiddenA ? 1 : -1; |  | ||||||
|           } |  | ||||||
|           if (orderA !== orderB) { |  | ||||||
|             if (orderA === -1) { |  | ||||||
|               return 1; |  | ||||||
|             } |  | ||||||
|             if (orderB === -1) { |  | ||||||
|               return -1; |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|           return orderA - orderB; |  | ||||||
|         }) |  | ||||||
|         .reduce( |  | ||||||
|           (arr, key) => { |  | ||||||
|             arr.push({ key, ...columns[key] }); |  | ||||||
|             return arr; |  | ||||||
|           }, |  | ||||||
|           [] as (DataTableColumnData & { key: string })[] |  | ||||||
|         ) |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   protected render() { |  | ||||||
|     if (!this._params) { |  | ||||||
|       return nothing; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const localize = this._params.localizeFunc || this.hass.localize; |  | ||||||
|  |  | ||||||
|     const columns = this._sortedColumns( |  | ||||||
|       this._params.columns, |  | ||||||
|       this._columnOrder, |  | ||||||
|       this._hiddenColumns |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     return html` |  | ||||||
|       <ha-dialog |  | ||||||
|         open |  | ||||||
|         @closed=${this.closeDialog} |  | ||||||
|         .heading=${createCloseHeading( |  | ||||||
|           this.hass, |  | ||||||
|           localize("ui.components.data-table.settings.header") |  | ||||||
|         )} |  | ||||||
|       > |  | ||||||
|         <ha-sortable |  | ||||||
|           @item-moved=${this._columnMoved} |  | ||||||
|           draggable-selector=".draggable" |  | ||||||
|           handle-selector=".handle" |  | ||||||
|         > |  | ||||||
|           <mwc-list> |  | ||||||
|             ${repeat( |  | ||||||
|               columns, |  | ||||||
|               (col) => col.key, |  | ||||||
|               (col, _idx) => { |  | ||||||
|                 const canMove = !col.main && col.moveable !== false; |  | ||||||
|                 const canHide = !col.main && col.hideable !== false; |  | ||||||
|                 const isVisible = !(this._columnOrder && |  | ||||||
|                 this._columnOrder.includes(col.key) |  | ||||||
|                   ? (this._hiddenColumns?.includes(col.key) ?? |  | ||||||
|                     col.defaultHidden) |  | ||||||
|                   : col.defaultHidden); |  | ||||||
|  |  | ||||||
|                 return html`<ha-list-item |  | ||||||
|                   hasMeta |  | ||||||
|                   class=${classMap({ |  | ||||||
|                     hidden: !isVisible, |  | ||||||
|                     draggable: canMove && isVisible, |  | ||||||
|                   })} |  | ||||||
|                   graphic="icon" |  | ||||||
|                   noninteractive |  | ||||||
|                   >${col.title || col.label || col.key} |  | ||||||
|                   ${canMove && isVisible |  | ||||||
|                     ? html`<ha-svg-icon |  | ||||||
|                         class="handle" |  | ||||||
|                         .path=${mdiDrag} |  | ||||||
|                         slot="graphic" |  | ||||||
|                       ></ha-svg-icon>` |  | ||||||
|                     : nothing} |  | ||||||
|                   <ha-icon-button |  | ||||||
|                     tabindex="0" |  | ||||||
|                     class="action" |  | ||||||
|                     .disabled=${!canHide} |  | ||||||
|                     .hidden=${!isVisible} |  | ||||||
|                     .path=${isVisible ? mdiEye : mdiEyeOff} |  | ||||||
|                     slot="meta" |  | ||||||
|                     .label=${this.hass!.localize( |  | ||||||
|                       `ui.components.data-table.settings.${isVisible ? "hide" : "show"}`, |  | ||||||
|                       { title: typeof col.title === "string" ? col.title : "" } |  | ||||||
|                     )} |  | ||||||
|                     .column=${col.key} |  | ||||||
|                     @click=${this._toggle} |  | ||||||
|                   ></ha-icon-button> |  | ||||||
|                 </ha-list-item>`; |  | ||||||
|               } |  | ||||||
|             )} |  | ||||||
|           </mwc-list> |  | ||||||
|         </ha-sortable> |  | ||||||
|         <ha-button slot="secondaryAction" @click=${this._reset} |  | ||||||
|           >${localize("ui.components.data-table.settings.restore")}</ha-button |  | ||||||
|         > |  | ||||||
|         <ha-button slot="primaryAction" @click=${this.closeDialog}> |  | ||||||
|           ${localize("ui.components.data-table.settings.done")} |  | ||||||
|         </ha-button> |  | ||||||
|       </ha-dialog> |  | ||||||
|     `; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _columnMoved(ev: CustomEvent): void { |  | ||||||
|     ev.stopPropagation(); |  | ||||||
|     if (!this._params) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     const { oldIndex, newIndex } = ev.detail; |  | ||||||
|  |  | ||||||
|     const columns = this._sortedColumns( |  | ||||||
|       this._params.columns, |  | ||||||
|       this._columnOrder, |  | ||||||
|       this._hiddenColumns |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     const columnOrder = columns.map((column) => column.key); |  | ||||||
|  |  | ||||||
|     const option = columnOrder.splice(oldIndex, 1)[0]; |  | ||||||
|     columnOrder.splice(newIndex, 0, option); |  | ||||||
|  |  | ||||||
|     this._columnOrder = columnOrder; |  | ||||||
|  |  | ||||||
|     this._params!.onUpdate(this._columnOrder, this._hiddenColumns); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   _toggle(ev) { |  | ||||||
|     if (!this._params) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     const column = ev.target.column; |  | ||||||
|     const wasHidden = ev.target.hidden; |  | ||||||
|  |  | ||||||
|     const hidden = [ |  | ||||||
|       ...(this._hiddenColumns ?? |  | ||||||
|         Object.entries(this._params.columns) |  | ||||||
|           .filter(([_key, col]) => col.defaultHidden) |  | ||||||
|           .map(([key]) => key)), |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     if (wasHidden && hidden.includes(column)) { |  | ||||||
|       hidden.splice(hidden.indexOf(column), 1); |  | ||||||
|     } else if (!wasHidden) { |  | ||||||
|       hidden.push(column); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const columns = this._sortedColumns( |  | ||||||
|       this._params.columns, |  | ||||||
|       this._columnOrder, |  | ||||||
|       hidden |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     if (!this._columnOrder) { |  | ||||||
|       this._columnOrder = columns.map((col) => col.key); |  | ||||||
|     } else { |  | ||||||
|       const newOrder = this._columnOrder.filter((col) => col !== column); |  | ||||||
|  |  | ||||||
|       // Array.findLastIndex when supported or core-js polyfill |  | ||||||
|       const findLastIndex = ( |  | ||||||
|         arr: Array<any>, |  | ||||||
|         fn: (item: any, index: number, arr: Array<any>) => boolean |  | ||||||
|       ) => { |  | ||||||
|         for (let i = arr.length - 1; i >= 0; i--) { |  | ||||||
|           if (fn(arr[i], i, arr)) return i; |  | ||||||
|         } |  | ||||||
|         return -1; |  | ||||||
|       }; |  | ||||||
|  |  | ||||||
|       let lastMoveable = findLastIndex( |  | ||||||
|         newOrder, |  | ||||||
|         (col) => |  | ||||||
|           col !== column && |  | ||||||
|           !hidden.includes(col) && |  | ||||||
|           !this._params!.columns[col].main && |  | ||||||
|           this._params!.columns[col].moveable !== false |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       if (lastMoveable === -1) { |  | ||||||
|         lastMoveable = newOrder.length - 1; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       columns.forEach((col) => { |  | ||||||
|         if (!newOrder.includes(col.key)) { |  | ||||||
|           if (col.moveable === false) { |  | ||||||
|             newOrder.unshift(col.key); |  | ||||||
|           } else { |  | ||||||
|             newOrder.splice(lastMoveable + 1, 0, col.key); |  | ||||||
|           } |  | ||||||
|  |  | ||||||
|           if ( |  | ||||||
|             col.key !== column && |  | ||||||
|             col.defaultHidden && |  | ||||||
|             !hidden.includes(col.key) |  | ||||||
|           ) { |  | ||||||
|             hidden.push(col.key); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|  |  | ||||||
|       this._columnOrder = newOrder; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     this._hiddenColumns = hidden; |  | ||||||
|  |  | ||||||
|     this._params!.onUpdate(this._columnOrder, this._hiddenColumns); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   _reset() { |  | ||||||
|     this._columnOrder = undefined; |  | ||||||
|     this._hiddenColumns = undefined; |  | ||||||
|  |  | ||||||
|     this._params!.onUpdate(this._columnOrder, this._hiddenColumns); |  | ||||||
|     this.closeDialog(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static get styles(): CSSResultGroup { |  | ||||||
|     return [ |  | ||||||
|       haStyleDialog, |  | ||||||
|       css` |  | ||||||
|         ha-dialog { |  | ||||||
|           --mdc-dialog-max-width: 500px; |  | ||||||
|           --dialog-z-index: 10; |  | ||||||
|           --dialog-content-padding: 0 8px; |  | ||||||
|         } |  | ||||||
|         @media all and (max-width: 451px) { |  | ||||||
|           ha-dialog { |  | ||||||
|             --vertical-align-dialog: flex-start; |  | ||||||
|             --dialog-surface-margin-top: 250px; |  | ||||||
|             --ha-dialog-border-radius: 28px 28px 0 0; |  | ||||||
|             --mdc-dialog-min-height: calc(100% - 250px); |  | ||||||
|             --mdc-dialog-max-height: calc(100% - 250px); |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|         ha-list-item { |  | ||||||
|           --mdc-list-side-padding: 12px; |  | ||||||
|           overflow: visible; |  | ||||||
|         } |  | ||||||
|         .hidden { |  | ||||||
|           color: var(--disabled-text-color); |  | ||||||
|         } |  | ||||||
|         .handle { |  | ||||||
|           cursor: move; /* fallback if grab cursor is unsupported */ |  | ||||||
|           cursor: grab; |  | ||||||
|         } |  | ||||||
|         .actions { |  | ||||||
|           display: flex; |  | ||||||
|           flex-direction: row; |  | ||||||
|         } |  | ||||||
|         ha-icon-button { |  | ||||||
|           display: block; |  | ||||||
|           margin: -12px; |  | ||||||
|         } |  | ||||||
|       `, |  | ||||||
|     ]; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| declare global { |  | ||||||
|   interface HTMLElementTagNameMap { |  | ||||||
|     "dialog-data-table-settings": DialogDataTableSettings; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -25,6 +25,7 @@ import { fireEvent } from "../../common/dom/fire_event"; | |||||||
| import { stringCompare } from "../../common/string/compare"; | import { stringCompare } from "../../common/string/compare"; | ||||||
| import { debounce } from "../../common/util/debounce"; | import { debounce } from "../../common/util/debounce"; | ||||||
| import { groupBy } from "../../common/util/group-by"; | import { groupBy } from "../../common/util/group-by"; | ||||||
|  | import { nextRender } from "../../common/util/render-status"; | ||||||
| import { haStyleScrollbar } from "../../resources/styles"; | import { haStyleScrollbar } from "../../resources/styles"; | ||||||
| import { loadVirtualizer } from "../../resources/virtualizer"; | import { loadVirtualizer } from "../../resources/virtualizer"; | ||||||
| import { HomeAssistant } from "../../types"; | import { HomeAssistant } from "../../types"; | ||||||
| @@ -33,8 +34,6 @@ import type { HaCheckbox } from "../ha-checkbox"; | |||||||
| import "../ha-svg-icon"; | import "../ha-svg-icon"; | ||||||
| import "../search-input"; | import "../search-input"; | ||||||
| import { filterData, sortData } from "./sort-filter"; | import { filterData, sortData } from "./sort-filter"; | ||||||
| import { LocalizeFunc } from "../../common/translations/localize"; |  | ||||||
| import { nextRender } from "../../common/util/render-status"; |  | ||||||
|  |  | ||||||
| export interface RowClickedEvent { | export interface RowClickedEvent { | ||||||
|   id: string; |   id: string; | ||||||
| @@ -66,10 +65,6 @@ export interface DataTableSortColumnData { | |||||||
|   valueColumn?: string; |   valueColumn?: string; | ||||||
|   direction?: SortingDirection; |   direction?: SortingDirection; | ||||||
|   groupable?: boolean; |   groupable?: boolean; | ||||||
|   moveable?: boolean; |  | ||||||
|   hideable?: boolean; |  | ||||||
|   defaultHidden?: boolean; |  | ||||||
|   showNarrow?: boolean; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface DataTableColumnData<T = any> extends DataTableSortColumnData { | export interface DataTableColumnData<T = any> extends DataTableSortColumnData { | ||||||
| @@ -84,10 +79,9 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData { | |||||||
|     | "overflow-menu" |     | "overflow-menu" | ||||||
|     | "flex"; |     | "flex"; | ||||||
|   template?: (row: T) => TemplateResult | string | typeof nothing; |   template?: (row: T) => TemplateResult | string | typeof nothing; | ||||||
|   extraTemplate?: (row: T) => TemplateResult | string | typeof nothing; |   width?: string; | ||||||
|   minWidth?: string; |  | ||||||
|   maxWidth?: string; |   maxWidth?: string; | ||||||
|   flex?: number; |   grows?: boolean; | ||||||
|   forceLTR?: boolean; |   forceLTR?: boolean; | ||||||
|   hidden?: boolean; |   hidden?: boolean; | ||||||
| } | } | ||||||
| @@ -111,10 +105,6 @@ const UNDEFINED_GROUP_KEY = "zzzzz_undefined"; | |||||||
| export class HaDataTable extends LitElement { | export class HaDataTable extends LitElement { | ||||||
|   @property({ attribute: false }) public hass!: HomeAssistant; |   @property({ attribute: false }) public hass!: HomeAssistant; | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public localizeFunc?: LocalizeFunc; |  | ||||||
|  |  | ||||||
|   @property({ type: Boolean }) public narrow = false; |  | ||||||
|  |  | ||||||
|   @property({ type: Object }) public columns: DataTableColumnContainer = {}; |   @property({ type: Object }) public columns: DataTableColumnContainer = {}; | ||||||
|  |  | ||||||
|   @property({ type: Array }) public data: DataTableRowData[] = []; |   @property({ type: Array }) public data: DataTableRowData[] = []; | ||||||
| @@ -155,10 +145,6 @@ export class HaDataTable extends LitElement { | |||||||
|  |  | ||||||
|   @property({ attribute: false }) public initialCollapsedGroups?: string[]; |   @property({ attribute: false }) public initialCollapsedGroups?: string[]; | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public hiddenColumns?: string[]; |  | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public columnOrder?: string[]; |  | ||||||
|  |  | ||||||
|   @state() private _filterable = false; |   @state() private _filterable = false; | ||||||
|  |  | ||||||
|   @state() private _filter = ""; |   @state() private _filter = ""; | ||||||
| @@ -169,6 +155,8 @@ export class HaDataTable extends LitElement { | |||||||
|  |  | ||||||
|   @query("slot[name='header']") private _header!: HTMLSlotElement; |   @query("slot[name='header']") private _header!: HTMLSlotElement; | ||||||
|  |  | ||||||
|  |   @state() private _items: DataTableRowData[] = []; | ||||||
|  |  | ||||||
|   @state() private _collapsedGroups: string[] = []; |   @state() private _collapsedGroups: string[] = []; | ||||||
|  |  | ||||||
|   private _checkableRowsCount?: number; |   private _checkableRowsCount?: number; | ||||||
| @@ -177,9 +165,7 @@ export class HaDataTable extends LitElement { | |||||||
|  |  | ||||||
|   private _sortColumns: SortableColumnContainer = {}; |   private _sortColumns: SortableColumnContainer = {}; | ||||||
|  |  | ||||||
|   private _curRequest = 0; |   private curRequest = 0; | ||||||
|  |  | ||||||
|   private _lastUpdate = 0; |  | ||||||
|  |  | ||||||
|   // @ts-ignore |   // @ts-ignore | ||||||
|   @restoreScroll(".scroller") private _savedScrollPos?: number; |   @restoreScroll(".scroller") private _savedScrollPos?: number; | ||||||
| @@ -206,9 +192,9 @@ export class HaDataTable extends LitElement { | |||||||
|  |  | ||||||
|   public connectedCallback() { |   public connectedCallback() { | ||||||
|     super.connectedCallback(); |     super.connectedCallback(); | ||||||
|     if (this._filteredData.length) { |     if (this._items.length) { | ||||||
|       // Force update of location of rows |       // Force update of location of rows | ||||||
|       this._filteredData = [...this._filteredData]; |       this._items = [...this._items]; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -216,18 +202,6 @@ export class HaDataTable extends LitElement { | |||||||
|     this.updateComplete.then(() => this._calcTableHeight()); |     this.updateComplete.then(() => this._calcTableHeight()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   protected updated() { |  | ||||||
|     const header = this.renderRoot.querySelector(".mdc-data-table__header-row"); |  | ||||||
|     if (!header) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     if (header.scrollWidth > header.clientWidth) { |  | ||||||
|       this.style.setProperty("--table-row-width", `${header.scrollWidth}px`); |  | ||||||
|     } else { |  | ||||||
|       this.style.removeProperty("--table-row-width"); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public willUpdate(properties: PropertyValues) { |   public willUpdate(properties: PropertyValues) { | ||||||
|     super.willUpdate(properties); |     super.willUpdate(properties); | ||||||
|  |  | ||||||
| @@ -261,7 +235,6 @@ export class HaDataTable extends LitElement { | |||||||
|         (column: ClonedDataTableColumnData) => { |         (column: ClonedDataTableColumnData) => { | ||||||
|           delete column.title; |           delete column.title; | ||||||
|           delete column.template; |           delete column.template; | ||||||
|           delete column.extraTemplate; |  | ||||||
|         } |         } | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
| @@ -291,51 +264,20 @@ export class HaDataTable extends LitElement { | |||||||
|       properties.has("columns") || |       properties.has("columns") || | ||||||
|       properties.has("_filter") || |       properties.has("_filter") || | ||||||
|       properties.has("sortColumn") || |       properties.has("sortColumn") || | ||||||
|       properties.has("sortDirection") |       properties.has("sortDirection") || | ||||||
|  |       properties.has("groupColumn") || | ||||||
|  |       properties.has("groupOrder") || | ||||||
|  |       properties.has("_collapsedGroups") | ||||||
|     ) { |     ) { | ||||||
|       this._sortFilterData(); |       this._sortFilterData(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (properties.has("selectable") || properties.has("hiddenColumns")) { |     if (properties.has("selectable")) { | ||||||
|       this._filteredData = [...this._filteredData]; |       this._items = [...this._items]; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _sortedColumns = memoizeOne( |  | ||||||
|     (columns: DataTableColumnContainer, columnOrder?: string[]) => { |  | ||||||
|       if (!columnOrder || !columnOrder.length) { |  | ||||||
|         return columns; |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       return Object.keys(columns) |  | ||||||
|         .sort((a, b) => { |  | ||||||
|           const orderA = columnOrder!.indexOf(a); |  | ||||||
|           const orderB = columnOrder!.indexOf(b); |  | ||||||
|           if (orderA !== orderB) { |  | ||||||
|             if (orderA === -1) { |  | ||||||
|               return 1; |  | ||||||
|             } |  | ||||||
|             if (orderB === -1) { |  | ||||||
|               return -1; |  | ||||||
|             } |  | ||||||
|           } |  | ||||||
|           return orderA - orderB; |  | ||||||
|         }) |  | ||||||
|         .reduce((obj, key) => { |  | ||||||
|           obj[key] = columns[key]; |  | ||||||
|           return obj; |  | ||||||
|         }, {}) as DataTableColumnContainer; |  | ||||||
|     } |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   protected render() { |   protected render() { | ||||||
|     const localize = this.localizeFunc || this.hass.localize; |  | ||||||
|  |  | ||||||
|     const columns = this._sortedColumns(this.columns, this.columnOrder); |  | ||||||
|  |  | ||||||
|     const renderRow = (row: DataTableRowData, index: number) => |  | ||||||
|       this._renderRow(columns, this.narrow, row, index); |  | ||||||
|  |  | ||||||
|     return html` |     return html` | ||||||
|       <div class="mdc-data-table"> |       <div class="mdc-data-table"> | ||||||
|         <slot name="header" @slotchange=${this._calcTableHeight}> |         <slot name="header" @slotchange=${this._calcTableHeight}> | ||||||
| @@ -364,12 +306,7 @@ export class HaDataTable extends LitElement { | |||||||
|               : `calc(100% - ${this._headerHeight}px)`, |               : `calc(100% - ${this._headerHeight}px)`, | ||||||
|           })} |           })} | ||||||
|         > |         > | ||||||
|           <div |           <div class="mdc-data-table__header-row" role="row" aria-rowindex="1"> | ||||||
|             class="mdc-data-table__header-row" |  | ||||||
|             role="row" |  | ||||||
|             aria-rowindex="1" |  | ||||||
|             @scroll=${this._scrollContent} |  | ||||||
|           > |  | ||||||
|             <slot name="header-row"> |             <slot name="header-row"> | ||||||
|               ${this.selectable |               ${this.selectable | ||||||
|                 ? html` |                 ? html` | ||||||
| @@ -389,15 +326,9 @@ export class HaDataTable extends LitElement { | |||||||
|                     </div> |                     </div> | ||||||
|                   ` |                   ` | ||||||
|                 : ""} |                 : ""} | ||||||
|               ${Object.entries(columns).map(([key, column]) => { |               ${Object.entries(this.columns).map(([key, column]) => { | ||||||
|                 if ( |                 if (column.hidden) { | ||||||
|                   column.hidden || |                   return ""; | ||||||
|                   (this.columnOrder && this.columnOrder.includes(key) |  | ||||||
|                     ? (this.hiddenColumns?.includes(key) ?? |  | ||||||
|                       column.defaultHidden) |  | ||||||
|                     : column.defaultHidden) |  | ||||||
|                 ) { |  | ||||||
|                   return nothing; |  | ||||||
|                 } |                 } | ||||||
|                 const sorted = key === this.sortColumn; |                 const sorted = key === this.sortColumn; | ||||||
|                 const classes = { |                 const classes = { | ||||||
| @@ -412,16 +343,18 @@ export class HaDataTable extends LitElement { | |||||||
|                     column.type === "overflow", |                     column.type === "overflow", | ||||||
|                   sortable: Boolean(column.sortable), |                   sortable: Boolean(column.sortable), | ||||||
|                   "not-sorted": Boolean(column.sortable && !sorted), |                   "not-sorted": Boolean(column.sortable && !sorted), | ||||||
|  |                   grows: Boolean(column.grows), | ||||||
|                 }; |                 }; | ||||||
|                 return html` |                 return html` | ||||||
|                   <div |                   <div | ||||||
|                     aria-label=${ifDefined(column.label)} |                     aria-label=${ifDefined(column.label)} | ||||||
|                     class="mdc-data-table__header-cell ${classMap(classes)}" |                     class="mdc-data-table__header-cell ${classMap(classes)}" | ||||||
|                     style=${styleMap({ |                     style=${column.width | ||||||
|                       minWidth: column.minWidth, |                       ? styleMap({ | ||||||
|                       maxWidth: column.maxWidth, |                           [column.grows ? "minWidth" : "width"]: column.width, | ||||||
|                       flex: column.flex || 1, |                           maxWidth: column.maxWidth || "", | ||||||
|                     })} |                         }) | ||||||
|  |                       : ""} | ||||||
|                     role="columnheader" |                     role="columnheader" | ||||||
|                     aria-sort=${ifDefined( |                     aria-sort=${ifDefined( | ||||||
|                       sorted |                       sorted | ||||||
| @@ -454,7 +387,7 @@ export class HaDataTable extends LitElement { | |||||||
|                   <div class="mdc-data-table__row" role="row"> |                   <div class="mdc-data-table__row" role="row"> | ||||||
|                     <div class="mdc-data-table__cell grows center" role="cell"> |                     <div class="mdc-data-table__cell grows center" role="cell"> | ||||||
|                       ${this.noDataText || |                       ${this.noDataText || | ||||||
|                       localize("ui.components.data-table.no-data")} |                       this.hass.localize("ui.components.data-table.no-data")} | ||||||
|                     </div> |                     </div> | ||||||
|                   </div> |                   </div> | ||||||
|                 </div> |                 </div> | ||||||
| @@ -464,17 +397,9 @@ export class HaDataTable extends LitElement { | |||||||
|                   scroller |                   scroller | ||||||
|                   class="mdc-data-table__content scroller ha-scrollbar" |                   class="mdc-data-table__content scroller ha-scrollbar" | ||||||
|                   @scroll=${this._saveScrollPos} |                   @scroll=${this._saveScrollPos} | ||||||
|                   .items=${this._groupData( |                   .items=${this._items} | ||||||
|                     this._filteredData, |  | ||||||
|                     localize, |  | ||||||
|                     this.appendRow, |  | ||||||
|                     this.hasFab, |  | ||||||
|                     this.groupColumn, |  | ||||||
|                     this.groupOrder, |  | ||||||
|                     this._collapsedGroups |  | ||||||
|                   )} |  | ||||||
|                   .keyFunction=${this._keyFunction} |                   .keyFunction=${this._keyFunction} | ||||||
|                   .renderItem=${renderRow} |                   .renderItem=${this._renderRow} | ||||||
|                 ></lit-virtualizer> |                 ></lit-virtualizer> | ||||||
|               `} |               `} | ||||||
|         </div> |         </div> | ||||||
| @@ -484,12 +409,7 @@ export class HaDataTable extends LitElement { | |||||||
|  |  | ||||||
|   private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row; |   private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row; | ||||||
|  |  | ||||||
|   private _renderRow = ( |   private _renderRow = (row: DataTableRowData, index: number) => { | ||||||
|     columns: DataTableColumnContainer, |  | ||||||
|     narrow: boolean, |  | ||||||
|     row: DataTableRowData, |  | ||||||
|     index: number |  | ||||||
|   ) => { |  | ||||||
|     // not sure how this happens... |     // not sure how this happens... | ||||||
|     if (!row) { |     if (!row) { | ||||||
|       return nothing; |       return nothing; | ||||||
| @@ -534,14 +454,8 @@ export class HaDataTable extends LitElement { | |||||||
|               </div> |               </div> | ||||||
|             ` |             ` | ||||||
|           : ""} |           : ""} | ||||||
|         ${Object.entries(columns).map(([key, column]) => { |         ${Object.entries(this.columns).map(([key, column]) => { | ||||||
|           if ( |           if (column.hidden) { | ||||||
|             (narrow && !column.main && !column.showNarrow) || |  | ||||||
|             column.hidden || |  | ||||||
|             (this.columnOrder && this.columnOrder.includes(key) |  | ||||||
|               ? (this.hiddenColumns?.includes(key) ?? column.defaultHidden) |  | ||||||
|               : column.defaultHidden) |  | ||||||
|           ) { |  | ||||||
|             return nothing; |             return nothing; | ||||||
|           } |           } | ||||||
|           return html` |           return html` | ||||||
| @@ -558,46 +472,17 @@ export class HaDataTable extends LitElement { | |||||||
|                 "mdc-data-table__cell--overflow-menu": |                 "mdc-data-table__cell--overflow-menu": | ||||||
|                   column.type === "overflow-menu", |                   column.type === "overflow-menu", | ||||||
|                 "mdc-data-table__cell--overflow": column.type === "overflow", |                 "mdc-data-table__cell--overflow": column.type === "overflow", | ||||||
|  |                 grows: Boolean(column.grows), | ||||||
|                 forceLTR: Boolean(column.forceLTR), |                 forceLTR: Boolean(column.forceLTR), | ||||||
|               })}" |               })}" | ||||||
|               style=${styleMap({ |               style=${column.width | ||||||
|                 minWidth: column.minWidth, |                 ? styleMap({ | ||||||
|                 maxWidth: column.maxWidth, |                     [column.grows ? "minWidth" : "width"]: column.width, | ||||||
|                 flex: column.flex || 1, |                     maxWidth: column.maxWidth ? column.maxWidth : "", | ||||||
|               })} |                   }) | ||||||
|  |                 : ""} | ||||||
|             > |             > | ||||||
|               ${column.template |               ${column.template ? column.template(row) : row[key]} | ||||||
|                 ? column.template(row) |  | ||||||
|                 : narrow && column.main |  | ||||||
|                   ? html`<div class="primary">${row[key]}</div> |  | ||||||
|                       <div class="secondary"> |  | ||||||
|                         ${Object.entries(columns) |  | ||||||
|                           .filter( |  | ||||||
|                             ([key2, column2]) => |  | ||||||
|                               !column2.hidden && |  | ||||||
|                               !column2.main && |  | ||||||
|                               !column2.showNarrow && |  | ||||||
|                               !(this.columnOrder && |  | ||||||
|                               this.columnOrder.includes(key2) |  | ||||||
|                                 ? (this.hiddenColumns?.includes(key2) ?? |  | ||||||
|                                   column2.defaultHidden) |  | ||||||
|                                 : column2.defaultHidden) |  | ||||||
|                           ) |  | ||||||
|                           .map( |  | ||||||
|                             ([key2, column2], i) => |  | ||||||
|                               html`${i !== 0 |  | ||||||
|                                 ? " ⸱ " |  | ||||||
|                                 : nothing}${column2.template |  | ||||||
|                                 ? column2.template(row) |  | ||||||
|                                 : row[key2]}` |  | ||||||
|                           )} |  | ||||||
|                       </div> |  | ||||||
|                       ${column.extraTemplate |  | ||||||
|                         ? column.extraTemplate(row) |  | ||||||
|                         : nothing}` |  | ||||||
|                   : html`${row[key]}${column.extraTemplate |  | ||||||
|                       ? column.extraTemplate(row) |  | ||||||
|                       : nothing}`} |  | ||||||
|             </div> |             </div> | ||||||
|           `; |           `; | ||||||
|         })} |         })} | ||||||
| @@ -607,27 +492,18 @@ export class HaDataTable extends LitElement { | |||||||
|  |  | ||||||
|   private async _sortFilterData() { |   private async _sortFilterData() { | ||||||
|     const startTime = new Date().getTime(); |     const startTime = new Date().getTime(); | ||||||
|     const timeBetweenUpdate = startTime - this._lastUpdate; |     this.curRequest++; | ||||||
|     const timeBetweenRequest = startTime - this._curRequest; |     const curRequest = this.curRequest; | ||||||
|     this._curRequest = startTime; |  | ||||||
|  |  | ||||||
|     const forceUpdate = |  | ||||||
|       !this._lastUpdate || |  | ||||||
|       (timeBetweenUpdate > 500 && timeBetweenRequest < 500); |  | ||||||
|  |  | ||||||
|     let filteredData = this.data; |     let filteredData = this.data; | ||||||
|     if (this._filter) { |     if (this._filter) { | ||||||
|       filteredData = await this._memFilterData( |       filteredData = await this._memFilterData( | ||||||
|         this.data, |         this.data, | ||||||
|         this._sortColumns, |         this._sortColumns, | ||||||
|         this._filter.trim() |         this._filter | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!forceUpdate && this._curRequest !== startTime) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const prom = this.sortColumn |     const prom = this.sortColumn | ||||||
|       ? sortData( |       ? sortData( | ||||||
|           filteredData, |           filteredData, | ||||||
| @@ -648,30 +524,15 @@ export class HaDataTable extends LitElement { | |||||||
|         setTimeout(resolve, 100 - elapsed); |         setTimeout(resolve, 100 - elapsed); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |     if (this.curRequest !== curRequest) { | ||||||
|     if (!forceUpdate && this._curRequest !== startTime) { |  | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     this._lastUpdate = startTime; |     if (this.appendRow || this.hasFab || this.groupColumn) { | ||||||
|     this._filteredData = data; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _groupData = memoizeOne( |  | ||||||
|     ( |  | ||||||
|       data: DataTableRowData[], |  | ||||||
|       localize: LocalizeFunc, |  | ||||||
|       appendRow, |  | ||||||
|       hasFab: boolean, |  | ||||||
|       groupColumn: string | undefined, |  | ||||||
|       groupOrder: string[] | undefined, |  | ||||||
|       collapsedGroups: string[] |  | ||||||
|     ) => { |  | ||||||
|       if (appendRow || hasFab || groupColumn) { |  | ||||||
|       let items = [...data]; |       let items = [...data]; | ||||||
|  |  | ||||||
|         if (groupColumn) { |       if (this.groupColumn) { | ||||||
|           const grouped = groupBy(items, (item) => item[groupColumn]); |         const grouped = groupBy(items, (item) => item[this.groupColumn!]); | ||||||
|         if (grouped.undefined) { |         if (grouped.undefined) { | ||||||
|           // make sure ungrouped items are at the bottom |           // make sure ungrouped items are at the bottom | ||||||
|           grouped[UNDEFINED_GROUP_KEY] = grouped.undefined; |           grouped[UNDEFINED_GROUP_KEY] = grouped.undefined; | ||||||
| @@ -681,8 +542,8 @@ export class HaDataTable extends LitElement { | |||||||
|           [key: string]: DataTableRowData[]; |           [key: string]: DataTableRowData[]; | ||||||
|         } = Object.keys(grouped) |         } = Object.keys(grouped) | ||||||
|           .sort((a, b) => { |           .sort((a, b) => { | ||||||
|               const orderA = groupOrder?.indexOf(a) ?? -1; |             const orderA = this.groupOrder?.indexOf(a) ?? -1; | ||||||
|               const orderB = groupOrder?.indexOf(b) ?? -1; |             const orderB = this.groupOrder?.indexOf(b) ?? -1; | ||||||
|             if (orderA !== orderB) { |             if (orderA !== orderB) { | ||||||
|               if (orderA === -1) { |               if (orderA === -1) { | ||||||
|                 return 1; |                 return 1; | ||||||
| @@ -714,36 +575,37 @@ export class HaDataTable extends LitElement { | |||||||
|             > |             > | ||||||
|               <ha-icon-button |               <ha-icon-button | ||||||
|                 .path=${mdiChevronUp} |                 .path=${mdiChevronUp} | ||||||
|                   class=${collapsedGroups.includes(groupName) |                 class=${this._collapsedGroups.includes(groupName) | ||||||
|                   ? "collapsed" |                   ? "collapsed" | ||||||
|                   : ""} |                   : ""} | ||||||
|               > |               > | ||||||
|               </ha-icon-button> |               </ha-icon-button> | ||||||
|               ${groupName === UNDEFINED_GROUP_KEY |               ${groupName === UNDEFINED_GROUP_KEY | ||||||
|                   ? localize("ui.components.data-table.ungrouped") |                 ? this.hass.localize("ui.components.data-table.ungrouped") | ||||||
|                 : groupName || ""} |                 : groupName || ""} | ||||||
|             </div>`, |             </div>`, | ||||||
|           }); |           }); | ||||||
|             if (!collapsedGroups.includes(groupName)) { |           if (!this._collapsedGroups.includes(groupName)) { | ||||||
|             groupedItems.push(...rows); |             groupedItems.push(...rows); | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|         items = groupedItems; |         items = groupedItems; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|         if (appendRow) { |       if (this.appendRow) { | ||||||
|           items.push({ append: true, content: appendRow }); |         items.push({ append: true, content: this.appendRow }); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|         if (hasFab) { |       if (this.hasFab) { | ||||||
|         items.push({ empty: true }); |         items.push({ empty: true }); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|         return items; |       this._items = items; | ||||||
|  |     } else { | ||||||
|  |       this._items = data; | ||||||
|     } |     } | ||||||
|       return data; |     this._filteredData = data; | ||||||
|   } |   } | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   private _memFilterData = memoizeOne( |   private _memFilterData = memoizeOne( | ||||||
|     ( |     ( | ||||||
| @@ -828,8 +690,8 @@ export class HaDataTable extends LitElement { | |||||||
|  |  | ||||||
|   private _checkedRowsChanged() { |   private _checkedRowsChanged() { | ||||||
|     // force scroller to update, change it's items |     // force scroller to update, change it's items | ||||||
|     if (this._filteredData.length) { |     if (this._items.length) { | ||||||
|       this._filteredData = [...this._filteredData]; |       this._items = [...this._items]; | ||||||
|     } |     } | ||||||
|     fireEvent(this, "selection-changed", { |     fireEvent(this, "selection-changed", { | ||||||
|       value: this._checkedRows, |       value: this._checkedRows, | ||||||
| @@ -854,17 +716,6 @@ export class HaDataTable extends LitElement { | |||||||
|   @eventOptions({ passive: true }) |   @eventOptions({ passive: true }) | ||||||
|   private _saveScrollPos(e: Event) { |   private _saveScrollPos(e: Event) { | ||||||
|     this._savedScrollPos = (e.target as HTMLDivElement).scrollTop; |     this._savedScrollPos = (e.target as HTMLDivElement).scrollTop; | ||||||
|  |  | ||||||
|     this.renderRoot.querySelector(".mdc-data-table__header-row")!.scrollLeft = ( |  | ||||||
|       e.target as HTMLDivElement |  | ||||||
|     ).scrollLeft; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @eventOptions({ passive: true }) |  | ||||||
|   private _scrollContent(e: Event) { |  | ||||||
|     this.renderRoot.querySelector("lit-virtualizer")!.scrollLeft = ( |  | ||||||
|       e.target as HTMLDivElement |  | ||||||
|     ).scrollLeft; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _collapseGroup = (ev: Event) => { |   private _collapseGroup = (ev: Event) => { | ||||||
| @@ -939,8 +790,8 @@ export class HaDataTable extends LitElement { | |||||||
|  |  | ||||||
|         .mdc-data-table__row { |         .mdc-data-table__row { | ||||||
|           display: flex; |           display: flex; | ||||||
|  |           width: 100%; | ||||||
|           height: var(--data-table-row-height, 52px); |           height: var(--data-table-row-height, 52px); | ||||||
|           width: var(--table-row-width, 100%); |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .mdc-data-table__row ~ .mdc-data-table__row { |         .mdc-data-table__row ~ .mdc-data-table__row { | ||||||
| @@ -964,26 +815,18 @@ export class HaDataTable extends LitElement { | |||||||
|         .mdc-data-table__header-row { |         .mdc-data-table__header-row { | ||||||
|           height: 56px; |           height: 56px; | ||||||
|           display: flex; |           display: flex; | ||||||
|  |           width: 100%; | ||||||
|           border-bottom: 1px solid var(--divider-color); |           border-bottom: 1px solid var(--divider-color); | ||||||
|           overflow: auto; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /* Hide scrollbar for Chrome, Safari and Opera */ |  | ||||||
|         .mdc-data-table__header-row::-webkit-scrollbar { |         .mdc-data-table__header-row::-webkit-scrollbar { | ||||||
|           display: none; |           display: none; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         /* Hide scrollbar for IE, Edge and Firefox */ |  | ||||||
|         .mdc-data-table__header-row { |  | ||||||
|           -ms-overflow-style: none; /* IE and Edge */ |  | ||||||
|           scrollbar-width: none; /* Firefox */ |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         .mdc-data-table__cell, |         .mdc-data-table__cell, | ||||||
|         .mdc-data-table__header-cell { |         .mdc-data-table__header-cell { | ||||||
|           padding-right: 16px; |           padding-right: 16px; | ||||||
|           padding-left: 16px; |           padding-left: 16px; | ||||||
|           min-width: 150px; |  | ||||||
|           align-self: center; |           align-self: center; | ||||||
|           overflow: hidden; |           overflow: hidden; | ||||||
|           text-overflow: ellipsis; |           text-overflow: ellipsis; | ||||||
| @@ -1018,7 +861,6 @@ export class HaDataTable extends LitElement { | |||||||
|           width: 100%; |           width: 100%; | ||||||
|           border: 0; |           border: 0; | ||||||
|           white-space: nowrap; |           white-space: nowrap; | ||||||
|           position: relative; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .mdc-data-table__cell { |         .mdc-data-table__cell { | ||||||
| @@ -1031,8 +873,6 @@ export class HaDataTable extends LitElement { | |||||||
|           letter-spacing: 0.0178571429em; |           letter-spacing: 0.0178571429em; | ||||||
|           text-decoration: inherit; |           text-decoration: inherit; | ||||||
|           text-transform: inherit; |           text-transform: inherit; | ||||||
|           flex-grow: 0; |  | ||||||
|           flex-shrink: 0; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .mdc-data-table__cell a { |         .mdc-data-table__cell a { | ||||||
| @@ -1051,8 +891,7 @@ export class HaDataTable extends LitElement { | |||||||
|  |  | ||||||
|         .mdc-data-table__header-cell--icon, |         .mdc-data-table__header-cell--icon, | ||||||
|         .mdc-data-table__cell--icon { |         .mdc-data-table__cell--icon { | ||||||
|           min-width: 64px; |           width: 54px; | ||||||
|           flex: 0 0 64px !important; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .mdc-data-table__cell--icon img { |         .mdc-data-table__cell--icon img { | ||||||
| @@ -1092,14 +931,11 @@ export class HaDataTable extends LitElement { | |||||||
|         .mdc-data-table__header-cell--overflow-menu, |         .mdc-data-table__header-cell--overflow-menu, | ||||||
|         .mdc-data-table__header-cell--icon-button, |         .mdc-data-table__header-cell--icon-button, | ||||||
|         .mdc-data-table__cell--icon-button { |         .mdc-data-table__cell--icon-button { | ||||||
|           min-width: 64px; |  | ||||||
|           flex: 0 0 64px !important; |  | ||||||
|           padding: 8px; |           padding: 8px; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         .mdc-data-table__header-cell--icon-button, |         .mdc-data-table__header-cell--icon-button, | ||||||
|         .mdc-data-table__cell--icon-button { |         .mdc-data-table__cell--icon-button { | ||||||
|           min-width: 56px; |  | ||||||
|           width: 56px; |           width: 56px; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,28 +0,0 @@ | |||||||
| import { fireEvent } from "../../common/dom/fire_event"; |  | ||||||
| import { LocalizeFunc } from "../../common/translations/localize"; |  | ||||||
| import { DataTableColumnContainer } from "./ha-data-table"; |  | ||||||
|  |  | ||||||
| export interface DataTableSettingsDialogParams { |  | ||||||
|   columns: DataTableColumnContainer; |  | ||||||
|   onUpdate: ( |  | ||||||
|     columnOrder: string[] | undefined, |  | ||||||
|     hiddenColumns: string[] | undefined |  | ||||||
|   ) => void; |  | ||||||
|   hiddenColumns?: string[]; |  | ||||||
|   columnOrder?: string[]; |  | ||||||
|   localizeFunc?: LocalizeFunc; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export const loadDataTableSettingsDialog = () => |  | ||||||
|   import("./dialog-data-table-settings"); |  | ||||||
|  |  | ||||||
| export const showDataTableSettingsDialog = ( |  | ||||||
|   element: HTMLElement, |  | ||||||
|   dialogParams: DataTableSettingsDialogParams |  | ||||||
| ): void => { |  | ||||||
|   fireEvent(element, "show-dialog", { |  | ||||||
|     dialogTag: "dialog-data-table-settings", |  | ||||||
|     dialogImport: loadDataTableSettingsDialog, |  | ||||||
|     dialogParams, |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| @@ -1,6 +1,5 @@ | |||||||
| import { expose } from "comlink"; | import { expose } from "comlink"; | ||||||
| import { stringCompare } from "../../common/string/compare"; | import { stringCompare } from "../../common/string/compare"; | ||||||
| import { stripDiacritics } from "../../common/string/strip-diacritics"; |  | ||||||
| import type { | import type { | ||||||
|   ClonedDataTableColumnData, |   ClonedDataTableColumnData, | ||||||
|   DataTableRowData, |   DataTableRowData, | ||||||
| @@ -13,18 +12,20 @@ const filterData = ( | |||||||
|   columns: SortableColumnContainer, |   columns: SortableColumnContainer, | ||||||
|   filter: string |   filter: string | ||||||
| ) => { | ) => { | ||||||
|   filter = stripDiacritics(filter.toLowerCase()); |   filter = filter.toUpperCase(); | ||||||
|   return data.filter((row) => |   return data.filter((row) => | ||||||
|     Object.entries(columns).some((columnEntry) => { |     Object.entries(columns).some((columnEntry) => { | ||||||
|       const [key, column] = columnEntry; |       const [key, column] = columnEntry; | ||||||
|       if (column.filterable) { |       if (column.filterable) { | ||||||
|         const value = String( |         if ( | ||||||
|  |           String( | ||||||
|             column.filterKey |             column.filterKey | ||||||
|               ? row[column.valueColumn || key][column.filterKey] |               ? row[column.valueColumn || key][column.filterKey] | ||||||
|               : row[column.valueColumn || key] |               : row[column.valueColumn || key] | ||||||
|         ); |           ) | ||||||
|  |             .toUpperCase() | ||||||
|         if (stripDiacritics(value).toLowerCase().includes(filter)) { |             .includes(filter) | ||||||
|  |         ) { | ||||||
|           return true; |           return true; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -1,330 +0,0 @@ | |||||||
| import { mdiDrag } from "@mdi/js"; |  | ||||||
| import { HassEntity } from "home-assistant-js-websocket"; |  | ||||||
| import { LitElement, PropertyValues, css, html, nothing } from "lit"; |  | ||||||
| import { customElement, property, query, state } from "lit/decorators"; |  | ||||||
| import { repeat } from "lit/directives/repeat"; |  | ||||||
| import memoizeOne from "memoize-one"; |  | ||||||
| import { ensureArray } from "../../common/array/ensure-array"; |  | ||||||
| import { fireEvent } from "../../common/dom/fire_event"; |  | ||||||
| import { computeDomain } from "../../common/entity/compute_domain"; |  | ||||||
| import { |  | ||||||
|   STATE_DISPLAY_SPECIAL_CONTENT, |  | ||||||
|   STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS, |  | ||||||
| } from "../../state-display/state-display"; |  | ||||||
| import { HomeAssistant, ValueChangedEvent } from "../../types"; |  | ||||||
| import "../ha-combo-box"; |  | ||||||
| import type { HaComboBox } from "../ha-combo-box"; |  | ||||||
|  |  | ||||||
| const HIDDEN_ATTRIBUTES = [ |  | ||||||
|   "access_token", |  | ||||||
|   "available_modes", |  | ||||||
|   "battery_icon", |  | ||||||
|   "battery_level", |  | ||||||
|   "code_arm_required", |  | ||||||
|   "code_format", |  | ||||||
|   "color_modes", |  | ||||||
|   "device_class", |  | ||||||
|   "editable", |  | ||||||
|   "effect_list", |  | ||||||
|   "entity_id", |  | ||||||
|   "entity_picture", |  | ||||||
|   "event_types", |  | ||||||
|   "fan_modes", |  | ||||||
|   "fan_speed_list", |  | ||||||
|   "friendly_name", |  | ||||||
|   "frontend_stream_type", |  | ||||||
|   "has_date", |  | ||||||
|   "has_time", |  | ||||||
|   "hvac_modes", |  | ||||||
|   "icon", |  | ||||||
|   "id", |  | ||||||
|   "max_color_temp_kelvin", |  | ||||||
|   "max_mireds", |  | ||||||
|   "max_temp", |  | ||||||
|   "max", |  | ||||||
|   "min_color_temp_kelvin", |  | ||||||
|   "min_mireds", |  | ||||||
|   "min_temp", |  | ||||||
|   "min", |  | ||||||
|   "mode", |  | ||||||
|   "operation_list", |  | ||||||
|   "options", |  | ||||||
|   "percentage_step", |  | ||||||
|   "precipitation_unit", |  | ||||||
|   "preset_modes", |  | ||||||
|   "pressure_unit", |  | ||||||
|   "remaining", |  | ||||||
|   "sound_mode_list", |  | ||||||
|   "source_list", |  | ||||||
|   "state_class", |  | ||||||
|   "step", |  | ||||||
|   "supported_color_modes", |  | ||||||
|   "supported_features", |  | ||||||
|   "swing_modes", |  | ||||||
|   "target_temp_step", |  | ||||||
|   "temperature_unit", |  | ||||||
|   "token", |  | ||||||
|   "unit_of_measurement", |  | ||||||
|   "visibility_unit", |  | ||||||
|   "wind_speed_unit", |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| @customElement("ha-entity-state-content-picker") |  | ||||||
| class HaEntityStatePicker extends LitElement { |  | ||||||
|   @property({ attribute: false }) public hass!: HomeAssistant; |  | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public entityId?: string; |  | ||||||
|  |  | ||||||
|   @property({ type: Boolean }) public autofocus = false; |  | ||||||
|  |  | ||||||
|   @property({ type: Boolean }) public disabled = false; |  | ||||||
|  |  | ||||||
|   @property({ type: Boolean }) public required = false; |  | ||||||
|  |  | ||||||
|   @property({ type: Boolean, attribute: "allow-name" }) public allowName = |  | ||||||
|     false; |  | ||||||
|  |  | ||||||
|   @property() public label?: string; |  | ||||||
|  |  | ||||||
|   @property() public value?: string[] | string; |  | ||||||
|  |  | ||||||
|   @property() public helper?: string; |  | ||||||
|  |  | ||||||
|   @state() private _opened = false; |  | ||||||
|  |  | ||||||
|   @query("ha-combo-box", true) private _comboBox!: HaComboBox; |  | ||||||
|  |  | ||||||
|   protected shouldUpdate(changedProps: PropertyValues) { |  | ||||||
|     return !(!changedProps.has("_opened") && this._opened); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private options = memoizeOne( |  | ||||||
|     (entityId?: string, stateObj?: HassEntity, allowName?: boolean) => { |  | ||||||
|       const domain = entityId ? computeDomain(entityId) : undefined; |  | ||||||
|       return [ |  | ||||||
|         { |  | ||||||
|           label: this.hass.localize("ui.components.state-content-picker.state"), |  | ||||||
|           value: "state", |  | ||||||
|         }, |  | ||||||
|         ...(allowName |  | ||||||
|           ? [ |  | ||||||
|               { |  | ||||||
|                 label: this.hass.localize( |  | ||||||
|                   "ui.components.state-content-picker.name" |  | ||||||
|                 ), |  | ||||||
|                 value: "name", |  | ||||||
|               }, |  | ||||||
|             ] |  | ||||||
|           : []), |  | ||||||
|         { |  | ||||||
|           label: this.hass.localize( |  | ||||||
|             "ui.components.state-content-picker.last_changed" |  | ||||||
|           ), |  | ||||||
|           value: "last_changed", |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|           label: this.hass.localize( |  | ||||||
|             "ui.components.state-content-picker.last_updated" |  | ||||||
|           ), |  | ||||||
|           value: "last_updated", |  | ||||||
|         }, |  | ||||||
|         ...(domain |  | ||||||
|           ? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) => |  | ||||||
|               STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content) |  | ||||||
|             ).map((content) => ({ |  | ||||||
|               label: this.hass.localize( |  | ||||||
|                 `ui.components.state-content-picker.${content}` |  | ||||||
|               ), |  | ||||||
|               value: content, |  | ||||||
|             })) |  | ||||||
|           : []), |  | ||||||
|         ...Object.keys(stateObj?.attributes ?? {}) |  | ||||||
|           .filter((a) => !HIDDEN_ATTRIBUTES.includes(a)) |  | ||||||
|           .map((attribute) => ({ |  | ||||||
|             value: attribute, |  | ||||||
|             label: this.hass.formatEntityAttributeName(stateObj!, attribute), |  | ||||||
|           })), |  | ||||||
|       ]; |  | ||||||
|     } |  | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   private _filter = ""; |  | ||||||
|  |  | ||||||
|   protected render() { |  | ||||||
|     if (!this.hass) { |  | ||||||
|       return nothing; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const value = this._value; |  | ||||||
|  |  | ||||||
|     const stateObj = this.entityId |  | ||||||
|       ? this.hass.states[this.entityId] |  | ||||||
|       : undefined; |  | ||||||
|  |  | ||||||
|     const options = this.options(this.entityId, stateObj, this.allowName); |  | ||||||
|     const optionItems = options.filter( |  | ||||||
|       (option) => !this._value.includes(option.value) |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     return html` |  | ||||||
|       ${value?.length |  | ||||||
|         ? html` |  | ||||||
|             <ha-sortable |  | ||||||
|               no-style |  | ||||||
|               @item-moved=${this._moveItem} |  | ||||||
|               .disabled=${this.disabled} |  | ||||||
|             > |  | ||||||
|               <ha-chip-set> |  | ||||||
|                 ${repeat( |  | ||||||
|                   this._value, |  | ||||||
|                   (item) => item, |  | ||||||
|                   (item, idx) => { |  | ||||||
|                     const label = |  | ||||||
|                       options.find((option) => option.value === item)?.label || |  | ||||||
|                       item; |  | ||||||
|                     return html` |  | ||||||
|                       <ha-input-chip |  | ||||||
|                         .idx=${idx} |  | ||||||
|                         @remove=${this._removeItem} |  | ||||||
|                         .label=${label} |  | ||||||
|                         selected |  | ||||||
|                       > |  | ||||||
|                         <ha-svg-icon |  | ||||||
|                           slot="icon" |  | ||||||
|                           .path=${mdiDrag} |  | ||||||
|                           data-handle |  | ||||||
|                         ></ha-svg-icon> |  | ||||||
|  |  | ||||||
|                         ${label} |  | ||||||
|                       </ha-input-chip> |  | ||||||
|                     `; |  | ||||||
|                   } |  | ||||||
|                 )} |  | ||||||
|               </ha-chip-set> |  | ||||||
|             </ha-sortable> |  | ||||||
|           ` |  | ||||||
|         : nothing} |  | ||||||
|  |  | ||||||
|       <ha-combo-box |  | ||||||
|         item-value-path="value" |  | ||||||
|         item-label-path="label" |  | ||||||
|         .hass=${this.hass} |  | ||||||
|         .label=${this.label} |  | ||||||
|         .helper=${this.helper} |  | ||||||
|         .disabled=${this.disabled} |  | ||||||
|         .required=${this.required && !value.length} |  | ||||||
|         .value=${""} |  | ||||||
|         .items=${optionItems} |  | ||||||
|         allow-custom-value |  | ||||||
|         @filter-changed=${this._filterChanged} |  | ||||||
|         @value-changed=${this._comboBoxValueChanged} |  | ||||||
|         @opened-changed=${this._openedChanged} |  | ||||||
|       ></ha-combo-box> |  | ||||||
|     `; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private get _value() { |  | ||||||
|     return !this.value ? [] : ensureArray(this.value); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _openedChanged(ev: ValueChangedEvent<boolean>) { |  | ||||||
|     this._opened = ev.detail.value; |  | ||||||
|     this._comboBox.filteredItems = this._comboBox.items; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _filterChanged(ev?: CustomEvent): void { |  | ||||||
|     this._filter = ev?.detail.value || ""; |  | ||||||
|  |  | ||||||
|     const filteredItems = this._comboBox.items?.filter((item) => { |  | ||||||
|       const label = item.label || item.value; |  | ||||||
|       return label.toLowerCase().includes(this._filter?.toLowerCase()); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     if (this._filter) { |  | ||||||
|       filteredItems?.unshift({ label: this._filter, value: this._filter }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     this._comboBox.filteredItems = filteredItems; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private async _moveItem(ev: CustomEvent) { |  | ||||||
|     ev.stopPropagation(); |  | ||||||
|     const { oldIndex, newIndex } = ev.detail; |  | ||||||
|     const value = this._value; |  | ||||||
|     const newValue = value.concat(); |  | ||||||
|     const element = newValue.splice(oldIndex, 1)[0]; |  | ||||||
|     newValue.splice(newIndex, 0, element); |  | ||||||
|     this._setValue(newValue); |  | ||||||
|     await this.updateComplete; |  | ||||||
|     this._filterChanged(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private async _removeItem(ev) { |  | ||||||
|     ev.stopPropagation(); |  | ||||||
|     const value: string[] = [...this._value]; |  | ||||||
|     value.splice(ev.target.idx, 1); |  | ||||||
|     this._setValue(value); |  | ||||||
|     await this.updateComplete; |  | ||||||
|     this._filterChanged(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _comboBoxValueChanged(ev: CustomEvent): void { |  | ||||||
|     ev.stopPropagation(); |  | ||||||
|     const newValue = ev.detail.value; |  | ||||||
|  |  | ||||||
|     if (this.disabled || newValue === "") { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const currentValue = this._value; |  | ||||||
|  |  | ||||||
|     if (currentValue.includes(newValue)) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     setTimeout(() => { |  | ||||||
|       this._filterChanged(); |  | ||||||
|       this._comboBox.setInputValue(""); |  | ||||||
|     }, 0); |  | ||||||
|  |  | ||||||
|     this._setValue([...currentValue, newValue]); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _setValue(value: string[]) { |  | ||||||
|     const newValue = |  | ||||||
|       value.length === 0 ? undefined : value.length === 1 ? value[0] : value; |  | ||||||
|     this.value = newValue; |  | ||||||
|     fireEvent(this, "value-changed", { |  | ||||||
|       value: newValue, |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static styles = css` |  | ||||||
|     :host { |  | ||||||
|       position: relative; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     ha-chip-set { |  | ||||||
|       padding: 8px 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .sortable-fallback { |  | ||||||
|       display: none; |  | ||||||
|       opacity: 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .sortable-ghost { |  | ||||||
|       opacity: 0.4; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     .sortable-drag { |  | ||||||
|       cursor: grabbing; |  | ||||||
|     } |  | ||||||
|   `; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| declare global { |  | ||||||
|   interface HTMLElementTagNameMap { |  | ||||||
|     "ha-entity-state-content-picker": HaEntityStatePicker; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -134,7 +134,7 @@ export class HaStateLabelBadge extends LitElement { | |||||||
|           this._timerTimeRemaining |           this._timerTimeRemaining | ||||||
|         )} |         )} | ||||||
|         .description=${this.showName |         .description=${this.showName | ||||||
|           ? (this.name ?? computeStateName(entityState)) |           ? this.name ?? computeStateName(entityState) | ||||||
|           : undefined} |           : undefined} | ||||||
|       > |       > | ||||||
|         ${!image && showIcon |         ${!image && showIcon | ||||||
|   | |||||||
| @@ -90,8 +90,7 @@ class HaAnsiToHtml extends LitElement { | |||||||
|  |  | ||||||
|   private _parseTextToColoredPre(text) { |   private _parseTextToColoredPre(text) { | ||||||
|     const pre = document.createElement("pre"); |     const pre = document.createElement("pre"); | ||||||
|     // eslint-disable-next-line no-control-regex |     const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g; | ||||||
|     const re = /\x1b(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1b\\))/g; |  | ||||||
|     let i = 0; |     let i = 0; | ||||||
|  |  | ||||||
|     const state: State = { |     const state: State = { | ||||||
|   | |||||||
| @@ -279,8 +279,6 @@ export class HaAreaPicker extends LitElement { | |||||||
|             icon: null, |             icon: null, | ||||||
|             aliases: [], |             aliases: [], | ||||||
|             labels: [], |             labels: [], | ||||||
|             created_at: 0, |  | ||||||
|             modified_at: 0, |  | ||||||
|           }, |           }, | ||||||
|         ]; |         ]; | ||||||
|       } |       } | ||||||
| @@ -297,8 +295,6 @@ export class HaAreaPicker extends LitElement { | |||||||
|               icon: "mdi:plus", |               icon: "mdi:plus", | ||||||
|               aliases: [], |               aliases: [], | ||||||
|               labels: [], |               labels: [], | ||||||
|               created_at: 0, |  | ||||||
|               modified_at: 0, |  | ||||||
|             }, |             }, | ||||||
|           ]; |           ]; | ||||||
|     } |     } | ||||||
| @@ -381,8 +377,6 @@ export class HaAreaPicker extends LitElement { | |||||||
|             picture: null, |             picture: null, | ||||||
|             labels: [], |             labels: [], | ||||||
|             aliases: [], |             aliases: [], | ||||||
|             created_at: 0, |  | ||||||
|             modified_at: 0, |  | ||||||
|           }, |           }, | ||||||
|         ] as AreaRegistryEntry[]; |         ] as AreaRegistryEntry[]; | ||||||
|       } else { |       } else { | ||||||
| @@ -399,8 +393,6 @@ export class HaAreaPicker extends LitElement { | |||||||
|             picture: null, |             picture: null, | ||||||
|             labels: [], |             labels: [], | ||||||
|             aliases: [], |             aliases: [], | ||||||
|             created_at: 0, |  | ||||||
|             modified_at: 0, |  | ||||||
|           }, |           }, | ||||||
|         ] as AreaRegistryEntry[]; |         ] as AreaRegistryEntry[]; | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -1,155 +0,0 @@ | |||||||
| import { css, CSSResultGroup, html, LitElement, nothing } from "lit"; |  | ||||||
| import { customElement, property } from "lit/decorators"; |  | ||||||
| import { classMap } from "lit/directives/class-map"; |  | ||||||
| import { ifDefined } from "lit/directives/if-defined"; |  | ||||||
| import "./ha-ripple"; |  | ||||||
|  |  | ||||||
| type BadgeType = "badge" | "button"; |  | ||||||
|  |  | ||||||
| @customElement("ha-badge") |  | ||||||
| export class HaBadge extends LitElement { |  | ||||||
|   @property() public type: BadgeType = "badge"; |  | ||||||
|  |  | ||||||
|   @property() public label?: string; |  | ||||||
|  |  | ||||||
|   @property({ type: Boolean, attribute: "icon-only" }) iconOnly = false; |  | ||||||
|  |  | ||||||
|   protected render() { |  | ||||||
|     const label = this.label; |  | ||||||
|  |  | ||||||
|     return html` |  | ||||||
|       <div |  | ||||||
|         class="badge ${classMap({ |  | ||||||
|           "icon-only": this.iconOnly, |  | ||||||
|         })}" |  | ||||||
|         role=${ifDefined(this.type === "button" ? "button" : undefined)} |  | ||||||
|         tabindex=${ifDefined(this.type === "button" ? "0" : undefined)} |  | ||||||
|       > |  | ||||||
|         <ha-ripple .disabled=${this.type !== "button"}></ha-ripple> |  | ||||||
|         <slot name="icon"></slot> |  | ||||||
|         ${this.iconOnly |  | ||||||
|           ? nothing |  | ||||||
|           : html`<span class="info"> |  | ||||||
|               ${label ? html`<span class="label">${label}</span>` : nothing} |  | ||||||
|               <span class="content"><slot></slot></span> |  | ||||||
|             </span>`} |  | ||||||
|       </div> |  | ||||||
|     `; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static get styles(): CSSResultGroup { |  | ||||||
|     return css` |  | ||||||
|       :host { |  | ||||||
|         --badge-color: var(--secondary-text-color); |  | ||||||
|         -webkit-tap-highlight-color: transparent; |  | ||||||
|       } |  | ||||||
|       .badge { |  | ||||||
|         position: relative; |  | ||||||
|         --ha-ripple-color: var(--badge-color); |  | ||||||
|         --ha-ripple-hover-opacity: 0.04; |  | ||||||
|         --ha-ripple-pressed-opacity: 0.12; |  | ||||||
|         transition: |  | ||||||
|           box-shadow 180ms ease-in-out, |  | ||||||
|           border-color 180ms ease-in-out; |  | ||||||
|         display: flex; |  | ||||||
|         flex-direction: row; |  | ||||||
|         align-items: center; |  | ||||||
|         justify-content: center; |  | ||||||
|         gap: 8px; |  | ||||||
|         height: var(--ha-badge-size, 36px); |  | ||||||
|         min-width: var(--ha-badge-size, 36px); |  | ||||||
|         padding: 0px 12px; |  | ||||||
|         box-sizing: border-box; |  | ||||||
|         width: auto; |  | ||||||
|         border-radius: var( |  | ||||||
|           --ha-badge-border-radius, |  | ||||||
|           calc(var(--ha-badge-size, 36px) / 2) |  | ||||||
|         ); |  | ||||||
|         background: var( |  | ||||||
|           --ha-card-background, |  | ||||||
|           var(--card-background-color, white) |  | ||||||
|         ); |  | ||||||
|         -webkit-backdrop-filter: var(--ha-card-backdrop-filter, none); |  | ||||||
|         backdrop-filter: var(--ha-card-backdrop-filter, none); |  | ||||||
|         border-width: var(--ha-card-border-width, 1px); |  | ||||||
|         box-shadow: var(--ha-card-box-shadow, none); |  | ||||||
|         border-style: solid; |  | ||||||
|         border-color: var( |  | ||||||
|           --ha-card-border-color, |  | ||||||
|           var(--divider-color, #e0e0e0) |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|       .badge:focus-visible { |  | ||||||
|         --shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent); |  | ||||||
|         --shadow-focus: 0 0 0 1px var(--badge-color); |  | ||||||
|         border-color: var(--badge-color); |  | ||||||
|         box-shadow: var(--shadow-default), var(--shadow-focus); |  | ||||||
|       } |  | ||||||
|       [role="button"] { |  | ||||||
|         cursor: pointer; |  | ||||||
|       } |  | ||||||
|       [role="button"]:focus { |  | ||||||
|         outline: none; |  | ||||||
|       } |  | ||||||
|       .info { |  | ||||||
|         display: flex; |  | ||||||
|         flex-direction: column; |  | ||||||
|         align-items: flex-start; |  | ||||||
|         padding-inline-start: initial; |  | ||||||
|         text-align: center; |  | ||||||
|         font-family: Roboto; |  | ||||||
|       } |  | ||||||
|       .label { |  | ||||||
|         font-size: 10px; |  | ||||||
|         font-style: normal; |  | ||||||
|         font-weight: 500; |  | ||||||
|         line-height: 10px; |  | ||||||
|         letter-spacing: 0.1px; |  | ||||||
|         color: var(--secondary-text-color); |  | ||||||
|       } |  | ||||||
|       .content { |  | ||||||
|         font-size: 12px; |  | ||||||
|         font-style: normal; |  | ||||||
|         font-weight: 500; |  | ||||||
|         line-height: 16px; |  | ||||||
|         letter-spacing: 0.1px; |  | ||||||
|         color: var(--primary-text-color); |  | ||||||
|       } |  | ||||||
|       ::slotted([slot="icon"]) { |  | ||||||
|         --mdc-icon-size: 18px; |  | ||||||
|         color: var(--badge-color); |  | ||||||
|         line-height: 0; |  | ||||||
|         margin-left: -4px; |  | ||||||
|         margin-right: 0; |  | ||||||
|         margin-inline-start: -4px; |  | ||||||
|         margin-inline-end: 0; |  | ||||||
|       } |  | ||||||
|       ::slotted(img[slot="icon"]) { |  | ||||||
|         width: 30px; |  | ||||||
|         height: 30px; |  | ||||||
|         border-radius: 50%; |  | ||||||
|         object-fit: cover; |  | ||||||
|         overflow: hidden; |  | ||||||
|         margin-left: -10px; |  | ||||||
|         margin-right: 0; |  | ||||||
|         margin-inline-start: -10px; |  | ||||||
|         margin-inline-end: 0; |  | ||||||
|       } |  | ||||||
|       .badge.icon-only { |  | ||||||
|         padding: 0; |  | ||||||
|       } |  | ||||||
|       .badge.icon-only ::slotted([slot="icon"]) { |  | ||||||
|         margin-left: 0; |  | ||||||
|         margin-right: 0; |  | ||||||
|         margin-inline-start: 0; |  | ||||||
|         margin-inline-end: 0; |  | ||||||
|       } |  | ||||||
|     `; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| declare global { |  | ||||||
|   interface HTMLElementTagNameMap { |  | ||||||
|     "ha-badge": HaBadge; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,12 +1,10 @@ | |||||||
| import "@material/mwc-list/mwc-list-item"; | import "@material/mwc-list/mwc-list-item"; | ||||||
| import { css, html, LitElement, TemplateResult, nothing } from "lit"; | import { css, html, LitElement, TemplateResult } from "lit"; | ||||||
| import { customElement, property } from "lit/decorators"; | import { customElement, property } from "lit/decorators"; | ||||||
| import { mdiClose } from "@mdi/js"; |  | ||||||
| import { ifDefined } from "lit/directives/if-defined"; | import { ifDefined } from "lit/directives/if-defined"; | ||||||
| import { fireEvent } from "../common/dom/fire_event"; | import { fireEvent } from "../common/dom/fire_event"; | ||||||
| import { stopPropagation } from "../common/dom/stop_propagation"; | import { stopPropagation } from "../common/dom/stop_propagation"; | ||||||
| import "./ha-select"; | import "./ha-select"; | ||||||
| import "./ha-icon-button"; |  | ||||||
| import { HaTextField } from "./ha-textfield"; | import { HaTextField } from "./ha-textfield"; | ||||||
| import "./ha-input-helper-text"; | import "./ha-input-helper-text"; | ||||||
|  |  | ||||||
| @@ -126,14 +124,11 @@ export class HaBaseTimeInput extends LitElement { | |||||||
|    */ |    */ | ||||||
|   @property() amPm: "AM" | "PM" = "AM"; |   @property() amPm: "AM" | "PM" = "AM"; | ||||||
|  |  | ||||||
|   @property({ type: Boolean, reflect: true }) public clearable?: boolean; |  | ||||||
|  |  | ||||||
|   protected render(): TemplateResult { |   protected render(): TemplateResult { | ||||||
|     return html` |     return html` | ||||||
|       ${this.label |       ${this.label | ||||||
|         ? html`<label>${this.label}${this.required ? " *" : ""}</label>` |         ? html`<label>${this.label}${this.required ? " *" : ""}</label>` | ||||||
|         : ""} |         : ""} | ||||||
|       <div class="time-input-wrap-wrap"> |  | ||||||
|       <div class="time-input-wrap"> |       <div class="time-input-wrap"> | ||||||
|         ${this.enableDay |         ${this.enableDay | ||||||
|           ? html` |           ? html` | ||||||
| @@ -239,15 +234,6 @@ export class HaBaseTimeInput extends LitElement { | |||||||
|             > |             > | ||||||
|             </ha-textfield>` |             </ha-textfield>` | ||||||
|           : ""} |           : ""} | ||||||
|           ${this.clearable && !this.required && !this.disabled |  | ||||||
|             ? html`<ha-icon-button |  | ||||||
|                 label="clear" |  | ||||||
|                 @click=${this._clearValue} |  | ||||||
|                 .path=${mdiClose} |  | ||||||
|               ></ha-icon-button>` |  | ||||||
|             : nothing} |  | ||||||
|         </div> |  | ||||||
|  |  | ||||||
|         ${this.format === 24 |         ${this.format === 24 | ||||||
|           ? "" |           ? "" | ||||||
|           : html`<ha-select |           : html`<ha-select | ||||||
| @@ -263,17 +249,13 @@ export class HaBaseTimeInput extends LitElement { | |||||||
|               <mwc-list-item value="AM">AM</mwc-list-item> |               <mwc-list-item value="AM">AM</mwc-list-item> | ||||||
|               <mwc-list-item value="PM">PM</mwc-list-item> |               <mwc-list-item value="PM">PM</mwc-list-item> | ||||||
|             </ha-select>`} |             </ha-select>`} | ||||||
|  |       </div> | ||||||
|       ${this.helper |       ${this.helper | ||||||
|         ? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>` |         ? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>` | ||||||
|         : ""} |         : ""} | ||||||
|       </div> |  | ||||||
|     `; |     `; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private _clearValue(): void { |  | ||||||
|     fireEvent(this, "value-changed"); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _valueChanged(ev: InputEvent) { |   private _valueChanged(ev: InputEvent) { | ||||||
|     const textField = ev.currentTarget as HaTextField; |     const textField = ev.currentTarget as HaTextField; | ||||||
|     this[textField.name] = |     this[textField.name] = | ||||||
| @@ -320,25 +302,18 @@ export class HaBaseTimeInput extends LitElement { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   static styles = css` |   static styles = css` | ||||||
|     :host([clearable]) { |  | ||||||
|       position: relative; |  | ||||||
|     } |  | ||||||
|     :host { |     :host { | ||||||
|       display: block; |       display: block; | ||||||
|     } |     } | ||||||
|     .time-input-wrap-wrap { |  | ||||||
|       display: flex; |  | ||||||
|     } |  | ||||||
|     .time-input-wrap { |     .time-input-wrap { | ||||||
|       display: flex; |       display: flex; | ||||||
|       border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0; |       border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0; | ||||||
|       overflow: hidden; |       overflow: hidden; | ||||||
|       position: relative; |       position: relative; | ||||||
|       direction: ltr; |       direction: ltr; | ||||||
|       padding-right: 3px; |  | ||||||
|     } |     } | ||||||
|     ha-textfield { |     ha-textfield { | ||||||
|       width: 55px; |       width: 40px; | ||||||
|       text-align: center; |       text-align: center; | ||||||
|       --mdc-shape-small: 0; |       --mdc-shape-small: 0; | ||||||
|       --text-field-appearance: none; |       --text-field-appearance: none; | ||||||
| @@ -360,21 +335,6 @@ export class HaBaseTimeInput extends LitElement { | |||||||
|       --mdc-shape-small: 0; |       --mdc-shape-small: 0; | ||||||
|       width: 85px; |       width: 85px; | ||||||
|     } |     } | ||||||
|     :host([clearable]) .mdc-select__anchor { |  | ||||||
|         padding-inline-end: var(--select-selected-text-padding-end, 12px); |  | ||||||
|     } |  | ||||||
|     ha-icon-button { |  | ||||||
|       position: relative |  | ||||||
|       --mdc-icon-button-size: 36px; |  | ||||||
|       --mdc-icon-size: 20px; |  | ||||||
|       color: var(--secondary-text-color); |  | ||||||
|       direction: var(--direction); |  | ||||||
|       display: flex; |  | ||||||
|       align-items: center; |  | ||||||
|       background-color:var(--mdc-text-field-fill-color, whitesmoke); |  | ||||||
|       border-bottom-style: solid; |  | ||||||
|       border-bottom-width: 1px; |  | ||||||
|     } |  | ||||||
|     label { |     label { | ||||||
|       -moz-osx-font-smoothing: grayscale; |       -moz-osx-font-smoothing: grayscale; | ||||||
|       -webkit-font-smoothing: antialiased; |       -webkit-font-smoothing: antialiased; | ||||||
|   | |||||||
| @@ -196,8 +196,8 @@ export class HaControlNumberButton extends LitElement { | |||||||
|         --control-number-buttons-background-opacity: 0.2; |         --control-number-buttons-background-opacity: 0.2; | ||||||
|         --control-number-buttons-border-radius: 10px; |         --control-number-buttons-border-radius: 10px; | ||||||
|         --mdc-icon-size: 16px; |         --mdc-icon-size: 16px; | ||||||
|         height: var(--feature-height); |         height: 40px; | ||||||
|         width: 100%; |         width: 200px; | ||||||
|         color: var(--primary-text-color); |         color: var(--primary-text-color); | ||||||
|         -webkit-tap-highlight-color: transparent; |         -webkit-tap-highlight-color: transparent; | ||||||
|         font-style: normal; |         font-style: normal; | ||||||
|   | |||||||
| @@ -45,35 +45,15 @@ export class HaConversationAgentPicker extends LitElement { | |||||||
|     if (!this._agents) { |     if (!this._agents) { | ||||||
|       return nothing; |       return nothing; | ||||||
|     } |     } | ||||||
|     let value = this.value; |     const value = | ||||||
|     if (!value && this.required) { |       this.value ?? | ||||||
|       // Select Home Assistant conversation agent if it supports the language |       (this.required && | ||||||
|       for (const agent of this._agents) { |       (!this.language || | ||||||
|         if ( |         this._agents | ||||||
|           agent.id === "conversation.home_assistant" && |           .find((agent) => agent.id === "homeassistant") | ||||||
|           agent.supported_languages.includes(this.language!) |           ?.supported_languages.includes(this.language)) | ||||||
|         ) { |         ? "homeassistant" | ||||||
|           value = agent.id; |         : NONE); | ||||||
|           break; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       if (!value) { |  | ||||||
|         // Select the first agent that supports the language |  | ||||||
|         for (const agent of this._agents) { |  | ||||||
|           if ( |  | ||||||
|             agent.supported_languages === "*" && |  | ||||||
|             agent.supported_languages.includes(this.language!) |  | ||||||
|           ) { |  | ||||||
|             value = agent.id; |  | ||||||
|             break; |  | ||||||
|           } |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     if (!value) { |  | ||||||
|       value = NONE; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return html` |     return html` | ||||||
|       <ha-select |       <ha-select | ||||||
|         .label=${this.label || |         .label=${this.label || | ||||||
|   | |||||||
| @@ -68,8 +68,8 @@ export class HaExpansionPanel extends LitElement { | |||||||
|                 ></ha-svg-icon> |                 ></ha-svg-icon> | ||||||
|               ` |               ` | ||||||
|             : ""} |             : ""} | ||||||
|           <slot name="icons"></slot> |  | ||||||
|         </div> |         </div> | ||||||
|  |         <slot name="icons"></slot> | ||||||
|       </div> |       </div> | ||||||
|       <div |       <div | ||||||
|         class="container ${classMap({ expanded: this.expanded })}" |         class="container ${classMap({ expanded: this.expanded })}" | ||||||
|   | |||||||
| @@ -295,8 +295,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) { | |||||||
|             icon: null, |             icon: null, | ||||||
|             level: null, |             level: null, | ||||||
|             aliases: [], |             aliases: [], | ||||||
|             created_at: 0, |  | ||||||
|             modified_at: 0, |  | ||||||
|           }, |           }, | ||||||
|         ]; |         ]; | ||||||
|       } |       } | ||||||
| @@ -311,8 +309,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) { | |||||||
|               icon: "mdi:plus", |               icon: "mdi:plus", | ||||||
|               level: null, |               level: null, | ||||||
|               aliases: [], |               aliases: [], | ||||||
|               created_at: 0, |  | ||||||
|               modified_at: 0, |  | ||||||
|             }, |             }, | ||||||
|           ]; |           ]; | ||||||
|     } |     } | ||||||
| @@ -395,8 +391,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) { | |||||||
|             icon: null, |             icon: null, | ||||||
|             level: null, |             level: null, | ||||||
|             aliases: [], |             aliases: [], | ||||||
|             created_at: 0, |  | ||||||
|             modified_at: 0, |  | ||||||
|           }, |           }, | ||||||
|         ] as FloorRegistryEntry[]; |         ] as FloorRegistryEntry[]; | ||||||
|       } else { |       } else { | ||||||
| @@ -411,8 +405,6 @@ export class HaFloorPicker extends SubscribeMixin(LitElement) { | |||||||
|             icon: "mdi:plus", |             icon: "mdi:plus", | ||||||
|             level: null, |             level: null, | ||||||
|             aliases: [], |             aliases: [], | ||||||
|             created_at: 0, |  | ||||||
|             modified_at: 0, |  | ||||||
|           }, |           }, | ||||||
|         ] as FloorRegistryEntry[]; |         ] as FloorRegistryEntry[]; | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -94,8 +94,6 @@ export const computeInitialHaFormData = ( | |||||||
|         data[field.name] = selector.color_temp?.min_mireds ?? 153; |         data[field.name] = selector.color_temp?.min_mireds ?? 153; | ||||||
|       } else if ( |       } else if ( | ||||||
|         "action" in selector || |         "action" in selector || | ||||||
|         "trigger" in selector || |  | ||||||
|         "condition" in selector || |  | ||||||
|         "media" in selector || |         "media" in selector || | ||||||
|         "target" in selector |         "target" in selector | ||||||
|       ) { |       ) { | ||||||
|   | |||||||
| @@ -21,45 +21,13 @@ export class HaFormExpendable extends LitElement implements HaFormElement { | |||||||
|  |  | ||||||
|   @property({ attribute: false }) public computeLabel?: ( |   @property({ attribute: false }) public computeLabel?: ( | ||||||
|     schema: HaFormSchema, |     schema: HaFormSchema, | ||||||
|     data?: HaFormDataContainer, |     data?: HaFormDataContainer | ||||||
|     options?: { path?: string[] } |  | ||||||
|   ) => string; |   ) => string; | ||||||
|  |  | ||||||
|   @property({ attribute: false }) public computeHelper?: ( |   @property({ attribute: false }) public computeHelper?: ( | ||||||
|     schema: HaFormSchema, |     schema: HaFormSchema | ||||||
|     options?: { path?: string[] } |  | ||||||
|   ) => string; |   ) => string; | ||||||
|  |  | ||||||
|   private _renderDescription() { |  | ||||||
|     const description = this.computeHelper?.(this.schema); |  | ||||||
|     return description ? html`<p>${description}</p>` : nothing; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private _computeLabel = ( |  | ||||||
|     schema: HaFormSchema, |  | ||||||
|     data?: HaFormDataContainer, |  | ||||||
|     options?: { path?: string[] } |  | ||||||
|   ) => { |  | ||||||
|     if (!this.computeLabel) return this.computeLabel; |  | ||||||
|  |  | ||||||
|     return this.computeLabel(schema, data, { |  | ||||||
|       ...options, |  | ||||||
|       path: [...(options?.path || []), this.schema.name], |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   private _computeHelper = ( |  | ||||||
|     schema: HaFormSchema, |  | ||||||
|     options?: { path?: string[] } |  | ||||||
|   ) => { |  | ||||||
|     if (!this.computeHelper) return this.computeHelper; |  | ||||||
|  |  | ||||||
|     return this.computeHelper(schema, { |  | ||||||
|       ...options, |  | ||||||
|       path: [...(options?.path || []), this.schema.name], |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   protected render() { |   protected render() { | ||||||
|     return html` |     return html` | ||||||
|       <ha-expansion-panel outlined .expanded=${Boolean(this.schema.expanded)}> |       <ha-expansion-panel outlined .expanded=${Boolean(this.schema.expanded)}> | ||||||
| @@ -75,17 +43,16 @@ export class HaFormExpendable extends LitElement implements HaFormElement { | |||||||
|                   <ha-svg-icon .path=${this.schema.iconPath}></ha-svg-icon> |                   <ha-svg-icon .path=${this.schema.iconPath}></ha-svg-icon> | ||||||
|                 ` |                 ` | ||||||
|               : nothing} |               : nothing} | ||||||
|           ${this.schema.title || this.computeLabel?.(this.schema)} |           ${this.schema.title} | ||||||
|         </div> |         </div> | ||||||
|         <div class="content"> |         <div class="content"> | ||||||
|           ${this._renderDescription()} |  | ||||||
|           <ha-form |           <ha-form | ||||||
|             .hass=${this.hass} |             .hass=${this.hass} | ||||||
|             .data=${this.data} |             .data=${this.data} | ||||||
|             .schema=${this.schema.schema} |             .schema=${this.schema.schema} | ||||||
|             .disabled=${this.disabled} |             .disabled=${this.disabled} | ||||||
|             .computeLabel=${this._computeLabel} |             .computeLabel=${this.computeLabel} | ||||||
|             .computeHelper=${this._computeHelper} |             .computeHelper=${this.computeHelper} | ||||||
|           ></ha-form> |           ></ha-form> | ||||||
|         </div> |         </div> | ||||||
|       </ha-expansion-panel> |       </ha-expansion-panel> | ||||||
| @@ -104,9 +71,6 @@ export class HaFormExpendable extends LitElement implements HaFormElement { | |||||||
|       .content { |       .content { | ||||||
|         padding: 12px; |         padding: 12px; | ||||||
|       } |       } | ||||||
|       .content p { |  | ||||||
|         margin: 0 0 24px; |  | ||||||
|       } |  | ||||||
|       ha-expansion-panel { |       ha-expansion-panel { | ||||||
|         display: block; |         display: block; | ||||||
|         --expansion-panel-content-padding: 0; |         --expansion-panel-content-padding: 0; | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ const LOAD_ELEMENTS = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| const getValue = (obj, item) => | const getValue = (obj, item) => | ||||||
|   obj ? (!item.name || item.flatten ? obj : obj[item.name]) : null; |   obj ? (!item.name ? obj : obj[item.name]) : null; | ||||||
|  |  | ||||||
| const getError = (obj, item) => (obj && item.name ? obj[item.name] : null); | const getError = (obj, item) => (obj && item.name ? obj[item.name] : null); | ||||||
|  |  | ||||||
| @@ -73,6 +73,10 @@ export class HaForm extends LitElement implements HaFormElement { | |||||||
|     schema: any |     schema: any | ||||||
|   ) => string | undefined; |   ) => string | undefined; | ||||||
|  |  | ||||||
|  |   @property({ attribute: false }) public localizeValue?: ( | ||||||
|  |     key: string | ||||||
|  |   ) => string; | ||||||
|  |  | ||||||
|   protected getFormProperties(): Record<string, any> { |   protected getFormProperties(): Record<string, any> { | ||||||
|     return {}; |     return {}; | ||||||
|   } |   } | ||||||
| @@ -145,6 +149,7 @@ export class HaForm extends LitElement implements HaFormElement { | |||||||
|                   .disabled=${item.disabled || this.disabled || false} |                   .disabled=${item.disabled || this.disabled || false} | ||||||
|                   .placeholder=${item.required ? "" : item.default} |                   .placeholder=${item.required ? "" : item.default} | ||||||
|                   .helper=${this._computeHelper(item)} |                   .helper=${this._computeHelper(item)} | ||||||
|  |                   .localizeValue=${this.localizeValue} | ||||||
|                   .required=${item.required || false} |                   .required=${item.required || false} | ||||||
|                   .context=${this._generateContext(item)} |                   .context=${this._generateContext(item)} | ||||||
|                 ></ha-selector>` |                 ></ha-selector>` | ||||||
| @@ -199,8 +204,7 @@ export class HaForm extends LitElement implements HaFormElement { | |||||||
|  |  | ||||||
|       if (ev.target === this) return; |       if (ev.target === this) return; | ||||||
|  |  | ||||||
|       const newValue = |       const newValue = !schema.name | ||||||
|         !schema.name || ("flatten" in schema && schema.flatten) |  | ||||||
|         ? ev.detail.value |         ? ev.detail.value | ||||||
|         : { [schema.name]: ev.detail.value }; |         : { [schema.name]: ev.detail.value }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -31,15 +31,15 @@ export interface HaFormBaseSchema { | |||||||
|  |  | ||||||
| export interface HaFormGridSchema extends HaFormBaseSchema { | export interface HaFormGridSchema extends HaFormBaseSchema { | ||||||
|   type: "grid"; |   type: "grid"; | ||||||
|   flatten?: boolean; |   name: string; | ||||||
|   column_min_width?: string; |   column_min_width?: string; | ||||||
|   schema: readonly HaFormSchema[]; |   schema: readonly HaFormSchema[]; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface HaFormExpandableSchema extends HaFormBaseSchema { | export interface HaFormExpandableSchema extends HaFormBaseSchema { | ||||||
|   type: "expandable"; |   type: "expandable"; | ||||||
|   flatten?: boolean; |   name: ""; | ||||||
|   title?: string; |   title: string; | ||||||
|   icon?: string; |   icon?: string; | ||||||
|   iconPath?: string; |   iconPath?: string; | ||||||
|   expanded?: boolean; |   expanded?: boolean; | ||||||
| @@ -100,7 +100,7 @@ export type SchemaUnion< | |||||||
|   SchemaArray extends readonly HaFormSchema[], |   SchemaArray extends readonly HaFormSchema[], | ||||||
|   Schema = SchemaArray[number], |   Schema = SchemaArray[number], | ||||||
| > = Schema extends HaFormGridSchema | HaFormExpandableSchema | > = Schema extends HaFormGridSchema | HaFormExpandableSchema | ||||||
|   ? SchemaUnion<Schema["schema"]> | Schema |   ? SchemaUnion<Schema["schema"]> | ||||||
|   : Schema; |   : Schema; | ||||||
|  |  | ||||||
| export interface HaFormDataContainer { | export interface HaFormDataContainer { | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user