Compare commits
	
		
			1 Commits
		
	
	
		
			20240626.1
			...
			remove-unc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					f4bfcc6a69 | 
@@ -1,5 +1,5 @@
 | 
			
		||||
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
 | 
			
		||||
FROM mcr.microsoft.com/devcontainers/python:3.12
 | 
			
		||||
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.11
 | 
			
		||||
 | 
			
		||||
ENV \
 | 
			
		||||
  DEBIAN_FRONTEND=noninteractive \
 | 
			
		||||
 
 | 
			
		||||
@@ -115,7 +115,6 @@
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "unused-imports/no-unused-imports": "error",
 | 
			
		||||
    "lit/attribute-names": "warn",
 | 
			
		||||
    "lit/attribute-value-entities": "off",
 | 
			
		||||
    "lit/no-template-map": "off",
 | 
			
		||||
    "lit/no-native-attributes": "warn",
 | 
			
		||||
@@ -126,5 +125,6 @@
 | 
			
		||||
    "lit-a11y/anchor-is-valid": "warn",
 | 
			
		||||
    "lit-a11y/role-has-required-aria-attrs": "warn"
 | 
			
		||||
  },
 | 
			
		||||
  "plugins": ["unused-imports"]
 | 
			
		||||
  "plugins": ["disable", "unused-imports"],
 | 
			
		||||
  "processor": "disable/disable"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -21,7 +21,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        with:
 | 
			
		||||
          ref: dev
 | 
			
		||||
 | 
			
		||||
@@ -57,7 +57,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        with:
 | 
			
		||||
          ref: master
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -24,7 +24,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
        with:
 | 
			
		||||
@@ -37,7 +37,7 @@ jobs:
 | 
			
		||||
      - name: Build resources
 | 
			
		||||
        run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
 | 
			
		||||
      - name: Setup lint cache
 | 
			
		||||
        uses: actions/cache@v4.0.2
 | 
			
		||||
        uses: actions/cache@v4.0.0
 | 
			
		||||
        with:
 | 
			
		||||
          path: |
 | 
			
		||||
            node_modules/.cache/prettier
 | 
			
		||||
@@ -58,7 +58,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
        with:
 | 
			
		||||
@@ -76,7 +76,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
        with:
 | 
			
		||||
@@ -89,7 +89,7 @@ jobs:
 | 
			
		||||
        env:
 | 
			
		||||
          IS_TEST: "true"
 | 
			
		||||
      - name: Upload bundle stats
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.3
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          name: frontend-bundle-stats
 | 
			
		||||
          path: build/stats/*.json
 | 
			
		||||
@@ -100,7 +100,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
        with:
 | 
			
		||||
@@ -113,7 +113,7 @@ jobs:
 | 
			
		||||
        env:
 | 
			
		||||
          IS_TEST: "true"
 | 
			
		||||
      - name: Upload bundle stats
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.3
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          name: supervisor-bundle-stats
 | 
			
		||||
          path: build/stats/*.json
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -23,7 +23,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout repository
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        with:
 | 
			
		||||
          # We must fetch at least the immediate parents so that if this is
 | 
			
		||||
          # a pull request then we can checkout the head.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -22,7 +22,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        with:
 | 
			
		||||
          ref: dev
 | 
			
		||||
 | 
			
		||||
@@ -58,7 +58,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
        with:
 | 
			
		||||
          ref: master
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -16,7 +16,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -21,7 +21,7 @@ jobs:
 | 
			
		||||
    if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.0.2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -6,7 +6,7 @@ on:
 | 
			
		||||
    - cron: "0 1 * * *"
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  PYTHON_VERSION: "3.12"
 | 
			
		||||
  PYTHON_VERSION: "3.11"
 | 
			
		||||
  NODE_OPTIONS: --max_old_space_size=6144
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
@@ -20,7 +20,7 @@ jobs:
 | 
			
		||||
      contents: write
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
 | 
			
		||||
      - name: Set up Python ${{ env.PYTHON_VERSION }}
 | 
			
		||||
        uses: actions/setup-python@v5
 | 
			
		||||
@@ -57,14 +57,14 @@ jobs:
 | 
			
		||||
        run: tar -czvf translations.tar.gz translations
 | 
			
		||||
 | 
			
		||||
      - name: Upload build artifacts
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.3
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          name: wheels
 | 
			
		||||
          path: dist/home_assistant_frontend*.whl
 | 
			
		||||
          if-no-files-found: error
 | 
			
		||||
 | 
			
		||||
      - name: Upload translations
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.3
 | 
			
		||||
        uses: actions/upload-artifact@v4.3.1
 | 
			
		||||
        with:
 | 
			
		||||
          name: translations
 | 
			
		||||
          path: translations.tar.gz
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -17,7 +17,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Send bundle stats and build information to RelativeCI
 | 
			
		||||
        uses: relative-ci/agent-action@v2.1.11
 | 
			
		||||
        uses: relative-ci/agent-action@v2.1.10
 | 
			
		||||
        with:
 | 
			
		||||
          key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
 | 
			
		||||
          token: ${{ github.token }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -6,7 +6,7 @@ on:
 | 
			
		||||
      - published
 | 
			
		||||
 | 
			
		||||
env:
 | 
			
		||||
  PYTHON_VERSION: "3.12"
 | 
			
		||||
  PYTHON_VERSION: "3.11"
 | 
			
		||||
  NODE_OPTIONS: --max_old_space_size=6144
 | 
			
		||||
 | 
			
		||||
# Set default workflow permissions
 | 
			
		||||
@@ -23,7 +23,7 @@ jobs:
 | 
			
		||||
      contents: write # Required to upload release assets
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
 | 
			
		||||
      - name: Verify version
 | 
			
		||||
        uses: home-assistant/actions/helpers/verify-version@master
 | 
			
		||||
@@ -55,7 +55,7 @@ jobs:
 | 
			
		||||
          script/release
 | 
			
		||||
 | 
			
		||||
      - name: Upload release assets
 | 
			
		||||
        uses: softprops/action-gh-release@v2.0.6
 | 
			
		||||
        uses: softprops/action-gh-release@v0.1.15
 | 
			
		||||
        with:
 | 
			
		||||
          files: |
 | 
			
		||||
            dist/*.whl
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -13,7 +13,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v4.1.7
 | 
			
		||||
        uses: actions/checkout@v4.1.1
 | 
			
		||||
 | 
			
		||||
      - name: Upload Translations
 | 
			
		||||
        run: |
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
diff --git a/simple-tooltip.js b/simple-tooltip.js
 | 
			
		||||
index 78a87f6a223925f0e29fbedb268c85a142ec6985..3d686dd6a3d5a93342b4b01408089fc316b408ca 100644
 | 
			
		||||
--- a/simple-tooltip.js
 | 
			
		||||
+++ b/simple-tooltip.js
 | 
			
		||||
@@ -195,6 +195,8 @@ class SimpleTooltip extends LitElement {
 | 
			
		||||
         .hidden {
 | 
			
		||||
           position: absolute;
 | 
			
		||||
           left: -10000px;
 | 
			
		||||
+          inset-inline-start: -10000px;
 | 
			
		||||
+          inset-inline-end: initial;
 | 
			
		||||
           top: auto;
 | 
			
		||||
           width: 1px;
 | 
			
		||||
           height: 1px;
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
diff --git a/dist/hls.light.mjs b/dist/hls.light.mjs
 | 
			
		||||
index eed9d788fafdb159975e1a2eb08ac88ba9c9ac33..ace881935e6665946f1c8110ebd2f739cde4427e 100644
 | 
			
		||||
--- a/dist/hls.light.mjs
 | 
			
		||||
+++ b/dist/hls.light.mjs
 | 
			
		||||
@@ -20523,9 +20523,9 @@ class Hls {
 | 
			
		||||
 }
 | 
			
		||||
 Hls.defaultConfig = void 0;
 | 
			
		||||
 
 | 
			
		||||
-var KeySystemFormats = empty.KeySystemFormats;
 | 
			
		||||
-var KeySystems = empty.KeySystems;
 | 
			
		||||
-var SubtitleStreamController = empty.SubtitleStreamController;
 | 
			
		||||
-var TimelineController = empty.TimelineController;
 | 
			
		||||
+var KeySystemFormats = empty;
 | 
			
		||||
+var KeySystems = empty;
 | 
			
		||||
+var SubtitleStreamController = empty;
 | 
			
		||||
+var TimelineController = empty;
 | 
			
		||||
 export { AbrController, AttrList, Cues as AudioStreamController, Cues as AudioTrackController, BasePlaylistController, BaseSegment, BaseStreamController, BufferController, Cues as CMCDController, CapLevelController, ChunkMetadata, ContentSteeringController, DateRange, Cues as EMEController, ErrorActionFlags, ErrorController, ErrorDetails, ErrorTypes, Events, FPSController, Fragment, Hls, HlsSkip, HlsUrlParameters, KeySystemFormats, KeySystems, Level, LevelDetails, LevelKey, LoadStats, MetadataSchema, NetworkErrorAction, Part, PlaylistLevelType, SubtitleStreamController, Cues as SubtitleTrackController, TimelineController, Hls as default, getMediaSource, isMSESupported, isSupported };
 | 
			
		||||
 //# sourceMappingURL=hls.light.mjs.map
 | 
			
		||||
							
								
								
									
										893
									
								
								.yarn/releases/yarn-4.1.0.cjs
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										894
									
								
								.yarn/releases/yarn-4.3.1.cjs
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -6,4 +6,4 @@ enableGlobalCache: false
 | 
			
		||||
 | 
			
		||||
nodeLinker: node-modules
 | 
			
		||||
 | 
			
		||||
yarnPath: .yarn/releases/yarn-4.3.1.cjs
 | 
			
		||||
yarnPath: .yarn/releases/yarn-4.1.0.cjs
 | 
			
		||||
 
 | 
			
		||||
@@ -1,56 +1,7 @@
 | 
			
		||||
import defineProvider from "@babel/helper-define-polyfill-provider";
 | 
			
		||||
import { join } from "node:path";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
 | 
			
		||||
const POLYFILL_DIR = join(paths.polymer_dir, "src/resources/polyfills");
 | 
			
		||||
 | 
			
		||||
// List of polyfill keys with supported browser targets for the functionality
 | 
			
		||||
const PolyfillSupport = {
 | 
			
		||||
  // Note states and shadowRoot properties should be supported.
 | 
			
		||||
  "element-internals": {
 | 
			
		||||
    android: 90,
 | 
			
		||||
    chrome: 90,
 | 
			
		||||
    edge: 90,
 | 
			
		||||
    firefox: 126,
 | 
			
		||||
    ios: 17.4,
 | 
			
		||||
    opera: 76,
 | 
			
		||||
    opera_mobile: 64,
 | 
			
		||||
    safari: 17.4,
 | 
			
		||||
    samsung: 15.0,
 | 
			
		||||
  },
 | 
			
		||||
  "element-append": {
 | 
			
		||||
    android: 54,
 | 
			
		||||
    chrome: 54,
 | 
			
		||||
    edge: 17,
 | 
			
		||||
    firefox: 49,
 | 
			
		||||
    ios: 10.0,
 | 
			
		||||
    opera: 41,
 | 
			
		||||
    opera_mobile: 41,
 | 
			
		||||
    safari: 10.0,
 | 
			
		||||
    samsung: 6.0,
 | 
			
		||||
  },
 | 
			
		||||
  "element-getattributenames": {
 | 
			
		||||
    android: 61,
 | 
			
		||||
    chrome: 61,
 | 
			
		||||
    edge: 18,
 | 
			
		||||
    firefox: 45,
 | 
			
		||||
    ios: 10.3,
 | 
			
		||||
    opera: 48,
 | 
			
		||||
    opera_mobile: 45,
 | 
			
		||||
    safari: 10.1,
 | 
			
		||||
    samsung: 8.0,
 | 
			
		||||
  },
 | 
			
		||||
  "element-toggleattribute": {
 | 
			
		||||
    android: 69,
 | 
			
		||||
    chrome: 69,
 | 
			
		||||
    edge: 18,
 | 
			
		||||
    firefox: 63,
 | 
			
		||||
    ios: 12.0,
 | 
			
		||||
    opera: 56,
 | 
			
		||||
    opera_mobile: 48,
 | 
			
		||||
    safari: 12.0,
 | 
			
		||||
    samsung: 10.0,
 | 
			
		||||
  },
 | 
			
		||||
  fetch: {
 | 
			
		||||
    android: 42,
 | 
			
		||||
    chrome: 42,
 | 
			
		||||
@@ -62,31 +13,6 @@ const PolyfillSupport = {
 | 
			
		||||
    safari: 10.1,
 | 
			
		||||
    samsung: 4.0,
 | 
			
		||||
  },
 | 
			
		||||
  "intl-getcanonicallocales": {
 | 
			
		||||
    android: 54,
 | 
			
		||||
    chrome: 54,
 | 
			
		||||
    edge: 16,
 | 
			
		||||
    firefox: 48,
 | 
			
		||||
    ios: 10.3,
 | 
			
		||||
    opera: 41,
 | 
			
		||||
    opera_mobile: 41,
 | 
			
		||||
    safari: 10.1,
 | 
			
		||||
    samsung: 6.0,
 | 
			
		||||
  },
 | 
			
		||||
  "intl-locale": {
 | 
			
		||||
    android: 74,
 | 
			
		||||
    chrome: 74,
 | 
			
		||||
    edge: 79,
 | 
			
		||||
    firefox: 75,
 | 
			
		||||
    ios: 14.0,
 | 
			
		||||
    opera: 62,
 | 
			
		||||
    opera_mobile: 53,
 | 
			
		||||
    safari: 14.0,
 | 
			
		||||
    samsung: 11.0,
 | 
			
		||||
  },
 | 
			
		||||
  "intl-other": {
 | 
			
		||||
    // Not specified (i.e. always try polyfill) since compatibility depends on supported locales
 | 
			
		||||
  },
 | 
			
		||||
  proxy: {
 | 
			
		||||
    android: 49,
 | 
			
		||||
    chrome: 49,
 | 
			
		||||
@@ -98,67 +24,17 @@ const PolyfillSupport = {
 | 
			
		||||
    safari: 10.0,
 | 
			
		||||
    samsung: 5.0,
 | 
			
		||||
  },
 | 
			
		||||
  "resize-observer": {
 | 
			
		||||
    android: 64,
 | 
			
		||||
    chrome: 64,
 | 
			
		||||
    edge: 79,
 | 
			
		||||
    firefox: 69,
 | 
			
		||||
    ios: 13.4,
 | 
			
		||||
    opera: 51,
 | 
			
		||||
    opera_mobile: 47,
 | 
			
		||||
    safari: 13.1,
 | 
			
		||||
    samsung: 9.0,
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Map of global variables and/or instance and static properties to the
 | 
			
		||||
// corresponding polyfill key and actual module to import
 | 
			
		||||
const polyfillMap = {
 | 
			
		||||
  global: {
 | 
			
		||||
    fetch: { key: "fetch", module: "unfetch/polyfill" },
 | 
			
		||||
    Proxy: { key: "proxy", module: "proxy-polyfill" },
 | 
			
		||||
    ResizeObserver: {
 | 
			
		||||
      key: "resize-observer",
 | 
			
		||||
      module: join(POLYFILL_DIR, "resize-observer.ts"),
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  instance: {
 | 
			
		||||
    attachInternals: {
 | 
			
		||||
      key: "element-internals",
 | 
			
		||||
      module: "element-internals-polyfill",
 | 
			
		||||
    },
 | 
			
		||||
    ...Object.fromEntries(
 | 
			
		||||
      ["append", "getAttributeNames", "toggleAttribute"].map((prop) => {
 | 
			
		||||
        const key = `element-${prop.toLowerCase()}`;
 | 
			
		||||
        return [prop, { key, module: join(POLYFILL_DIR, `${key}.ts`) }];
 | 
			
		||||
      })
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  static: {
 | 
			
		||||
    Intl: {
 | 
			
		||||
      getCanonicalLocales: {
 | 
			
		||||
        key: "intl-getcanonicallocales",
 | 
			
		||||
        module: join(POLYFILL_DIR, "intl-polyfill.ts"),
 | 
			
		||||
      },
 | 
			
		||||
      Locale: {
 | 
			
		||||
        key: "intl-locale",
 | 
			
		||||
        module: join(POLYFILL_DIR, "intl-polyfill.ts"),
 | 
			
		||||
      },
 | 
			
		||||
      ...Object.fromEntries(
 | 
			
		||||
        [
 | 
			
		||||
          "DateTimeFormat",
 | 
			
		||||
          "DisplayNames",
 | 
			
		||||
          "ListFormat",
 | 
			
		||||
          "NumberFormat",
 | 
			
		||||
          "PluralRules",
 | 
			
		||||
          "RelativeTimeFormat",
 | 
			
		||||
        ].map((obj) => [
 | 
			
		||||
          obj,
 | 
			
		||||
          { key: "intl-other", module: join(POLYFILL_DIR, "intl-polyfill.ts") },
 | 
			
		||||
        ])
 | 
			
		||||
      ),
 | 
			
		||||
    },
 | 
			
		||||
    fetch: { key: "fetch", module: "unfetch/polyfill" },
 | 
			
		||||
  },
 | 
			
		||||
  instance: {},
 | 
			
		||||
  static: {},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Create plugin using the same factory as for CoreJS
 | 
			
		||||
@@ -166,16 +42,14 @@ export default defineProvider(
 | 
			
		||||
  ({ createMetaResolver, debug, shouldInjectPolyfill }) => {
 | 
			
		||||
    const resolvePolyfill = createMetaResolver(polyfillMap);
 | 
			
		||||
    return {
 | 
			
		||||
      name: "custom-polyfill",
 | 
			
		||||
      name: "HA Custom",
 | 
			
		||||
      polyfills: PolyfillSupport,
 | 
			
		||||
      usageGlobal(meta, utils) {
 | 
			
		||||
        const polyfill = resolvePolyfill(meta);
 | 
			
		||||
        if (polyfill && shouldInjectPolyfill(polyfill.desc.key)) {
 | 
			
		||||
          debug(polyfill.desc.key);
 | 
			
		||||
          utils.injectGlobalImport(polyfill.desc.module);
 | 
			
		||||
          return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,6 @@ const env = require("./env.cjs");
 | 
			
		||||
const paths = require("./paths.cjs");
 | 
			
		||||
const { dependencies } = require("../package.json");
 | 
			
		||||
 | 
			
		||||
const BABEL_PLUGINS = path.join(__dirname, "babel-plugins");
 | 
			
		||||
 | 
			
		||||
// GitHub base URL to use for production source maps
 | 
			
		||||
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
 | 
			
		||||
module.exports.sourceMapURL = () => {
 | 
			
		||||
@@ -92,8 +90,8 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
 | 
			
		||||
    [
 | 
			
		||||
      "@babel/preset-env",
 | 
			
		||||
      {
 | 
			
		||||
        useBuiltIns: "usage",
 | 
			
		||||
        corejs: dependencies["core-js"],
 | 
			
		||||
        useBuiltIns: latestBuild ? false : "usage",
 | 
			
		||||
        corejs: latestBuild ? false : dependencies["core-js"],
 | 
			
		||||
        bugfixes: true,
 | 
			
		||||
        shippedProposals: true,
 | 
			
		||||
      },
 | 
			
		||||
@@ -102,12 +100,22 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
 | 
			
		||||
  ],
 | 
			
		||||
  plugins: [
 | 
			
		||||
    [
 | 
			
		||||
      path.join(BABEL_PLUGINS, "inline-constants-plugin.cjs"),
 | 
			
		||||
      path.resolve(
 | 
			
		||||
        paths.polymer_dir,
 | 
			
		||||
        "build-scripts/babel-plugins/inline-constants-plugin.cjs"
 | 
			
		||||
      ),
 | 
			
		||||
      {
 | 
			
		||||
        modules: ["@mdi/js"],
 | 
			
		||||
        ignoreModuleNotFound: true,
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    [
 | 
			
		||||
      path.resolve(
 | 
			
		||||
        paths.polymer_dir,
 | 
			
		||||
        "build-scripts/babel-plugins/custom-polyfill-plugin.js"
 | 
			
		||||
      ),
 | 
			
		||||
      { method: "usage-global" },
 | 
			
		||||
    ],
 | 
			
		||||
    // Minify template literals for production
 | 
			
		||||
    isProdBuild && [
 | 
			
		||||
      "template-html-minifier",
 | 
			
		||||
@@ -145,27 +153,6 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
 | 
			
		||||
  ],
 | 
			
		||||
  sourceMaps: !isTestBuild,
 | 
			
		||||
  overrides: [
 | 
			
		||||
    {
 | 
			
		||||
      // Add plugin to inject various polyfills, excluding the polyfills
 | 
			
		||||
      // themselves to prevent self-injection.
 | 
			
		||||
      plugins: [
 | 
			
		||||
        [
 | 
			
		||||
          path.join(BABEL_PLUGINS, "custom-polyfill-plugin.js"),
 | 
			
		||||
          { method: "usage-global" },
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      exclude: [
 | 
			
		||||
        path.join(paths.polymer_dir, "src/resources/polyfills"),
 | 
			
		||||
        ...[
 | 
			
		||||
          "@formatjs/(?:ecma402-abstract|intl-\\w+)",
 | 
			
		||||
          "@lit-labs/virtualizer/polyfills",
 | 
			
		||||
          "@webcomponents/scoped-custom-element-registry",
 | 
			
		||||
          "element-internals-polyfill",
 | 
			
		||||
          "proxy-polyfill",
 | 
			
		||||
          "unfetch",
 | 
			
		||||
        ].map((p) => new RegExp(`/node_modules/${p}/`)),
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      // Use unambiguous for dependencies so that require() is correctly injected into CommonJS files
 | 
			
		||||
      // Exclusions are needed in some cases where ES modules have no static imports or exports, such as polyfills
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,26 @@
 | 
			
		||||
// Tasks to compress
 | 
			
		||||
 | 
			
		||||
import { deleteAsync } from "del";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import gulpIf from "gulp-if";
 | 
			
		||||
import vinylPaths from "vinyl-paths";
 | 
			
		||||
import zopfli from "gulp-zopfli-green";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
 | 
			
		||||
const zopfliOptions = { threshold: 150 };
 | 
			
		||||
 | 
			
		||||
const compressedExt = /\.gz$/;
 | 
			
		||||
const deleteUncompressed = (p) => deleteAsync(p.replace(compressedExt, ""));
 | 
			
		||||
 | 
			
		||||
const compressDist = (rootDir) =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([
 | 
			
		||||
      `${rootDir}/**/*.{js,json,css,svg,xml}`,
 | 
			
		||||
      `${rootDir}/**/*.{js?(.map),json,css,svg,xml}`,
 | 
			
		||||
      `${rootDir}/{authorize,onboarding}.html`,
 | 
			
		||||
    ])
 | 
			
		||||
    .pipe(zopfli(zopfliOptions))
 | 
			
		||||
    .pipe(gulp.dest(rootDir));
 | 
			
		||||
    .pipe(gulp.dest(rootDir))
 | 
			
		||||
    .pipe(gulpIf(compressedExt, vinylPaths(deleteUncompressed)));
 | 
			
		||||
 | 
			
		||||
gulp.task("compress-app", () => compressDist(paths.app_output_root));
 | 
			
		||||
gulp.task("compress-hassio", () => compressDist(paths.hassio_output_root));
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import gulp from "gulp";
 | 
			
		||||
import jszip from "jszip";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import process from "process";
 | 
			
		||||
import { extract } from "tar";
 | 
			
		||||
import tar from "tar";
 | 
			
		||||
 | 
			
		||||
const MAX_AGE = 24; // hours
 | 
			
		||||
const OWNER = "home-assistant";
 | 
			
		||||
@@ -156,7 +156,7 @@ gulp.task("fetch-nightly-translations", async function () {
 | 
			
		||||
  console.log("Unpacking downloaded translations...");
 | 
			
		||||
  const zip = await jszip.loadAsync(downloadResponse.data);
 | 
			
		||||
  await deleteCurrent;
 | 
			
		||||
  const extractStream = zip.file(/.*/)[0].nodeStream().pipe(extract());
 | 
			
		||||
  const extractStream = zip.file(/.*/)[0].nodeStream().pipe(tar.extract());
 | 
			
		||||
  await new Promise((resolve, reject) => {
 | 
			
		||||
    extractStream.on("close", resolve).on("error", reject);
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -1,112 +1,92 @@
 | 
			
		||||
/* eslint-disable max-classes-per-file */
 | 
			
		||||
 | 
			
		||||
import { deleteAsync } from "del";
 | 
			
		||||
import { glob } from "glob";
 | 
			
		||||
import { createHash } from "crypto";
 | 
			
		||||
import { deleteSync } from "del";
 | 
			
		||||
import { mkdirSync, readdirSync, readFileSync, renameSync } from "fs";
 | 
			
		||||
import { writeFile } from "node:fs/promises";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import flatmap from "gulp-flatmap";
 | 
			
		||||
import transform from "gulp-json-transform";
 | 
			
		||||
import merge from "gulp-merge-json";
 | 
			
		||||
import rename from "gulp-rename";
 | 
			
		||||
import merge from "lodash.merge";
 | 
			
		||||
import { createHash } from "node:crypto";
 | 
			
		||||
import { mkdir, readFile } from "node:fs/promises";
 | 
			
		||||
import { basename, join } from "node:path";
 | 
			
		||||
import { PassThrough, Transform } from "node:stream";
 | 
			
		||||
import { finished } from "node:stream/promises";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import vinylBuffer from "vinyl-buffer";
 | 
			
		||||
import source from "vinyl-source-stream";
 | 
			
		||||
import env from "../env.cjs";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import { mapFiles } from "../util.cjs";
 | 
			
		||||
import "./fetch-nightly-translations.js";
 | 
			
		||||
 | 
			
		||||
const inFrontendDir = "translations/frontend";
 | 
			
		||||
const inBackendDir = "translations/backend";
 | 
			
		||||
const workDir = "build/translations";
 | 
			
		||||
const outDir = join(workDir, "output");
 | 
			
		||||
const EN_SRC = join(paths.translations_src, "en.json");
 | 
			
		||||
const TEST_LOCALE = "en-x-test";
 | 
			
		||||
 | 
			
		||||
const fullDir = workDir + "/full";
 | 
			
		||||
const coreDir = workDir + "/core";
 | 
			
		||||
const outDir = workDir + "/output";
 | 
			
		||||
let mergeBackend = false;
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "translations-enable-merge-backend",
 | 
			
		||||
  gulp.parallel(async () => {
 | 
			
		||||
  gulp.parallel((done) => {
 | 
			
		||||
    mergeBackend = true;
 | 
			
		||||
    done();
 | 
			
		||||
  }, "allow-setup-fetch-nightly-translations")
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// Transform stream to apply a function on Vinyl JSON files (buffer mode only).
 | 
			
		||||
// The provided function can either return a new object, or an array of
 | 
			
		||||
// [object, subdirectory] pairs for fragmentizing the JSON.
 | 
			
		||||
class CustomJSON extends Transform {
 | 
			
		||||
  constructor(func, reviver = null) {
 | 
			
		||||
    super({ objectMode: true });
 | 
			
		||||
    this._func = func;
 | 
			
		||||
    this._reviver = reviver;
 | 
			
		||||
  }
 | 
			
		||||
// Panel translations which should be split from the core translations.
 | 
			
		||||
const TRANSLATION_FRAGMENTS = Object.keys(
 | 
			
		||||
  JSON.parse(
 | 
			
		||||
    readFileSync(
 | 
			
		||||
      path.resolve(paths.polymer_dir, "src/translations/en.json"),
 | 
			
		||||
      "utf-8"
 | 
			
		||||
    )
 | 
			
		||||
  ).ui.panel
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
  async _transform(file, _, callback) {
 | 
			
		||||
    try {
 | 
			
		||||
      let obj = JSON.parse(file.contents.toString(), this._reviver);
 | 
			
		||||
      if (this._func) obj = this._func(obj, file.path);
 | 
			
		||||
      for (const [outObj, dir] of Array.isArray(obj) ? obj : [[obj, ""]]) {
 | 
			
		||||
        const outFile = file.clone({ contents: false });
 | 
			
		||||
        outFile.contents = Buffer.from(JSON.stringify(outObj));
 | 
			
		||||
        outFile.dirname += `/${dir}`;
 | 
			
		||||
        this.push(outFile);
 | 
			
		||||
      }
 | 
			
		||||
      callback(null);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      callback(err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Transform stream to merge Vinyl JSON files (buffer mode only).
 | 
			
		||||
class MergeJSON extends Transform {
 | 
			
		||||
  _objects = [];
 | 
			
		||||
 | 
			
		||||
  constructor(stem, startObj = {}, reviver = null) {
 | 
			
		||||
    super({ objectMode: true, allowHalfOpen: false });
 | 
			
		||||
    this._stem = stem;
 | 
			
		||||
    this._startObj = structuredClone(startObj);
 | 
			
		||||
    this._reviver = reviver;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _transform(file, _, callback) {
 | 
			
		||||
    try {
 | 
			
		||||
      this._objects.push(JSON.parse(file.contents.toString(), this._reviver));
 | 
			
		||||
      if (!this._outFile) this._outFile = file.clone({ contents: false });
 | 
			
		||||
      callback(null);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      callback(err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _flush(callback) {
 | 
			
		||||
    try {
 | 
			
		||||
      const mergedObj = merge(this._startObj, ...this._objects);
 | 
			
		||||
      this._outFile.contents = Buffer.from(JSON.stringify(mergedObj));
 | 
			
		||||
      this._outFile.stem = this._stem;
 | 
			
		||||
      callback(null, this._outFile);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      callback(err);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Utility to flatten object keys to single level using separator
 | 
			
		||||
const flatten = (data, prefix = "", sep = ".") => {
 | 
			
		||||
  const output = {};
 | 
			
		||||
  for (const [key, value] of Object.entries(data)) {
 | 
			
		||||
    if (typeof value === "object") {
 | 
			
		||||
      Object.assign(output, flatten(value, prefix + key + sep, sep));
 | 
			
		||||
function recursiveFlatten(prefix, data) {
 | 
			
		||||
  let output = {};
 | 
			
		||||
  Object.keys(data).forEach((key) => {
 | 
			
		||||
    if (typeof data[key] === "object") {
 | 
			
		||||
      output = {
 | 
			
		||||
        ...output,
 | 
			
		||||
        ...recursiveFlatten(prefix + key + ".", data[key]),
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      output[prefix + key] = value;
 | 
			
		||||
    }
 | 
			
		||||
      output[prefix + key] = data[key];
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  return output;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Filter functions that can be passed directly to JSON.parse()
 | 
			
		||||
const emptyReviver = (_key, value) => value || undefined;
 | 
			
		||||
const testReviver = (_key, value) =>
 | 
			
		||||
  value && typeof value === "string" ? "TRANSLATED" : value;
 | 
			
		||||
function flatten(data) {
 | 
			
		||||
  return recursiveFlatten("", data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function emptyFilter(data) {
 | 
			
		||||
  const newData = {};
 | 
			
		||||
  Object.keys(data).forEach((key) => {
 | 
			
		||||
    if (data[key]) {
 | 
			
		||||
      if (typeof data[key] === "object") {
 | 
			
		||||
        newData[key] = emptyFilter(data[key]);
 | 
			
		||||
      } else {
 | 
			
		||||
        newData[key] = data[key];
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  return newData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function recursiveEmpty(data) {
 | 
			
		||||
  const newData = {};
 | 
			
		||||
  Object.keys(data).forEach((key) => {
 | 
			
		||||
    if (data[key]) {
 | 
			
		||||
      if (typeof data[key] === "object") {
 | 
			
		||||
        newData[key] = recursiveEmpty(data[key]);
 | 
			
		||||
      } else {
 | 
			
		||||
        newData[key] = "TRANSLATED";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  return newData;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Replace Lokalise key placeholders with their actual values.
 | 
			
		||||
@@ -115,44 +95,60 @@ const testReviver = (_key, value) =>
 | 
			
		||||
 * be included in src/translations/en.json, but still be usable while
 | 
			
		||||
 * developing locally.
 | 
			
		||||
 *
 | 
			
		||||
 * @link https://docs.lokalise.com/en/articles/1400528-key-referencing
 | 
			
		||||
 * @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
 | 
			
		||||
 */
 | 
			
		||||
const KEY_REFERENCE = /\[%key:([^%]+)%\]/;
 | 
			
		||||
const lokaliseTransform = (data, path, original = data) => {
 | 
			
		||||
const re_key_reference = /\[%key:([^%]+)%\]/;
 | 
			
		||||
function lokaliseTransform(data, original, file) {
 | 
			
		||||
  const output = {};
 | 
			
		||||
  for (const [key, value] of Object.entries(data)) {
 | 
			
		||||
    if (typeof value === "object") {
 | 
			
		||||
      output[key] = lokaliseTransform(value, path, original);
 | 
			
		||||
  Object.entries(data).forEach(([key, value]) => {
 | 
			
		||||
    if (value instanceof Object) {
 | 
			
		||||
      output[key] = lokaliseTransform(value, original, file);
 | 
			
		||||
    } else {
 | 
			
		||||
      output[key] = value.replace(KEY_REFERENCE, (_match, lokalise_key) => {
 | 
			
		||||
      output[key] = value.replace(re_key_reference, (_match, lokalise_key) => {
 | 
			
		||||
        const replace = lokalise_key.split("::").reduce((tr, k) => {
 | 
			
		||||
          if (!tr) {
 | 
			
		||||
            throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
 | 
			
		||||
            throw Error(
 | 
			
		||||
              `Invalid key placeholder ${lokalise_key} in ${file.path}`
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          return tr[k];
 | 
			
		||||
        }, original);
 | 
			
		||||
        if (typeof replace !== "string") {
 | 
			
		||||
          throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
 | 
			
		||||
          throw Error(
 | 
			
		||||
            `Invalid key placeholder ${lokalise_key} in ${file.path}`
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        return replace;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  });
 | 
			
		||||
  return output;
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
gulp.task("clean-translations", () => deleteAsync([workDir]));
 | 
			
		||||
gulp.task("clean-translations", async () => deleteSync([workDir]));
 | 
			
		||||
 | 
			
		||||
const makeWorkDir = () => mkdir(workDir, { recursive: true });
 | 
			
		||||
gulp.task("ensure-translations-build-dir", async () => {
 | 
			
		||||
  mkdirSync(workDir, { recursive: true });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const createTestTranslation = () =>
 | 
			
		||||
gulp.task("create-test-metadata", () =>
 | 
			
		||||
  env.isProdBuild()
 | 
			
		||||
    ? Promise.resolve()
 | 
			
		||||
    : writeFile(
 | 
			
		||||
        workDir + "/testMetadata.json",
 | 
			
		||||
        JSON.stringify({ test: { nativeName: "Test" } })
 | 
			
		||||
      )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task("create-test-translation", () =>
 | 
			
		||||
  env.isProdBuild()
 | 
			
		||||
    ? Promise.resolve()
 | 
			
		||||
    : gulp
 | 
			
		||||
        .src(EN_SRC)
 | 
			
		||||
        .pipe(new CustomJSON(null, testReviver))
 | 
			
		||||
        .pipe(rename(`${TEST_LOCALE}.json`))
 | 
			
		||||
        .pipe(gulp.dest(workDir));
 | 
			
		||||
        .src(path.join(paths.translations_src, "en.json"))
 | 
			
		||||
        .pipe(transform((data, _file) => recursiveEmpty(data)))
 | 
			
		||||
        .pipe(rename("test.json"))
 | 
			
		||||
        .pipe(gulp.dest(workDir))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This task will build a master translation file, to be used as the base for
 | 
			
		||||
@@ -163,164 +159,279 @@ const createTestTranslation = () =>
 | 
			
		||||
 * project is buildable immediately after merging new translation keys, since
 | 
			
		||||
 * the Lokalise update to translations/en.json will not happen immediately.
 | 
			
		||||
 */
 | 
			
		||||
const createMasterTranslation = () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
 | 
			
		||||
    .pipe(new CustomJSON(lokaliseTransform))
 | 
			
		||||
    .pipe(new MergeJSON("en"))
 | 
			
		||||
    .pipe(gulp.dest(workDir));
 | 
			
		||||
gulp.task("build-master-translation", () => {
 | 
			
		||||
  const src = [path.join(paths.translations_src, "en.json")];
 | 
			
		||||
 | 
			
		||||
const FRAGMENTS = ["base"];
 | 
			
		||||
 | 
			
		||||
const toggleSupervisorFragment = async () => {
 | 
			
		||||
  FRAGMENTS[0] = "supervisor";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const panelFragment = (fragment) =>
 | 
			
		||||
  fragment !== "base" && fragment !== "supervisor";
 | 
			
		||||
 | 
			
		||||
const HASHES = new Map();
 | 
			
		||||
 | 
			
		||||
const createTranslations = async () => {
 | 
			
		||||
  // Parse and store the master to avoid repeating this for each locale, then
 | 
			
		||||
  // add the panel fragments when processing the app.
 | 
			
		||||
  const enMaster = JSON.parse(await readFile(`${workDir}/en.json`, "utf-8"));
 | 
			
		||||
  if (FRAGMENTS[0] === "base") {
 | 
			
		||||
    FRAGMENTS.push(...Object.keys(enMaster.ui.panel));
 | 
			
		||||
  if (mergeBackend) {
 | 
			
		||||
    src.push(path.join(inBackendDir, "en.json"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // The downstream pipeline is setup first.  It hashes the merged data for
 | 
			
		||||
  // each locale, then fragmentizes and flattens the data for final output.
 | 
			
		||||
  const translationFiles = await glob([
 | 
			
		||||
    `${inFrontendDir}/!(en).json`,
 | 
			
		||||
    ...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
 | 
			
		||||
  ]);
 | 
			
		||||
  const hashStream = new Transform({
 | 
			
		||||
    objectMode: true,
 | 
			
		||||
    transform: async (file, _, callback) => {
 | 
			
		||||
      const hash = env.isProdBuild()
 | 
			
		||||
        ? createHash("md5").update(file.contents).digest("hex")
 | 
			
		||||
        : "dev";
 | 
			
		||||
      HASHES.set(file.stem, hash);
 | 
			
		||||
      file.stem += `-${hash}`;
 | 
			
		||||
      callback(null, file);
 | 
			
		||||
    },
 | 
			
		||||
  }).setMaxListeners(translationFiles.length + 1);
 | 
			
		||||
  const fragmentsStream = hashStream
 | 
			
		||||
  return gulp
 | 
			
		||||
    .src(src)
 | 
			
		||||
    .pipe(transform((data, file) => lokaliseTransform(data, data, file)))
 | 
			
		||||
    .pipe(
 | 
			
		||||
      new CustomJSON((data) =>
 | 
			
		||||
        FRAGMENTS.map((fragment) => {
 | 
			
		||||
          switch (fragment) {
 | 
			
		||||
            case "base":
 | 
			
		||||
              // Remove the panels and supervisor to create the base translations
 | 
			
		||||
              return [
 | 
			
		||||
                flatten({
 | 
			
		||||
                  ...data,
 | 
			
		||||
                  ui: { ...data.ui, panel: undefined },
 | 
			
		||||
                  supervisor: undefined,
 | 
			
		||||
                }),
 | 
			
		||||
                "",
 | 
			
		||||
              ];
 | 
			
		||||
            case "supervisor":
 | 
			
		||||
              // Supervisor key is at the top level
 | 
			
		||||
              return [flatten(data.supervisor), ""];
 | 
			
		||||
            default:
 | 
			
		||||
              // Create a fragment with only the given panel
 | 
			
		||||
              return [
 | 
			
		||||
                flatten(data.ui.panel[fragment], `ui.panel.${fragment}.`),
 | 
			
		||||
                fragment,
 | 
			
		||||
              ];
 | 
			
		||||
          }
 | 
			
		||||
      merge({
 | 
			
		||||
        fileName: "en.json",
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(outDir));
 | 
			
		||||
    .pipe(gulp.dest(fullDir));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
  // Send the English master downstream first, then for each other locale
 | 
			
		||||
  // generate merged JSON data to continue piping. It begins with the master
 | 
			
		||||
gulp.task("build-merged-translations", () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([
 | 
			
		||||
      inFrontendDir + "/*.json",
 | 
			
		||||
      "!" + inFrontendDir + "/en.json",
 | 
			
		||||
      ...(env.isProdBuild() ? [] : [workDir + "/test.json"]),
 | 
			
		||||
    ])
 | 
			
		||||
    .pipe(transform((data, file) => lokaliseTransform(data, data, file)))
 | 
			
		||||
    .pipe(
 | 
			
		||||
      flatmap((stream, file) => {
 | 
			
		||||
        // For each language generate a merged json file. It begins with the master
 | 
			
		||||
        // translation as a failsafe for untranslated strings, and merges all parent
 | 
			
		||||
        // tags into one file for each specific subtag
 | 
			
		||||
        //
 | 
			
		||||
        // 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
 | 
			
		||||
        //       than a base translation + region.
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(`${workDir}/en.json`)
 | 
			
		||||
    .pipe(new PassThrough({ objectMode: true }))
 | 
			
		||||
    .pipe(hashStream, { end: false });
 | 
			
		||||
  const mergesFinished = [];
 | 
			
		||||
  for (const translationFile of translationFiles) {
 | 
			
		||||
    const locale = basename(translationFile, ".json");
 | 
			
		||||
    const subtags = locale.split("-");
 | 
			
		||||
    const mergeFiles = [];
 | 
			
		||||
        const tr = path.basename(file.history[0], ".json");
 | 
			
		||||
        const subtags = tr.split("-");
 | 
			
		||||
        const src = [fullDir + "/en.json"];
 | 
			
		||||
        for (let i = 1; i <= subtags.length; i++) {
 | 
			
		||||
          const lang = subtags.slice(0, i).join("-");
 | 
			
		||||
      if (lang === TEST_LOCALE) {
 | 
			
		||||
        mergeFiles.push(`${workDir}/${TEST_LOCALE}.json`);
 | 
			
		||||
          if (lang === "test") {
 | 
			
		||||
            src.push(workDir + "/test.json");
 | 
			
		||||
          } else if (lang !== "en") {
 | 
			
		||||
        mergeFiles.push(`${inFrontendDir}/${lang}.json`);
 | 
			
		||||
            src.push(inFrontendDir + "/" + lang + ".json");
 | 
			
		||||
            if (mergeBackend) {
 | 
			
		||||
          mergeFiles.push(`${inBackendDir}/${lang}.json`);
 | 
			
		||||
              src.push(inBackendDir + "/" + lang + ".json");
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
    const mergeStream = gulp
 | 
			
		||||
      .src(mergeFiles, { allowEmpty: true })
 | 
			
		||||
      .pipe(new MergeJSON(locale, enMaster, emptyReviver));
 | 
			
		||||
    mergesFinished.push(finished(mergeStream));
 | 
			
		||||
    mergeStream.pipe(hashStream, { end: false });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Wait for all merges to finish, then it's safe to end writing to the
 | 
			
		||||
  // downstream pipeline and wait for all fragments to finish writing.
 | 
			
		||||
  await Promise.all(mergesFinished);
 | 
			
		||||
  hashStream.end();
 | 
			
		||||
  await finished(fragmentsStream);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const writeTranslationMetaData = () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([`${paths.translations_src}/translationMetadata.json`])
 | 
			
		||||
        return gulp
 | 
			
		||||
          .src(src, { allowEmpty: true })
 | 
			
		||||
          .pipe(transform((data) => emptyFilter(data)))
 | 
			
		||||
          .pipe(
 | 
			
		||||
      new CustomJSON((meta) => {
 | 
			
		||||
        // Add the test translation in development.
 | 
			
		||||
        if (!env.isProdBuild()) {
 | 
			
		||||
          meta[TEST_LOCALE] = { nativeName: "Translation Test" };
 | 
			
		||||
        }
 | 
			
		||||
        // Filter out locales without a native name, and add the hashes.
 | 
			
		||||
        for (const locale of Object.keys(meta)) {
 | 
			
		||||
          if (!meta[locale].nativeName) {
 | 
			
		||||
            meta[locale] = undefined;
 | 
			
		||||
            console.warn(
 | 
			
		||||
              `Skipping locale ${locale} because native name is not translated.`
 | 
			
		||||
            );
 | 
			
		||||
          } else {
 | 
			
		||||
            meta[locale].hash = HASHES.get(locale);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return {
 | 
			
		||||
          fragments: FRAGMENTS.filter(panelFragment),
 | 
			
		||||
          translations: meta,
 | 
			
		||||
        };
 | 
			
		||||
            merge({
 | 
			
		||||
              fileName: tr + ".json",
 | 
			
		||||
            })
 | 
			
		||||
          )
 | 
			
		||||
    .pipe(gulp.dest(workDir));
 | 
			
		||||
          .pipe(gulp.dest(fullDir));
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
let taskName;
 | 
			
		||||
 | 
			
		||||
const splitTasks = [];
 | 
			
		||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
 | 
			
		||||
  taskName = "build-translation-fragment-" + fragment;
 | 
			
		||||
  gulp.task(taskName, () =>
 | 
			
		||||
    // Return only the translations for this fragment.
 | 
			
		||||
    gulp
 | 
			
		||||
      .src(fullDir + "/*.json")
 | 
			
		||||
      .pipe(
 | 
			
		||||
        transform((data) => ({
 | 
			
		||||
          ui: {
 | 
			
		||||
            panel: {
 | 
			
		||||
              [fragment]: data.ui.panel[fragment],
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        }))
 | 
			
		||||
      )
 | 
			
		||||
      .pipe(gulp.dest(workDir + "/" + fragment))
 | 
			
		||||
  );
 | 
			
		||||
  splitTasks.push(taskName);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
taskName = "build-translation-core";
 | 
			
		||||
gulp.task(taskName, () =>
 | 
			
		||||
  // Remove the fragment translations from the core translation.
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(fullDir + "/*.json")
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data, _file) => {
 | 
			
		||||
        TRANSLATION_FRAGMENTS.forEach((fragment) => {
 | 
			
		||||
          delete data.ui.panel[fragment];
 | 
			
		||||
        });
 | 
			
		||||
        delete data.supervisor;
 | 
			
		||||
        return data;
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(coreDir))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
splitTasks.push(taskName);
 | 
			
		||||
 | 
			
		||||
gulp.task("build-flattened-translations", () =>
 | 
			
		||||
  // Flatten the split versions of our translations, and move them into outDir
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(
 | 
			
		||||
      TRANSLATION_FRAGMENTS.map(
 | 
			
		||||
        (fragment) => workDir + "/" + fragment + "/*.json"
 | 
			
		||||
      ).concat(coreDir + "/*.json"),
 | 
			
		||||
      { base: workDir }
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data) =>
 | 
			
		||||
        // Polymer.AppLocalizeBehavior requires flattened json
 | 
			
		||||
        flatten(data)
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(
 | 
			
		||||
      rename((filePath) => {
 | 
			
		||||
        if (filePath.dirname === "core") {
 | 
			
		||||
          filePath.dirname = "";
 | 
			
		||||
        }
 | 
			
		||||
        // In dev we create the file with the fake hash in the filename
 | 
			
		||||
        if (!env.isProdBuild()) {
 | 
			
		||||
          filePath.basename += "-dev";
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(outDir))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const fingerprints = {};
 | 
			
		||||
 | 
			
		||||
gulp.task("build-translation-fingerprints", () => {
 | 
			
		||||
  // Fingerprint full file of each language
 | 
			
		||||
  const files = readdirSync(fullDir);
 | 
			
		||||
 | 
			
		||||
  for (let i = 0; i < files.length; i++) {
 | 
			
		||||
    fingerprints[files[i].split(".")[0]] = {
 | 
			
		||||
      // In dev we create fake hashes
 | 
			
		||||
      hash: env.isProdBuild()
 | 
			
		||||
        ? createHash("md5")
 | 
			
		||||
            .update(readFileSync(path.join(fullDir, files[i]), "utf-8"))
 | 
			
		||||
            .digest("hex")
 | 
			
		||||
        : "dev",
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // In dev we create the file with the fake hash in the filename
 | 
			
		||||
  if (env.isProdBuild()) {
 | 
			
		||||
    mapFiles(outDir, ".json", (filename) => {
 | 
			
		||||
      const parsed = path.parse(filename);
 | 
			
		||||
 | 
			
		||||
      // nl.json -> nl-<hash>.json
 | 
			
		||||
      if (!(parsed.name in fingerprints)) {
 | 
			
		||||
        throw new Error(`Unable to find hash for ${filename}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      renameSync(
 | 
			
		||||
        filename,
 | 
			
		||||
        `${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${
 | 
			
		||||
          parsed.ext
 | 
			
		||||
        }`
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const stream = source("translationFingerprints.json");
 | 
			
		||||
  stream.write(JSON.stringify(fingerprints));
 | 
			
		||||
  process.nextTick(() => stream.end());
 | 
			
		||||
  return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
gulp.task("build-translation-fragment-supervisor", () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(fullDir + "/*.json")
 | 
			
		||||
    .pipe(transform((data) => data.supervisor))
 | 
			
		||||
    .pipe(
 | 
			
		||||
      rename((filePath) => {
 | 
			
		||||
        // In dev we create the file with the fake hash in the filename
 | 
			
		||||
        if (!env.isProdBuild()) {
 | 
			
		||||
          filePath.basename += "-dev";
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(workDir + "/supervisor"))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task("build-translation-flatten-supervisor", () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(workDir + "/supervisor/*.json")
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data) =>
 | 
			
		||||
        // Polymer.AppLocalizeBehavior requires flattened json
 | 
			
		||||
        flatten(data)
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(outDir))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task("build-translation-write-metadata", () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([
 | 
			
		||||
      path.join(paths.translations_src, "translationMetadata.json"),
 | 
			
		||||
      ...(env.isProdBuild() ? [] : [workDir + "/testMetadata.json"]),
 | 
			
		||||
      workDir + "/translationFingerprints.json",
 | 
			
		||||
    ])
 | 
			
		||||
    .pipe(merge({}))
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data) => {
 | 
			
		||||
        const newData = {};
 | 
			
		||||
        Object.entries(data).forEach(([key, value]) => {
 | 
			
		||||
          // Filter out translations without native name.
 | 
			
		||||
          if (value.nativeName) {
 | 
			
		||||
            newData[key] = value;
 | 
			
		||||
          } else {
 | 
			
		||||
            console.warn(
 | 
			
		||||
              `Skipping language ${key}. Native name was not translated.`
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        return newData;
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data) => ({
 | 
			
		||||
        fragments: TRANSLATION_FRAGMENTS,
 | 
			
		||||
        translations: data,
 | 
			
		||||
      }))
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(rename("translationMetadata.json"))
 | 
			
		||||
    .pipe(gulp.dest(workDir))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "create-translations",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    gulp.parallel("create-test-metadata", "create-test-translation"),
 | 
			
		||||
    "build-master-translation",
 | 
			
		||||
    "build-merged-translations",
 | 
			
		||||
    gulp.parallel(...splitTasks),
 | 
			
		||||
    "build-flattened-translations"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-translations",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "fetch-nightly-translations",
 | 
			
		||||
      gulp.series("clean-translations", makeWorkDir)
 | 
			
		||||
      gulp.series("clean-translations", "ensure-translations-build-dir")
 | 
			
		||||
    ),
 | 
			
		||||
    createTestTranslation,
 | 
			
		||||
    createMasterTranslation,
 | 
			
		||||
    createTranslations,
 | 
			
		||||
    writeTranslationMetaData
 | 
			
		||||
    "create-translations",
 | 
			
		||||
    "build-translation-fingerprints",
 | 
			
		||||
    "build-translation-write-metadata"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-supervisor-translations",
 | 
			
		||||
  gulp.series(toggleSupervisorFragment, "build-translations")
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "fetch-nightly-translations",
 | 
			
		||||
      gulp.series("clean-translations", "ensure-translations-build-dir")
 | 
			
		||||
    ),
 | 
			
		||||
    gulp.parallel("create-test-metadata", "create-test-translation"),
 | 
			
		||||
    "build-master-translation",
 | 
			
		||||
    "build-merged-translations",
 | 
			
		||||
    "build-translation-fragment-supervisor",
 | 
			
		||||
    "build-translation-flatten-supervisor",
 | 
			
		||||
    "build-translation-fingerprints",
 | 
			
		||||
    "build-translation-write-metadata"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -99,7 +99,7 @@ gulp.task("webpack-watch-app", () => {
 | 
			
		||||
  ).watch({ poll: isWsl }, doneHandler());
 | 
			
		||||
  gulp.watch(
 | 
			
		||||
    path.join(paths.translations_src, "en.json"),
 | 
			
		||||
    gulp.series("build-translations", "copy-translations-app")
 | 
			
		||||
    gulp.series("create-translations", "copy-translations-app")
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								build-scripts/util.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,16 @@
 | 
			
		||||
const path = require("path");
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
 | 
			
		||||
// Helper function to map recursively over files in a folder and it's subfolders
 | 
			
		||||
module.exports.mapFiles = function mapFiles(startPath, filter, mapFunc) {
 | 
			
		||||
  const files = fs.readdirSync(startPath);
 | 
			
		||||
  for (let i = 0; i < files.length; i++) {
 | 
			
		||||
    const filename = path.join(startPath, files[i]);
 | 
			
		||||
    const stat = fs.lstatSync(filename);
 | 
			
		||||
    if (stat.isDirectory()) {
 | 
			
		||||
      mapFiles(filename, filter, mapFunc);
 | 
			
		||||
    } else if (filename.indexOf(filter) >= 0) {
 | 
			
		||||
      mapFunc(filename);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
@@ -10,7 +10,6 @@ const WebpackBar = require("webpackbar");
 | 
			
		||||
const {
 | 
			
		||||
  TransformAsyncModulesPlugin,
 | 
			
		||||
} = require("transform-async-modules-webpack-plugin");
 | 
			
		||||
const { dependencies } = require("../package.json");
 | 
			
		||||
const paths = require("./paths.cjs");
 | 
			
		||||
const bundle = require("./bundle.cjs");
 | 
			
		||||
 | 
			
		||||
@@ -157,15 +156,11 @@ const createWebpackConfig = ({
 | 
			
		||||
          transform: (stats) => JSON.stringify(filterStats(stats)),
 | 
			
		||||
        }),
 | 
			
		||||
      !latestBuild &&
 | 
			
		||||
        new TransformAsyncModulesPlugin({
 | 
			
		||||
          browserslistEnv: "legacy",
 | 
			
		||||
          runtime: { version: dependencies["@babel/runtime"] },
 | 
			
		||||
        }),
 | 
			
		||||
        new TransformAsyncModulesPlugin({ browserslistEnv: "legacy" }),
 | 
			
		||||
    ].filter(Boolean),
 | 
			
		||||
    resolve: {
 | 
			
		||||
      extensions: [".ts", ".js", ".json"],
 | 
			
		||||
      alias: {
 | 
			
		||||
        "lit/static-html$": "lit/static-html.js",
 | 
			
		||||
        "lit/decorators$": "lit/decorators.js",
 | 
			
		||||
        "lit/directive$": "lit/directive.js",
 | 
			
		||||
        "lit/directives/until$": "lit/directives/until.js",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
import { ActionDetail } from "@material/mwc-list/mwc-list";
 | 
			
		||||
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
 | 
			
		||||
import { mdiCast, mdiCastConnected } from "@mdi/js";
 | 
			
		||||
import "@polymer/paper-item/paper-icon-item";
 | 
			
		||||
import "@polymer/paper-listbox/paper-listbox";
 | 
			
		||||
import { Auth, Connection } from "home-assistant-js-websocket";
 | 
			
		||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
@@ -27,7 +28,6 @@ import { LovelaceViewConfig } from "../../../../src/data/lovelace/config/view";
 | 
			
		||||
import "../../../../src/layouts/hass-loading-screen";
 | 
			
		||||
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
 | 
			
		||||
import "./hc-layout";
 | 
			
		||||
import "../../../../src/components/ha-list-item";
 | 
			
		||||
 | 
			
		||||
@customElement("hc-cast")
 | 
			
		||||
class HcCast extends LitElement {
 | 
			
		||||
@@ -83,37 +83,34 @@ class HcCast extends LitElement {
 | 
			
		||||
              `
 | 
			
		||||
            : html`
 | 
			
		||||
                <div class="section-header">PICK A VIEW</div>
 | 
			
		||||
                <mwc-list @action=${this._handlePickView} activatable>
 | 
			
		||||
                <paper-listbox
 | 
			
		||||
                  attr-for-selected="data-path"
 | 
			
		||||
                  .selected=${this.castManager.status.lovelacePath || ""}
 | 
			
		||||
                >
 | 
			
		||||
                  ${(
 | 
			
		||||
                    this.lovelaceViews ?? [
 | 
			
		||||
                      generateDefaultViewConfig({}, {}, {}, {}, () => ""),
 | 
			
		||||
                    ]
 | 
			
		||||
                  ).map(
 | 
			
		||||
                    (view, idx) =>
 | 
			
		||||
                      html`<ha-list-item
 | 
			
		||||
                        graphic="avatar"
 | 
			
		||||
                        .activated=${this.castManager.status?.lovelacePath ===
 | 
			
		||||
                        (view.path ?? idx)}
 | 
			
		||||
                        .selected=${this.castManager.status?.lovelacePath ===
 | 
			
		||||
                        (view.path ?? idx)}
 | 
			
		||||
                    (view, idx) => html`
 | 
			
		||||
                      <paper-icon-item
 | 
			
		||||
                        @click=${this._handlePickView}
 | 
			
		||||
                        data-path=${view.path || idx}
 | 
			
		||||
                      >
 | 
			
		||||
                        ${view.title || view.path || "Unnamed view"}
 | 
			
		||||
                        ${view.icon
 | 
			
		||||
                          ? html`
 | 
			
		||||
                              <ha-icon
 | 
			
		||||
                                .icon=${view.icon}
 | 
			
		||||
                                slot="graphic"
 | 
			
		||||
                                slot="item-icon"
 | 
			
		||||
                              ></ha-icon>
 | 
			
		||||
                            `
 | 
			
		||||
                          : html`<ha-svg-icon
 | 
			
		||||
                              slot="item-icon"
 | 
			
		||||
                              .path=${mdiViewDashboard}
 | 
			
		||||
                            ></ha-svg-icon>`}</ha-list-item
 | 
			
		||||
                      > `
 | 
			
		||||
                  )}</mwc-list
 | 
			
		||||
                >
 | 
			
		||||
                          : ""}
 | 
			
		||||
                        ${view.title || view.path}
 | 
			
		||||
                      </paper-icon-item>
 | 
			
		||||
                    `
 | 
			
		||||
                  )}
 | 
			
		||||
                </paper-listbox>
 | 
			
		||||
              `}
 | 
			
		||||
 | 
			
		||||
        <div class="card-actions">
 | 
			
		||||
          ${this.castManager.status
 | 
			
		||||
            ? html`
 | 
			
		||||
@@ -185,8 +182,8 @@ class HcCast extends LitElement {
 | 
			
		||||
    this.castManager.requestSession();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _handlePickView(ev: CustomEvent<ActionDetail>) {
 | 
			
		||||
    const path = this.lovelaceViews![ev.detail.index].path ?? ev.detail.index;
 | 
			
		||||
  private async _handlePickView(ev: Event) {
 | 
			
		||||
    const path = (ev.currentTarget as any).getAttribute("data-path");
 | 
			
		||||
    await ensureConnectedCastSession(this.castManager!, this.auth!);
 | 
			
		||||
    castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
 | 
			
		||||
  }
 | 
			
		||||
@@ -249,14 +246,25 @@ class HcCast extends LitElement {
 | 
			
		||||
        height: 18px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      ha-list-item ha-icon,
 | 
			
		||||
      ha-list-item ha-svg-icon {
 | 
			
		||||
      paper-listbox {
 | 
			
		||||
        padding-top: 0;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      paper-listbox ha-icon {
 | 
			
		||||
        padding: 12px;
 | 
			
		||||
        color: var(--secondary-text-color);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      :host([hide-icons]) ha-icon {
 | 
			
		||||
        display: none;
 | 
			
		||||
      paper-icon-item {
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      paper-icon-item[disabled] {
 | 
			
		||||
        cursor: initial;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      :host([hide-icons]) paper-icon-item {
 | 
			
		||||
        --paper-item-icon-width: 0px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .spacer {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ class HcLaunchScreen extends LitElement {
 | 
			
		||||
      :host {
 | 
			
		||||
        display: block;
 | 
			
		||||
        height: 100vh;
 | 
			
		||||
        background-color: #f2f4f9;
 | 
			
		||||
        background-color: white;
 | 
			
		||||
        font-size: 24px;
 | 
			
		||||
      }
 | 
			
		||||
      .container {
 | 
			
		||||
@@ -43,9 +43,6 @@ class HcLaunchScreen extends LitElement {
 | 
			
		||||
        max-width: 80%;
 | 
			
		||||
        object-fit: cover;
 | 
			
		||||
      }
 | 
			
		||||
      .status {
 | 
			
		||||
        color: #1d2126;
 | 
			
		||||
      }
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
			
		||||
import { customElement, property, query } from "lit/decorators";
 | 
			
		||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
			
		||||
import { LovelaceConfig } from "../../../../src/data/lovelace/config/types";
 | 
			
		||||
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
 | 
			
		||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
 | 
			
		||||
import "../../../../src/panels/lovelace/views/hui-view";
 | 
			
		||||
import { HomeAssistant } from "../../../../src/types";
 | 
			
		||||
@@ -62,12 +61,7 @@ class HcLovelace extends LitElement {
 | 
			
		||||
      const index = this._viewIndex;
 | 
			
		||||
 | 
			
		||||
      if (index !== undefined) {
 | 
			
		||||
        const title = getPanelTitleFromUrlPath(
 | 
			
		||||
          this.hass,
 | 
			
		||||
          this.urlPath || "lovelace"
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const dashboardTitle = title || this.urlPath;
 | 
			
		||||
        const dashboardTitle = this.lovelaceConfig.title || this.urlPath;
 | 
			
		||||
 | 
			
		||||
        const viewTitle =
 | 
			
		||||
          this.lovelaceConfig.views[index].title ||
 | 
			
		||||
@@ -86,17 +80,10 @@ class HcLovelace extends LitElement {
 | 
			
		||||
          this.lovelaceConfig.views[index].background ||
 | 
			
		||||
          this.lovelaceConfig.background;
 | 
			
		||||
 | 
			
		||||
        const backgroundStyle =
 | 
			
		||||
          typeof configBackground === "string"
 | 
			
		||||
            ? configBackground
 | 
			
		||||
            : configBackground?.image
 | 
			
		||||
              ? `center / cover no-repeat url('${configBackground.image}')`
 | 
			
		||||
              : undefined;
 | 
			
		||||
 | 
			
		||||
        if (backgroundStyle) {
 | 
			
		||||
        if (configBackground) {
 | 
			
		||||
          this._huiView!.style.setProperty(
 | 
			
		||||
            "--lovelace-background",
 | 
			
		||||
            backgroundStyle
 | 
			
		||||
            configBackground
 | 
			
		||||
          );
 | 
			
		||||
        } else {
 | 
			
		||||
          this._huiView!.style.removeProperty("--lovelace-background");
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,6 @@ import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/lo
 | 
			
		||||
import { HassElement } from "../../../../src/state/hass-element";
 | 
			
		||||
import { castContext } from "../cast_context";
 | 
			
		||||
import "./hc-launch-screen";
 | 
			
		||||
import { getPanelTitleFromUrlPath } from "../../../../src/data/panel";
 | 
			
		||||
 | 
			
		||||
const DEFAULT_CONFIG: LovelaceDashboardStrategyConfig = {
 | 
			
		||||
  strategy: {
 | 
			
		||||
@@ -271,7 +270,7 @@ export class HcMain extends HassElement {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this._error = undefined;
 | 
			
		||||
    if (msg.urlPath === "lovelace" || msg.urlPath === undefined) {
 | 
			
		||||
    if (msg.urlPath === "lovelace") {
 | 
			
		||||
      msg.urlPath = null;
 | 
			
		||||
    }
 | 
			
		||||
    this._lovelacePath = msg.viewPath;
 | 
			
		||||
@@ -360,11 +359,7 @@ export class HcMain extends HassElement {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
 | 
			
		||||
    const title = getPanelTitleFromUrlPath(
 | 
			
		||||
      this.hass!,
 | 
			
		||||
      this._urlPath || "lovelace"
 | 
			
		||||
    );
 | 
			
		||||
    castContext.setApplicationState(title || "");
 | 
			
		||||
    castContext.setApplicationState(lovelaceConfig.title || "");
 | 
			
		||||
    this._lovelaceConfig = lovelaceConfig;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import { energyEntities } from "../stubs/entities";
 | 
			
		||||
import { DemoConfig } from "./types";
 | 
			
		||||
 | 
			
		||||
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
 | 
			
		||||
  () => import("./sections").then((mod) => mod.demoSections),
 | 
			
		||||
  () => import("./arsaboo").then((mod) => mod.demoArsaboo),
 | 
			
		||||
  () => import("./teachingbirds").then((mod) => mod.demoTeachingbirds),
 | 
			
		||||
  () => import("./kernehed").then((mod) => mod.demoKernehed),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
import { html } from "lit";
 | 
			
		||||
import { DemoConfig } from "../types";
 | 
			
		||||
 | 
			
		||||
export const demoLovelaceDescription: DemoConfig["description"] = (
 | 
			
		||||
  localize
 | 
			
		||||
) => html`
 | 
			
		||||
  <p>
 | 
			
		||||
    ${localize("ui.panel.page-demo.config.sections.description", {
 | 
			
		||||
      blog_post: html`<a
 | 
			
		||||
        href="https://www.home-assistant.io/blog/2024/03/04/dashboard-chapter-1/"
 | 
			
		||||
        target="_blank"
 | 
			
		||||
        >${localize("ui.panel.page-demo.config.sections.description_blog_post")}
 | 
			
		||||
      </a>`,
 | 
			
		||||
    })}
 | 
			
		||||
  </p>
 | 
			
		||||
`;
 | 
			
		||||
@@ -1,474 +0,0 @@
 | 
			
		||||
import { convertEntities } from "../../../../src/fake_data/entity";
 | 
			
		||||
import { DemoConfig } from "../types";
 | 
			
		||||
 | 
			
		||||
export const demoEntitiesSections: DemoConfig["entities"] = () =>
 | 
			
		||||
  convertEntities({
 | 
			
		||||
    "cover.living_room_garden_shutter": {
 | 
			
		||||
      entity_id: "cover.living_room_garden_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Living room garden shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.living_room_graveyard_shutter": {
 | 
			
		||||
      entity_id: "cover.living_room_graveyard_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Living room graveyard shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.living_room_left_shutter": {
 | 
			
		||||
      entity_id: "cover.living_room_left_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Living room left shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.living_room_right_shutter": {
 | 
			
		||||
      entity_id: "cover.living_room_right_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Living room right shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.floor_lamp": {
 | 
			
		||||
      entity_id: "light.floor_lamp",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        min_color_temp_kelvin: 2000,
 | 
			
		||||
        max_color_temp_kelvin: 6535,
 | 
			
		||||
        min_mireds: 153,
 | 
			
		||||
        max_mireds: 500,
 | 
			
		||||
        supported_color_modes: ["color_temp", "xy"],
 | 
			
		||||
        color_mode: "color_temp",
 | 
			
		||||
        brightness: 178,
 | 
			
		||||
        color_temp_kelvin: 2583,
 | 
			
		||||
        color_temp: 387,
 | 
			
		||||
        hs_color: [28.664, 69.597],
 | 
			
		||||
        rgb_color: [255, 162, 77],
 | 
			
		||||
        xy_color: [0.538, 0.389],
 | 
			
		||||
        icon: "mdi:floor-lamp",
 | 
			
		||||
        friendly_name: "Floor lamp",
 | 
			
		||||
        supported_features: 44,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.living_room_spotlights": {
 | 
			
		||||
      entity_id: "light.living_room_spotlights",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: "brightness",
 | 
			
		||||
        brightness: 126,
 | 
			
		||||
        icon: "mdi:ceiling-light-multiple",
 | 
			
		||||
        friendly_name: "Living room spotlights",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.bar_lamp": {
 | 
			
		||||
      entity_id: "light.bar_lamp",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        min_color_temp_kelvin: 2202,
 | 
			
		||||
        max_color_temp_kelvin: 4504,
 | 
			
		||||
        min_mireds: 222,
 | 
			
		||||
        max_mireds: 454,
 | 
			
		||||
        effect_list: ["None", "candle"],
 | 
			
		||||
        supported_color_modes: ["color_temp"],
 | 
			
		||||
        effect: null,
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        color_temp_kelvin: null,
 | 
			
		||||
        color_temp: null,
 | 
			
		||||
        hs_color: null,
 | 
			
		||||
        rgb_color: null,
 | 
			
		||||
        xy_color: null,
 | 
			
		||||
        mode: "normal",
 | 
			
		||||
        dynamics: "none",
 | 
			
		||||
        icon: "mdi:lightbulb-variant",
 | 
			
		||||
        friendly_name: "Bar lamp",
 | 
			
		||||
        supported_features: 44,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.living_room_temperature": {
 | 
			
		||||
      entity_id: "sensor.living_room_temperature",
 | 
			
		||||
      state: "22.8",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        unit_of_measurement: "°C",
 | 
			
		||||
        device_class: "temperature",
 | 
			
		||||
        friendly_name: "Living room Temperature",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "media_player.living_room_nest_mini": {
 | 
			
		||||
      entity_id: "media_player.living_room_nest_mini",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "speaker",
 | 
			
		||||
        friendly_name: "Living room Nest Mini",
 | 
			
		||||
        supported_features: 152461,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.kitchen_shutter": {
 | 
			
		||||
      entity_id: "cover.kitchen_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Kitchen shutter ",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.kitchen_spotlights": {
 | 
			
		||||
      entity_id: "light.kitchen_spotlights",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        icon: "mdi:ceiling-light-multiple",
 | 
			
		||||
        friendly_name: "Kitchen spotlights ",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.worktop_spotlights": {
 | 
			
		||||
      entity_id: "light.worktop_spotlights",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        icon: "mdi:ceiling-light-multiple",
 | 
			
		||||
        friendly_name: "Worktop spotlights ",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "binary_sensor.fridge_door": {
 | 
			
		||||
      entity_id: "binary_sensor.fridge_door",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "door",
 | 
			
		||||
        icon: "mdi:fridge",
 | 
			
		||||
        friendly_name: "Fridge door",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "media_player.kitchen_nest_audio": {
 | 
			
		||||
      entity_id: "media_player.kitchen_nest_audio",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "speaker",
 | 
			
		||||
        friendly_name: "Kitchen Nest Audio",
 | 
			
		||||
        supported_features: 152461,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "binary_sensor.tesla_wall_connector_vehicle_connected": {
 | 
			
		||||
      entity_id: "binary_sensor.tesla_wall_connector_vehicle_connected",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "plug",
 | 
			
		||||
        friendly_name: "Wall Connector Vehicle connected",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.tesla_wall_connector_session_energy": {
 | 
			
		||||
      entity_id: "sensor.tesla_wall_connector_session_energy",
 | 
			
		||||
      state: "16.3",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "total_increasing",
 | 
			
		||||
        unit_of_measurement: "kWh",
 | 
			
		||||
        device_class: "energy",
 | 
			
		||||
        friendly_name: "Tesla Wall Connector Session energy",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.electric_meter_power": {
 | 
			
		||||
      entity_id: "sensor.electric_meter_power",
 | 
			
		||||
      state: "797.86",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        unit_of_measurement: "W",
 | 
			
		||||
        device_class: "power",
 | 
			
		||||
        icon: "mdi:meter-electric",
 | 
			
		||||
        friendly_name: "Electric meter Power",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.eletric_meter_voltage": {
 | 
			
		||||
      entity_id: "sensor.eletric_meter_voltage",
 | 
			
		||||
      state: "232.19",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        unit_of_measurement: "V",
 | 
			
		||||
        device_class: "voltage",
 | 
			
		||||
        friendly_name: "Electric meter voltage",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.electricity_maps_grid_fossil_fuel_percentage": {
 | 
			
		||||
      entity_id: "sensor.electricity_maps_grid_fossil_fuel_percentage",
 | 
			
		||||
      state: "9.84",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        country_code: "FR",
 | 
			
		||||
        unit_of_measurement: "%",
 | 
			
		||||
        attribution: "Data provided by Electricity Maps",
 | 
			
		||||
        icon: "mdi:barrel",
 | 
			
		||||
        friendly_name: "Electricity Maps Grid fossil fuel percentage",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.electricity_maps_co2_intensity": {
 | 
			
		||||
      entity_id: "sensor.electricity_maps_co2_intensity",
 | 
			
		||||
      state: "62.0",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        country_code: "FR",
 | 
			
		||||
        unit_of_measurement: "gCO2eq/kWh",
 | 
			
		||||
        attribution: "Data provided by Electricity Maps",
 | 
			
		||||
        friendly_name: "Electricity Maps CO2 intensity",
 | 
			
		||||
        icon: "mdi:molecule-co2",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sun.sun": {
 | 
			
		||||
      entity_id: "sun.sun",
 | 
			
		||||
      state: "above_horizon",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        next_dawn: "2024-03-05T05:50:21.964405+00:00",
 | 
			
		||||
        next_dusk: "2024-03-04T18:08:54.311334+00:00",
 | 
			
		||||
        next_midnight: "2024-03-05T00:00:00+00:00",
 | 
			
		||||
        next_noon: "2024-03-05T12:00:05+00:00",
 | 
			
		||||
        next_rising: "2024-03-05T06:23:42.739159+00:00",
 | 
			
		||||
        next_setting: "2024-03-04T17:35:26.271171+00:00",
 | 
			
		||||
        elevation: 30.38,
 | 
			
		||||
        azimuth: 204.42,
 | 
			
		||||
        rising: false,
 | 
			
		||||
        friendly_name: "Sun",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.rain": {
 | 
			
		||||
      entity_id: "sensor.moon_phase",
 | 
			
		||||
      state: "7.2",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "total_increasing",
 | 
			
		||||
        unit_of_measurement: "mm",
 | 
			
		||||
        device_class: "precipitation",
 | 
			
		||||
        friendly_name: "Rain",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "climate.ground_floor": {
 | 
			
		||||
      entity_id: "climate.ground_floor",
 | 
			
		||||
      state: "heat",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        hvac_modes: ["auto", "heat", "off"],
 | 
			
		||||
        min_temp: 7,
 | 
			
		||||
        max_temp: 35,
 | 
			
		||||
        preset_modes: [
 | 
			
		||||
          "comfort",
 | 
			
		||||
          "away",
 | 
			
		||||
          "eco",
 | 
			
		||||
          "frost_protection",
 | 
			
		||||
          "external",
 | 
			
		||||
          "home",
 | 
			
		||||
        ],
 | 
			
		||||
        current_temperature: 20.8,
 | 
			
		||||
        temperature: 21,
 | 
			
		||||
        preset_mode: "comfort",
 | 
			
		||||
        icon: "mdi:home-floor-0",
 | 
			
		||||
        friendly_name: "Ground floor Thermostat",
 | 
			
		||||
        supported_features: 401,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "climate.first_floor": {
 | 
			
		||||
      entity_id: "climate.first_floor",
 | 
			
		||||
      state: "heat",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        hvac_modes: ["auto", "heat", "off"],
 | 
			
		||||
        min_temp: 7,
 | 
			
		||||
        max_temp: 35,
 | 
			
		||||
        preset_modes: [
 | 
			
		||||
          "comfort",
 | 
			
		||||
          "away",
 | 
			
		||||
          "eco",
 | 
			
		||||
          "frost_protection",
 | 
			
		||||
          "external",
 | 
			
		||||
          "home",
 | 
			
		||||
        ],
 | 
			
		||||
        current_temperature: 21.7,
 | 
			
		||||
        temperature: 21,
 | 
			
		||||
        preset_mode: "comfort",
 | 
			
		||||
        icon: "mdi:home-floor-1",
 | 
			
		||||
        friendly_name: "First floor Thermostat",
 | 
			
		||||
        supported_features: 401,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "cover.study_shutter": {
 | 
			
		||||
      entity_id: "cover.study_shutter",
 | 
			
		||||
      state: "open",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        current_position: 100,
 | 
			
		||||
        device_class: "shutter",
 | 
			
		||||
        friendly_name: "Study shutter",
 | 
			
		||||
        supported_features: 15,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.study_spotlights": {
 | 
			
		||||
      entity_id: "light.study_spotlights",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        icon: "mdi:ceiling-light-multiple",
 | 
			
		||||
        friendly_name: "Study spotlights",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "media_player.study_nest_hub": {
 | 
			
		||||
      entity_id: "media_player.study_nest_hub",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        friendly_name: "Study Nest Hub",
 | 
			
		||||
        supported_features: 152461,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.standing_desk_height": {
 | 
			
		||||
      entity_id: "sensor.standing_desk_height",
 | 
			
		||||
      state: "72",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        unit_of_measurement: "cm",
 | 
			
		||||
        icon: "mdi:tape-measure",
 | 
			
		||||
        friendly_name: "Standing desk Height",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.outdoor_light": {
 | 
			
		||||
      entity_id: "light.outdoor_light",
 | 
			
		||||
      state: "on",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: 255,
 | 
			
		||||
        icon: "mdi:outdoor-lamp",
 | 
			
		||||
        friendly_name: "Outdoor light",
 | 
			
		||||
        supported_features: 32,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "light.flood_light": {
 | 
			
		||||
      entity_id: "light.flood_light",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        effect_list: ["None", "candle"],
 | 
			
		||||
        supported_color_modes: ["brightness"],
 | 
			
		||||
        effect: null,
 | 
			
		||||
        color_mode: null,
 | 
			
		||||
        brightness: null,
 | 
			
		||||
        mode: "normal",
 | 
			
		||||
        dynamics: "none",
 | 
			
		||||
        icon: "mdi:light-flood-down",
 | 
			
		||||
        friendly_name: "Flood light",
 | 
			
		||||
        supported_features: 44,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.outdoor_motion_sensor_temperature": {
 | 
			
		||||
      entity_id: "sensor.outdoor_motion_sensor_temperature",
 | 
			
		||||
      state: "10.2",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        unit_of_measurement: "°C",
 | 
			
		||||
        device_class: "temperature",
 | 
			
		||||
        friendly_name: "Outdoor motion sensor Temperature",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "binary_sensor.outdoor_motion_sensor_motion": {
 | 
			
		||||
      entity_id: "binary_sensor.outdoor_motion_sensor_motion",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        device_class: "motion",
 | 
			
		||||
        friendly_name: "Outdoor motion sensor Motion",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "sensor.outdoor_motion_sensor_illuminance": {
 | 
			
		||||
      entity_id: "sensor.outdoor_motion_sensor_illuminance",
 | 
			
		||||
      state: "555",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        state_class: "measurement",
 | 
			
		||||
        light_level: 27444,
 | 
			
		||||
        unit_of_measurement: "lx",
 | 
			
		||||
        device_class: "illuminance",
 | 
			
		||||
        friendly_name: "Outdoor motion sensor Illuminance",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "automation.home_assistant_auto_update": {
 | 
			
		||||
      entity_id: "automation.home_assistant_auto_update",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        id: "1700669321947",
 | 
			
		||||
        last_triggered: "2024-02-29T18:02:05.343139+00:00",
 | 
			
		||||
        mode: "queued",
 | 
			
		||||
        current: 0,
 | 
			
		||||
        max: 50,
 | 
			
		||||
        icon: "mdi:auto-mode",
 | 
			
		||||
        friendly_name: "Home Assistant Auto-update",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "update.home_assistant_operating_system_update": {
 | 
			
		||||
      entity_id: "update.home_assistant_operating_system_update",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        auto_update: false,
 | 
			
		||||
        installed_version: "12.1",
 | 
			
		||||
        in_progress: false,
 | 
			
		||||
        latest_version: "12.1",
 | 
			
		||||
        release_summary: null,
 | 
			
		||||
        release_url:
 | 
			
		||||
          "https://github.com/home-assistant/operating-system/commits/dev",
 | 
			
		||||
        skipped_version: null,
 | 
			
		||||
        title: "Home Assistant Operating System",
 | 
			
		||||
        entity_picture:
 | 
			
		||||
          "https://brands.home-assistant.io/homeassistant/icon.png",
 | 
			
		||||
        friendly_name: "Home Assistant Operating System Update",
 | 
			
		||||
        supported_features: 3,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "update.home_assistant_supervisor_update": {
 | 
			
		||||
      entity_id: "update.home_assistant_supervisor_update",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        auto_update: true,
 | 
			
		||||
        installed_version: "2024.02.2",
 | 
			
		||||
        in_progress: false,
 | 
			
		||||
        latest_version: "2024.02.2",
 | 
			
		||||
        release_summary: null,
 | 
			
		||||
        release_url:
 | 
			
		||||
          "https://github.com/home-assistant/supervisor/commits/main",
 | 
			
		||||
        skipped_version: null,
 | 
			
		||||
        title: "Home Assistant Supervisor",
 | 
			
		||||
        entity_picture: "https://brands.home-assistant.io/hassio/icon.png",
 | 
			
		||||
        friendly_name: "Home Assistant Supervisor Update",
 | 
			
		||||
        supported_features: 1,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    "update.home_assistant_core_update": {
 | 
			
		||||
      entity_id: "update.home_assistant_supervisor_update",
 | 
			
		||||
      state: "off",
 | 
			
		||||
      attributes: {
 | 
			
		||||
        auto_update: false,
 | 
			
		||||
        installed_version: "2024.4.0",
 | 
			
		||||
        in_progress: false,
 | 
			
		||||
        latest_version: "2024.4.0",
 | 
			
		||||
        release_summary: null,
 | 
			
		||||
        release_url: "https://github.com/home-assistant/core/commits/dev",
 | 
			
		||||
        skipped_version: null,
 | 
			
		||||
        title: "Home Assistant Core",
 | 
			
		||||
        entity_picture:
 | 
			
		||||
          "https://brands.home-assistant.io/homeassistant/icon.png",
 | 
			
		||||
        friendly_name: "Home Assistant Core Update",
 | 
			
		||||
        supported_features: 11,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
import { DemoConfig } from "../types";
 | 
			
		||||
import { demoLovelaceDescription } from "./description";
 | 
			
		||||
import { demoEntitiesSections } from "./entities";
 | 
			
		||||
import { demoLovelaceSections } from "./lovelace";
 | 
			
		||||
 | 
			
		||||
export const demoSections: DemoConfig = {
 | 
			
		||||
  authorName: "Home Assistant",
 | 
			
		||||
  authorUrl: "https://github.com/home-assistant/frontend/",
 | 
			
		||||
  name: "Home Demo",
 | 
			
		||||
  description: demoLovelaceDescription,
 | 
			
		||||
  lovelace: demoLovelaceSections,
 | 
			
		||||
  entities: demoEntitiesSections,
 | 
			
		||||
  theme: () => ({}),
 | 
			
		||||
};
 | 
			
		||||
@@ -1,281 +0,0 @@
 | 
			
		||||
import { DemoConfig } from "../types";
 | 
			
		||||
 | 
			
		||||
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
 | 
			
		||||
  title: "Home Assistant Demo",
 | 
			
		||||
  views: [
 | 
			
		||||
    {
 | 
			
		||||
      type: "sections",
 | 
			
		||||
      title: "Demo",
 | 
			
		||||
      path: "home",
 | 
			
		||||
      icon: "mdi:home-assistant",
 | 
			
		||||
      sections: [
 | 
			
		||||
        {
 | 
			
		||||
          title: "Welcome 👋",
 | 
			
		||||
          cards: [{ type: "custom:ha-demo-card" }],
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          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",
 | 
			
		||||
              entity: "light.floor_lamp",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.living_room_spotlights",
 | 
			
		||||
              name: "Spotlights",
 | 
			
		||||
              features: [
 | 
			
		||||
                {
 | 
			
		||||
                  type: "light-brightness",
 | 
			
		||||
                },
 | 
			
		||||
              ],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.bar_lamp",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              graph: "line",
 | 
			
		||||
              type: "sensor",
 | 
			
		||||
              entity: "sensor.living_room_temperature",
 | 
			
		||||
              detail: 1,
 | 
			
		||||
              name: "Temperature",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "media_player.living_room_nest_mini",
 | 
			
		||||
              name: "Nest Mini",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🛋️ Living room ",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "cover.kitchen_shutter",
 | 
			
		||||
              name: "Shutter",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.kitchen_spotlights",
 | 
			
		||||
              name: "Spotlights",
 | 
			
		||||
              features: [
 | 
			
		||||
                {
 | 
			
		||||
                  type: "light-brightness",
 | 
			
		||||
                },
 | 
			
		||||
              ],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.worktop_spotlights",
 | 
			
		||||
              name: "Worktop",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "binary_sensor.fridge_door",
 | 
			
		||||
              name: "Fridge",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "media_player.kitchen_nest_audio",
 | 
			
		||||
              name: "Nest Audio",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "👩🍳 Kitchen",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "binary_sensor.tesla_wall_connector_vehicle_connected",
 | 
			
		||||
              name: "EV",
 | 
			
		||||
              icon: "mdi:car",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.tesla_wall_connector_session_energy",
 | 
			
		||||
              name: "Last charge",
 | 
			
		||||
              color: "green",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.electric_meter_power",
 | 
			
		||||
              color: "deep-orange",
 | 
			
		||||
              name: "Home power",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.eletric_meter_voltage",
 | 
			
		||||
              name: "Voltage",
 | 
			
		||||
              color: "deep-orange",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.electricity_maps_grid_fossil_fuel_percentage",
 | 
			
		||||
              name: "Fossil fuel",
 | 
			
		||||
              color: "brown",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.electricity_maps_co2_intensity",
 | 
			
		||||
              name: "CO2 Intensity",
 | 
			
		||||
              color: "dark-grey",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "⚡️ Energy",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sun.sun",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.rain",
 | 
			
		||||
              color: "blue",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              features: [
 | 
			
		||||
                {
 | 
			
		||||
                  type: "target-temperature",
 | 
			
		||||
                },
 | 
			
		||||
              ],
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              name: "Downstairs",
 | 
			
		||||
              entity: "climate.ground_floor",
 | 
			
		||||
              state_content: ["preset_mode", "current_temperature"],
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              features: [
 | 
			
		||||
                {
 | 
			
		||||
                  type: "target-temperature",
 | 
			
		||||
                },
 | 
			
		||||
              ],
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              name: "Upstairs",
 | 
			
		||||
              entity: "climate.first_floor",
 | 
			
		||||
              state_content: ["preset_mode", "current_temperature"],
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🌤️ Climate",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "cover.study_shutter",
 | 
			
		||||
              name: "Shutter",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.study_spotlights",
 | 
			
		||||
              name: "Spotlights",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "media_player.study_nest_hub",
 | 
			
		||||
              name: "Nest Hub",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.standing_desk_height",
 | 
			
		||||
              name: "Desk",
 | 
			
		||||
              color: "brown",
 | 
			
		||||
              icon: "mdi:desk",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🧑💻 Study",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.outdoor_light",
 | 
			
		||||
              name: "Door light",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "light.flood_light",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              graph: "line",
 | 
			
		||||
              type: "sensor",
 | 
			
		||||
              entity: "sensor.outdoor_motion_sensor_temperature",
 | 
			
		||||
              detail: 1,
 | 
			
		||||
              name: "Temperature",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "binary_sensor.outdoor_motion_sensor_motion",
 | 
			
		||||
              name: "Motion",
 | 
			
		||||
              color: "blue",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "sensor.outdoor_motion_sensor_illuminance",
 | 
			
		||||
              color: "amber",
 | 
			
		||||
              name: "Illuminance",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🌳 Outdoor",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          type: "grid",
 | 
			
		||||
          cards: [
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "automation.home_assistant_auto_update",
 | 
			
		||||
              name: "Auto-update",
 | 
			
		||||
              color: "green",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "update.home_assistant_operating_system_update",
 | 
			
		||||
              name: "OS",
 | 
			
		||||
              icon: "mdi:home-assistant",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "update.home_assistant_supervisor_update",
 | 
			
		||||
              icon: "mdi:home-assistant",
 | 
			
		||||
              name: "Supervisor",
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              type: "tile",
 | 
			
		||||
              entity: "update.home_assistant_core_update",
 | 
			
		||||
              name: "Core",
 | 
			
		||||
              icon: "mdi:home-assistant",
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          title: "🎉 Updates",
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
import { TemplateResult } from "lit";
 | 
			
		||||
import { LocalizeFunc } from "../../../src/common/translations/localize";
 | 
			
		||||
import { LovelaceConfig } from "../../../src/data/lovelace/config/types";
 | 
			
		||||
import { Entity } from "../../../src/fake_data/entity";
 | 
			
		||||
@@ -8,9 +7,6 @@ export interface DemoConfig {
 | 
			
		||||
  name: string;
 | 
			
		||||
  authorName: string;
 | 
			
		||||
  authorUrl: string;
 | 
			
		||||
  description?:
 | 
			
		||||
    | string
 | 
			
		||||
    | ((localize: LocalizeFunc) => string | TemplateResult<1>);
 | 
			
		||||
  lovelace: (localize: LocalizeFunc) => LovelaceConfig;
 | 
			
		||||
  entities: (localize: LocalizeFunc) => Entity[];
 | 
			
		||||
  theme: () => Record<string, string> | null;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import { until } from "lit/directives/until";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../src/components/ha-card";
 | 
			
		||||
import "../../../src/components/ha-button";
 | 
			
		||||
import "../../../src/components/ha-circular-progress";
 | 
			
		||||
import { LovelaceCardConfig } from "../../../src/data/lovelace/config/card";
 | 
			
		||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
@@ -12,6 +11,7 @@ import {
 | 
			
		||||
  demoConfigs,
 | 
			
		||||
  selectedDemoConfig,
 | 
			
		||||
  selectedDemoConfigIndex,
 | 
			
		||||
  setDemoConfig,
 | 
			
		||||
} from "../configs/demo-configs";
 | 
			
		||||
 | 
			
		||||
@customElement("ha-demo-card")
 | 
			
		||||
@@ -39,57 +39,38 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
        <div class="picker">
 | 
			
		||||
          <div class="label">
 | 
			
		||||
            ${this._switching
 | 
			
		||||
              ? html`
 | 
			
		||||
                  <ha-circular-progress indeterminate></ha-circular-progress>
 | 
			
		||||
                `
 | 
			
		||||
              ? html`<ha-circular-progress
 | 
			
		||||
                  indeterminate
 | 
			
		||||
                ></ha-circular-progress>`
 | 
			
		||||
              : until(
 | 
			
		||||
                  selectedDemoConfig.then(
 | 
			
		||||
                    (conf) => html`
 | 
			
		||||
                      ${conf.name}
 | 
			
		||||
                      <small>
 | 
			
		||||
                        <a target="_blank" href=${conf.authorUrl}>
 | 
			
		||||
                          ${this.hass.localize(
 | 
			
		||||
                            "ui.panel.page-demo.cards.demo.demo_by",
 | 
			
		||||
                          {
 | 
			
		||||
                            name: html`
 | 
			
		||||
                              <a target="_blank" href=${conf.authorUrl}>
 | 
			
		||||
                                ${conf.authorName}
 | 
			
		||||
                              </a>
 | 
			
		||||
                            `,
 | 
			
		||||
                          }
 | 
			
		||||
                            { name: conf.authorName }
 | 
			
		||||
                          )}
 | 
			
		||||
                        </a>
 | 
			
		||||
                      </small>
 | 
			
		||||
                    `
 | 
			
		||||
                  ),
 | 
			
		||||
                  ""
 | 
			
		||||
                )}
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <ha-button @click=${this._nextConfig} .disabled=${this._switching}>
 | 
			
		||||
          <mwc-button @click=${this._nextConfig} .disabled=${this._switching}>
 | 
			
		||||
            ${this.hass.localize("ui.panel.page-demo.cards.demo.next_demo")}
 | 
			
		||||
          </ha-button>
 | 
			
		||||
          </mwc-button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="content">
 | 
			
		||||
          <p class="small-hidden">
 | 
			
		||||
        <div class="content small-hidden">
 | 
			
		||||
          ${this.hass.localize("ui.panel.page-demo.cards.demo.introduction")}
 | 
			
		||||
          </p>
 | 
			
		||||
          ${until(
 | 
			
		||||
            selectedDemoConfig.then((conf) => {
 | 
			
		||||
              if (typeof conf.description === "function") {
 | 
			
		||||
                return conf.description(this.hass.localize);
 | 
			
		||||
              }
 | 
			
		||||
              if (conf.description) {
 | 
			
		||||
                return html`<p>${conf.description}</p>`;
 | 
			
		||||
              }
 | 
			
		||||
              return nothing;
 | 
			
		||||
            }),
 | 
			
		||||
            nothing
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="actions small-hidden">
 | 
			
		||||
          <a href="https://www.home-assistant.io" target="_blank">
 | 
			
		||||
            <ha-button>
 | 
			
		||||
            <mwc-button>
 | 
			
		||||
              ${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
 | 
			
		||||
            </ha-button>
 | 
			
		||||
            </mwc-button>
 | 
			
		||||
          </a>
 | 
			
		||||
        </div>
 | 
			
		||||
      </ha-card>
 | 
			
		||||
@@ -113,7 +94,13 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
 | 
			
		||||
  private async _updateConfig(index: number) {
 | 
			
		||||
    this._switching = true;
 | 
			
		||||
    fireEvent(this, "set-demo-config" as any, { index });
 | 
			
		||||
    try {
 | 
			
		||||
      await setDemoConfig(this.hass, this.lovelace!, index);
 | 
			
		||||
    } catch (err: any) {
 | 
			
		||||
      alert("Failed to switch config :-(");
 | 
			
		||||
    } finally {
 | 
			
		||||
      this._switching = false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get styles(): CSSResultGroup {
 | 
			
		||||
@@ -121,7 +108,6 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
      css`
 | 
			
		||||
        a {
 | 
			
		||||
          color: var(--primary-color);
 | 
			
		||||
          display: inline-block;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .actions a {
 | 
			
		||||
@@ -129,11 +115,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .content {
 | 
			
		||||
          padding: 0 16px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .content p {
 | 
			
		||||
          margin: 16px 0;
 | 
			
		||||
          padding: 16px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .picker {
 | 
			
		||||
@@ -143,7 +125,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
          height: 60px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .picker ha-button {
 | 
			
		||||
        .picker mwc-button {
 | 
			
		||||
          margin-right: 8px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -156,8 +138,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .actions {
 | 
			
		||||
          padding: 0px 8px 4px 8px;
 | 
			
		||||
          padding-left: 8px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @media only screen and (max-width: 500px) {
 | 
			
		||||
          .small-hidden {
 | 
			
		||||
            display: none;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ import {
 | 
			
		||||
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
 | 
			
		||||
import { HomeAssistant } from "../../src/types";
 | 
			
		||||
import { selectedDemoConfig } from "./configs/demo-configs";
 | 
			
		||||
import { mockAreaRegistry } from "./stubs/area_registry";
 | 
			
		||||
import { mockAuth } from "./stubs/auth";
 | 
			
		||||
import { mockConfigEntries } from "./stubs/config_entries";
 | 
			
		||||
import { mockEnergy } from "./stubs/energy";
 | 
			
		||||
@@ -24,10 +23,10 @@ import { mockLovelace } from "./stubs/lovelace";
 | 
			
		||||
import { mockMediaPlayer } from "./stubs/media_player";
 | 
			
		||||
import { mockPersistentNotification } from "./stubs/persistent_notification";
 | 
			
		||||
import { mockRecorder } from "./stubs/recorder";
 | 
			
		||||
import { mockTodo } from "./stubs/todo";
 | 
			
		||||
import { mockSensor } from "./stubs/sensor";
 | 
			
		||||
import { mockSystemLog } from "./stubs/system_log";
 | 
			
		||||
import { mockTemplate } from "./stubs/template";
 | 
			
		||||
import { mockTodo } from "./stubs/todo";
 | 
			
		||||
import { mockTranslations } from "./stubs/translations";
 | 
			
		||||
 | 
			
		||||
@customElement("ha-demo")
 | 
			
		||||
@@ -63,7 +62,6 @@ export class HaDemo extends HomeAssistantAppEl {
 | 
			
		||||
    mockEnergy(hass);
 | 
			
		||||
    mockPersistentNotification(hass);
 | 
			
		||||
    mockConfigEntries(hass);
 | 
			
		||||
    mockAreaRegistry(hass);
 | 
			
		||||
    mockEntityRegistry(hass, [
 | 
			
		||||
      {
 | 
			
		||||
        config_entry_id: "co2signal",
 | 
			
		||||
@@ -74,8 +72,6 @@ export class HaDemo extends HomeAssistantAppEl {
 | 
			
		||||
        id: "sensor.co2_intensity",
 | 
			
		||||
        name: null,
 | 
			
		||||
        icon: null,
 | 
			
		||||
        labels: [],
 | 
			
		||||
        categories: {},
 | 
			
		||||
        platform: "co2signal",
 | 
			
		||||
        hidden_by: null,
 | 
			
		||||
        entity_category: null,
 | 
			
		||||
@@ -92,8 +88,6 @@ export class HaDemo extends HomeAssistantAppEl {
 | 
			
		||||
        id: "sensor.co2_intensity",
 | 
			
		||||
        name: null,
 | 
			
		||||
        icon: null,
 | 
			
		||||
        labels: [],
 | 
			
		||||
        categories: {},
 | 
			
		||||
        platform: "co2signal",
 | 
			
		||||
        hidden_by: null,
 | 
			
		||||
        entity_category: null,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,4 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
export const mockAreaRegistry = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
  data: AreaRegistryEntry[] = []
 | 
			
		||||
) => {
 | 
			
		||||
  hass.mockWS("config/area_registry/list", () => data);
 | 
			
		||||
  const areas = {};
 | 
			
		||||
  data.forEach((area) => {
 | 
			
		||||
    areas[area.area_id] = area;
 | 
			
		||||
  });
 | 
			
		||||
  hass.updateHass({ areas });
 | 
			
		||||
};
 | 
			
		||||
) => hass.mockWS("config/area_registry/list", () => data);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ export const mockConfigEntries = (hass: MockHomeAssistant) => {
 | 
			
		||||
    supports_options: false,
 | 
			
		||||
    supports_remove_device: false,
 | 
			
		||||
    supports_unload: true,
 | 
			
		||||
    supports_reconfigure: true,
 | 
			
		||||
    pref_disable_new_entities: false,
 | 
			
		||||
    pref_disable_polling: false,
 | 
			
		||||
    disabled_by: null,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,4 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
export const mockDeviceRegistry = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
  data: DeviceRegistryEntry[] = []
 | 
			
		||||
) => {
 | 
			
		||||
  hass.mockWS("config/device_registry/list", () => data);
 | 
			
		||||
  const devices = {};
 | 
			
		||||
  data.forEach((device) => {
 | 
			
		||||
    devices[device.id] = device;
 | 
			
		||||
  });
 | 
			
		||||
  hass.updateHass({ devices });
 | 
			
		||||
};
 | 
			
		||||
) => hass.mockWS("config/device_registry/list", () => data);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { format, startOfToday, startOfTomorrow } from "date-fns";
 | 
			
		||||
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
 | 
			
		||||
import {
 | 
			
		||||
  EnergyInfo,
 | 
			
		||||
  EnergyPreferences,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
import { FloorRegistryEntry } from "../../../src/data/floor_registry";
 | 
			
		||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
 | 
			
		||||
export const mockFloorRegistry = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
  data: FloorRegistryEntry[] = []
 | 
			
		||||
) => hass.mockWS("config/floor_registry/list", () => data);
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
import { LabelRegistryEntry } from "../../../src/data/label_registry";
 | 
			
		||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
 | 
			
		||||
export const mockLabelRegistry = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
  data: LabelRegistryEntry[] = []
 | 
			
		||||
) => hass.mockWS("config/label_registry/list", () => data);
 | 
			
		||||
@@ -1,12 +1,9 @@
 | 
			
		||||
import type { LocalizeFunc } from "../../../src/common/translations/localize";
 | 
			
		||||
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
 | 
			
		||||
import {
 | 
			
		||||
  selectedDemoConfig,
 | 
			
		||||
  selectedDemoConfigIndex,
 | 
			
		||||
  setDemoConfig,
 | 
			
		||||
} from "../configs/demo-configs";
 | 
			
		||||
import { selectedDemoConfig } from "../configs/demo-configs";
 | 
			
		||||
import "../custom-cards/cast-demo-row";
 | 
			
		||||
import "../custom-cards/ha-demo-card";
 | 
			
		||||
import type { HADemoCard } from "../custom-cards/ha-demo-card";
 | 
			
		||||
 | 
			
		||||
export const mockLovelace = (
 | 
			
		||||
  hass: MockHomeAssistant,
 | 
			
		||||
@@ -22,22 +19,17 @@ export const mockLovelace = (
 | 
			
		||||
  hass.mockWS("lovelace/resources", () => Promise.resolve([]));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
customElements.whenDefined("hui-root").then(() => {
 | 
			
		||||
customElements.whenDefined("hui-view").then(() => {
 | 
			
		||||
  // eslint-disable-next-line
 | 
			
		||||
  const HUIRoot = customElements.get("hui-root")!;
 | 
			
		||||
  const HUIView = customElements.get("hui-view");
 | 
			
		||||
  // Patch HUI-VIEW to make the lovelace object available to the demo card
 | 
			
		||||
  const oldCreateCard = HUIView!.prototype.createCardElement;
 | 
			
		||||
 | 
			
		||||
  const oldFirstUpdated = HUIRoot.prototype.firstUpdated;
 | 
			
		||||
 | 
			
		||||
  HUIRoot.prototype.firstUpdated = function (changedProperties) {
 | 
			
		||||
    oldFirstUpdated.call(this, changedProperties);
 | 
			
		||||
    this.addEventListener("set-demo-config", async (ev) => {
 | 
			
		||||
      const index = (ev as CustomEvent).detail.index;
 | 
			
		||||
      try {
 | 
			
		||||
        await setDemoConfig(this.hass, this.lovelace!, index);
 | 
			
		||||
      } catch (err: any) {
 | 
			
		||||
        setDemoConfig(this.hass, this.lovelace!, selectedDemoConfigIndex);
 | 
			
		||||
        alert("Failed to switch config :-(");
 | 
			
		||||
  HUIView!.prototype.createCardElement = function (config) {
 | 
			
		||||
    const el = oldCreateCard.call(this, config);
 | 
			
		||||
    if (el.tagName === "HA-DEMO-CARD") {
 | 
			
		||||
      (el as HADemoCard).lovelace = this.lovelace;
 | 
			
		||||
    }
 | 
			
		||||
    });
 | 
			
		||||
    return el;
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,7 @@
 | 
			
		||||
import { load } from "js-yaml";
 | 
			
		||||
import { LitElement, PropertyValueMap, css, html, nothing } from "lit";
 | 
			
		||||
import { html, css, LitElement, PropertyValues } from "lit";
 | 
			
		||||
import { customElement, property, query, state } from "lit/decorators";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import "../../../src/panels/lovelace/cards/hui-card";
 | 
			
		||||
import type { HuiCard } from "../../../src/panels/lovelace/cards/hui-card";
 | 
			
		||||
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
 | 
			
		||||
import { HomeAssistant } from "../../../src/types";
 | 
			
		||||
 | 
			
		||||
export interface DemoCardConfig {
 | 
			
		||||
@@ -21,12 +19,7 @@ class DemoCard extends LitElement {
 | 
			
		||||
 | 
			
		||||
  @state() private _size?: number;
 | 
			
		||||
 | 
			
		||||
  @query("hui-card", false) private _card?: HuiCard;
 | 
			
		||||
 | 
			
		||||
  private _config = memoizeOne((config: string) => {
 | 
			
		||||
    const c = (load(config) as any)[0];
 | 
			
		||||
    return c;
 | 
			
		||||
  });
 | 
			
		||||
  @query("#card") private _card!: HTMLElement;
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return html`
 | 
			
		||||
@@ -37,32 +30,63 @@ class DemoCard extends LitElement {
 | 
			
		||||
          : ""}
 | 
			
		||||
      </h2>
 | 
			
		||||
      <div class="root">
 | 
			
		||||
        <hui-card
 | 
			
		||||
          .config=${this._config(this.config.config)}
 | 
			
		||||
          .hass=${this.hass}
 | 
			
		||||
          @card-updated=${this._cardUpdated}
 | 
			
		||||
        ></hui-card>
 | 
			
		||||
        ${this.showConfig
 | 
			
		||||
          ? html`<pre>${this.config.config.trim()}</pre>`
 | 
			
		||||
          : nothing}
 | 
			
		||||
        <div id="card"></div>
 | 
			
		||||
        ${this.showConfig ? html`<pre>${this.config.config.trim()}</pre>` : ""}
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _cardUpdated(ev) {
 | 
			
		||||
  updated(changedProps: PropertyValues) {
 | 
			
		||||
    super.updated(changedProps);
 | 
			
		||||
 | 
			
		||||
    if (changedProps.has("config")) {
 | 
			
		||||
      const card = this._card;
 | 
			
		||||
      while (card.lastChild) {
 | 
			
		||||
        card.removeChild(card.lastChild);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const el = this._createCardElement((load(this.config.config) as any)[0]);
 | 
			
		||||
      card.appendChild(el);
 | 
			
		||||
      this._getSize(el);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (changedProps.has("hass")) {
 | 
			
		||||
      const card = this._card.lastChild;
 | 
			
		||||
      if (card) {
 | 
			
		||||
        (card as any).hass = this.hass;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async _getSize(el) {
 | 
			
		||||
    await customElements.whenDefined(el.localName);
 | 
			
		||||
 | 
			
		||||
    if (!("getCardSize" in el)) {
 | 
			
		||||
      this._size = undefined;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    this._size = await el.getCardSize();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _createCardElement(cardConfig) {
 | 
			
		||||
    const element = createCardElement(cardConfig);
 | 
			
		||||
    if (this.hass) {
 | 
			
		||||
      element.hass = this.hass;
 | 
			
		||||
    }
 | 
			
		||||
    element.addEventListener(
 | 
			
		||||
      "ll-rebuild",
 | 
			
		||||
      (ev) => {
 | 
			
		||||
        ev.stopPropagation();
 | 
			
		||||
    this._updateSize();
 | 
			
		||||
        this._rebuildCard(element, cardConfig);
 | 
			
		||||
      },
 | 
			
		||||
      { once: true }
 | 
			
		||||
    );
 | 
			
		||||
    return element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _updateSize() {
 | 
			
		||||
    this._size = await this._card?.getCardSize();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected update(
 | 
			
		||||
    _changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
 | 
			
		||||
  ): void {
 | 
			
		||||
    super.update(_changedProperties);
 | 
			
		||||
    this._updateSize();
 | 
			
		||||
  _rebuildCard(cardElToReplace, config) {
 | 
			
		||||
    const newCardEl = this._createCardElement(config);
 | 
			
		||||
    cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static styles = css`
 | 
			
		||||
@@ -77,7 +101,7 @@ class DemoCard extends LitElement {
 | 
			
		||||
      font-size: 0.5em;
 | 
			
		||||
      color: var(--primary-text-color);
 | 
			
		||||
    }
 | 
			
		||||
    hui-card {
 | 
			
		||||
    #card {
 | 
			
		||||
      max-width: 400px;
 | 
			
		||||
      width: 100vw;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ export const basicTrace: DemoTrace = {
 | 
			
		||||
        {
 | 
			
		||||
          path: "trigger/0",
 | 
			
		||||
          timestamp: "2021-03-25T04:36:51.223693+00:00",
 | 
			
		||||
          changed_variables: {},
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      "condition/0": [
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ export const motionLightTrace: DemoTrace = {
 | 
			
		||||
        {
 | 
			
		||||
          path: "trigger/0",
 | 
			
		||||
          timestamp: "2021-03-25T04:36:51.223693+00:00",
 | 
			
		||||
          changed_variables: {},
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      "action/0": [
 | 
			
		||||
 
 | 
			
		||||
@@ -64,12 +64,6 @@ const ACTIONS = [
 | 
			
		||||
      entity_id: "input_boolean.toggle_4",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    sequence: [
 | 
			
		||||
      { scene: "scene.kitchen_morning" },
 | 
			
		||||
      { service: "light.turn_off", target: { entity_id: "light.kitchen" } },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    parallel: [
 | 
			
		||||
      { scene: "scene.kitchen_morning" },
 | 
			
		||||
@@ -142,7 +136,7 @@ export class DemoAutomationDescribeAction extends LitElement {
 | 
			
		||||
        <div class="action">
 | 
			
		||||
          <span>
 | 
			
		||||
            ${this._action
 | 
			
		||||
              ? describeAction(this.hass, [], [], [], this._action)
 | 
			
		||||
              ? describeAction(this.hass, [], this._action)
 | 
			
		||||
              : "<invalid YAML>"}
 | 
			
		||||
          </span>
 | 
			
		||||
          <ha-yaml-editor
 | 
			
		||||
@@ -155,7 +149,7 @@ export class DemoAutomationDescribeAction extends LitElement {
 | 
			
		||||
        ${ACTIONS.map(
 | 
			
		||||
          (conf) => html`
 | 
			
		||||
            <div class="action">
 | 
			
		||||
              <span>${describeAction(this.hass, [], [], [], conf as any)}</span>
 | 
			
		||||
              <span>${describeAction(this.hass, [], conf as any)}</span>
 | 
			
		||||
              <pre>${dump(conf)}</pre>
 | 
			
		||||
            </div>
 | 
			
		||||
          `
 | 
			
		||||
 
 | 
			
		||||
@@ -21,10 +21,10 @@ const ENTITIES = [
 | 
			
		||||
  }),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const conditions: Condition[] = [
 | 
			
		||||
  { condition: "and", conditions: [] },
 | 
			
		||||
  { condition: "not", conditions: [] },
 | 
			
		||||
  { condition: "or", conditions: [] },
 | 
			
		||||
const conditions = [
 | 
			
		||||
  { condition: "and" },
 | 
			
		||||
  { condition: "not" },
 | 
			
		||||
  { condition: "or" },
 | 
			
		||||
  { condition: "state", entity_id: "light.kitchen", state: "on" },
 | 
			
		||||
  {
 | 
			
		||||
    condition: "numeric_state",
 | 
			
		||||
@@ -34,11 +34,11 @@ const conditions: Condition[] = [
 | 
			
		||||
    above: 20,
 | 
			
		||||
  },
 | 
			
		||||
  { condition: "sun", after: "sunset" },
 | 
			
		||||
  { condition: "sun", after: "sunrise", before_offset: 3600 },
 | 
			
		||||
  { condition: "sun", after: "sunrise", offset: "-01:00" },
 | 
			
		||||
  { condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" },
 | 
			
		||||
  { condition: "trigger", id: "motion" },
 | 
			
		||||
  { condition: "time" },
 | 
			
		||||
  { condition: "template", value_template: "" },
 | 
			
		||||
  { condition: "template" },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const initialCondition: Condition = {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@ import { HaWaitForTriggerAction } from "../../../../src/panels/config/automation
 | 
			
		||||
import { HaWaitAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
 | 
			
		||||
import { Action } from "../../../../src/data/script";
 | 
			
		||||
import { HaConditionAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-condition";
 | 
			
		||||
import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence";
 | 
			
		||||
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
 | 
			
		||||
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
 | 
			
		||||
import { HaStopAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-stop";
 | 
			
		||||
@@ -40,7 +39,6 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
 | 
			
		||||
  { name: "If-Then", actions: [HaIfAction.defaultConfig] },
 | 
			
		||||
  { name: "Choose", actions: [HaChooseAction.defaultConfig] },
 | 
			
		||||
  { name: "Variables", actions: [{ variables: { hello: "1" } }] },
 | 
			
		||||
  { name: "Sequence", actions: [HaSequenceAction.defaultConfig] },
 | 
			
		||||
  { name: "Parallel", actions: [HaParallelAction.defaultConfig] },
 | 
			
		||||
  { name: "Stop", actions: [HaStopAction.defaultConfig] },
 | 
			
		||||
];
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,6 @@ export class DemoAutomationTraceTimeline extends LitElement {
 | 
			
		||||
    super.firstUpdated(changedProps);
 | 
			
		||||
    const hass = provideHass(this);
 | 
			
		||||
    hass.updateTranslations(null, "en");
 | 
			
		||||
    hass.updateTranslations("config", "en");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get styles() {
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,6 @@ export class DemoAutomationTrace extends LitElement {
 | 
			
		||||
    super.firstUpdated(changedProps);
 | 
			
		||||
    const hass = provideHass(this);
 | 
			
		||||
    hass.updateTranslations(null, "en");
 | 
			
		||||
    hass.updateTranslations("config", "en");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get styles() {
 | 
			
		||||
 
 | 
			
		||||
@@ -162,7 +162,7 @@ export class DemoHaBarButton extends LitElement {
 | 
			
		||||
      }
 | 
			
		||||
      .custom-group {
 | 
			
		||||
        --control-button-group-thickness: 100px;
 | 
			
		||||
        --control-button-group-border-radius: 36px;
 | 
			
		||||
        --control-button-group-border-radius: 18px;
 | 
			
		||||
        --control-button-group-spacing: 20px;
 | 
			
		||||
      }
 | 
			
		||||
      .custom-group ha-control-button {
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ export class DemoHarControlNumberButtons extends LitElement {
 | 
			
		||||
        --control-number-buttons-background-color: #2196f3;
 | 
			
		||||
        --control-number-buttons-background-opacity: 0.1;
 | 
			
		||||
        --control-number-buttons-thickness: 100px;
 | 
			
		||||
        --control-number-buttons-border-radius: 36px;
 | 
			
		||||
        --control-number-buttons-border-radius: 24px;
 | 
			
		||||
      }
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -186,8 +186,8 @@ export class DemoHaControlSelect extends LitElement {
 | 
			
		||||
      .custom {
 | 
			
		||||
        --mdc-icon-size: 24px;
 | 
			
		||||
        --control-select-color: var(--state-fan-active-color);
 | 
			
		||||
        --control-select-thickness: 130px;
 | 
			
		||||
        --control-select-border-radius: 36px;
 | 
			
		||||
        --control-select-thickness: 100px;
 | 
			
		||||
        --control-select-border-radius: 24px;
 | 
			
		||||
      }
 | 
			
		||||
      .vertical-selects {
 | 
			
		||||
        height: 300px;
 | 
			
		||||
 
 | 
			
		||||
@@ -150,8 +150,8 @@ export class DemoHaBarSlider extends LitElement {
 | 
			
		||||
        --control-slider-color: #ffcf4c;
 | 
			
		||||
        --control-slider-background: #ffcf4c;
 | 
			
		||||
        --control-slider-background-opacity: 0.2;
 | 
			
		||||
        --control-slider-thickness: 130px;
 | 
			
		||||
        --control-slider-border-radius: 36px;
 | 
			
		||||
        --control-slider-thickness: 100px;
 | 
			
		||||
        --control-slider-border-radius: 24px;
 | 
			
		||||
      }
 | 
			
		||||
      .vertical-sliders {
 | 
			
		||||
        height: 300px;
 | 
			
		||||
 
 | 
			
		||||
@@ -117,8 +117,8 @@ export class DemoHaControlSwitch extends LitElement {
 | 
			
		||||
      .custom {
 | 
			
		||||
        --control-switch-on-color: var(--green-color);
 | 
			
		||||
        --control-switch-off-color: var(--red-color);
 | 
			
		||||
        --control-switch-thickness: 130px;
 | 
			
		||||
        --control-switch-border-radius: 36px;
 | 
			
		||||
        --control-switch-thickness: 100px;
 | 
			
		||||
        --control-switch-border-radius: 24px;
 | 
			
		||||
        --control-switch-padding: 6px;
 | 
			
		||||
        --mdc-icon-size: 24px;
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,6 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "backyard",
 | 
			
		||||
@@ -78,7 +77,6 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: null,
 | 
			
		||||
@@ -97,37 +95,30 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const AREAS: AreaRegistryEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "backyard",
 | 
			
		||||
    floor_id: null,
 | 
			
		||||
    name: "Backyard",
 | 
			
		||||
    icon: null,
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "bedroom",
 | 
			
		||||
    floor_id: null,
 | 
			
		||||
    name: "Bedroom",
 | 
			
		||||
    icon: "mdi:bed",
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "livingroom",
 | 
			
		||||
    floor_id: null,
 | 
			
		||||
    name: "Livingroom",
 | 
			
		||||
    icon: "mdi:sofa",
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,10 +17,6 @@ import { provideHass } from "../../../../src/fake_data/provide_hass";
 | 
			
		||||
import { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
 | 
			
		||||
import type { HomeAssistant } from "../../../../src/types";
 | 
			
		||||
import "../../components/demo-black-white-row";
 | 
			
		||||
import { FloorRegistryEntry } from "../../../../src/data/floor_registry";
 | 
			
		||||
import { LabelRegistryEntry } from "../../../../src/data/label_registry";
 | 
			
		||||
import { mockFloorRegistry } from "../../../../demo/src/stubs/floor_registry";
 | 
			
		||||
import { mockLabelRegistry } from "../../../../demo/src/stubs/label_registry";
 | 
			
		||||
 | 
			
		||||
const ENTITIES = [
 | 
			
		||||
  getEntity("alarm_control_panel", "alarm", "disarmed", {
 | 
			
		||||
@@ -59,7 +55,6 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "backyard",
 | 
			
		||||
@@ -78,7 +73,6 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: null,
 | 
			
		||||
@@ -97,78 +91,30 @@ const DEVICES = [
 | 
			
		||||
    hw_version: null,
 | 
			
		||||
    via_device_id: null,
 | 
			
		||||
    serial_number: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const AREAS: AreaRegistryEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "backyard",
 | 
			
		||||
    floor_id: "ground",
 | 
			
		||||
    name: "Backyard",
 | 
			
		||||
    icon: null,
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "bedroom",
 | 
			
		||||
    floor_id: "first",
 | 
			
		||||
    name: "Bedroom",
 | 
			
		||||
    icon: "mdi:bed",
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    area_id: "livingroom",
 | 
			
		||||
    floor_id: "ground",
 | 
			
		||||
    name: "Livingroom",
 | 
			
		||||
    icon: "mdi:sofa",
 | 
			
		||||
    picture: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const FLOORS: FloorRegistryEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    floor_id: "ground",
 | 
			
		||||
    name: "Ground floor",
 | 
			
		||||
    level: 0,
 | 
			
		||||
    icon: null,
 | 
			
		||||
    aliases: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    floor_id: "first",
 | 
			
		||||
    name: "First floor",
 | 
			
		||||
    level: 1,
 | 
			
		||||
    icon: "mdi:numeric-1",
 | 
			
		||||
    aliases: [],
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    floor_id: "second",
 | 
			
		||||
    name: "Second floor",
 | 
			
		||||
    level: 2,
 | 
			
		||||
    icon: "mdi:numeric-2",
 | 
			
		||||
    aliases: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const LABELS: LabelRegistryEntry[] = [
 | 
			
		||||
  {
 | 
			
		||||
    label_id: "energy",
 | 
			
		||||
    name: "Energy",
 | 
			
		||||
    icon: null,
 | 
			
		||||
    color: "yellow",
 | 
			
		||||
    description: null,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label_id: "entertainment",
 | 
			
		||||
    name: "Entertainment",
 | 
			
		||||
    icon: "mdi:popcorn",
 | 
			
		||||
    color: "blue",
 | 
			
		||||
    description: null,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@@ -179,12 +125,7 @@ const SCHEMAS: {
 | 
			
		||||
  {
 | 
			
		||||
    name: "One of each",
 | 
			
		||||
    input: {
 | 
			
		||||
      label: { name: "Label", selector: { label: {} } },
 | 
			
		||||
      floor: { name: "Floor", selector: { floor: {} } },
 | 
			
		||||
      area: { name: "Area", selector: { area: {} } },
 | 
			
		||||
      device: { name: "Device", selector: { device: {} } },
 | 
			
		||||
      entity: { name: "Entity", selector: { entity: {} } },
 | 
			
		||||
      target: { name: "Target", selector: { target: {} } },
 | 
			
		||||
      state: {
 | 
			
		||||
        name: "State",
 | 
			
		||||
        selector: { state: { entity_id: "alarm_control_panel.alarm" } },
 | 
			
		||||
@@ -193,12 +134,15 @@ const SCHEMAS: {
 | 
			
		||||
        name: "Attribute",
 | 
			
		||||
        selector: { attribute: { entity_id: "" } },
 | 
			
		||||
      },
 | 
			
		||||
      device: { name: "Device", selector: { device: {} } },
 | 
			
		||||
      config_entry: {
 | 
			
		||||
        name: "Integration",
 | 
			
		||||
        selector: { config_entry: {} },
 | 
			
		||||
      },
 | 
			
		||||
      duration: { name: "Duration", selector: { duration: {} } },
 | 
			
		||||
      addon: { name: "Addon", selector: { addon: {} } },
 | 
			
		||||
      area: { name: "Area", selector: { area: {} } },
 | 
			
		||||
      target: { name: "Target", selector: { target: {} } },
 | 
			
		||||
      number_box: {
 | 
			
		||||
        name: "Number Box",
 | 
			
		||||
        selector: {
 | 
			
		||||
@@ -347,8 +291,6 @@ const SCHEMAS: {
 | 
			
		||||
      entity: { name: "Entity", selector: { entity: { multiple: true } } },
 | 
			
		||||
      device: { name: "Device", selector: { device: { multiple: true } } },
 | 
			
		||||
      area: { name: "Area", selector: { area: { multiple: true } } },
 | 
			
		||||
      floor: { name: "Floor", selector: { floor: { multiple: true } } },
 | 
			
		||||
      label: { name: "Label", selector: { label: { multiple: true } } },
 | 
			
		||||
      select: {
 | 
			
		||||
        name: "Select Multiple",
 | 
			
		||||
        selector: {
 | 
			
		||||
@@ -405,8 +347,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
 | 
			
		||||
    mockDeviceRegistry(hass, DEVICES);
 | 
			
		||||
    mockConfigEntries(hass);
 | 
			
		||||
    mockAreaRegistry(hass, AREAS);
 | 
			
		||||
    mockFloorRegistry(hass, FLOORS);
 | 
			
		||||
    mockLabelRegistry(hass, LABELS);
 | 
			
		||||
    mockHassioSupervisor(hass);
 | 
			
		||||
    hass.mockWS("auth/sign_path", (params) => params);
 | 
			
		||||
    hass.mockWS("media_player/browse_media", this._browseMedia);
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,9 @@ export class DemoDateTimeDateTimeNumeric extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,9 @@ export class DemoDateTimeDateTimeSeconds extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,9 @@ export class DemoDateTimeDateTimeShortYear extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,9 @@ export class DemoDateTimeDateTimeShort extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,9 @@ export class DemoDateTimeDateTime extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,9 @@ export class DemoDateTimeDate extends LitElement {
 | 
			
		||||
          <div class="center">Month-Day-Year</div>
 | 
			
		||||
          <div class="center">Year-Month-Day</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,9 @@ export class DemoDateTimeTimeSeconds extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,9 @@ export class DemoDateTimeTimeWeekday extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,9 @@ export class DemoDateTimeTime extends LitElement {
 | 
			
		||||
          <div class="center">12 Hours</div>
 | 
			
		||||
          <div class="center">24 Hours</div>
 | 
			
		||||
        </div>
 | 
			
		||||
        ${Object.entries(translationMetadata.translations).map(
 | 
			
		||||
        ${Object.entries(translationMetadata.translations)
 | 
			
		||||
          .filter(([key, _]) => key !== "test")
 | 
			
		||||
          .map(
 | 
			
		||||
            ([key, value]) => html`
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div>${value.nativeName}</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ const ENTITIES = [
 | 
			
		||||
    latitude: 32.877105,
 | 
			
		||||
    longitude: 117.232185,
 | 
			
		||||
    gps_accuracy: 91,
 | 
			
		||||
    battery: 25,
 | 
			
		||||
    battery: 71,
 | 
			
		||||
    friendly_name: "Paulus",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("device_tracker", "demo_anne_therese", "school", {
 | 
			
		||||
@@ -19,7 +19,7 @@ const ENTITIES = [
 | 
			
		||||
    latitude: 32.877105,
 | 
			
		||||
    longitude: 117.232185,
 | 
			
		||||
    gps_accuracy: 91,
 | 
			
		||||
    battery: 50,
 | 
			
		||||
    battery: 71,
 | 
			
		||||
    friendly_name: "Anne Therese",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("device_tracker", "demo_home_boy", "home", {
 | 
			
		||||
@@ -27,7 +27,7 @@ const ENTITIES = [
 | 
			
		||||
    latitude: 32.877105,
 | 
			
		||||
    longitude: 117.232185,
 | 
			
		||||
    gps_accuracy: 91,
 | 
			
		||||
    battery: 75,
 | 
			
		||||
    battery: 71,
 | 
			
		||||
    friendly_name: "Home Boy",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("light", "bed_light", "on", {
 | 
			
		||||
@@ -39,53 +39,21 @@ const ENTITIES = [
 | 
			
		||||
  getEntity("light", "ceiling_lights", "off", {
 | 
			
		||||
    friendly_name: "Ceiling Lights",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("sensor", "battery_1", 20, {
 | 
			
		||||
    device_class: "battery",
 | 
			
		||||
    friendly_name: "Battery 1",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("sensor", "battery_2", 35, {
 | 
			
		||||
    device_class: "battery",
 | 
			
		||||
    friendly_name: "Battery 2",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("sensor", "battery_3", 40, {
 | 
			
		||||
    device_class: "battery",
 | 
			
		||||
    friendly_name: "Battery 3",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("sensor", "battery_4", 80, {
 | 
			
		||||
    device_class: "battery",
 | 
			
		||||
    friendly_name: "Battery 4",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("input_number", "min_battery_level", 30, {
 | 
			
		||||
    mode: "slider",
 | 
			
		||||
    step: 10,
 | 
			
		||||
    min: 0,
 | 
			
		||||
    max: 100,
 | 
			
		||||
    icon: "mdi:battery-alert-variant",
 | 
			
		||||
    friendly_name: "Minimum Battery Level",
 | 
			
		||||
    unit_of_measurement: "%",
 | 
			
		||||
  }),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const CONFIGS = [
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Unfiltered entities",
 | 
			
		||||
    heading: "Unfiltered controller",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entities
 | 
			
		||||
  entities:
 | 
			
		||||
    - device_tracker.demo_anne_therese
 | 
			
		||||
    - device_tracker.demo_home_boy
 | 
			
		||||
    - device_tracker.demo_paulus
 | 
			
		||||
  - light.bed_light
 | 
			
		||||
  - light.ceiling_lights
 | 
			
		||||
  - light.kitchen_lights
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "On and home entities",
 | 
			
		||||
    heading: "Filtered entities card",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
@@ -95,30 +63,11 @@ const CONFIGS = [
 | 
			
		||||
    - light.bed_light
 | 
			
		||||
    - light.ceiling_lights
 | 
			
		||||
    - light.kitchen_lights
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: state
 | 
			
		||||
      state:
 | 
			
		||||
  state_filter:
 | 
			
		||||
    - "on"
 | 
			
		||||
    - home
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Same state as Bed Light",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - device_tracker.demo_anne_therese
 | 
			
		||||
    - device_tracker.demo_home_boy
 | 
			
		||||
    - device_tracker.demo_paulus
 | 
			
		||||
    - light.bed_light
 | 
			
		||||
    - light.ceiling_lights
 | 
			
		||||
    - light.kitchen_lights
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: state
 | 
			
		||||
      state:
 | 
			
		||||
        - light.bed_light
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: 'With "entities" card config',
 | 
			
		||||
    config: `
 | 
			
		||||
@@ -130,11 +79,9 @@ const CONFIGS = [
 | 
			
		||||
    - light.bed_light
 | 
			
		||||
    - light.ceiling_lights
 | 
			
		||||
    - light.kitchen_lights
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: state
 | 
			
		||||
      state:
 | 
			
		||||
  state_filter:
 | 
			
		||||
    - "on"
 | 
			
		||||
        - home
 | 
			
		||||
    - not_home
 | 
			
		||||
  card:
 | 
			
		||||
    type: entities
 | 
			
		||||
    title: Custom Title
 | 
			
		||||
@@ -152,101 +99,15 @@ const CONFIGS = [
 | 
			
		||||
    - light.bed_light
 | 
			
		||||
    - light.ceiling_lights
 | 
			
		||||
    - light.kitchen_lights
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: state
 | 
			
		||||
      state:
 | 
			
		||||
  state_filter:
 | 
			
		||||
    - "on"
 | 
			
		||||
        - home
 | 
			
		||||
    - not_home
 | 
			
		||||
  card:
 | 
			
		||||
    type: glance
 | 
			
		||||
    show_state: true
 | 
			
		||||
    title: Custom Title
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading:
 | 
			
		||||
      "Filtered entities by battery attribute (< '30') using state filter",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - device_tracker.demo_anne_therese
 | 
			
		||||
    - device_tracker.demo_home_boy
 | 
			
		||||
    - device_tracker.demo_paulus
 | 
			
		||||
  state_filter:
 | 
			
		||||
    - operator: <
 | 
			
		||||
      attribute: battery
 | 
			
		||||
      value: "30"
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Unfiltered number entities",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entities
 | 
			
		||||
  entities:
 | 
			
		||||
    - input_number.min_battery_level
 | 
			
		||||
    - sensor.battery_1
 | 
			
		||||
    - sensor.battery_3
 | 
			
		||||
    - sensor.battery_2
 | 
			
		||||
    - sensor.battery_4
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Battery lower than 50%",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - sensor.battery_1
 | 
			
		||||
    - sensor.battery_3
 | 
			
		||||
    - sensor.battery_2
 | 
			
		||||
    - sensor.battery_4
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: numeric_state
 | 
			
		||||
      below: 50
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Battery lower than min battery level",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - sensor.battery_1
 | 
			
		||||
    - sensor.battery_3
 | 
			
		||||
    - sensor.battery_2
 | 
			
		||||
    - sensor.battery_4
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: numeric_state
 | 
			
		||||
      below: input_number.min_battery_level
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Battery between min battery level and 70%",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - sensor.battery_1
 | 
			
		||||
    - sensor.battery_3
 | 
			
		||||
    - sensor.battery_2
 | 
			
		||||
    - sensor.battery_4
 | 
			
		||||
  conditions:
 | 
			
		||||
    - condition: numeric_state
 | 
			
		||||
      above: input_number.min_battery_level
 | 
			
		||||
      below: 70
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Error: Entities must be specified",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Error: Incorrect filter config",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: entity-filter
 | 
			
		||||
  entities:
 | 
			
		||||
    - sensor.gas_station_lowest_price
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@customElement("demo-lovelace-entity-filter-card")
 | 
			
		||||
 
 | 
			
		||||
@@ -36,45 +36,6 @@ const ENTITIES = [
 | 
			
		||||
    friendly_name: "Nest",
 | 
			
		||||
    supported_features: 43,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "overkiz_radiator", "heat", {
 | 
			
		||||
    current_temperature: 18,
 | 
			
		||||
    min_temp: 7,
 | 
			
		||||
    max_temp: 35,
 | 
			
		||||
    temperature: 20,
 | 
			
		||||
    hvac_modes: ["heat", "auto", "off"],
 | 
			
		||||
    friendly_name: "Overkiz radiator",
 | 
			
		||||
    supported_features: 17,
 | 
			
		||||
    preset_mode: "comfort",
 | 
			
		||||
    preset_modes: [
 | 
			
		||||
      "none",
 | 
			
		||||
      "frost_protection",
 | 
			
		||||
      "eco",
 | 
			
		||||
      "comfort",
 | 
			
		||||
      "comfort-1",
 | 
			
		||||
      "comfort-2",
 | 
			
		||||
      "auto",
 | 
			
		||||
      "boost",
 | 
			
		||||
      "external",
 | 
			
		||||
      "prog",
 | 
			
		||||
    ],
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "overkiz_towel_dryer", "heat", {
 | 
			
		||||
    current_temperature: null,
 | 
			
		||||
    min_temp: 7,
 | 
			
		||||
    max_temp: 35,
 | 
			
		||||
    hvac_modes: ["heat", "off"],
 | 
			
		||||
    friendly_name: "Overkiz towel dryer",
 | 
			
		||||
    supported_features: 16,
 | 
			
		||||
    preset_mode: "eco",
 | 
			
		||||
    preset_modes: [
 | 
			
		||||
      "none",
 | 
			
		||||
      "frost_protection",
 | 
			
		||||
      "eco",
 | 
			
		||||
      "comfort",
 | 
			
		||||
      "comfort-1",
 | 
			
		||||
      "comfort-2",
 | 
			
		||||
    ],
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "sensibo", "fan_only", {
 | 
			
		||||
    current_temperature: null,
 | 
			
		||||
    temperature: null,
 | 
			
		||||
@@ -85,9 +46,7 @@ const ENTITIES = [
 | 
			
		||||
    friendly_name: "Sensibo purifier",
 | 
			
		||||
    fan_modes: ["low", "high"],
 | 
			
		||||
    fan_mode: "low",
 | 
			
		||||
    swing_modes: ["on", "off", "both", "vertical", "horizontal"],
 | 
			
		||||
    swing_mode: "vertical",
 | 
			
		||||
    supported_features: 41,
 | 
			
		||||
    supported_features: 9,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "unavailable", "unavailable", {
 | 
			
		||||
    supported_features: 43,
 | 
			
		||||
@@ -100,6 +59,8 @@ const CONFIGS = [
 | 
			
		||||
    config: `
 | 
			
		||||
- type: thermostat
 | 
			
		||||
  entity: climate.ecobee
 | 
			
		||||
- type: thermostat
 | 
			
		||||
  entity: climate.nest
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
@@ -109,66 +70,6 @@ const CONFIGS = [
 | 
			
		||||
  entity: climate.nest
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Feature example",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: thermostat
 | 
			
		||||
  entity: climate.overkiz_radiator
 | 
			
		||||
  features:
 | 
			
		||||
    - type: climate-hvac-modes
 | 
			
		||||
      hvac_modes:
 | 
			
		||||
        - heat
 | 
			
		||||
        - 'off'
 | 
			
		||||
        - auto
 | 
			
		||||
    - type: climate-preset-modes
 | 
			
		||||
      style: icons
 | 
			
		||||
      preset_modes:
 | 
			
		||||
        - none
 | 
			
		||||
        - frost_protection
 | 
			
		||||
        - eco
 | 
			
		||||
        - comfort
 | 
			
		||||
        - comfort-1
 | 
			
		||||
        - comfort-2
 | 
			
		||||
        - auto
 | 
			
		||||
        - boost
 | 
			
		||||
        - external
 | 
			
		||||
        - prog
 | 
			
		||||
    - type: climate-preset-modes
 | 
			
		||||
      style: dropdown
 | 
			
		||||
      preset_modes:
 | 
			
		||||
        - none
 | 
			
		||||
        - frost_protection
 | 
			
		||||
        - eco
 | 
			
		||||
        - comfort
 | 
			
		||||
        - comfort-1
 | 
			
		||||
        - comfort-2
 | 
			
		||||
        - auto
 | 
			
		||||
        - boost
 | 
			
		||||
        - external
 | 
			
		||||
        - prog
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Preset only example",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: thermostat
 | 
			
		||||
  entity: climate.overkiz_towel_dryer
 | 
			
		||||
  features:
 | 
			
		||||
    - type: climate-hvac-modes
 | 
			
		||||
      hvac_modes:
 | 
			
		||||
        - heat
 | 
			
		||||
        - 'off'
 | 
			
		||||
    - type: climate-preset-modes
 | 
			
		||||
      style: icons
 | 
			
		||||
      preset_modes:
 | 
			
		||||
        - none
 | 
			
		||||
        - frost_protection
 | 
			
		||||
        - eco
 | 
			
		||||
        - comfort
 | 
			
		||||
        - comfort-1
 | 
			
		||||
        - comfort-2
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Fan only example",
 | 
			
		||||
    config: `
 | 
			
		||||
@@ -184,14 +85,6 @@ const CONFIGS = [
 | 
			
		||||
      fan_modes:
 | 
			
		||||
        - low
 | 
			
		||||
        - high
 | 
			
		||||
    - type: climate-swing-modes
 | 
			
		||||
      style: icons
 | 
			
		||||
      swing_modes:
 | 
			
		||||
        - 'on'
 | 
			
		||||
        - 'off'
 | 
			
		||||
        - 'both'
 | 
			
		||||
        - 'vertical'
 | 
			
		||||
        - 'horizontal'
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
 | 
			
		||||
import { customElement, query } from "lit/decorators";
 | 
			
		||||
import { CoverEntityFeature } from "../../../../src/data/cover";
 | 
			
		||||
import { LightColorMode } from "../../../../src/data/light";
 | 
			
		||||
import { LockEntityFeature } from "../../../../src/data/lock";
 | 
			
		||||
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
 | 
			
		||||
import { getEntity } from "../../../../src/fake_data/entity";
 | 
			
		||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
 | 
			
		||||
@@ -21,11 +20,6 @@ const ENTITIES = [
 | 
			
		||||
  getEntity("light", "unavailable", "unavailable", {
 | 
			
		||||
    friendly_name: "Unavailable entity",
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("lock", "front_door", "locked", {
 | 
			
		||||
    friendly_name: "Front Door Lock",
 | 
			
		||||
    device_class: "lock",
 | 
			
		||||
    supported_features: LockEntityFeature.OPEN,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "thermostat", "heat", {
 | 
			
		||||
    current_temperature: 73,
 | 
			
		||||
    min_temp: 45,
 | 
			
		||||
@@ -144,24 +138,6 @@ const CONFIGS = [
 | 
			
		||||
    - type: "color-temp"
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Lock commands feature",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: tile
 | 
			
		||||
  entity: lock.front_door
 | 
			
		||||
  features:
 | 
			
		||||
    - type: "lock-commands"
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Lock open door feature",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: tile
 | 
			
		||||
  entity: lock.front_door
 | 
			
		||||
  features:
 | 
			
		||||
    - type: "lock-open-door"
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Vacuum commands feature",
 | 
			
		||||
    config: `
 | 
			
		||||
 
 | 
			
		||||
@@ -368,7 +368,6 @@ export class DemoEntityState extends LitElement {
 | 
			
		||||
              hass.localize,
 | 
			
		||||
              entry.stateObj,
 | 
			
		||||
              hass.locale,
 | 
			
		||||
              [], // numericDeviceClasses
 | 
			
		||||
              hass.config,
 | 
			
		||||
              hass.entities
 | 
			
		||||
            )}`,
 | 
			
		||||
@@ -407,7 +406,6 @@ export class DemoEntityState extends LitElement {
 | 
			
		||||
          entity_id: "select.speed",
 | 
			
		||||
          translation_key: "speed",
 | 
			
		||||
          platform: "demo",
 | 
			
		||||
          labels: [],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -31,13 +31,10 @@ const createConfigEntry = (
 | 
			
		||||
  supports_options: false,
 | 
			
		||||
  supports_remove_device: false,
 | 
			
		||||
  supports_unload: true,
 | 
			
		||||
  supports_reconfigure: true,
 | 
			
		||||
  disabled_by: null,
 | 
			
		||||
  pref_disable_new_entities: false,
 | 
			
		||||
  pref_disable_polling: false,
 | 
			
		||||
  reason: null,
 | 
			
		||||
  error_reason_translation_key: null,
 | 
			
		||||
  error_reason_translation_placeholders: null,
 | 
			
		||||
  ...override,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@@ -201,8 +198,6 @@ const createEntityRegistryEntries = (
 | 
			
		||||
    has_entity_name: false,
 | 
			
		||||
    unique_id: "updater",
 | 
			
		||||
    options: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
    categories: {},
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@@ -226,7 +221,6 @@ const createDeviceRegistryEntries = (
 | 
			
		||||
    name_by_user: null,
 | 
			
		||||
    disabled_by: null,
 | 
			
		||||
    configuration_url: null,
 | 
			
		||||
    labels: [],
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ import "../../components/demo-more-infos";
 | 
			
		||||
import { ClimateEntityFeature } from "../../../../src/data/climate";
 | 
			
		||||
 | 
			
		||||
const ENTITIES = [
 | 
			
		||||
  getEntity("climate", "radiator", "heat", {
 | 
			
		||||
  getEntity("climate", "thermostat", "heat", {
 | 
			
		||||
    friendly_name: "Basic heater",
 | 
			
		||||
    hvac_modes: ["heat", "off"],
 | 
			
		||||
    hvac_mode: "heat",
 | 
			
		||||
@@ -80,24 +80,6 @@ const ENTITIES = [
 | 
			
		||||
    max_humidity: 100,
 | 
			
		||||
    humidity: 50,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "towel_dryer", "heat", {
 | 
			
		||||
    friendly_name: "Preset only heater",
 | 
			
		||||
    hvac_modes: ["heat", "off"],
 | 
			
		||||
    hvac_mode: "heat",
 | 
			
		||||
    preset_modes: [
 | 
			
		||||
      "none",
 | 
			
		||||
      "frost_protection",
 | 
			
		||||
      "eco",
 | 
			
		||||
      "comfort",
 | 
			
		||||
      "comfort-1",
 | 
			
		||||
      "comfort-2",
 | 
			
		||||
    ],
 | 
			
		||||
    preset_mode: "eco",
 | 
			
		||||
    current_temperature: null,
 | 
			
		||||
    min_temp: 7,
 | 
			
		||||
    max_temp: 35,
 | 
			
		||||
    supported_features: ClimateEntityFeature.PRESET_MODE,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("climate", "unavailable", "unavailable", {
 | 
			
		||||
    friendly_name: "Unavailable heater",
 | 
			
		||||
    hvac_modes: ["heat", "off"],
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,4 @@
 | 
			
		||||
import { globIterate } from "glob";
 | 
			
		||||
import { availableParallelism } from "node:os";
 | 
			
		||||
 | 
			
		||||
process.env.UV_THREADPOOL_SIZE = availableParallelism();
 | 
			
		||||
 | 
			
		||||
const gulpImports = [];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1263,7 +1263,6 @@ class HassioAddonInfo extends LitElement {
 | 
			
		||||
        .card-actions {
 | 
			
		||||
          justify-content: space-between;
 | 
			
		||||
          display: flex;
 | 
			
		||||
          direction: var(--direction);
 | 
			
		||||
        }
 | 
			
		||||
        .changelog {
 | 
			
		||||
          display: contents;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
import type { IFuseOptions } 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 { getStripDiacriticsFn } from "../../../src/util/fuse";
 | 
			
		||||
 | 
			
		||||
export function filterAndSort(addons: StoreAddon[], filter: string) {
 | 
			
		||||
  const options: IFuseOptions<StoreAddon> = {
 | 
			
		||||
@@ -10,8 +8,7 @@ export function filterAndSort(addons: StoreAddon[], filter: string) {
 | 
			
		||||
    isCaseSensitive: false,
 | 
			
		||||
    minMatchCharLength: Math.min(filter.length, 2),
 | 
			
		||||
    threshold: 0.2,
 | 
			
		||||
    getFn: getStripDiacriticsFn,
 | 
			
		||||
  };
 | 
			
		||||
  const fuse = new Fuse(addons, options);
 | 
			
		||||
  return fuse.search(stripDiacritics(filter)).map((result) => result.item);
 | 
			
		||||
  return fuse.search(filter).map((result) => result.item);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,19 @@
 | 
			
		||||
import { mdiRefresh, mdiStorePlus } from "@mdi/js";
 | 
			
		||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
 | 
			
		||||
import { mdiStorePlus, mdiUpdate } from "@mdi/js";
 | 
			
		||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators";
 | 
			
		||||
import { atLeastVersion } from "../../../src/common/config/version";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../src/components/ha-fab";
 | 
			
		||||
import { reloadHassioAddons } from "../../../src/data/hassio/addon";
 | 
			
		||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
 | 
			
		||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
 | 
			
		||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
 | 
			
		||||
import "../../../src/layouts/hass-subpage";
 | 
			
		||||
import "../../../src/layouts/hass-tabs-subpage";
 | 
			
		||||
import { haStyle } from "../../../src/resources/styles";
 | 
			
		||||
import { HomeAssistant, Route } from "../../../src/types";
 | 
			
		||||
import { supervisorTabs } from "../hassio-tabs";
 | 
			
		||||
import "./hassio-addons";
 | 
			
		||||
import "../../../src/layouts/hass-subpage";
 | 
			
		||||
import { reloadHassioAddons } from "../../../src/data/hassio/addon";
 | 
			
		||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
 | 
			
		||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
 | 
			
		||||
@customElement("hassio-dashboard")
 | 
			
		||||
class HassioDashboard extends LitElement {
 | 
			
		||||
@@ -43,7 +43,7 @@ class HassioDashboard extends LitElement {
 | 
			
		||||
        <ha-icon-button
 | 
			
		||||
          slot="toolbar-icon"
 | 
			
		||||
          @click=${this._handleCheckUpdates}
 | 
			
		||||
          .path=${mdiRefresh}
 | 
			
		||||
          .path=${mdiUpdate}
 | 
			
		||||
          .label=${this.supervisor.localize("store.check_updates")}
 | 
			
		||||
        ></ha-icon-button>
 | 
			
		||||
        <hassio-addons
 | 
			
		||||
 
 | 
			
		||||
@@ -154,16 +154,12 @@ class HassioHardwareDialog extends LitElement {
 | 
			
		||||
        ha-icon-button {
 | 
			
		||||
          position: absolute;
 | 
			
		||||
          right: 16px;
 | 
			
		||||
          inset-inline-end: 16px;
 | 
			
		||||
          inset-inline-start: initial;
 | 
			
		||||
          top: 10px;
 | 
			
		||||
          text-decoration: none;
 | 
			
		||||
          color: var(--primary-text-color);
 | 
			
		||||
        }
 | 
			
		||||
        h2 {
 | 
			
		||||
          margin: 18px 42px 0 18px;
 | 
			
		||||
          margin-inline-start: 18px;
 | 
			
		||||
          margin-inline-end: 42px;
 | 
			
		||||
          color: var(--primary-text-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
import { mdiDelete, mdiDeleteOff } from "@mdi/js";
 | 
			
		||||
import "@polymer/paper-item/paper-item";
 | 
			
		||||
import "@polymer/paper-item/paper-item-body";
 | 
			
		||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
 | 
			
		||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
 | 
			
		||||
import { customElement, property, query, state } from "lit/decorators";
 | 
			
		||||
@@ -25,8 +27,6 @@ import type { HomeAssistant } from "../../../../src/types";
 | 
			
		||||
import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
 | 
			
		||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
 | 
			
		||||
import "../../../../src/components/ha-textfield";
 | 
			
		||||
import "../../../../src/components/ha-list-new";
 | 
			
		||||
import "../../../../src/components/ha-list-item-new";
 | 
			
		||||
 | 
			
		||||
@customElement("dialog-hassio-repositories")
 | 
			
		||||
class HassioRepositoriesDialog extends LitElement {
 | 
			
		||||
@@ -106,17 +106,16 @@ class HassioRepositoriesDialog extends LitElement {
 | 
			
		||||
          ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
 | 
			
		||||
          : ""}
 | 
			
		||||
        <div class="form">
 | 
			
		||||
          <ha-list-new>
 | 
			
		||||
          ${repositories.length
 | 
			
		||||
            ? repositories.map(
 | 
			
		||||
                (repo) => html`
 | 
			
		||||
                    <ha-list-item-new class="option">
 | 
			
		||||
                      ${repo.name}
 | 
			
		||||
                      <div slot="supporting-text">
 | 
			
		||||
                        <div>${repo.maintainer}</div>
 | 
			
		||||
                        <div>${repo.url}</div>
 | 
			
		||||
                      </div>
 | 
			
		||||
                      <div class="delete" slot="end">
 | 
			
		||||
                  <paper-item class="option">
 | 
			
		||||
                    <paper-item-body three-line>
 | 
			
		||||
                      <div>${repo.name}</div>
 | 
			
		||||
                      <div secondary>${repo.maintainer}</div>
 | 
			
		||||
                      <div secondary>${repo.url}</div>
 | 
			
		||||
                    </paper-item-body>
 | 
			
		||||
                    <div class="delete">
 | 
			
		||||
                      <ha-icon-button
 | 
			
		||||
                        .label=${this._dialogParams!.supervisor.localize(
 | 
			
		||||
                          "dialog.repositories.remove"
 | 
			
		||||
@@ -141,11 +140,10 @@ class HassioRepositoriesDialog extends LitElement {
 | 
			
		||||
                        )}
 | 
			
		||||
                      </simple-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    </ha-list-item-new>
 | 
			
		||||
                  </paper-item>
 | 
			
		||||
                `
 | 
			
		||||
              )
 | 
			
		||||
              : html`<ha-list-item-new> No repositories </ha-list-item-new>`}
 | 
			
		||||
          </ha-list-new>
 | 
			
		||||
            : html`<paper-item> No repositories </paper-item>`}
 | 
			
		||||
          <div class="layout horizontal bottom">
 | 
			
		||||
            <ha-textfield
 | 
			
		||||
              class="flex-auto"
 | 
			
		||||
@@ -208,9 +206,6 @@ class HassioRepositoriesDialog extends LitElement {
 | 
			
		||||
        div.delete ha-icon-button {
 | 
			
		||||
          color: var(--error-color);
 | 
			
		||||
        }
 | 
			
		||||
        ha-list-item-new {
 | 
			
		||||
          position: relative;
 | 
			
		||||
        }
 | 
			
		||||
      `,
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										196
									
								
								package.json
									
									
									
									
									
								
							
							
						
						@@ -25,36 +25,36 @@
 | 
			
		||||
  "license": "Apache-2.0",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@babel/runtime": "7.24.7",
 | 
			
		||||
    "@braintree/sanitize-url": "7.0.3",
 | 
			
		||||
    "@codemirror/autocomplete": "6.16.3",
 | 
			
		||||
    "@codemirror/commands": "6.6.0",
 | 
			
		||||
    "@codemirror/language": "6.10.2",
 | 
			
		||||
    "@codemirror/legacy-modes": "6.4.0",
 | 
			
		||||
    "@babel/runtime": "7.23.9",
 | 
			
		||||
    "@braintree/sanitize-url": "7.0.0",
 | 
			
		||||
    "@codemirror/autocomplete": "6.12.0",
 | 
			
		||||
    "@codemirror/commands": "6.3.3",
 | 
			
		||||
    "@codemirror/language": "6.10.1",
 | 
			
		||||
    "@codemirror/legacy-modes": "6.3.3",
 | 
			
		||||
    "@codemirror/search": "6.5.6",
 | 
			
		||||
    "@codemirror/state": "6.4.1",
 | 
			
		||||
    "@codemirror/view": "6.28.2",
 | 
			
		||||
    "@codemirror/state": "6.4.0",
 | 
			
		||||
    "@codemirror/view": "6.24.0",
 | 
			
		||||
    "@egjs/hammerjs": "2.0.17",
 | 
			
		||||
    "@formatjs/intl-datetimeformat": "6.12.5",
 | 
			
		||||
    "@formatjs/intl-displaynames": "6.6.8",
 | 
			
		||||
    "@formatjs/intl-datetimeformat": "6.12.2",
 | 
			
		||||
    "@formatjs/intl-displaynames": "6.6.6",
 | 
			
		||||
    "@formatjs/intl-getcanonicallocales": "2.3.0",
 | 
			
		||||
    "@formatjs/intl-listformat": "7.5.7",
 | 
			
		||||
    "@formatjs/intl-locale": "4.0.0",
 | 
			
		||||
    "@formatjs/intl-numberformat": "8.10.3",
 | 
			
		||||
    "@formatjs/intl-pluralrules": "5.2.14",
 | 
			
		||||
    "@formatjs/intl-relativetimeformat": "11.2.14",
 | 
			
		||||
    "@fullcalendar/core": "6.1.11",
 | 
			
		||||
    "@fullcalendar/daygrid": "6.1.11",
 | 
			
		||||
    "@fullcalendar/interaction": "6.1.11",
 | 
			
		||||
    "@fullcalendar/list": "6.1.11",
 | 
			
		||||
    "@fullcalendar/luxon3": "6.1.11",
 | 
			
		||||
    "@fullcalendar/timegrid": "6.1.11",
 | 
			
		||||
    "@formatjs/intl-listformat": "7.5.5",
 | 
			
		||||
    "@formatjs/intl-locale": "3.4.5",
 | 
			
		||||
    "@formatjs/intl-numberformat": "8.10.0",
 | 
			
		||||
    "@formatjs/intl-pluralrules": "5.2.12",
 | 
			
		||||
    "@formatjs/intl-relativetimeformat": "11.2.12",
 | 
			
		||||
    "@fullcalendar/core": "6.1.10",
 | 
			
		||||
    "@fullcalendar/daygrid": "6.1.10",
 | 
			
		||||
    "@fullcalendar/interaction": "6.1.10",
 | 
			
		||||
    "@fullcalendar/list": "6.1.10",
 | 
			
		||||
    "@fullcalendar/luxon3": "6.1.10",
 | 
			
		||||
    "@fullcalendar/timegrid": "6.1.10",
 | 
			
		||||
    "@lezer/highlight": "1.2.0",
 | 
			
		||||
    "@lit-labs/context": "0.4.1",
 | 
			
		||||
    "@lit-labs/motion": "1.0.7",
 | 
			
		||||
    "@lit-labs/observers": "2.0.2",
 | 
			
		||||
    "@lit-labs/virtualizer": "2.0.13",
 | 
			
		||||
    "@lrnwebcomponents/simple-tooltip": "8.0.2",
 | 
			
		||||
    "@lit-labs/virtualizer": "2.0.12",
 | 
			
		||||
    "@lrnwebcomponents/simple-tooltip": "patch:@lrnwebcomponents/simple-tooltip@npm%3A8.0.0#~/.yarn/patches/@lrnwebcomponents-simple-tooltip-npm-8.0.0-77591f2e0c.patch",
 | 
			
		||||
    "@material/chips": "=14.0.0-canary.53b3cad2f.0",
 | 
			
		||||
    "@material/data-table": "=14.0.0-canary.53b3cad2f.0",
 | 
			
		||||
    "@material/mwc-base": "0.27.0",
 | 
			
		||||
@@ -70,8 +70,8 @@
 | 
			
		||||
    "@material/mwc-list": "0.27.0",
 | 
			
		||||
    "@material/mwc-menu": "0.27.0",
 | 
			
		||||
    "@material/mwc-radio": "0.27.0",
 | 
			
		||||
    "@material/mwc-ripple": "0.27.0",
 | 
			
		||||
    "@material/mwc-select": "0.27.0",
 | 
			
		||||
    "@material/mwc-snackbar": "0.27.0",
 | 
			
		||||
    "@material/mwc-switch": "0.27.0",
 | 
			
		||||
    "@material/mwc-tab": "0.27.0",
 | 
			
		||||
    "@material/mwc-tab-bar": "0.27.0",
 | 
			
		||||
@@ -80,16 +80,17 @@
 | 
			
		||||
    "@material/mwc-top-app-bar": "0.27.0",
 | 
			
		||||
    "@material/mwc-top-app-bar-fixed": "0.27.0",
 | 
			
		||||
    "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
 | 
			
		||||
    "@material/web": "1.5.0",
 | 
			
		||||
    "@material/web": "=1.2.0",
 | 
			
		||||
    "@mdi/js": "7.4.47",
 | 
			
		||||
    "@mdi/svg": "7.4.47",
 | 
			
		||||
    "@polymer/paper-item": "3.0.1",
 | 
			
		||||
    "@polymer/paper-listbox": "3.0.1",
 | 
			
		||||
    "@polymer/paper-tabs": "3.1.0",
 | 
			
		||||
    "@polymer/paper-toast": "3.0.1",
 | 
			
		||||
    "@polymer/polymer": "3.5.1",
 | 
			
		||||
    "@thomasloven/round-slider": "0.6.0",
 | 
			
		||||
    "@vaadin/combo-box": "24.4.0",
 | 
			
		||||
    "@vaadin/vaadin-themable-mixin": "24.4.0",
 | 
			
		||||
    "@vaadin/combo-box": "24.3.6",
 | 
			
		||||
    "@vaadin/vaadin-themable-mixin": "24.3.6",
 | 
			
		||||
    "@vibrant/color": "3.2.1-alpha.1",
 | 
			
		||||
    "@vibrant/core": "3.2.1-alpha.1",
 | 
			
		||||
    "@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
 | 
			
		||||
@@ -97,28 +98,27 @@
 | 
			
		||||
    "@webcomponents/scoped-custom-element-registry": "0.0.9",
 | 
			
		||||
    "@webcomponents/webcomponentsjs": "2.8.0",
 | 
			
		||||
    "app-datepicker": "5.1.1",
 | 
			
		||||
    "chart.js": "4.4.3",
 | 
			
		||||
    "color-name": "2.0.0",
 | 
			
		||||
    "chart.js": "4.4.1",
 | 
			
		||||
    "comlink": "4.4.1",
 | 
			
		||||
    "core-js": "3.37.1",
 | 
			
		||||
    "cropperjs": "1.6.2",
 | 
			
		||||
    "date-fns": "3.6.0",
 | 
			
		||||
    "date-fns-tz": "3.1.3",
 | 
			
		||||
    "core-js": "3.36.0",
 | 
			
		||||
    "cropperjs": "1.6.1",
 | 
			
		||||
    "date-fns": "2.30.0",
 | 
			
		||||
    "date-fns-tz": "2.0.0",
 | 
			
		||||
    "deep-clone-simple": "1.1.1",
 | 
			
		||||
    "deep-freeze": "0.0.1",
 | 
			
		||||
    "element-internals-polyfill": "1.3.11",
 | 
			
		||||
    "element-internals-polyfill": "1.3.10",
 | 
			
		||||
    "fuse.js": "7.0.0",
 | 
			
		||||
    "google-timezones-json": "1.2.0",
 | 
			
		||||
    "hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
 | 
			
		||||
    "home-assistant-js-websocket": "9.4.0",
 | 
			
		||||
    "hls.js": "1.5.5",
 | 
			
		||||
    "home-assistant-js-websocket": "9.1.0",
 | 
			
		||||
    "idb-keyval": "6.2.1",
 | 
			
		||||
    "intl-messageformat": "10.5.14",
 | 
			
		||||
    "intl-messageformat": "10.5.11",
 | 
			
		||||
    "js-yaml": "4.1.0",
 | 
			
		||||
    "leaflet": "1.9.4",
 | 
			
		||||
    "leaflet-draw": "1.0.4",
 | 
			
		||||
    "lit": "2.8.0",
 | 
			
		||||
    "luxon": "3.4.4",
 | 
			
		||||
    "marked": "12.0.2",
 | 
			
		||||
    "marked": "12.0.0",
 | 
			
		||||
    "memoize-one": "6.0.0",
 | 
			
		||||
    "node-vibrant": "3.2.1-alpha.1",
 | 
			
		||||
    "proxy-polyfill": "0.3.2",
 | 
			
		||||
@@ -129,122 +129,127 @@
 | 
			
		||||
    "rrule": "2.8.1",
 | 
			
		||||
    "sortablejs": "1.15.2",
 | 
			
		||||
    "stacktrace-js": "2.0.2",
 | 
			
		||||
    "superstruct": "1.0.4",
 | 
			
		||||
    "superstruct": "1.0.3",
 | 
			
		||||
    "tinykeys": "2.1.0",
 | 
			
		||||
    "tsparticles-engine": "2.12.0",
 | 
			
		||||
    "tsparticles-preset-links": "2.12.0",
 | 
			
		||||
    "ua-parser-js": "1.0.38",
 | 
			
		||||
    "ua-parser-js": "1.0.37",
 | 
			
		||||
    "unfetch": "5.0.0",
 | 
			
		||||
    "vis-data": "7.1.9",
 | 
			
		||||
    "vis-network": "9.1.9",
 | 
			
		||||
    "vue": "2.7.16",
 | 
			
		||||
    "vue2-daterange-picker": "0.6.8",
 | 
			
		||||
    "weekstart": "2.0.0",
 | 
			
		||||
    "workbox-cacheable-response": "7.1.0",
 | 
			
		||||
    "workbox-core": "7.1.0",
 | 
			
		||||
    "workbox-expiration": "7.1.0",
 | 
			
		||||
    "workbox-precaching": "7.1.0",
 | 
			
		||||
    "workbox-routing": "7.1.0",
 | 
			
		||||
    "workbox-strategies": "7.1.0",
 | 
			
		||||
    "xss": "1.0.15"
 | 
			
		||||
    "workbox-cacheable-response": "7.0.0",
 | 
			
		||||
    "workbox-core": "7.0.0",
 | 
			
		||||
    "workbox-expiration": "7.0.0",
 | 
			
		||||
    "workbox-precaching": "7.0.0",
 | 
			
		||||
    "workbox-routing": "7.0.0",
 | 
			
		||||
    "workbox-strategies": "7.0.0",
 | 
			
		||||
    "xss": "1.0.14"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/core": "7.24.7",
 | 
			
		||||
    "@babel/helper-define-polyfill-provider": "0.6.2",
 | 
			
		||||
    "@babel/plugin-proposal-decorators": "7.24.7",
 | 
			
		||||
    "@babel/plugin-transform-runtime": "7.24.7",
 | 
			
		||||
    "@babel/preset-env": "7.24.7",
 | 
			
		||||
    "@babel/preset-typescript": "7.24.7",
 | 
			
		||||
    "@bundle-stats/plugin-webpack-filter": "4.13.2",
 | 
			
		||||
    "@babel/core": "7.23.9",
 | 
			
		||||
    "@babel/helper-define-polyfill-provider": "0.5.0",
 | 
			
		||||
    "@babel/plugin-proposal-decorators": "7.23.9",
 | 
			
		||||
    "@babel/plugin-transform-runtime": "7.23.9",
 | 
			
		||||
    "@babel/preset-env": "7.23.9",
 | 
			
		||||
    "@babel/preset-typescript": "7.23.3",
 | 
			
		||||
    "@bundle-stats/plugin-webpack-filter": "4.10.0",
 | 
			
		||||
    "@koa/cors": "5.0.0",
 | 
			
		||||
    "@lokalise/node-api": "12.5.0",
 | 
			
		||||
    "@octokit/auth-oauth-device": "7.1.1",
 | 
			
		||||
    "@octokit/plugin-retry": "7.1.1",
 | 
			
		||||
    "@octokit/rest": "21.0.0",
 | 
			
		||||
    "@lokalise/node-api": "12.1.0",
 | 
			
		||||
    "@octokit/auth-oauth-device": "6.0.1",
 | 
			
		||||
    "@octokit/plugin-retry": "6.0.1",
 | 
			
		||||
    "@octokit/rest": "20.0.2",
 | 
			
		||||
    "@open-wc/dev-server-hmr": "0.1.4",
 | 
			
		||||
    "@rollup/plugin-babel": "6.0.4",
 | 
			
		||||
    "@rollup/plugin-commonjs": "26.0.1",
 | 
			
		||||
    "@rollup/plugin-commonjs": "25.0.7",
 | 
			
		||||
    "@rollup/plugin-json": "6.1.0",
 | 
			
		||||
    "@rollup/plugin-node-resolve": "15.2.3",
 | 
			
		||||
    "@rollup/plugin-replace": "5.0.7",
 | 
			
		||||
    "@rollup/plugin-replace": "5.0.5",
 | 
			
		||||
    "@types/babel__plugin-transform-runtime": "7.9.5",
 | 
			
		||||
    "@types/chromecast-caf-receiver": "6.0.15",
 | 
			
		||||
    "@types/chromecast-caf-sender": "1.0.10",
 | 
			
		||||
    "@types/color-name": "1.1.4",
 | 
			
		||||
    "@types/chromecast-caf-receiver": "6.0.13",
 | 
			
		||||
    "@types/chromecast-caf-sender": "1.0.8",
 | 
			
		||||
    "@types/glob": "8.1.0",
 | 
			
		||||
    "@types/gulp-if": "^3",
 | 
			
		||||
    "@types/html-minifier-terser": "7.0.2",
 | 
			
		||||
    "@types/js-yaml": "4.0.9",
 | 
			
		||||
    "@types/leaflet": "1.9.12",
 | 
			
		||||
    "@types/leaflet": "1.9.8",
 | 
			
		||||
    "@types/leaflet-draw": "1.0.11",
 | 
			
		||||
    "@types/lodash.merge": "4.6.9",
 | 
			
		||||
    "@types/luxon": "3.4.2",
 | 
			
		||||
    "@types/mocha": "10.0.7",
 | 
			
		||||
    "@types/mocha": "10.0.6",
 | 
			
		||||
    "@types/qrcode": "1.5.5",
 | 
			
		||||
    "@types/serve-handler": "6.1.4",
 | 
			
		||||
    "@types/sortablejs": "1.15.8",
 | 
			
		||||
    "@types/tar": "6.1.13",
 | 
			
		||||
    "@types/sortablejs": "1.15.7",
 | 
			
		||||
    "@types/tar": "6.1.11",
 | 
			
		||||
    "@types/ua-parser-js": "0.7.39",
 | 
			
		||||
    "@types/webspeechapi": "0.0.29",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "7.13.1",
 | 
			
		||||
    "@typescript-eslint/parser": "7.13.1",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "7.0.1",
 | 
			
		||||
    "@typescript-eslint/parser": "7.0.1",
 | 
			
		||||
    "@web/dev-server": "0.1.38",
 | 
			
		||||
    "@web/dev-server-rollup": "0.4.1",
 | 
			
		||||
    "babel-loader": "9.1.3",
 | 
			
		||||
    "babel-plugin-template-html-minifier": "4.1.0",
 | 
			
		||||
    "chai": "5.1.1",
 | 
			
		||||
    "chai": "5.1.0",
 | 
			
		||||
    "del": "7.1.0",
 | 
			
		||||
    "eslint": "8.57.0",
 | 
			
		||||
    "eslint": "8.56.0",
 | 
			
		||||
    "eslint-config-airbnb-base": "15.0.0",
 | 
			
		||||
    "eslint-config-airbnb-typescript": "18.0.0",
 | 
			
		||||
    "eslint-config-airbnb-typescript": "17.1.0",
 | 
			
		||||
    "eslint-config-prettier": "9.1.0",
 | 
			
		||||
    "eslint-import-resolver-webpack": "0.13.8",
 | 
			
		||||
    "eslint-plugin-disable": "2.0.3",
 | 
			
		||||
    "eslint-plugin-import": "2.29.1",
 | 
			
		||||
    "eslint-plugin-lit": "1.14.0",
 | 
			
		||||
    "eslint-plugin-lit": "1.11.0",
 | 
			
		||||
    "eslint-plugin-lit-a11y": "4.1.2",
 | 
			
		||||
    "eslint-plugin-unused-imports": "4.0.0",
 | 
			
		||||
    "eslint-plugin-wc": "2.1.0",
 | 
			
		||||
    "eslint-plugin-unused-imports": "3.1.0",
 | 
			
		||||
    "eslint-plugin-wc": "2.0.4",
 | 
			
		||||
    "fancy-log": "2.0.0",
 | 
			
		||||
    "fs-extra": "11.2.0",
 | 
			
		||||
    "glob": "10.4.2",
 | 
			
		||||
    "gulp": "5.0.0",
 | 
			
		||||
    "gulp-json-transform": "0.5.0",
 | 
			
		||||
    "glob": "10.3.10",
 | 
			
		||||
    "gulp": "4.0.2",
 | 
			
		||||
    "gulp-flatmap": "1.0.2",
 | 
			
		||||
    "gulp-if": "3.0.0",
 | 
			
		||||
    "gulp-json-transform": "0.4.8",
 | 
			
		||||
    "gulp-merge-json": "2.1.2",
 | 
			
		||||
    "gulp-rename": "2.0.0",
 | 
			
		||||
    "gulp-zopfli-green": "6.0.1",
 | 
			
		||||
    "html-minifier-terser": "7.2.0",
 | 
			
		||||
    "husky": "9.0.11",
 | 
			
		||||
    "instant-mocha": "1.5.2",
 | 
			
		||||
    "jszip": "3.10.1",
 | 
			
		||||
    "lint-staged": "15.2.7",
 | 
			
		||||
    "lint-staged": "15.2.2",
 | 
			
		||||
    "lit-analyzer": "2.0.3",
 | 
			
		||||
    "lodash.merge": "4.6.2",
 | 
			
		||||
    "lodash.template": "4.5.0",
 | 
			
		||||
    "magic-string": "0.30.10",
 | 
			
		||||
    "magic-string": "0.30.7",
 | 
			
		||||
    "map-stream": "0.0.7",
 | 
			
		||||
    "mocha": "10.4.0",
 | 
			
		||||
    "mocha": "10.3.0",
 | 
			
		||||
    "object-hash": "3.0.0",
 | 
			
		||||
    "open": "10.1.0",
 | 
			
		||||
    "open": "10.0.3",
 | 
			
		||||
    "pinst": "3.0.0",
 | 
			
		||||
    "prettier": "3.3.2",
 | 
			
		||||
    "prettier": "3.2.5",
 | 
			
		||||
    "rollup": "2.79.1",
 | 
			
		||||
    "rollup-plugin-string": "3.0.0",
 | 
			
		||||
    "rollup-plugin-terser": "7.0.2",
 | 
			
		||||
    "rollup-plugin-visualizer": "5.12.0",
 | 
			
		||||
    "serve-handler": "6.1.5",
 | 
			
		||||
    "sinon": "18.0.0",
 | 
			
		||||
    "sinon": "17.0.1",
 | 
			
		||||
    "source-map-url": "0.4.1",
 | 
			
		||||
    "systemjs": "6.15.1",
 | 
			
		||||
    "tar": "7.4.0",
 | 
			
		||||
    "systemjs": "6.14.3",
 | 
			
		||||
    "tar": "6.2.0",
 | 
			
		||||
    "terser-webpack-plugin": "5.3.10",
 | 
			
		||||
    "transform-async-modules-webpack-plugin": "1.1.1",
 | 
			
		||||
    "transform-async-modules-webpack-plugin": "1.0.2",
 | 
			
		||||
    "ts-lit-plugin": "2.0.2",
 | 
			
		||||
    "typescript": "5.5.2",
 | 
			
		||||
    "webpack": "5.92.1",
 | 
			
		||||
    "typescript": "5.3.3",
 | 
			
		||||
    "vinyl-buffer": "1.0.1",
 | 
			
		||||
    "vinyl-paths": "5.0.0",
 | 
			
		||||
    "vinyl-source-stream": "2.0.0",
 | 
			
		||||
    "webpack": "5.90.2",
 | 
			
		||||
    "webpack-cli": "5.1.4",
 | 
			
		||||
    "webpack-dev-server": "5.0.4",
 | 
			
		||||
    "webpack-dev-server": "4.15.1",
 | 
			
		||||
    "webpack-manifest-plugin": "5.0.0",
 | 
			
		||||
    "webpack-stats-plugin": "1.1.3",
 | 
			
		||||
    "webpackbar": "6.0.1",
 | 
			
		||||
    "workbox-build": "7.1.1"
 | 
			
		||||
    "webpackbar": "6.0.0",
 | 
			
		||||
    "workbox-build": "7.0.0"
 | 
			
		||||
  },
 | 
			
		||||
  "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
 | 
			
		||||
  "resolutions": {
 | 
			
		||||
@@ -253,9 +258,8 @@
 | 
			
		||||
    "lit": "2.8.0",
 | 
			
		||||
    "clean-css": "5.3.3",
 | 
			
		||||
    "@lit/reactive-element": "1.6.3",
 | 
			
		||||
    "@fullcalendar/daygrid": "6.1.11",
 | 
			
		||||
    "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"
 | 
			
		||||
  },
 | 
			
		||||
  "packageManager": "yarn@4.3.1"
 | 
			
		||||
  "packageManager": "yarn@4.1.0"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 7.1 KiB  | 
| 
		 Before Width: | Height: | Size: 19 KiB  | 
| 
		 Before Width: | Height: | Size: 8.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/static/images/logo_twitter.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 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  | 
| 
		 Before Width: | Height: | Size: 6.2 KiB  | 
| 
		 Before Width: | Height: | Size: 52 KiB  | 
| 
		 Before Width: | Height: | Size: 69 KiB  | 
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
 | 
			
		||||
 | 
			
		||||
[project]
 | 
			
		||||
name         = "home-assistant-frontend"
 | 
			
		||||
version      = "20240626.1"
 | 
			
		||||
version      = "20240207.0"
 | 
			
		||||
license      = {text = "Apache-2.0"}
 | 
			
		||||
description  = "The Home Assistant frontend"
 | 
			
		||||
readme       = "README.md"
 | 
			
		||||
authors      = [
 | 
			
		||||
    {name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
 | 
			
		||||
]
 | 
			
		||||
requires-python = ">=3.11.0"
 | 
			
		||||
requires-python = ">=3.10.0"
 | 
			
		||||
 | 
			
		||||
[project.urls]
 | 
			
		||||
"Homepage" = "https://github.com/home-assistant/frontend"
 | 
			
		||||
 
 | 
			
		||||
@@ -40,11 +40,6 @@
 | 
			
		||||
      "matchPackageNames": ["tsparticles-engine"],
 | 
			
		||||
      "matchPackagePrefixes": ["tsparticles-preset-"]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "description": "Group date-fns with dependent timezone package",
 | 
			
		||||
      "groupName": "date-fns",
 | 
			
		||||
      "matchPackageNames": ["date-fns", "date-fns-tz"]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "description": "Group and temporarily disable WDS packages",
 | 
			
		||||
      "groupName": "Web Dev Server",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
import { theme2hex } from "./convert-color";
 | 
			
		||||
 | 
			
		||||
export const COLORS = [
 | 
			
		||||
  "#44739e",
 | 
			
		||||
  "#984ea3",
 | 
			
		||||
@@ -67,10 +65,10 @@ export function getColorByIndex(index: number) {
 | 
			
		||||
export function getGraphColorByIndex(
 | 
			
		||||
  index: number,
 | 
			
		||||
  style: CSSStyleDeclaration
 | 
			
		||||
): string {
 | 
			
		||||
) {
 | 
			
		||||
  // The CSS vars for the colors use range 1..n, so we need to adjust the index from the internal 0..n color index range.
 | 
			
		||||
  const themeColor =
 | 
			
		||||
  return (
 | 
			
		||||
    style.getPropertyValue(`--graph-color-${index + 1}`) ||
 | 
			
		||||
    getColorByIndex(index);
 | 
			
		||||
  return theme2hex(themeColor);
 | 
			
		||||
    getColorByIndex(index)
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
import colors from "color-name";
 | 
			
		||||
import { expandHex } from "./hex";
 | 
			
		||||
 | 
			
		||||
const rgb_hex = (component: number): string => {
 | 
			
		||||
@@ -127,18 +126,3 @@ export const rgb2hs = (rgb: [number, number, number]): [number, number] =>
 | 
			
		||||
 | 
			
		||||
export const hs2rgb = (hs: [number, number]): [number, number, number] =>
 | 
			
		||||
  hsv2rgb([hs[0], hs[1], 255]);
 | 
			
		||||
 | 
			
		||||
export function theme2hex(themeColor: string): string {
 | 
			
		||||
  if (themeColor.startsWith("#")) {
 | 
			
		||||
    return themeColor;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const rgbFromColorName = colors[themeColor];
 | 
			
		||||
  if (!rgbFromColorName) {
 | 
			
		||||
    // We have a named color, and there's nothing in the table,
 | 
			
		||||
    // so nothing further we can do with it.
 | 
			
		||||
    // Compare/border/background color will all be the same.
 | 
			
		||||
    return themeColor;
 | 
			
		||||
  }
 | 
			
		||||
  return rgb2hex(rgbFromColorName);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,19 @@
 | 
			
		||||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
 | 
			
		||||
import { HomeAssistant } from "../../types";
 | 
			
		||||
import { ensureArray } from "../array/ensure-array";
 | 
			
		||||
import { isComponentLoaded } from "./is_component_loaded";
 | 
			
		||||
 | 
			
		||||
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) =>
 | 
			
		||||
  (isCore(page) || isLoadedIntegration(hass, page)) &&
 | 
			
		||||
  !hideAdvancedPage(hass, page) &&
 | 
			
		||||
  isNotLoadedIntegration(hass, page);
 | 
			
		||||
  !hideAdvancedPage(hass, page);
 | 
			
		||||
 | 
			
		||||
const isLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
 | 
			
		||||
  !page.component ||
 | 
			
		||||
  ensureArray(page.component).some((integration) =>
 | 
			
		||||
  page.component
 | 
			
		||||
    ? isComponentLoaded(hass, page.component)
 | 
			
		||||
    : page.components
 | 
			
		||||
      ? page.components.some((integration) =>
 | 
			
		||||
          isComponentLoaded(hass, integration)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
const isNotLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
 | 
			
		||||
  !page.not_component ||
 | 
			
		||||
  !ensureArray(page.not_component).some((integration) =>
 | 
			
		||||
    isComponentLoaded(hass, integration)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
        )
 | 
			
		||||
      : true;
 | 
			
		||||
const isCore = (page: PageNavigation) => page.core;
 | 
			
		||||
const isAdvancedPage = (page: PageNavigation) => page.advancedOnly;
 | 
			
		||||
const userWantsAdvanced = (hass: HomeAssistant) => hass.userData?.showAdvanced;
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,6 @@ import {
 | 
			
		||||
  mdiFormatListBulleted,
 | 
			
		||||
  mdiFormatListCheckbox,
 | 
			
		||||
  mdiFormTextbox,
 | 
			
		||||
  mdiForumOutline,
 | 
			
		||||
  mdiGauge,
 | 
			
		||||
  mdiGoogleAssistant,
 | 
			
		||||
  mdiGoogleCirclesCommunities,
 | 
			
		||||
@@ -99,7 +98,7 @@ export const FIXED_DOMAIN_ICONS = {
 | 
			
		||||
  calendar: mdiCalendar,
 | 
			
		||||
  climate: mdiThermostat,
 | 
			
		||||
  configurator: mdiCog,
 | 
			
		||||
  conversation: mdiForumOutline,
 | 
			
		||||
  conversation: mdiMicrophoneMessage,
 | 
			
		||||
  counter: mdiCounter,
 | 
			
		||||
  date: mdiCalendar,
 | 
			
		||||
  datetime: mdiCalendarClock,
 | 
			
		||||
@@ -232,12 +231,9 @@ export const SENSOR_ENTITIES = [
 | 
			
		||||
  "calendar",
 | 
			
		||||
  "camera",
 | 
			
		||||
  "device_tracker",
 | 
			
		||||
  "image",
 | 
			
		||||
  "weather",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const ASSIST_ENTITIES = ["conversation", "stt", "tts"];
 | 
			
		||||
 | 
			
		||||
/** Domains that render an input element instead of a text value when displayed in a row.
 | 
			
		||||
 *  Those rows should then not show a cursor pointer when hovered (which would normally
 | 
			
		||||
 *  be the default) unless the element itself enforces it (e.g. a button). Also those elements
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { toZonedTime, fromZonedTime } from "date-fns-tz";
 | 
			
		||||
import { utcToZonedTime, zonedTimeToUtc } from "date-fns-tz";
 | 
			
		||||
import { HassConfig } from "home-assistant-js-websocket";
 | 
			
		||||
import { FrontendLocaleData, TimeZone } from "../../data/translation";
 | 
			
		||||
 | 
			
		||||
@@ -8,10 +8,10 @@ const calcZonedDate = (
 | 
			
		||||
  fn: (date: Date, options?: any) => Date | number | boolean,
 | 
			
		||||
  options?
 | 
			
		||||
) => {
 | 
			
		||||
  const inputZoned = toZonedTime(date, tz);
 | 
			
		||||
  const inputZoned = utcToZonedTime(date, tz);
 | 
			
		||||
  const fnZoned = fn(inputZoned, options);
 | 
			
		||||
  if (fnZoned instanceof Date) {
 | 
			
		||||
    return fromZonedTime(fnZoned, tz) as Date;
 | 
			
		||||
    return zonedTimeToUtc(fnZoned, tz) as Date;
 | 
			
		||||
  }
 | 
			
		||||
  return fnZoned;
 | 
			
		||||
};
 | 
			
		||||
@@ -37,20 +37,3 @@ export const calcDateProperty = (
 | 
			
		||||
  locale.time_zone === TimeZone.server
 | 
			
		||||
    ? (calcZonedDate(date, config.time_zone, fn, options) as number | boolean)
 | 
			
		||||
    : fn(date, options);
 | 
			
		||||
 | 
			
		||||
export const calcDateDifferenceProperty = (
 | 
			
		||||
  endDate: Date,
 | 
			
		||||
  startDate: Date,
 | 
			
		||||
  fn: (date: Date, options?: any) => boolean | number,
 | 
			
		||||
  locale: FrontendLocaleData,
 | 
			
		||||
  config: HassConfig
 | 
			
		||||
) =>
 | 
			
		||||
  calcDateProperty(
 | 
			
		||||
    endDate,
 | 
			
		||||
    fn,
 | 
			
		||||
    locale,
 | 
			
		||||
    config,
 | 
			
		||||
    locale.time_zone === TimeZone.server
 | 
			
		||||
      ? toZonedTime(startDate, config.time_zone)
 | 
			
		||||
      : startDate
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
import { getWeekStartByLocale } from "weekstart";
 | 
			
		||||
import { FrontendLocaleData, FirstWeekday } from "../../data/translation";
 | 
			
		||||
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
 | 
			
		||||
export const weekdays = [
 | 
			
		||||
  "sunday",
 | 
			
		||||
  "monday",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { HassConfig } from "home-assistant-js-websocket";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { DateFormat, FrontendLocaleData } from "../../data/translation";
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
import { resolveTimeZone } from "./resolve-time-zone";
 | 
			
		||||
 | 
			
		||||
// Tuesday, August 10
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { HassConfig } from "home-assistant-js-websocket";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { FrontendLocaleData } from "../../data/translation";
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
import { formatDateNumeric } from "./format_date";
 | 
			
		||||
import { formatTime } from "./format_time";
 | 
			
		||||
import { resolveTimeZone } from "./resolve-time-zone";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { HaDurationData } from "../../components/ha-duration-input";
 | 
			
		||||
import { FrontendLocaleData } from "../../data/translation";
 | 
			
		||||
import "../../resources/intl-polyfill";
 | 
			
		||||
 | 
			
		||||
const leftPad = (num: number) => (num < 10 ? `0${num}` : num);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||