mirror of
				https://github.com/home-assistant/frontend.git
				synced 2025-11-04 00:19:47 +00:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
			20250903.2
			...
			gulp-ts
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					efece17f50 | ||
| 
						 | 
					79e5c59fdf | ||
| 
						 | 
					0aa34a14dd | ||
| 
						 | 
					1ced9959fa | ||
| 
						 | 
					1b67a6f358 | ||
| 
						 | 
					62f2b286ae | ||
| 
						 | 
					8f7760f88f | ||
| 
						 | 
					ff3b65605e | 
							
								
								
									
										6
									
								
								.github/copilot-instructions.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/copilot-instructions.md
									
									
									
									
										vendored
									
									
								
							@@ -310,11 +310,7 @@ export class DialogMyFeature
 | 
			
		||||
        .heading=${createCloseHeading(this.hass, this._params.title)}
 | 
			
		||||
      >
 | 
			
		||||
        <!-- Dialog content -->
 | 
			
		||||
        <ha-button
 | 
			
		||||
          appearance="plain"
 | 
			
		||||
          @click=${this.closeDialog}
 | 
			
		||||
          slot="secondaryAction"
 | 
			
		||||
        >
 | 
			
		||||
        <ha-button @click=${this.closeDialog} slot="secondaryAction">
 | 
			
		||||
          ${this.hass.localize("ui.common.cancel")}
 | 
			
		||||
        </ha-button>
 | 
			
		||||
        <ha-button @click=${this._submit} slot="primaryAction">
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/cast_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.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@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
        with:
 | 
			
		||||
          ref: dev
 | 
			
		||||
 | 
			
		||||
@@ -35,7 +35,7 @@ jobs:
 | 
			
		||||
        run: yarn install --immutable
 | 
			
		||||
 | 
			
		||||
      - name: Build Cast
 | 
			
		||||
        run: ./node_modules/.bin/gulp build-cast
 | 
			
		||||
        run: yarn run-task build-cast
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +56,7 @@ jobs:
 | 
			
		||||
      url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
        with:
 | 
			
		||||
          ref: master
 | 
			
		||||
 | 
			
		||||
@@ -70,7 +70,7 @@ jobs:
 | 
			
		||||
        run: yarn install --immutable
 | 
			
		||||
 | 
			
		||||
      - name: Build Cast
 | 
			
		||||
        run: ./node_modules/.bin/gulp build-cast
 | 
			
		||||
        run: yarn run-task build-cast
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							@@ -24,7 +24,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.4.0
 | 
			
		||||
        with:
 | 
			
		||||
@@ -35,9 +35,9 @@ jobs:
 | 
			
		||||
      - name: Check for duplicate dependencies
 | 
			
		||||
        run: yarn dedupe --check
 | 
			
		||||
      - name: Build resources
 | 
			
		||||
        run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
 | 
			
		||||
        run: yarn run-task gen-icons-json build-translations build-locale-data gather-gallery-pages
 | 
			
		||||
      - name: Setup lint cache
 | 
			
		||||
        uses: actions/cache@v4.2.4
 | 
			
		||||
        uses: actions/cache@v4.2.3
 | 
			
		||||
        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@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.4.0
 | 
			
		||||
        with:
 | 
			
		||||
@@ -67,7 +67,7 @@ jobs:
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: yarn install --immutable
 | 
			
		||||
      - name: Build resources
 | 
			
		||||
        run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data
 | 
			
		||||
        run: yarn run-task gen-icons-json build-translations build-locale-data
 | 
			
		||||
      - name: Run Tests
 | 
			
		||||
        run: yarn run test
 | 
			
		||||
  build:
 | 
			
		||||
@@ -76,7 +76,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.4.0
 | 
			
		||||
        with:
 | 
			
		||||
@@ -85,7 +85,7 @@ jobs:
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: yarn install --immutable
 | 
			
		||||
      - name: Build Application
 | 
			
		||||
        run: ./node_modules/.bin/gulp build-app
 | 
			
		||||
        run: yarn run-task build-app
 | 
			
		||||
        env:
 | 
			
		||||
          IS_TEST: "true"
 | 
			
		||||
      - name: Upload bundle stats
 | 
			
		||||
@@ -100,7 +100,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check out files from GitHub
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.4.0
 | 
			
		||||
        with:
 | 
			
		||||
@@ -109,7 +109,7 @@ jobs:
 | 
			
		||||
      - name: Install dependencies
 | 
			
		||||
        run: yarn install --immutable
 | 
			
		||||
      - name: Build Application
 | 
			
		||||
        run: ./node_modules/.bin/gulp build-hassio
 | 
			
		||||
        run: yarn run-task build-hassio
 | 
			
		||||
        env:
 | 
			
		||||
          IS_TEST: "true"
 | 
			
		||||
      - name: Upload bundle stats
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							@@ -23,7 +23,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout repository
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
        with:
 | 
			
		||||
          # We must fetch at least the immediate parents so that if this is
 | 
			
		||||
          # a pull request then we can checkout the head.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/demo_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.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@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
        with:
 | 
			
		||||
          ref: dev
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +36,7 @@ jobs:
 | 
			
		||||
        run: yarn install --immutable
 | 
			
		||||
 | 
			
		||||
      - name: Build Demo
 | 
			
		||||
        run: ./node_modules/.bin/gulp build-demo
 | 
			
		||||
        run: yarn run-task build-demo
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
@@ -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@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
        with:
 | 
			
		||||
          ref: master
 | 
			
		||||
 | 
			
		||||
@@ -71,7 +71,7 @@ jobs:
 | 
			
		||||
        run: yarn install --immutable
 | 
			
		||||
 | 
			
		||||
      - name: Build Demo
 | 
			
		||||
        run: ./node_modules/.bin/gulp build-demo
 | 
			
		||||
        run: yarn run-task build-demo
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/design_deployment.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.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@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.4.0
 | 
			
		||||
@@ -28,7 +28,7 @@ jobs:
 | 
			
		||||
        run: yarn install --immutable
 | 
			
		||||
 | 
			
		||||
      - name: Build Gallery
 | 
			
		||||
        run: ./node_modules/.bin/gulp build-gallery
 | 
			
		||||
        run: yarn run-task build-gallery
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.github/workflows/design_preview.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.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@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.4.0
 | 
			
		||||
@@ -33,7 +33,7 @@ jobs:
 | 
			
		||||
        run: yarn install --immutable
 | 
			
		||||
 | 
			
		||||
      - name: Build Gallery
 | 
			
		||||
        run: ./node_modules/.bin/gulp build-gallery
 | 
			
		||||
        run: yarn run-task build-gallery
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/nightly.yaml
									
									
									
									
										vendored
									
									
								
							@@ -20,7 +20,7 @@ jobs:
 | 
			
		||||
      contents: write
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
 | 
			
		||||
      - name: Set up Python ${{ env.PYTHON_VERSION }}
 | 
			
		||||
        uses: actions/setup-python@v5
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/relative-ci.yaml
									
									
									
									
										vendored
									
									
								
							@@ -17,7 +17,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Send bundle stats and build information to RelativeCI
 | 
			
		||||
        uses: relative-ci/agent-action@v3.0.1
 | 
			
		||||
        uses: relative-ci/agent-action@v3.0.0
 | 
			
		||||
        with:
 | 
			
		||||
          key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
 | 
			
		||||
          token: ${{ github.token }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
								
							@@ -23,7 +23,7 @@ jobs:
 | 
			
		||||
      contents: write # Required to upload release assets
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
 | 
			
		||||
      - name: Set up Python ${{ env.PYTHON_VERSION }}
 | 
			
		||||
        uses: actions/setup-python@v5
 | 
			
		||||
@@ -74,7 +74,7 @@ jobs:
 | 
			
		||||
          echo "home-assistant-frontend==$version" > ./requirements.txt
 | 
			
		||||
 | 
			
		||||
      - name: Build wheels
 | 
			
		||||
        uses: home-assistant/wheels@2025.07.0
 | 
			
		||||
        uses: home-assistant/wheels@2025.03.0
 | 
			
		||||
        with:
 | 
			
		||||
          abi: cp313
 | 
			
		||||
          tag: musllinux_1_2
 | 
			
		||||
@@ -90,7 +90,7 @@ jobs:
 | 
			
		||||
      contents: write # Required to upload release assets
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.4.0
 | 
			
		||||
        with:
 | 
			
		||||
@@ -119,7 +119,7 @@ jobs:
 | 
			
		||||
      contents: write # Required to upload release assets
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
      - name: Setup Node
 | 
			
		||||
        uses: actions/setup-node@v4.4.0
 | 
			
		||||
        with:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/restrict-task-creation.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/restrict-task-creation.yml
									
									
									
									
										vendored
									
									
								
							@@ -9,7 +9,7 @@ jobs:
 | 
			
		||||
  check-authorization:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    # Only run if this is a Task issue type (from the issue form)
 | 
			
		||||
    if: github.event.issue.type.name == 'Task'
 | 
			
		||||
    if: github.event.issue.issue_type == 'Task'
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Check if user is authorized
 | 
			
		||||
        uses: actions/github-script@v7
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/translations.yaml
									
									
									
									
										vendored
									
									
								
							@@ -14,7 +14,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout the repository
 | 
			
		||||
        uses: actions/checkout@v5.0.0
 | 
			
		||||
        uses: actions/checkout@v4.2.2
 | 
			
		||||
 | 
			
		||||
      - name: Upload Translations
 | 
			
		||||
        run: |
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -6,4 +6,4 @@ enableGlobalCache: false
 | 
			
		||||
 | 
			
		||||
nodeLinker: node-modules
 | 
			
		||||
 | 
			
		||||
yarnPath: .yarn/releases/yarn-4.9.3.cjs
 | 
			
		||||
yarnPath: .yarn/releases/yarn-4.9.2.cjs
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
# People marked here will be automatically requested for a review
 | 
			
		||||
# when the code that they own is touched.
 | 
			
		||||
# https://github.com/blog/2392-introducing-code-owners
 | 
			
		||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
 | 
			
		||||
 | 
			
		||||
# Part of the frontend that mobile developper should review
 | 
			
		||||
src/external_app/ @bgoncal @TimoPtr
 | 
			
		||||
test/external_app/ @bgoncal @TimoPtr
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
import defineProvider from "@babel/helper-define-polyfill-provider";
 | 
			
		||||
import { join } from "node:path";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import paths from "../paths";
 | 
			
		||||
 | 
			
		||||
const POLYFILL_DIR = join(paths.root_dir, "src/resources/polyfills");
 | 
			
		||||
 | 
			
		||||
@@ -1,42 +1,41 @@
 | 
			
		||||
const path = require("path");
 | 
			
		||||
const env = require("./env.cjs");
 | 
			
		||||
const paths = require("./paths.cjs");
 | 
			
		||||
const { dependencies } = require("../package.json");
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import packageJson from "../package.json" assert { type: "json" };
 | 
			
		||||
import { version } from "./env.ts";
 | 
			
		||||
import paths, { dirname } from "./paths.ts";
 | 
			
		||||
 | 
			
		||||
const BABEL_PLUGINS = path.join(__dirname, "babel-plugins");
 | 
			
		||||
const dependencies = packageJson.dependencies;
 | 
			
		||||
 | 
			
		||||
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 = () => {
 | 
			
		||||
  const ref = env.version().endsWith("dev")
 | 
			
		||||
export const sourceMapURL = () => {
 | 
			
		||||
  const ref = version().endsWith("dev")
 | 
			
		||||
    ? process.env.GITHUB_SHA || "dev"
 | 
			
		||||
    : env.version();
 | 
			
		||||
    : version();
 | 
			
		||||
  return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}/`;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Files from NPM Packages that should not be imported
 | 
			
		||||
module.exports.ignorePackages = () => [];
 | 
			
		||||
 | 
			
		||||
// Files from NPM packages that we should replace with empty file
 | 
			
		||||
module.exports.emptyPackages = ({ isHassioBuild }) =>
 | 
			
		||||
export const emptyPackages = ({ isHassioBuild }) =>
 | 
			
		||||
  [
 | 
			
		||||
    require.resolve("@vaadin/vaadin-material-styles/typography.js"),
 | 
			
		||||
    require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
 | 
			
		||||
    import.meta.resolve("@vaadin/vaadin-material-styles/typography.js"),
 | 
			
		||||
    import.meta.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
 | 
			
		||||
    // Icons in supervisor conflict with icons in HA so we don't load.
 | 
			
		||||
    isHassioBuild &&
 | 
			
		||||
      require.resolve(
 | 
			
		||||
      import.meta.resolve(
 | 
			
		||||
        path.resolve(paths.root_dir, "src/components/ha-icon.ts")
 | 
			
		||||
      ),
 | 
			
		||||
    isHassioBuild &&
 | 
			
		||||
      require.resolve(
 | 
			
		||||
      import.meta.resolve(
 | 
			
		||||
        path.resolve(paths.root_dir, "src/components/ha-icon-picker.ts")
 | 
			
		||||
      ),
 | 
			
		||||
  ].filter(Boolean);
 | 
			
		||||
 | 
			
		||||
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
 | 
			
		||||
export const definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
 | 
			
		||||
  __DEV__: !isProdBuild,
 | 
			
		||||
  __BUILD__: JSON.stringify(latestBuild ? "modern" : "legacy"),
 | 
			
		||||
  __VERSION__: JSON.stringify(env.version()),
 | 
			
		||||
  __VERSION__: JSON.stringify(version()),
 | 
			
		||||
  __DEMO__: false,
 | 
			
		||||
  __SUPERVISOR__: false,
 | 
			
		||||
  __BACKWARDS_COMPAT__: false,
 | 
			
		||||
@@ -53,7 +52,7 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
 | 
			
		||||
  ...defineOverlay,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports.htmlMinifierOptions = {
 | 
			
		||||
export const htmlMinifierOptions = {
 | 
			
		||||
  caseSensitive: true,
 | 
			
		||||
  collapseWhitespace: true,
 | 
			
		||||
  conservativeCollapse: true,
 | 
			
		||||
@@ -65,16 +64,16 @@ module.exports.htmlMinifierOptions = {
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
 | 
			
		||||
export const terserOptions = ({ latestBuild, isTestBuild }) => ({
 | 
			
		||||
  safari10: !latestBuild,
 | 
			
		||||
  ecma: latestBuild ? 2015 : 5,
 | 
			
		||||
  ecma: latestBuild ? (2015 as const) : (5 as const),
 | 
			
		||||
  module: latestBuild,
 | 
			
		||||
  format: { comments: false },
 | 
			
		||||
  sourceMap: !isTestBuild,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/** @type {import('@rspack/core').SwcLoaderOptions} */
 | 
			
		||||
module.exports.swcOptions = () => ({
 | 
			
		||||
export const swcOptions = () => ({
 | 
			
		||||
  jsc: {
 | 
			
		||||
    loose: true,
 | 
			
		||||
    externalHelpers: true,
 | 
			
		||||
@@ -86,11 +85,16 @@ module.exports.swcOptions = () => ({
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports.babelOptions = ({
 | 
			
		||||
export const babelOptions = ({
 | 
			
		||||
  latestBuild,
 | 
			
		||||
  isProdBuild,
 | 
			
		||||
  isTestBuild,
 | 
			
		||||
  sw,
 | 
			
		||||
}: {
 | 
			
		||||
  latestBuild?: boolean;
 | 
			
		||||
  isProdBuild?: boolean;
 | 
			
		||||
  isTestBuild?: boolean;
 | 
			
		||||
  sw?: boolean;
 | 
			
		||||
}) => ({
 | 
			
		||||
  babelrc: false,
 | 
			
		||||
  compact: false,
 | 
			
		||||
@@ -137,7 +141,7 @@ module.exports.babelOptions = ({
 | 
			
		||||
          "@polymer/polymer/lib/utils/html-tag.js": ["html"],
 | 
			
		||||
        },
 | 
			
		||||
        strictCSS: true,
 | 
			
		||||
        htmlMinifier: module.exports.htmlMinifierOptions,
 | 
			
		||||
        htmlMinifier: htmlMinifierOptions,
 | 
			
		||||
        failOnError: false, // we can turn this off in case of false positives
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
@@ -160,7 +164,7 @@ module.exports.babelOptions = ({
 | 
			
		||||
      // themselves to prevent self-injection.
 | 
			
		||||
      plugins: [
 | 
			
		||||
        [
 | 
			
		||||
          path.join(BABEL_PLUGINS, "custom-polyfill-plugin.js"),
 | 
			
		||||
          path.join(BABEL_PLUGINS, "custom-polyfill-plugin.ts"),
 | 
			
		||||
          { method: "usage-global" },
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
@@ -221,8 +225,20 @@ const publicPath = (latestBuild, root = "") =>
 | 
			
		||||
  }
 | 
			
		||||
  */
 | 
			
		||||
 | 
			
		||||
module.exports.config = {
 | 
			
		||||
  app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
 | 
			
		||||
export const config = {
 | 
			
		||||
  app({
 | 
			
		||||
    isProdBuild,
 | 
			
		||||
    latestBuild,
 | 
			
		||||
    isStatsBuild,
 | 
			
		||||
    isTestBuild,
 | 
			
		||||
    isWDS,
 | 
			
		||||
  }: {
 | 
			
		||||
    isProdBuild?: boolean;
 | 
			
		||||
    latestBuild?: boolean;
 | 
			
		||||
    isStatsBuild?: boolean;
 | 
			
		||||
    isTestBuild?: boolean;
 | 
			
		||||
    isWDS?: boolean;
 | 
			
		||||
  }) {
 | 
			
		||||
    return {
 | 
			
		||||
      name: "frontend" + nameSuffix(latestBuild),
 | 
			
		||||
      entry: {
 | 
			
		||||
@@ -257,7 +273,7 @@ module.exports.config = {
 | 
			
		||||
      outputPath: outputPath(paths.demo_output_root, latestBuild),
 | 
			
		||||
      publicPath: publicPath(latestBuild),
 | 
			
		||||
      defineOverlay: {
 | 
			
		||||
        __VERSION__: JSON.stringify(`DEMO-${env.version()}`),
 | 
			
		||||
        __VERSION__: JSON.stringify(`DEMO-${version()}`),
 | 
			
		||||
        __DEMO__: true,
 | 
			
		||||
      },
 | 
			
		||||
      isProdBuild,
 | 
			
		||||
@@ -267,7 +283,7 @@ module.exports.config = {
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  cast({ isProdBuild, latestBuild }) {
 | 
			
		||||
    const entry = {
 | 
			
		||||
    const entry: Record<string, string> = {
 | 
			
		||||
      launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
 | 
			
		||||
      media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"),
 | 
			
		||||
    };
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
const fs = require("fs");
 | 
			
		||||
const path = require("path");
 | 
			
		||||
const paths = require("./paths.cjs");
 | 
			
		||||
 | 
			
		||||
const isTrue = (value) => value === "1" || value?.toLowerCase() === "true";
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  isProdBuild() {
 | 
			
		||||
    return (
 | 
			
		||||
      process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
  isStatsBuild() {
 | 
			
		||||
    return isTrue(process.env.STATS);
 | 
			
		||||
  },
 | 
			
		||||
  isTestBuild() {
 | 
			
		||||
    return isTrue(process.env.IS_TEST);
 | 
			
		||||
  },
 | 
			
		||||
  isNetlify() {
 | 
			
		||||
    return isTrue(process.env.NETLIFY);
 | 
			
		||||
  },
 | 
			
		||||
  version() {
 | 
			
		||||
    const version = fs
 | 
			
		||||
      .readFileSync(path.resolve(paths.root_dir, "pyproject.toml"), "utf8")
 | 
			
		||||
      .match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
 | 
			
		||||
    if (!version) {
 | 
			
		||||
      throw Error("Version not found");
 | 
			
		||||
    }
 | 
			
		||||
    return version[1];
 | 
			
		||||
  },
 | 
			
		||||
  isDevContainer() {
 | 
			
		||||
    return isTrue(process.env.DEV_CONTAINER);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										21
									
								
								build-scripts/env.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								build-scripts/env.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
import fs from "node:fs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import paths from "./paths.ts";
 | 
			
		||||
 | 
			
		||||
const isTrue = (value) => value === "1" || value?.toLowerCase() === "true";
 | 
			
		||||
 | 
			
		||||
export const isProdBuild = () =>
 | 
			
		||||
  process.env.NODE_ENV === "production" || isStatsBuild();
 | 
			
		||||
export const isStatsBuild = () => isTrue(process.env.STATS);
 | 
			
		||||
export const isTestBuild = () => isTrue(process.env.IS_TEST);
 | 
			
		||||
export const isNetlify = () => isTrue(process.env.NETLIFY);
 | 
			
		||||
export const version = () => {
 | 
			
		||||
  const pyProjectVersion = fs
 | 
			
		||||
    .readFileSync(path.resolve(paths.root_dir, "pyproject.toml"), "utf8")
 | 
			
		||||
    .match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
 | 
			
		||||
  if (!pyProjectVersion) {
 | 
			
		||||
    throw Error("Version not found");
 | 
			
		||||
  }
 | 
			
		||||
  return pyProjectVersion[1];
 | 
			
		||||
};
 | 
			
		||||
export const isDevContainer = () => isTrue(process.env.DEV_CONTAINER);
 | 
			
		||||
@@ -1,57 +0,0 @@
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import env from "../env.cjs";
 | 
			
		||||
import "./clean.js";
 | 
			
		||||
import "./compress.js";
 | 
			
		||||
import "./entry-html.js";
 | 
			
		||||
import "./gather-static.js";
 | 
			
		||||
import "./gen-icons-json.js";
 | 
			
		||||
import "./locale-data.js";
 | 
			
		||||
import "./service-worker.js";
 | 
			
		||||
import "./translations.js";
 | 
			
		||||
import "./rspack.js";
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "develop-app",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "development";
 | 
			
		||||
    },
 | 
			
		||||
    "clean",
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "gen-service-worker-app-dev",
 | 
			
		||||
      "gen-icons-json",
 | 
			
		||||
      "gen-pages-app-dev",
 | 
			
		||||
      "build-translations",
 | 
			
		||||
      "build-locale-data"
 | 
			
		||||
    ),
 | 
			
		||||
    "copy-static-app",
 | 
			
		||||
    "rspack-watch-app"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-app",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "production";
 | 
			
		||||
    },
 | 
			
		||||
    "clean",
 | 
			
		||||
    gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
 | 
			
		||||
    "copy-static-app",
 | 
			
		||||
    "rspack-prod-app",
 | 
			
		||||
    gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod"),
 | 
			
		||||
    // Don't compress running tests
 | 
			
		||||
    ...(env.isTestBuild() || env.isStatsBuild() ? [] : ["compress-app"])
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "analyze-app",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.STATS = "1";
 | 
			
		||||
    },
 | 
			
		||||
    "clean",
 | 
			
		||||
    "rspack-prod-app"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										54
									
								
								build-scripts/gulp/app.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								build-scripts/gulp/app.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
import { parallel, series } from "gulp";
 | 
			
		||||
import { isStatsBuild, isTestBuild } from "../env.ts";
 | 
			
		||||
import { clean } from "./clean.ts";
 | 
			
		||||
import { compressApp } from "./compress.ts";
 | 
			
		||||
import { genPagesAppDev, genPagesAppProd } from "./entry-html.ts";
 | 
			
		||||
import { copyStaticApp } from "./gather-static.ts";
 | 
			
		||||
import { genIconsJson } from "./gen-icons-json.ts";
 | 
			
		||||
import { buildLocaleData } from "./locale-data.ts";
 | 
			
		||||
import { rspackProdApp, rspackWatchApp } from "./rspack.ts";
 | 
			
		||||
import {
 | 
			
		||||
  genServiceWorkerAppDev,
 | 
			
		||||
  genServiceWorkerAppProd,
 | 
			
		||||
} from "./service-worker.ts";
 | 
			
		||||
import { buildTranslations } from "./translations.ts";
 | 
			
		||||
 | 
			
		||||
// develop-app
 | 
			
		||||
export const developApp = series(
 | 
			
		||||
  async () => {
 | 
			
		||||
    process.env.NODE_ENV = "development";
 | 
			
		||||
  },
 | 
			
		||||
  clean,
 | 
			
		||||
  parallel(
 | 
			
		||||
    genServiceWorkerAppDev,
 | 
			
		||||
    genIconsJson,
 | 
			
		||||
    genPagesAppDev,
 | 
			
		||||
    buildTranslations,
 | 
			
		||||
    buildLocaleData
 | 
			
		||||
  ),
 | 
			
		||||
  copyStaticApp,
 | 
			
		||||
  rspackWatchApp
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// build-app
 | 
			
		||||
export const buildApp = series(
 | 
			
		||||
  async () => {
 | 
			
		||||
    process.env.NODE_ENV = "production";
 | 
			
		||||
  },
 | 
			
		||||
  clean,
 | 
			
		||||
  parallel(genIconsJson, buildTranslations, buildLocaleData),
 | 
			
		||||
  copyStaticApp,
 | 
			
		||||
  rspackProdApp,
 | 
			
		||||
  parallel(genPagesAppProd, genServiceWorkerAppProd),
 | 
			
		||||
  // Don't compress running tests
 | 
			
		||||
  ...(isTestBuild() || isStatsBuild() ? [] : [compressApp])
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// analyze-app
 | 
			
		||||
export const analyzeApp = series(
 | 
			
		||||
  async () => {
 | 
			
		||||
    process.env.STATS = "1";
 | 
			
		||||
  },
 | 
			
		||||
  clean,
 | 
			
		||||
  rspackProdApp
 | 
			
		||||
);
 | 
			
		||||
@@ -1,37 +0,0 @@
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import "./clean.js";
 | 
			
		||||
import "./entry-html.js";
 | 
			
		||||
import "./gather-static.js";
 | 
			
		||||
import "./service-worker.js";
 | 
			
		||||
import "./translations.js";
 | 
			
		||||
import "./rspack.js";
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "develop-cast",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "development";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-cast",
 | 
			
		||||
    "translations-enable-merge-backend",
 | 
			
		||||
    gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
 | 
			
		||||
    "copy-static-cast",
 | 
			
		||||
    "gen-pages-cast-dev",
 | 
			
		||||
    "rspack-dev-server-cast"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-cast",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "production";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-cast",
 | 
			
		||||
    "translations-enable-merge-backend",
 | 
			
		||||
    gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
 | 
			
		||||
    "copy-static-cast",
 | 
			
		||||
    "rspack-prod-cast",
 | 
			
		||||
    "gen-pages-cast-prod"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										38
									
								
								build-scripts/gulp/cast.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								build-scripts/gulp/cast.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
import { parallel, series } from "gulp";
 | 
			
		||||
import { cleanCast } from "./clean.ts";
 | 
			
		||||
import { genPagesCastDev, genPagesCastProd } from "./entry-html.ts";
 | 
			
		||||
import { copyStaticCast } from "./gather-static.ts";
 | 
			
		||||
import { genIconsJson } from "./gen-icons-json.ts";
 | 
			
		||||
import { buildLocaleData } from "./locale-data.ts";
 | 
			
		||||
import { rspackDevServerCast, rspackProdCast } from "./rspack.ts";
 | 
			
		||||
import "./service-worker.ts";
 | 
			
		||||
import {
 | 
			
		||||
  buildTranslations,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
} from "./translations.ts";
 | 
			
		||||
 | 
			
		||||
// develop-cast
 | 
			
		||||
export const developCast = series(
 | 
			
		||||
  async () => {
 | 
			
		||||
    process.env.NODE_ENV = "development";
 | 
			
		||||
  },
 | 
			
		||||
  cleanCast,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
  parallel(genIconsJson, buildTranslations, buildLocaleData),
 | 
			
		||||
  copyStaticCast,
 | 
			
		||||
  genPagesCastDev,
 | 
			
		||||
  rspackDevServerCast
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// build-cast
 | 
			
		||||
export const buildCast = series(
 | 
			
		||||
  async () => {
 | 
			
		||||
    process.env.NODE_ENV = "production";
 | 
			
		||||
  },
 | 
			
		||||
  cleanCast,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
  parallel(genIconsJson, buildTranslations, buildLocaleData),
 | 
			
		||||
  copyStaticCast,
 | 
			
		||||
  rspackProdCast,
 | 
			
		||||
  genPagesCastProd
 | 
			
		||||
);
 | 
			
		||||
@@ -1,51 +0,0 @@
 | 
			
		||||
import { deleteSync } from "del";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import "./translations.js";
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "clean",
 | 
			
		||||
  gulp.parallel("clean-translations", async () =>
 | 
			
		||||
    deleteSync([paths.app_output_root, paths.build_dir])
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "clean-demo",
 | 
			
		||||
  gulp.parallel("clean-translations", async () =>
 | 
			
		||||
    deleteSync([paths.demo_output_root, paths.build_dir])
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "clean-cast",
 | 
			
		||||
  gulp.parallel("clean-translations", async () =>
 | 
			
		||||
    deleteSync([paths.cast_output_root, paths.build_dir])
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task("clean-hassio", async () =>
 | 
			
		||||
  deleteSync([paths.hassio_output_root, paths.build_dir])
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "clean-gallery",
 | 
			
		||||
  gulp.parallel("clean-translations", async () =>
 | 
			
		||||
    deleteSync([
 | 
			
		||||
      paths.gallery_output_root,
 | 
			
		||||
      paths.gallery_build,
 | 
			
		||||
      paths.build_dir,
 | 
			
		||||
    ])
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "clean-landing-page",
 | 
			
		||||
  gulp.parallel("clean-translations", async () =>
 | 
			
		||||
    deleteSync([
 | 
			
		||||
      paths.landingPage_output_root,
 | 
			
		||||
      paths.landingPage_build,
 | 
			
		||||
      paths.build_dir,
 | 
			
		||||
    ])
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										31
									
								
								build-scripts/gulp/clean.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								build-scripts/gulp/clean.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
import { deleteSync } from "del";
 | 
			
		||||
import { parallel } from "gulp";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
import { cleanTranslations } from "./translations.ts";
 | 
			
		||||
 | 
			
		||||
export const clean = parallel(cleanTranslations, async () =>
 | 
			
		||||
  deleteSync([paths.app_output_root, paths.build_dir])
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const cleanDemo = parallel(cleanTranslations, async () =>
 | 
			
		||||
  deleteSync([paths.demo_output_root, paths.build_dir])
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const cleanCast = parallel(cleanTranslations, async () =>
 | 
			
		||||
  deleteSync([paths.cast_output_root, paths.build_dir])
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const cleanHassio = async () =>
 | 
			
		||||
  deleteSync([paths.hassio_output_root, paths.build_dir]);
 | 
			
		||||
 | 
			
		||||
export const cleanGallery = parallel(cleanTranslations, async () =>
 | 
			
		||||
  deleteSync([paths.gallery_output_root, paths.gallery_build, paths.build_dir])
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const cleanLandingPage = parallel(cleanTranslations, async () =>
 | 
			
		||||
  deleteSync([
 | 
			
		||||
    paths.landingPage_output_root,
 | 
			
		||||
    paths.landingPage_build,
 | 
			
		||||
    paths.build_dir,
 | 
			
		||||
  ])
 | 
			
		||||
);
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
// Tasks to compress
 | 
			
		||||
 | 
			
		||||
import { constants } from "node:zlib";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import { dest, parallel, src } from "gulp";
 | 
			
		||||
import brotli from "gulp-brotli";
 | 
			
		||||
import zopfli from "gulp-zopfli-green";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import { constants } from "node:zlib";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
 | 
			
		||||
const filesGlob = "*.{js,json,css,svg,xml}";
 | 
			
		||||
const brotliOptions = {
 | 
			
		||||
@@ -16,27 +16,25 @@ const brotliOptions = {
 | 
			
		||||
const zopfliOptions = { threshold: 150 };
 | 
			
		||||
 | 
			
		||||
const compressModern = (rootDir, modernDir, compress) =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
 | 
			
		||||
      base: rootDir,
 | 
			
		||||
      allowEmpty: true,
 | 
			
		||||
    })
 | 
			
		||||
  src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
 | 
			
		||||
    base: rootDir,
 | 
			
		||||
    allowEmpty: true,
 | 
			
		||||
  })
 | 
			
		||||
    .pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions))
 | 
			
		||||
    .pipe(gulp.dest(rootDir));
 | 
			
		||||
    .pipe(dest(rootDir));
 | 
			
		||||
 | 
			
		||||
const compressOther = (rootDir, modernDir, compress) =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src(
 | 
			
		||||
      [
 | 
			
		||||
        `${rootDir}/**/${filesGlob}`,
 | 
			
		||||
        `!${modernDir}/**/${filesGlob}`,
 | 
			
		||||
        `!${rootDir}/{sw-modern,service_worker}.js`,
 | 
			
		||||
        `${rootDir}/{authorize,onboarding}.html`,
 | 
			
		||||
      ],
 | 
			
		||||
      { base: rootDir, allowEmpty: true }
 | 
			
		||||
    )
 | 
			
		||||
  src(
 | 
			
		||||
    [
 | 
			
		||||
      `${rootDir}/**/${filesGlob}`,
 | 
			
		||||
      `!${modernDir}/**/${filesGlob}`,
 | 
			
		||||
      `!${rootDir}/{sw-modern,service_worker}.js`,
 | 
			
		||||
      `${rootDir}/{authorize,onboarding}.html`,
 | 
			
		||||
    ],
 | 
			
		||||
    { base: rootDir, allowEmpty: true }
 | 
			
		||||
  )
 | 
			
		||||
    .pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions))
 | 
			
		||||
    .pipe(gulp.dest(rootDir));
 | 
			
		||||
    .pipe(dest(rootDir));
 | 
			
		||||
 | 
			
		||||
const compressAppModernBrotli = () =>
 | 
			
		||||
  compressModern(paths.app_output_root, paths.app_output_latest, "brotli");
 | 
			
		||||
@@ -66,21 +64,16 @@ const compressHassioOtherBrotli = () =>
 | 
			
		||||
const compressHassioOtherZopfli = () =>
 | 
			
		||||
  compressOther(paths.hassio_output_root, paths.hassio_output_latest, "zopfli");
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "compress-app",
 | 
			
		||||
  gulp.parallel(
 | 
			
		||||
    compressAppModernBrotli,
 | 
			
		||||
    compressAppOtherBrotli,
 | 
			
		||||
    compressAppModernZopfli,
 | 
			
		||||
    compressAppOtherZopfli
 | 
			
		||||
  )
 | 
			
		||||
export const compressApp = parallel(
 | 
			
		||||
  compressAppModernBrotli,
 | 
			
		||||
  compressAppOtherBrotli,
 | 
			
		||||
  compressAppModernZopfli,
 | 
			
		||||
  compressAppOtherZopfli
 | 
			
		||||
);
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "compress-hassio",
 | 
			
		||||
  gulp.parallel(
 | 
			
		||||
    compressHassioModernBrotli,
 | 
			
		||||
    compressHassioOtherBrotli,
 | 
			
		||||
    compressHassioModernZopfli,
 | 
			
		||||
    compressHassioOtherZopfli
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
export const compressHassio = parallel(
 | 
			
		||||
  compressHassioModernBrotli,
 | 
			
		||||
  compressHassioOtherBrotli,
 | 
			
		||||
  compressHassioModernZopfli,
 | 
			
		||||
  compressHassioOtherZopfli
 | 
			
		||||
);
 | 
			
		||||
@@ -1,54 +0,0 @@
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import "./clean.js";
 | 
			
		||||
import "./entry-html.js";
 | 
			
		||||
import "./gather-static.js";
 | 
			
		||||
import "./gen-icons-json.js";
 | 
			
		||||
import "./service-worker.js";
 | 
			
		||||
import "./translations.js";
 | 
			
		||||
import "./rspack.js";
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "develop-demo",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "development";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-demo",
 | 
			
		||||
    "translations-enable-merge-backend",
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "gen-icons-json",
 | 
			
		||||
      "gen-pages-demo-dev",
 | 
			
		||||
      "build-translations",
 | 
			
		||||
      "build-locale-data"
 | 
			
		||||
    ),
 | 
			
		||||
    "copy-static-demo",
 | 
			
		||||
    "rspack-dev-server-demo"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-demo",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "production";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-demo",
 | 
			
		||||
    // Cast needs to be backwards compatible and older HA has no translations
 | 
			
		||||
    "translations-enable-merge-backend",
 | 
			
		||||
    gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
 | 
			
		||||
    "copy-static-demo",
 | 
			
		||||
    "rspack-prod-demo",
 | 
			
		||||
    "gen-pages-demo-prod"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "analyze-demo",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.STATS = "1";
 | 
			
		||||
    },
 | 
			
		||||
    "clean",
 | 
			
		||||
    "rspack-prod-demo"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										47
									
								
								build-scripts/gulp/demo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								build-scripts/gulp/demo.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
import { parallel, series } from "gulp";
 | 
			
		||||
import { clean, cleanDemo } from "./clean.ts";
 | 
			
		||||
import { genPagesDemoDev, genPagesDemoProd } from "./entry-html.ts";
 | 
			
		||||
import { copyStaticDemo } from "./gather-static.ts";
 | 
			
		||||
import { genIconsJson } from "./gen-icons-json.ts";
 | 
			
		||||
import { buildLocaleData } from "./locale-data.ts";
 | 
			
		||||
import { rspackDevServerDemo, rspackProdDemo } from "./rspack.ts";
 | 
			
		||||
import "./service-worker.ts";
 | 
			
		||||
import {
 | 
			
		||||
  buildTranslations,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
} from "./translations.ts";
 | 
			
		||||
 | 
			
		||||
// develop-demo
 | 
			
		||||
export const developDemo = series(
 | 
			
		||||
  async function setEnv() {
 | 
			
		||||
    process.env.NODE_ENV = "development";
 | 
			
		||||
  },
 | 
			
		||||
  cleanDemo,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
  parallel(genIconsJson, genPagesDemoDev, buildTranslations, buildLocaleData),
 | 
			
		||||
  copyStaticDemo,
 | 
			
		||||
  rspackDevServerDemo
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// build-demo
 | 
			
		||||
export const buildDemo = series(
 | 
			
		||||
  async function setEnv() {
 | 
			
		||||
    process.env.NODE_ENV = "production";
 | 
			
		||||
  },
 | 
			
		||||
  cleanDemo,
 | 
			
		||||
  // Cast needs to be backwards compatible and older HA has no translations
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
  parallel(genIconsJson, buildTranslations, buildLocaleData),
 | 
			
		||||
  copyStaticDemo,
 | 
			
		||||
  rspackProdDemo,
 | 
			
		||||
  genPagesDemoProd
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// analyze-demo
 | 
			
		||||
export const analyzeDemo = series(
 | 
			
		||||
  async function setEnv() {
 | 
			
		||||
    process.env.STATS = "1";
 | 
			
		||||
  },
 | 
			
		||||
  clean,
 | 
			
		||||
  rspackProdDemo
 | 
			
		||||
);
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
import fs from "fs/promises";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import mapStream from "map-stream";
 | 
			
		||||
import transform from "gulp-json-transform";
 | 
			
		||||
import { LokaliseApi } from "@lokalise/node-api";
 | 
			
		||||
import { dest, series, src } from "gulp";
 | 
			
		||||
import transform from "gulp-json-transform";
 | 
			
		||||
import JSZip from "jszip";
 | 
			
		||||
import mapStream from "map-stream";
 | 
			
		||||
import fs from "node:fs/promises";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
 | 
			
		||||
const inDir = "translations";
 | 
			
		||||
const inDirFrontend = `${inDir}/frontend`;
 | 
			
		||||
@@ -12,11 +12,14 @@ const inDirBackend = `${inDir}/backend`;
 | 
			
		||||
const srcMeta = "src/translations/translationMetadata.json";
 | 
			
		||||
const encoding = "utf8";
 | 
			
		||||
 | 
			
		||||
function hasHtml(data) {
 | 
			
		||||
  return /<\S*>/i.test(data);
 | 
			
		||||
}
 | 
			
		||||
const hasHtml = (data) => /<\S*>/i.test(data);
 | 
			
		||||
 | 
			
		||||
function recursiveCheckHasHtml(file, data, errors, recKey) {
 | 
			
		||||
const recursiveCheckHasHtml = (
 | 
			
		||||
  file,
 | 
			
		||||
  data,
 | 
			
		||||
  errors: string[],
 | 
			
		||||
  recKey?: string
 | 
			
		||||
) => {
 | 
			
		||||
  Object.keys(data).forEach(function (key) {
 | 
			
		||||
    if (typeof data[key] === "object") {
 | 
			
		||||
      const nextRecKey = recKey ? `${recKey}.${key}` : key;
 | 
			
		||||
@@ -25,9 +28,9 @@ function recursiveCheckHasHtml(file, data, errors, recKey) {
 | 
			
		||||
      errors.push(`HTML found in ${file.path} at key ${recKey}.${key}`);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function checkHtml() {
 | 
			
		||||
const checkHtml = () => {
 | 
			
		||||
  const errors = [];
 | 
			
		||||
 | 
			
		||||
  return mapStream(function (file, cb) {
 | 
			
		||||
@@ -44,9 +47,9 @@ function checkHtml() {
 | 
			
		||||
    }
 | 
			
		||||
    cb(error, file);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function convertBackendTranslations(data, _file) {
 | 
			
		||||
const convertBackendTranslationsTransform = (data, _file) => {
 | 
			
		||||
  const output = { component: {} };
 | 
			
		||||
  if (!data.component) {
 | 
			
		||||
    return output;
 | 
			
		||||
@@ -62,25 +65,22 @@ function convertBackendTranslations(data, _file) {
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  return output;
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("convert-backend-translations", function () {
 | 
			
		||||
  return gulp
 | 
			
		||||
    .src([`${inDirBackend}/*.json`])
 | 
			
		||||
    .pipe(transform((data, file) => convertBackendTranslations(data, file)))
 | 
			
		||||
    .pipe(gulp.dest(inDirBackend));
 | 
			
		||||
});
 | 
			
		||||
const convertBackendTranslations = () =>
 | 
			
		||||
  src([`${inDirBackend}/*.json`])
 | 
			
		||||
    .pipe(
 | 
			
		||||
      transform((data, file) => convertBackendTranslationsTransform(data, file))
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(dest(inDirBackend));
 | 
			
		||||
 | 
			
		||||
gulp.task("check-translations-html", function () {
 | 
			
		||||
  return gulp
 | 
			
		||||
    .src([`${inDirFrontend}/*.json`, `${inDirBackend}/*.json`])
 | 
			
		||||
    .pipe(checkHtml());
 | 
			
		||||
});
 | 
			
		||||
const checkTranslationsHtml = () =>
 | 
			
		||||
  src([`${inDirFrontend}/*.json`, `${inDirBackend}/*.json`]).pipe(checkHtml());
 | 
			
		||||
 | 
			
		||||
gulp.task("check-all-files-exist", async function () {
 | 
			
		||||
const checkAllFilesExist = async () => {
 | 
			
		||||
  const file = await fs.readFile(srcMeta, { encoding });
 | 
			
		||||
  const meta = JSON.parse(file);
 | 
			
		||||
  const writings = [];
 | 
			
		||||
  const writings: Promise<void>[] = [];
 | 
			
		||||
  Object.keys(meta).forEach((lang) => {
 | 
			
		||||
    writings.push(
 | 
			
		||||
      fs.writeFile(`${inDirFrontend}/${lang}.json`, JSON.stringify({}), {
 | 
			
		||||
@@ -92,14 +92,14 @@ gulp.task("check-all-files-exist", async function () {
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  await Promise.allSettled(writings);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const lokaliseProjects = {
 | 
			
		||||
  backend: "130246255a974bd3b5e8a1.51616605",
 | 
			
		||||
  frontend: "3420425759f6d6d241f598.13594006",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("fetch-lokalise", async function () {
 | 
			
		||||
const fetchLokalise = async () => {
 | 
			
		||||
  let apiKey;
 | 
			
		||||
  try {
 | 
			
		||||
    apiKey =
 | 
			
		||||
@@ -168,14 +168,11 @@ gulp.task("fetch-lokalise", async function () {
 | 
			
		||||
        })
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "download-translations",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    "fetch-lokalise",
 | 
			
		||||
    "convert-backend-translations",
 | 
			
		||||
    "check-translations-html",
 | 
			
		||||
    "check-all-files-exist"
 | 
			
		||||
  )
 | 
			
		||||
export const downloadTranslations = series(
 | 
			
		||||
  fetchLokalise,
 | 
			
		||||
  convertBackendTranslations,
 | 
			
		||||
  checkTranslationsHtml,
 | 
			
		||||
  checkAllFilesExist
 | 
			
		||||
);
 | 
			
		||||
@@ -6,12 +6,11 @@ import {
 | 
			
		||||
  getPreUserAgentRegexes,
 | 
			
		||||
} from "browserslist-useragent-regexp";
 | 
			
		||||
import fs from "fs-extra";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import { minify } from "html-minifier-terser";
 | 
			
		||||
import template from "lodash.template";
 | 
			
		||||
import { dirname, extname, resolve } from "node:path";
 | 
			
		||||
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import { htmlMinifierOptions, terserOptions } from "../bundle.ts";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
 | 
			
		||||
// macOS companion app has no way to obtain the Safari version used by WKWebView,
 | 
			
		||||
// and it is not in the default user agent string. So we add an additional regex
 | 
			
		||||
@@ -34,9 +33,9 @@ const getCommonTemplateVars = () => {
 | 
			
		||||
    mobileToDesktop: true,
 | 
			
		||||
    throwOnMissing: true,
 | 
			
		||||
  });
 | 
			
		||||
  const minSafariVersion = browserRegexes.find(
 | 
			
		||||
    (regex) => regex.family === "safari"
 | 
			
		||||
  )?.matchedVersions[0][0];
 | 
			
		||||
  const minSafariVersion =
 | 
			
		||||
    browserRegexes.find((regex) => regex.family === "safari")
 | 
			
		||||
      ?.matchedVersions[0][0] ?? 18;
 | 
			
		||||
  const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
 | 
			
		||||
  if (!minMacOSVersion) {
 | 
			
		||||
    throw Error(
 | 
			
		||||
@@ -106,10 +105,10 @@ const genPagesDevTask =
 | 
			
		||||
        resolve(inputRoot, inputSub, `${page}.template`),
 | 
			
		||||
        {
 | 
			
		||||
          ...commonVars,
 | 
			
		||||
          latestEntryJS: entries.map(
 | 
			
		||||
          latestEntryJS: (entries as string[]).map(
 | 
			
		||||
            (entry) => `${publicRoot}/frontend_latest/${entry}.js`
 | 
			
		||||
          ),
 | 
			
		||||
          es5EntryJS: entries.map(
 | 
			
		||||
          es5EntryJS: (entries as string[]).map(
 | 
			
		||||
            (entry) => `${publicRoot}/frontend_es5/${entry}.js`
 | 
			
		||||
          ),
 | 
			
		||||
          latestCustomPanelJS: `${publicRoot}/frontend_latest/custom-panel.js`,
 | 
			
		||||
@@ -128,7 +127,7 @@ const genPagesProdTask =
 | 
			
		||||
    inputRoot,
 | 
			
		||||
    outputRoot,
 | 
			
		||||
    outputLatest,
 | 
			
		||||
    outputES5,
 | 
			
		||||
    outputES5?: string,
 | 
			
		||||
    inputSub = "src/html"
 | 
			
		||||
  ) =>
 | 
			
		||||
  async () => {
 | 
			
		||||
@@ -139,14 +138,18 @@ const genPagesProdTask =
 | 
			
		||||
      ? fs.readJsonSync(resolve(outputES5, "manifest.json"))
 | 
			
		||||
      : {};
 | 
			
		||||
    const commonVars = getCommonTemplateVars();
 | 
			
		||||
    const minifiedHTML = [];
 | 
			
		||||
    const minifiedHTML: Promise<void>[] = [];
 | 
			
		||||
    for (const [page, entries] of Object.entries(pageEntries)) {
 | 
			
		||||
      const content = renderTemplate(
 | 
			
		||||
        resolve(inputRoot, inputSub, `${page}.template`),
 | 
			
		||||
        {
 | 
			
		||||
          ...commonVars,
 | 
			
		||||
          latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
 | 
			
		||||
          es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
 | 
			
		||||
          latestEntryJS: (entries as string[]).map(
 | 
			
		||||
            (entry) => latestManifest[`${entry}.js`]
 | 
			
		||||
          ),
 | 
			
		||||
          es5EntryJS: (entries as string[]).map(
 | 
			
		||||
            (entry) => es5Manifest[`${entry}.js`]
 | 
			
		||||
          ),
 | 
			
		||||
          latestCustomPanelJS: latestManifest["custom-panel.js"],
 | 
			
		||||
          es5CustomPanelJS: es5Manifest["custom-panel.js"],
 | 
			
		||||
        }
 | 
			
		||||
@@ -167,20 +170,18 @@ const APP_PAGE_ENTRIES = {
 | 
			
		||||
  "index.html": ["core", "app"],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-app-dev",
 | 
			
		||||
  genPagesDevTask(APP_PAGE_ENTRIES, paths.root_dir, paths.app_output_root)
 | 
			
		||||
export const genPagesAppDev = genPagesDevTask(
 | 
			
		||||
  APP_PAGE_ENTRIES,
 | 
			
		||||
  paths.root_dir,
 | 
			
		||||
  paths.app_output_root
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-app-prod",
 | 
			
		||||
  genPagesProdTask(
 | 
			
		||||
    APP_PAGE_ENTRIES,
 | 
			
		||||
    paths.root_dir,
 | 
			
		||||
    paths.app_output_root,
 | 
			
		||||
    paths.app_output_latest,
 | 
			
		||||
    paths.app_output_es5
 | 
			
		||||
  )
 | 
			
		||||
export const genPagesAppProd = genPagesProdTask(
 | 
			
		||||
  APP_PAGE_ENTRIES,
 | 
			
		||||
  paths.root_dir,
 | 
			
		||||
  paths.app_output_root,
 | 
			
		||||
  paths.app_output_latest,
 | 
			
		||||
  paths.app_output_es5
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const CAST_PAGE_ENTRIES = {
 | 
			
		||||
@@ -190,104 +191,82 @@ const CAST_PAGE_ENTRIES = {
 | 
			
		||||
  "receiver.html": ["receiver"],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-cast-dev",
 | 
			
		||||
  genPagesDevTask(CAST_PAGE_ENTRIES, paths.cast_dir, paths.cast_output_root)
 | 
			
		||||
export const genPagesCastDev = genPagesDevTask(
 | 
			
		||||
  CAST_PAGE_ENTRIES,
 | 
			
		||||
  paths.cast_dir,
 | 
			
		||||
  paths.cast_output_root
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-cast-prod",
 | 
			
		||||
  genPagesProdTask(
 | 
			
		||||
    CAST_PAGE_ENTRIES,
 | 
			
		||||
    paths.cast_dir,
 | 
			
		||||
    paths.cast_output_root,
 | 
			
		||||
    paths.cast_output_latest,
 | 
			
		||||
    paths.cast_output_es5
 | 
			
		||||
  )
 | 
			
		||||
export const genPagesCastProd = genPagesProdTask(
 | 
			
		||||
  CAST_PAGE_ENTRIES,
 | 
			
		||||
  paths.cast_dir,
 | 
			
		||||
  paths.cast_output_root,
 | 
			
		||||
  paths.cast_output_latest,
 | 
			
		||||
  paths.cast_output_es5
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const DEMO_PAGE_ENTRIES = { "index.html": ["main"] };
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-demo-dev",
 | 
			
		||||
  genPagesDevTask(DEMO_PAGE_ENTRIES, paths.demo_dir, paths.demo_output_root)
 | 
			
		||||
export const genPagesDemoDev = genPagesDevTask(
 | 
			
		||||
  DEMO_PAGE_ENTRIES,
 | 
			
		||||
  paths.demo_dir,
 | 
			
		||||
  paths.demo_output_root
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-demo-prod",
 | 
			
		||||
  genPagesProdTask(
 | 
			
		||||
    DEMO_PAGE_ENTRIES,
 | 
			
		||||
    paths.demo_dir,
 | 
			
		||||
    paths.demo_output_root,
 | 
			
		||||
    paths.demo_output_latest,
 | 
			
		||||
    paths.demo_output_es5
 | 
			
		||||
  )
 | 
			
		||||
export const genPagesDemoProd = genPagesProdTask(
 | 
			
		||||
  DEMO_PAGE_ENTRIES,
 | 
			
		||||
  paths.demo_dir,
 | 
			
		||||
  paths.demo_output_root,
 | 
			
		||||
  paths.demo_output_latest,
 | 
			
		||||
  paths.demo_output_es5
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const GALLERY_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-gallery-dev",
 | 
			
		||||
  genPagesDevTask(
 | 
			
		||||
    GALLERY_PAGE_ENTRIES,
 | 
			
		||||
    paths.gallery_dir,
 | 
			
		||||
    paths.gallery_output_root
 | 
			
		||||
  )
 | 
			
		||||
export const genPagesGalleryDev = genPagesDevTask(
 | 
			
		||||
  GALLERY_PAGE_ENTRIES,
 | 
			
		||||
  paths.gallery_dir,
 | 
			
		||||
  paths.gallery_output_root
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-gallery-prod",
 | 
			
		||||
  genPagesProdTask(
 | 
			
		||||
    GALLERY_PAGE_ENTRIES,
 | 
			
		||||
    paths.gallery_dir,
 | 
			
		||||
    paths.gallery_output_root,
 | 
			
		||||
    paths.gallery_output_latest
 | 
			
		||||
  )
 | 
			
		||||
export const genPagesGalleryProd = genPagesProdTask(
 | 
			
		||||
  GALLERY_PAGE_ENTRIES,
 | 
			
		||||
  paths.gallery_dir,
 | 
			
		||||
  paths.gallery_output_root,
 | 
			
		||||
  paths.gallery_output_latest
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const LANDING_PAGE_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-landing-page-dev",
 | 
			
		||||
  genPagesDevTask(
 | 
			
		||||
    LANDING_PAGE_PAGE_ENTRIES,
 | 
			
		||||
    paths.landingPage_dir,
 | 
			
		||||
    paths.landingPage_output_root
 | 
			
		||||
  )
 | 
			
		||||
export const genPagesLandingPageDev = genPagesDevTask(
 | 
			
		||||
  LANDING_PAGE_PAGE_ENTRIES,
 | 
			
		||||
  paths.landingPage_dir,
 | 
			
		||||
  paths.landingPage_output_root
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-landing-page-prod",
 | 
			
		||||
  genPagesProdTask(
 | 
			
		||||
    LANDING_PAGE_PAGE_ENTRIES,
 | 
			
		||||
    paths.landingPage_dir,
 | 
			
		||||
    paths.landingPage_output_root,
 | 
			
		||||
    paths.landingPage_output_latest,
 | 
			
		||||
    paths.landingPage_output_es5
 | 
			
		||||
  )
 | 
			
		||||
export const genPagesLandingPageProd = genPagesProdTask(
 | 
			
		||||
  LANDING_PAGE_PAGE_ENTRIES,
 | 
			
		||||
  paths.landingPage_dir,
 | 
			
		||||
  paths.landingPage_output_root,
 | 
			
		||||
  paths.landingPage_output_latest,
 | 
			
		||||
  paths.landingPage_output_es5
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] };
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-hassio-dev",
 | 
			
		||||
  genPagesDevTask(
 | 
			
		||||
    HASSIO_PAGE_ENTRIES,
 | 
			
		||||
    paths.hassio_dir,
 | 
			
		||||
    paths.hassio_output_root,
 | 
			
		||||
    "src",
 | 
			
		||||
    paths.hassio_publicPath
 | 
			
		||||
  )
 | 
			
		||||
export const genPagesHassioDev = genPagesDevTask(
 | 
			
		||||
  HASSIO_PAGE_ENTRIES,
 | 
			
		||||
  paths.hassio_dir,
 | 
			
		||||
  paths.hassio_output_root,
 | 
			
		||||
  "src",
 | 
			
		||||
  paths.hassio_publicPath
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "gen-pages-hassio-prod",
 | 
			
		||||
  genPagesProdTask(
 | 
			
		||||
    HASSIO_PAGE_ENTRIES,
 | 
			
		||||
    paths.hassio_dir,
 | 
			
		||||
    paths.hassio_output_root,
 | 
			
		||||
    paths.hassio_output_latest,
 | 
			
		||||
    paths.hassio_output_es5,
 | 
			
		||||
    "src"
 | 
			
		||||
  )
 | 
			
		||||
export const genPagesHassioProd = genPagesProdTask(
 | 
			
		||||
  HASSIO_PAGE_ENTRIES,
 | 
			
		||||
  paths.hassio_dir,
 | 
			
		||||
  paths.hassio_output_root,
 | 
			
		||||
  paths.hassio_output_latest,
 | 
			
		||||
  paths.hassio_output_es5,
 | 
			
		||||
  "src"
 | 
			
		||||
);
 | 
			
		||||
@@ -1,14 +1,14 @@
 | 
			
		||||
// Task to download the latest Lokalise translations from the nightly workflow artifacts
 | 
			
		||||
// Task to download the latest 00Lokalise translations from the nightly workflow artifacts
 | 
			
		||||
 | 
			
		||||
import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device";
 | 
			
		||||
import { retry } from "@octokit/plugin-retry";
 | 
			
		||||
import { Octokit } from "@octokit/rest";
 | 
			
		||||
import { deleteAsync } from "del";
 | 
			
		||||
import { mkdir, readFile, writeFile } from "fs/promises";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import { series } from "gulp";
 | 
			
		||||
import jszip from "jszip";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import process from "process";
 | 
			
		||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import process from "node:process";
 | 
			
		||||
import { extract } from "tar";
 | 
			
		||||
 | 
			
		||||
const MAX_AGE = 24; // hours
 | 
			
		||||
@@ -22,12 +22,13 @@ const TOKEN_FILE = path.posix.join(EXTRACT_DIR, "token.json");
 | 
			
		||||
const ARTIFACT_FILE = path.posix.join(EXTRACT_DIR, "artifact.json");
 | 
			
		||||
 | 
			
		||||
let allowTokenSetup = false;
 | 
			
		||||
gulp.task("allow-setup-fetch-nightly-translations", (done) => {
 | 
			
		||||
 | 
			
		||||
export const allowSetupFetchNightlyTranslations = (done) => {
 | 
			
		||||
  allowTokenSetup = true;
 | 
			
		||||
  done();
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("fetch-nightly-translations", async function () {
 | 
			
		||||
export const fetchNightlyTranslations = async () => {
 | 
			
		||||
  // Skip all when environment flag is set (assumes translations are already in place)
 | 
			
		||||
  if (process.env?.SKIP_FETCH_NIGHTLY_TRANSLATIONS) {
 | 
			
		||||
    console.log("Skipping fetch due to environment signal");
 | 
			
		||||
@@ -54,7 +55,7 @@ gulp.task("fetch-nightly-translations", async function () {
 | 
			
		||||
 | 
			
		||||
  // To store file writing promises
 | 
			
		||||
  const createExtractDir = mkdir(EXTRACT_DIR, { recursive: true });
 | 
			
		||||
  const writings = [];
 | 
			
		||||
  const writings: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
  // Authenticate to GitHub using GitHub action token if it exists,
 | 
			
		||||
  // otherwise look for a saved user token or generate a new one if none
 | 
			
		||||
@@ -87,7 +88,7 @@ gulp.task("fetch-nightly-translations", async function () {
 | 
			
		||||
      });
 | 
			
		||||
      tokenAuth = await auth({ type: "oauth" });
 | 
			
		||||
      writings.push(
 | 
			
		||||
        createExtractDir.then(
 | 
			
		||||
        createExtractDir.then(() =>
 | 
			
		||||
          writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2))
 | 
			
		||||
        )
 | 
			
		||||
      );
 | 
			
		||||
@@ -131,13 +132,13 @@ gulp.task("fetch-nightly-translations", async function () {
 | 
			
		||||
    throw Error("Latest nightly workflow run has no translations artifact");
 | 
			
		||||
  }
 | 
			
		||||
  writings.push(
 | 
			
		||||
    createExtractDir.then(
 | 
			
		||||
    createExtractDir.then(() =>
 | 
			
		||||
      writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2))
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Remove the current translations
 | 
			
		||||
  const deleteCurrent = Promise.all(writings).then(
 | 
			
		||||
  const deleteCurrent = Promise.all(writings).then(() =>
 | 
			
		||||
    deleteAsync([`${EXTRACT_DIR}/*`, `!${ARTIFACT_FILE}`, `!${TOKEN_FILE}`])
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
@@ -148,24 +149,22 @@ gulp.task("fetch-nightly-translations", async function () {
 | 
			
		||||
    artifact_id: latestArtifact.id,
 | 
			
		||||
    archive_format: "zip",
 | 
			
		||||
  });
 | 
			
		||||
  // @ts-ignore OctokitResponse<unknown, 302> doesn't allow to check for 200
 | 
			
		||||
  if (downloadResponse.status !== 200) {
 | 
			
		||||
    throw Error("Failure downloading translations artifact");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Artifact is a tarball, but GitHub adds it to a zip file
 | 
			
		||||
  console.log("Unpacking downloaded translations...");
 | 
			
		||||
  const zip = await jszip.loadAsync(downloadResponse.data);
 | 
			
		||||
  const zip = await jszip.loadAsync(downloadResponse.data as any);
 | 
			
		||||
  await deleteCurrent;
 | 
			
		||||
  const extractStream = zip.file(/.*/)[0].nodeStream().pipe(extract());
 | 
			
		||||
  await new Promise((resolve, reject) => {
 | 
			
		||||
    extractStream.on("close", resolve).on("error", reject);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "setup-and-fetch-nightly-translations",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    "allow-setup-fetch-nightly-translations",
 | 
			
		||||
    "fetch-nightly-translations"
 | 
			
		||||
  )
 | 
			
		||||
export const setupAndFetchNightlyTranslations = series(
 | 
			
		||||
  allowSetupFetchNightlyTranslations,
 | 
			
		||||
  fetchNightlyTranslations
 | 
			
		||||
);
 | 
			
		||||
@@ -1,19 +1,23 @@
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import { glob } from "glob";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import { parallel, series, watch } from "gulp";
 | 
			
		||||
import yaml from "js-yaml";
 | 
			
		||||
import { marked } from "marked";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import "./clean.js";
 | 
			
		||||
import "./entry-html.js";
 | 
			
		||||
import "./gather-static.js";
 | 
			
		||||
import "./gen-icons-json.js";
 | 
			
		||||
import "./service-worker.js";
 | 
			
		||||
import "./translations.js";
 | 
			
		||||
import "./rspack.js";
 | 
			
		||||
import fs from "node:fs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
import { cleanGallery } from "./clean.ts";
 | 
			
		||||
import { genPagesGalleryDev, genPagesGalleryProd } from "./entry-html.ts";
 | 
			
		||||
import { copyStaticGallery } from "./gather-static.ts";
 | 
			
		||||
import { genIconsJson } from "./gen-icons-json.ts";
 | 
			
		||||
import { buildLocaleData } from "./locale-data.ts";
 | 
			
		||||
import { rspackDevServerGallery, rspackProdGallery } from "./rspack.ts";
 | 
			
		||||
import {
 | 
			
		||||
  buildTranslations,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
} from "./translations.ts";
 | 
			
		||||
 | 
			
		||||
gulp.task("gather-gallery-pages", async function gatherPages() {
 | 
			
		||||
// gather-gallery-pages
 | 
			
		||||
export const gatherGalleryPages = async function gatherPages() {
 | 
			
		||||
  const pageDir = path.resolve(paths.gallery_dir, "src/pages");
 | 
			
		||||
  const files = await glob(path.resolve(pageDir, "**/*"));
 | 
			
		||||
 | 
			
		||||
@@ -22,7 +26,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
 | 
			
		||||
 | 
			
		||||
  let content = "export const PAGES = {\n";
 | 
			
		||||
 | 
			
		||||
  const processed = new Set();
 | 
			
		||||
  const processed = new Set<string>();
 | 
			
		||||
 | 
			
		||||
  for (const file of files) {
 | 
			
		||||
    if (fs.lstatSync(file).isDirectory()) {
 | 
			
		||||
@@ -47,7 +51,9 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
 | 
			
		||||
 | 
			
		||||
      if (descriptionContent.startsWith("---")) {
 | 
			
		||||
        const metadataEnd = descriptionContent.indexOf("---", 3);
 | 
			
		||||
        metadata = yaml.load(descriptionContent.substring(3, metadataEnd));
 | 
			
		||||
        metadata = yaml.load(
 | 
			
		||||
          descriptionContent.substring(3, metadataEnd)
 | 
			
		||||
        ) as any;
 | 
			
		||||
        descriptionContent = descriptionContent
 | 
			
		||||
          .substring(metadataEnd + 3)
 | 
			
		||||
          .trim();
 | 
			
		||||
@@ -57,7 +63,9 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
 | 
			
		||||
      if (descriptionContent === "") {
 | 
			
		||||
        hasDescription = false;
 | 
			
		||||
      } else {
 | 
			
		||||
        descriptionContent = marked(descriptionContent).replace(/`/g, "\\`");
 | 
			
		||||
        // eslint-disable-next-line no-await-in-loop
 | 
			
		||||
        descriptionContent = await marked(descriptionContent);
 | 
			
		||||
        descriptionContent = descriptionContent.replace(/`/g, "\\`");
 | 
			
		||||
        fs.mkdirSync(path.resolve(galleryBuild, category), { recursive: true });
 | 
			
		||||
        fs.writeFileSync(
 | 
			
		||||
          path.resolve(galleryBuild, `${pageId}-description.ts`),
 | 
			
		||||
@@ -95,7 +103,10 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
 | 
			
		||||
    pagesToProcess[category].add(page);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (const group of Object.values(sidebar)) {
 | 
			
		||||
  for (const group of Object.values(sidebar) as {
 | 
			
		||||
    category: string;
 | 
			
		||||
    pages?: string[];
 | 
			
		||||
  }[]) {
 | 
			
		||||
    const toProcess = pagesToProcess[group.category];
 | 
			
		||||
    delete pagesToProcess[group.category];
 | 
			
		||||
 | 
			
		||||
@@ -118,7 +129,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
 | 
			
		||||
      group.pages = [];
 | 
			
		||||
    }
 | 
			
		||||
    for (const page of Array.from(toProcess).sort()) {
 | 
			
		||||
      group.pages.push(page);
 | 
			
		||||
      group.pages.push(page as string);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -126,7 +137,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
 | 
			
		||||
    sidebar.push({
 | 
			
		||||
      category,
 | 
			
		||||
      header: category,
 | 
			
		||||
      pages: Array.from(pages).sort(),
 | 
			
		||||
      pages: Array.from(pages as Set<string>).sort(),
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -137,55 +148,48 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
 | 
			
		||||
    content,
 | 
			
		||||
    "utf-8"
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "develop-gallery",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "development";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-gallery",
 | 
			
		||||
    "translations-enable-merge-backend",
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "gen-icons-json",
 | 
			
		||||
      "build-translations",
 | 
			
		||||
      "build-locale-data",
 | 
			
		||||
      "gather-gallery-pages"
 | 
			
		||||
    ),
 | 
			
		||||
    "copy-static-gallery",
 | 
			
		||||
    "gen-pages-gallery-dev",
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "rspack-dev-server-gallery",
 | 
			
		||||
      async function watchMarkdownFiles() {
 | 
			
		||||
        gulp.watch(
 | 
			
		||||
          [
 | 
			
		||||
            path.resolve(paths.gallery_dir, "src/pages/**/*.markdown"),
 | 
			
		||||
            path.resolve(paths.gallery_dir, "sidebar.js"),
 | 
			
		||||
          ],
 | 
			
		||||
          gulp.series("gather-gallery-pages")
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    )
 | 
			
		||||
  )
 | 
			
		||||
// develop-gallery
 | 
			
		||||
export const developGallery = series(
 | 
			
		||||
  async function setEnv() {
 | 
			
		||||
    process.env.NODE_ENV = "development";
 | 
			
		||||
  },
 | 
			
		||||
  cleanGallery,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
  parallel(
 | 
			
		||||
    genIconsJson,
 | 
			
		||||
    buildTranslations,
 | 
			
		||||
    buildLocaleData,
 | 
			
		||||
    gatherGalleryPages
 | 
			
		||||
  ),
 | 
			
		||||
  copyStaticGallery,
 | 
			
		||||
  genPagesGalleryDev,
 | 
			
		||||
  parallel(rspackDevServerGallery, async function watchMarkdownFiles() {
 | 
			
		||||
    watch(
 | 
			
		||||
      [
 | 
			
		||||
        path.resolve(paths.gallery_dir, "src/pages/**/*.markdown"),
 | 
			
		||||
        path.resolve(paths.gallery_dir, "sidebar.js"),
 | 
			
		||||
      ],
 | 
			
		||||
      series(gatherGalleryPages)
 | 
			
		||||
    );
 | 
			
		||||
  })
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-gallery",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "production";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-gallery",
 | 
			
		||||
    "translations-enable-merge-backend",
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "gen-icons-json",
 | 
			
		||||
      "build-translations",
 | 
			
		||||
      "build-locale-data",
 | 
			
		||||
      "gather-gallery-pages"
 | 
			
		||||
    ),
 | 
			
		||||
    "copy-static-gallery",
 | 
			
		||||
    "rspack-prod-gallery",
 | 
			
		||||
    "gen-pages-gallery-prod"
 | 
			
		||||
  )
 | 
			
		||||
// build-gallery
 | 
			
		||||
export const buildGallery = series(
 | 
			
		||||
  async function setEnv() {
 | 
			
		||||
    process.env.NODE_ENV = "production";
 | 
			
		||||
  },
 | 
			
		||||
  cleanGallery,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
  parallel(
 | 
			
		||||
    genIconsJson,
 | 
			
		||||
    buildTranslations,
 | 
			
		||||
    buildLocaleData,
 | 
			
		||||
    gatherGalleryPages
 | 
			
		||||
  ),
 | 
			
		||||
  copyStaticGallery,
 | 
			
		||||
  rspackProdGallery,
 | 
			
		||||
  genPagesGalleryProd
 | 
			
		||||
);
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
// Gulp task to gather all static files.
 | 
			
		||||
 | 
			
		||||
import fs from "fs-extra";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
 | 
			
		||||
const npmPath = (...parts) =>
 | 
			
		||||
  path.resolve(paths.root_dir, "node_modules", ...parts);
 | 
			
		||||
@@ -17,7 +16,7 @@ const genStaticPath =
 | 
			
		||||
  (...parts) =>
 | 
			
		||||
    path.resolve(staticDir, ...parts);
 | 
			
		||||
 | 
			
		||||
function copyTranslations(staticDir) {
 | 
			
		||||
const copyTranslations = (staticDir) => {
 | 
			
		||||
  const staticPath = genStaticPath(staticDir);
 | 
			
		||||
 | 
			
		||||
  // Translation output
 | 
			
		||||
@@ -25,23 +24,23 @@ function copyTranslations(staticDir) {
 | 
			
		||||
    polyPath("build/translations/output"),
 | 
			
		||||
    staticPath("translations")
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function copyLocaleData(staticDir) {
 | 
			
		||||
const copyLocaleData = (staticDir) => {
 | 
			
		||||
  const staticPath = genStaticPath(staticDir);
 | 
			
		||||
 | 
			
		||||
  // Locale data output
 | 
			
		||||
  fs.copySync(polyPath("build/locale-data"), staticPath("locale-data"));
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function copyMdiIcons(staticDir) {
 | 
			
		||||
const copyMdiIcons = (staticDir) => {
 | 
			
		||||
  const staticPath = genStaticPath(staticDir);
 | 
			
		||||
 | 
			
		||||
  // MDI icons output
 | 
			
		||||
  fs.copySync(polyPath("build/mdi"), staticPath("mdi"));
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function copyPolyfills(staticDir) {
 | 
			
		||||
const copyPolyfills = (staticDir) => {
 | 
			
		||||
  const staticPath = genStaticPath(staticDir);
 | 
			
		||||
 | 
			
		||||
  // For custom panels using ES5 builds that don't use Babel 7+
 | 
			
		||||
@@ -70,9 +69,9 @@ function copyPolyfills(staticDir) {
 | 
			
		||||
    npmPath("dialog-polyfill/dialog-polyfill.css"),
 | 
			
		||||
    staticPath("polyfills/")
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function copyFonts(staticDir) {
 | 
			
		||||
const copyFonts = (staticDir) => {
 | 
			
		||||
  const staticPath = genStaticPath(staticDir);
 | 
			
		||||
  // Local fonts
 | 
			
		||||
  fs.copySync(
 | 
			
		||||
@@ -82,14 +81,14 @@ function copyFonts(staticDir) {
 | 
			
		||||
      filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function copyQrScannerWorker(staticDir) {
 | 
			
		||||
const copyQrScannerWorker = (staticDir) => {
 | 
			
		||||
  const staticPath = genStaticPath(staticDir);
 | 
			
		||||
  copyFileDir(npmPath("qr-scanner/qr-scanner-worker.min.js"), staticPath("js"));
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function copyMapPanel(staticDir) {
 | 
			
		||||
const copyMapPanel = (staticDir) => {
 | 
			
		||||
  const staticPath = genStaticPath(staticDir);
 | 
			
		||||
  copyFileDir(
 | 
			
		||||
    npmPath("leaflet/dist/leaflet.css"),
 | 
			
		||||
@@ -103,43 +102,38 @@ function copyMapPanel(staticDir) {
 | 
			
		||||
    npmPath("leaflet/dist/images"),
 | 
			
		||||
    staticPath("images/leaflet/images/")
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function copyZXingWasm(staticDir) {
 | 
			
		||||
const copyZXingWasm = (staticDir) => {
 | 
			
		||||
  const staticPath = genStaticPath(staticDir);
 | 
			
		||||
  copyFileDir(
 | 
			
		||||
    npmPath("zxing-wasm/dist/reader/zxing_reader.wasm"),
 | 
			
		||||
    staticPath("js")
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-locale-data", async () => {
 | 
			
		||||
  const staticDir = paths.app_output_static;
 | 
			
		||||
  copyLocaleData(staticDir);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-translations-app", async () => {
 | 
			
		||||
export const copyTranslationsApp = async () => {
 | 
			
		||||
  const staticDir = paths.app_output_static;
 | 
			
		||||
  copyTranslations(staticDir);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-translations-supervisor", async () => {
 | 
			
		||||
export const copyTranslationsSupervisor = async () => {
 | 
			
		||||
  const staticDir = paths.hassio_output_static;
 | 
			
		||||
  copyTranslations(staticDir);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-translations-landing-page", async () => {
 | 
			
		||||
export const copyTranslationsLandingPage = async () => {
 | 
			
		||||
  const staticDir = paths.landingPage_output_static;
 | 
			
		||||
  copyTranslations(staticDir);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-static-supervisor", async () => {
 | 
			
		||||
export const copyStaticSupervisor = async () => {
 | 
			
		||||
  const staticDir = paths.hassio_output_static;
 | 
			
		||||
  copyLocaleData(staticDir);
 | 
			
		||||
  copyFonts(staticDir);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-static-app", async () => {
 | 
			
		||||
export const copyStaticApp = async () => {
 | 
			
		||||
  const staticDir = paths.app_output_static;
 | 
			
		||||
  // Basic static files
 | 
			
		||||
  fs.copySync(polyPath("public"), paths.app_output_root);
 | 
			
		||||
@@ -155,9 +149,9 @@ gulp.task("copy-static-app", async () => {
 | 
			
		||||
  // Qr Scanner assets
 | 
			
		||||
  copyZXingWasm(staticDir);
 | 
			
		||||
  copyQrScannerWorker(staticDir);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-static-demo", async () => {
 | 
			
		||||
export const copyStaticDemo = async () => {
 | 
			
		||||
  // Copy app static files
 | 
			
		||||
  fs.copySync(
 | 
			
		||||
    polyPath("public/static"),
 | 
			
		||||
@@ -171,9 +165,9 @@ gulp.task("copy-static-demo", async () => {
 | 
			
		||||
  copyTranslations(paths.demo_output_static);
 | 
			
		||||
  copyLocaleData(paths.demo_output_static);
 | 
			
		||||
  copyMdiIcons(paths.demo_output_static);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-static-cast", async () => {
 | 
			
		||||
export const copyStaticCast = async () => {
 | 
			
		||||
  // Copy app static files
 | 
			
		||||
  fs.copySync(polyPath("public/static"), paths.cast_output_static);
 | 
			
		||||
  // Copy cast static files
 | 
			
		||||
@@ -184,9 +178,9 @@ gulp.task("copy-static-cast", async () => {
 | 
			
		||||
  copyTranslations(paths.cast_output_static);
 | 
			
		||||
  copyLocaleData(paths.cast_output_static);
 | 
			
		||||
  copyMdiIcons(paths.cast_output_static);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-static-gallery", async () => {
 | 
			
		||||
export const copyStaticGallery = async () => {
 | 
			
		||||
  // Copy app static files
 | 
			
		||||
  fs.copySync(polyPath("public/static"), paths.gallery_output_static);
 | 
			
		||||
  // Copy gallery static files
 | 
			
		||||
@@ -200,9 +194,9 @@ gulp.task("copy-static-gallery", async () => {
 | 
			
		||||
  copyTranslations(paths.gallery_output_static);
 | 
			
		||||
  copyLocaleData(paths.gallery_output_static);
 | 
			
		||||
  copyMdiIcons(paths.gallery_output_static);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("copy-static-landing-page", async () => {
 | 
			
		||||
export const copyStaticLandingPage = async () => {
 | 
			
		||||
  // Copy landing-page static files
 | 
			
		||||
  fs.copySync(
 | 
			
		||||
    path.resolve(paths.landingPage_dir, "public"),
 | 
			
		||||
@@ -211,4 +205,4 @@ gulp.task("copy-static-landing-page", async () => {
 | 
			
		||||
 | 
			
		||||
  copyFonts(paths.landingPage_output_static);
 | 
			
		||||
  copyTranslations(paths.landingPage_output_static);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
@@ -1,8 +1,7 @@
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import fs from "node:fs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import hash from "object-hash";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
 | 
			
		||||
const ICON_PACKAGE_PATH = path.resolve("node_modules/@mdi/svg/");
 | 
			
		||||
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
 | 
			
		||||
@@ -21,7 +20,7 @@ const getMeta = () => {
 | 
			
		||||
      encoding,
 | 
			
		||||
    });
 | 
			
		||||
    return {
 | 
			
		||||
      path: svg.match(/ d="([^"]+)"/)[1],
 | 
			
		||||
      path: svg.match(/ d="([^"]+)"/)?.[1],
 | 
			
		||||
      name: icon.name,
 | 
			
		||||
      tags: icon.tags,
 | 
			
		||||
      aliases: icon.aliases,
 | 
			
		||||
@@ -55,14 +54,14 @@ const orderMeta = (meta) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const splitBySize = (meta) => {
 | 
			
		||||
  const chunks = [];
 | 
			
		||||
  const chunks: any[] = [];
 | 
			
		||||
  const CHUNK_SIZE = 50000;
 | 
			
		||||
 | 
			
		||||
  let curSize = 0;
 | 
			
		||||
  let startKey;
 | 
			
		||||
  let icons = [];
 | 
			
		||||
  let icons: any[] = [];
 | 
			
		||||
 | 
			
		||||
  Object.values(meta).forEach((icon) => {
 | 
			
		||||
  Object.values(meta).forEach((icon: any) => {
 | 
			
		||||
    if (startKey === undefined) {
 | 
			
		||||
      startKey = icon.name;
 | 
			
		||||
    }
 | 
			
		||||
@@ -94,10 +93,10 @@ const findDifferentiator = (curString, prevString) => {
 | 
			
		||||
      return curString.substring(0, i + 1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  throw new Error("Cannot find differentiator", curString, prevString);
 | 
			
		||||
  throw new Error(`Cannot find differentiator; ${curString}; ${prevString}`);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("gen-icons-json", (done) => {
 | 
			
		||||
export const genIconsJson = (done) => {
 | 
			
		||||
  const meta = getMeta();
 | 
			
		||||
 | 
			
		||||
  const metaAndRemoved = addRemovedMeta(meta);
 | 
			
		||||
@@ -106,7 +105,7 @@ gulp.task("gen-icons-json", (done) => {
 | 
			
		||||
  if (!fs.existsSync(OUTPUT_DIR)) {
 | 
			
		||||
    fs.mkdirSync(OUTPUT_DIR, { recursive: true });
 | 
			
		||||
  }
 | 
			
		||||
  const parts = [];
 | 
			
		||||
  const parts: any[] = [];
 | 
			
		||||
 | 
			
		||||
  let lastEnd;
 | 
			
		||||
  split.forEach((chunk) => {
 | 
			
		||||
@@ -153,13 +152,13 @@ gulp.task("gen-icons-json", (done) => {
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  done();
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("gen-dummy-icons-json", (done) => {
 | 
			
		||||
export const genDummyIconsJson = (done) => {
 | 
			
		||||
  if (!fs.existsSync(OUTPUT_DIR)) {
 | 
			
		||||
    fs.mkdirSync(OUTPUT_DIR, { recursive: true });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fs.writeFileSync(path.resolve(OUTPUT_DIR, "iconList.json"), "[]");
 | 
			
		||||
  done();
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import env from "../env.cjs";
 | 
			
		||||
import "./clean.js";
 | 
			
		||||
import "./compress.js";
 | 
			
		||||
import "./entry-html.js";
 | 
			
		||||
import "./gather-static.js";
 | 
			
		||||
import "./gen-icons-json.js";
 | 
			
		||||
import "./translations.js";
 | 
			
		||||
import "./rspack.js";
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "develop-hassio",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "development";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-hassio",
 | 
			
		||||
    "gen-dummy-icons-json",
 | 
			
		||||
    "gen-pages-hassio-dev",
 | 
			
		||||
    "build-supervisor-translations",
 | 
			
		||||
    "copy-translations-supervisor",
 | 
			
		||||
    "build-locale-data",
 | 
			
		||||
    "copy-static-supervisor",
 | 
			
		||||
    "rspack-watch-hassio"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-hassio",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "production";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-hassio",
 | 
			
		||||
    "gen-dummy-icons-json",
 | 
			
		||||
    "build-supervisor-translations",
 | 
			
		||||
    "copy-translations-supervisor",
 | 
			
		||||
    "build-locale-data",
 | 
			
		||||
    "copy-static-supervisor",
 | 
			
		||||
    "rspack-prod-hassio",
 | 
			
		||||
    "gen-pages-hassio-prod",
 | 
			
		||||
    ...// Don't compress running tests
 | 
			
		||||
    (env.isTestBuild() ? [] : ["compress-hassio"])
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										45
									
								
								build-scripts/gulp/hassio.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								build-scripts/gulp/hassio.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
import { series } from "gulp";
 | 
			
		||||
import { isTestBuild } from "../env.ts";
 | 
			
		||||
import { cleanHassio } from "./clean.ts";
 | 
			
		||||
import { compressHassio } from "./compress.ts";
 | 
			
		||||
import { genPagesHassioDev, genPagesHassioProd } from "./entry-html.ts";
 | 
			
		||||
import {
 | 
			
		||||
  copyStaticSupervisor,
 | 
			
		||||
  copyTranslationsSupervisor,
 | 
			
		||||
} from "./gather-static.ts";
 | 
			
		||||
import { genDummyIconsJson } from "./gen-icons-json.ts";
 | 
			
		||||
import { buildLocaleData } from "./locale-data.ts";
 | 
			
		||||
import { rspackProdHassio, rspackWatchHassio } from "./rspack.ts";
 | 
			
		||||
import { buildSupervisorTranslations } from "./translations.ts";
 | 
			
		||||
 | 
			
		||||
// develop-hassio
 | 
			
		||||
export const developHassio = series(
 | 
			
		||||
  async function setEnv() {
 | 
			
		||||
    process.env.NODE_ENV = "development";
 | 
			
		||||
  },
 | 
			
		||||
  cleanHassio,
 | 
			
		||||
  genDummyIconsJson,
 | 
			
		||||
  genPagesHassioDev,
 | 
			
		||||
  buildSupervisorTranslations,
 | 
			
		||||
  copyTranslationsSupervisor,
 | 
			
		||||
  buildLocaleData,
 | 
			
		||||
  copyStaticSupervisor,
 | 
			
		||||
  rspackWatchHassio
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// build-hassio
 | 
			
		||||
export const buildHassio = series(
 | 
			
		||||
  async function setEnv() {
 | 
			
		||||
    process.env.NODE_ENV = "production";
 | 
			
		||||
  },
 | 
			
		||||
  cleanHassio,
 | 
			
		||||
  genDummyIconsJson,
 | 
			
		||||
  buildSupervisorTranslations,
 | 
			
		||||
  copyTranslationsSupervisor,
 | 
			
		||||
  buildLocaleData,
 | 
			
		||||
  copyStaticSupervisor,
 | 
			
		||||
  rspackProdHassio,
 | 
			
		||||
  genPagesHassioProd,
 | 
			
		||||
  ...// Don't compress running tests
 | 
			
		||||
  (isTestBuild() ? [] : [compressHassio])
 | 
			
		||||
);
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
import "./app.js";
 | 
			
		||||
import "./cast.js";
 | 
			
		||||
import "./clean.js";
 | 
			
		||||
import "./compress.js";
 | 
			
		||||
import "./demo.js";
 | 
			
		||||
import "./download-translations.js";
 | 
			
		||||
import "./entry-html.js";
 | 
			
		||||
import "./fetch-nightly-translations.js";
 | 
			
		||||
import "./gallery.js";
 | 
			
		||||
import "./gather-static.js";
 | 
			
		||||
import "./gen-icons-json.js";
 | 
			
		||||
import "./hassio.js";
 | 
			
		||||
import "./landing-page.js";
 | 
			
		||||
import "./locale-data.js";
 | 
			
		||||
import "./rspack.js";
 | 
			
		||||
import "./service-worker.js";
 | 
			
		||||
import "./translations.js";
 | 
			
		||||
							
								
								
									
										42
									
								
								build-scripts/gulp/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								build-scripts/gulp/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
import { analyzeApp, buildApp, developApp } from "./app";
 | 
			
		||||
import { buildCast, developCast } from "./cast";
 | 
			
		||||
import { analyzeDemo, buildDemo, developDemo } from "./demo";
 | 
			
		||||
import { downloadTranslations } from "./download-translations";
 | 
			
		||||
import { setupAndFetchNightlyTranslations } from "./fetch-nightly-translations";
 | 
			
		||||
import { buildGallery, developGallery, gatherGalleryPages } from "./gallery";
 | 
			
		||||
import { genIconsJson } from "./gen-icons-json";
 | 
			
		||||
import { buildHassio, developHassio } from "./hassio";
 | 
			
		||||
import { buildLandingPage, developLandingPage } from "./landing-page";
 | 
			
		||||
import { buildLocaleData } from "./locale-data";
 | 
			
		||||
import { buildTranslations } from "./translations";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  "develop-app": developApp,
 | 
			
		||||
  "build-app": buildApp,
 | 
			
		||||
  "analyze-app": analyzeApp,
 | 
			
		||||
 | 
			
		||||
  "develop-cast": developCast,
 | 
			
		||||
  "build-cast": buildCast,
 | 
			
		||||
 | 
			
		||||
  "develop-demo": developDemo,
 | 
			
		||||
  "build-demo": buildDemo,
 | 
			
		||||
  "analyze-demo": analyzeDemo,
 | 
			
		||||
 | 
			
		||||
  "develop-gallery": developGallery,
 | 
			
		||||
  "build-gallery": buildGallery,
 | 
			
		||||
  "gather-gallery-pages": gatherGalleryPages,
 | 
			
		||||
 | 
			
		||||
  "develop-hassio": developHassio,
 | 
			
		||||
  "build-hassio": buildHassio,
 | 
			
		||||
 | 
			
		||||
  "develop-landing-page": developLandingPage,
 | 
			
		||||
  "build-landing-page": buildLandingPage,
 | 
			
		||||
 | 
			
		||||
  "setup-and-fetch-nightly-translations": setupAndFetchNightlyTranslations,
 | 
			
		||||
  "download-translations": downloadTranslations,
 | 
			
		||||
  "build-translations": buildTranslations,
 | 
			
		||||
 | 
			
		||||
  "gen-icons-json": genIconsJson,
 | 
			
		||||
 | 
			
		||||
  "build-locale-data": buildLocaleData,
 | 
			
		||||
};
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import "./clean.js";
 | 
			
		||||
import "./compress.js";
 | 
			
		||||
import "./entry-html.js";
 | 
			
		||||
import "./gather-static.js";
 | 
			
		||||
import "./gen-icons-json.js";
 | 
			
		||||
import "./translations.js";
 | 
			
		||||
import "./rspack.js";
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "develop-landing-page",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "development";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-landing-page",
 | 
			
		||||
    "translations-enable-merge-backend",
 | 
			
		||||
    "build-landing-page-translations",
 | 
			
		||||
    "copy-translations-landing-page",
 | 
			
		||||
    "build-locale-data",
 | 
			
		||||
    "copy-static-landing-page",
 | 
			
		||||
    "gen-pages-landing-page-dev",
 | 
			
		||||
    "rspack-watch-landing-page"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-landing-page",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    async function setEnv() {
 | 
			
		||||
      process.env.NODE_ENV = "production";
 | 
			
		||||
    },
 | 
			
		||||
    "clean-landing-page",
 | 
			
		||||
    "build-landing-page-translations",
 | 
			
		||||
    "copy-translations-landing-page",
 | 
			
		||||
    "build-locale-data",
 | 
			
		||||
    "copy-static-landing-page",
 | 
			
		||||
    "rspack-prod-landing-page",
 | 
			
		||||
    "gen-pages-landing-page-prod"
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										46
									
								
								build-scripts/gulp/landing-page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								build-scripts/gulp/landing-page.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
import { series } from "gulp";
 | 
			
		||||
import { cleanLandingPage } from "./clean.ts";
 | 
			
		||||
import "./compress.ts";
 | 
			
		||||
import {
 | 
			
		||||
  genPagesLandingPageDev,
 | 
			
		||||
  genPagesLandingPageProd,
 | 
			
		||||
} from "./entry-html.ts";
 | 
			
		||||
import {
 | 
			
		||||
  copyStaticLandingPage,
 | 
			
		||||
  copyTranslationsLandingPage,
 | 
			
		||||
} from "./gather-static.ts";
 | 
			
		||||
import { buildLocaleData } from "./locale-data.ts";
 | 
			
		||||
import { rspackProdLandingPage, rspackWatchLandingPage } from "./rspack.ts";
 | 
			
		||||
import {
 | 
			
		||||
  buildLandingPageTranslations,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
} from "./translations.ts";
 | 
			
		||||
 | 
			
		||||
// develop-landing-page
 | 
			
		||||
export const developLandingPage = series(
 | 
			
		||||
  async function setEnv() {
 | 
			
		||||
    process.env.NODE_ENV = "development";
 | 
			
		||||
  },
 | 
			
		||||
  cleanLandingPage,
 | 
			
		||||
  translationsEnableMergeBackend,
 | 
			
		||||
  buildLandingPageTranslations,
 | 
			
		||||
  copyTranslationsLandingPage,
 | 
			
		||||
  buildLocaleData,
 | 
			
		||||
  copyStaticLandingPage,
 | 
			
		||||
  genPagesLandingPageDev,
 | 
			
		||||
  rspackWatchLandingPage
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// build-landing-page
 | 
			
		||||
export const buildLandingPage = series(
 | 
			
		||||
  async function setEnv() {
 | 
			
		||||
    process.env.NODE_ENV = "production";
 | 
			
		||||
  },
 | 
			
		||||
  cleanLandingPage,
 | 
			
		||||
  buildLandingPageTranslations,
 | 
			
		||||
  copyTranslationsLandingPage,
 | 
			
		||||
  buildLocaleData,
 | 
			
		||||
  copyStaticLandingPage,
 | 
			
		||||
  rspackProdLandingPage,
 | 
			
		||||
  genPagesLandingPageProd
 | 
			
		||||
);
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import { deleteSync } from "del";
 | 
			
		||||
import { mkdir, readFile, writeFile } from "fs/promises";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import { series } from "gulp";
 | 
			
		||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
 | 
			
		||||
import { join, resolve } from "node:path";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
 | 
			
		||||
const formatjsDir = join(paths.root_dir, "node_modules", "@formatjs");
 | 
			
		||||
const outDir = join(paths.build_dir, "locale-data");
 | 
			
		||||
@@ -31,7 +31,7 @@ const convertToJSON = async (
 | 
			
		||||
      join(formatjsDir, pkg, subDir, `${language}.js`),
 | 
			
		||||
      "utf-8"
 | 
			
		||||
    );
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
  } catch (e: any) {
 | 
			
		||||
    // Ignore if language is missing (i.e. not supported by @formatjs)
 | 
			
		||||
    if (e.code === "ENOENT" && skipMissing) {
 | 
			
		||||
      console.warn(`Skipped missing data for language ${lang} from ${pkg}`);
 | 
			
		||||
@@ -54,16 +54,16 @@ const convertToJSON = async (
 | 
			
		||||
  await writeFile(join(outDir, `${pkg}/${lang}.json`), localeData);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("clean-locale-data", async () => deleteSync([outDir]));
 | 
			
		||||
const cleanLocaleData = async () => deleteSync([outDir]);
 | 
			
		||||
 | 
			
		||||
gulp.task("create-locale-data", async () => {
 | 
			
		||||
const createLocaleData = async () => {
 | 
			
		||||
  const translationMeta = JSON.parse(
 | 
			
		||||
    await readFile(
 | 
			
		||||
      resolve(paths.translations_src, "translationMetadata.json"),
 | 
			
		||||
      "utf-8"
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
  const conversions = [];
 | 
			
		||||
  const conversions: any[] = [];
 | 
			
		||||
  for (const pkg of Object.keys(INTL_POLYFILLS)) {
 | 
			
		||||
    // eslint-disable-next-line no-await-in-loop
 | 
			
		||||
    await mkdir(join(outDir, pkg), { recursive: true });
 | 
			
		||||
@@ -81,9 +81,6 @@ gulp.task("create-locale-data", async () => {
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
  await Promise.all(conversions);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-locale-data",
 | 
			
		||||
  gulp.series("clean-locale-data", "create-locale-data")
 | 
			
		||||
);
 | 
			
		||||
export const buildLocaleData = series(cleanLocaleData, createLocaleData);
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
// Tasks to run rspack.
 | 
			
		||||
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import log from "fancy-log";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import rspack from "@rspack/core";
 | 
			
		||||
import { RspackDevServer } from "@rspack/dev-server";
 | 
			
		||||
import env from "../env.cjs";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import log from "fancy-log";
 | 
			
		||||
import { series, watch } from "gulp";
 | 
			
		||||
import fs from "node:fs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import { isDevContainer, isStatsBuild, isTestBuild } from "../env.ts";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
import {
 | 
			
		||||
  createAppConfig,
 | 
			
		||||
  createCastConfig,
 | 
			
		||||
@@ -15,7 +15,17 @@ import {
 | 
			
		||||
  createGalleryConfig,
 | 
			
		||||
  createHassioConfig,
 | 
			
		||||
  createLandingPageConfig,
 | 
			
		||||
} from "../rspack.cjs";
 | 
			
		||||
} from "../rspack.ts";
 | 
			
		||||
import {
 | 
			
		||||
  copyTranslationsApp,
 | 
			
		||||
  copyTranslationsLandingPage,
 | 
			
		||||
  copyTranslationsSupervisor,
 | 
			
		||||
} from "./gather-static.ts";
 | 
			
		||||
import {
 | 
			
		||||
  buildLandingPageTranslations,
 | 
			
		||||
  buildSupervisorTranslations,
 | 
			
		||||
  buildTranslations,
 | 
			
		||||
} from "./translations.ts";
 | 
			
		||||
 | 
			
		||||
const bothBuilds = (createConfigFunc, params) => [
 | 
			
		||||
  createConfigFunc({ ...params, latestBuild: true }),
 | 
			
		||||
@@ -29,6 +39,14 @@ const isWsl =
 | 
			
		||||
    .toLocaleLowerCase()
 | 
			
		||||
    .includes("microsoft");
 | 
			
		||||
 | 
			
		||||
interface RunDevServer {
 | 
			
		||||
  compiler: any;
 | 
			
		||||
  contentBase: string;
 | 
			
		||||
  port: number;
 | 
			
		||||
  listenHost?: string;
 | 
			
		||||
  proxy?: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @param {{
 | 
			
		||||
 *   compiler: import("@rspack/core").Compiler,
 | 
			
		||||
@@ -41,12 +59,12 @@ const runDevServer = async ({
 | 
			
		||||
  compiler,
 | 
			
		||||
  contentBase,
 | 
			
		||||
  port,
 | 
			
		||||
  listenHost = undefined,
 | 
			
		||||
  proxy = undefined,
 | 
			
		||||
}) => {
 | 
			
		||||
  listenHost,
 | 
			
		||||
  proxy,
 | 
			
		||||
}: RunDevServer) => {
 | 
			
		||||
  if (listenHost === undefined) {
 | 
			
		||||
    // For dev container, we need to listen on all hosts
 | 
			
		||||
    listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost";
 | 
			
		||||
    listenHost = isDevContainer() ? "0.0.0.0" : "localhost";
 | 
			
		||||
  }
 | 
			
		||||
  const server = new RspackDevServer(
 | 
			
		||||
    {
 | 
			
		||||
@@ -68,7 +86,7 @@ const runDevServer = async ({
 | 
			
		||||
  log("[rspack-dev-server]", `Project is running at http://localhost:${port}`);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const doneHandler = (done) => (err, stats) => {
 | 
			
		||||
const doneHandler = (done?: (value?: unknown) => void) => (err, stats) => {
 | 
			
		||||
  if (err) {
 | 
			
		||||
    log.error(err.stack || err);
 | 
			
		||||
    if (err.details) {
 | 
			
		||||
@@ -97,49 +115,46 @@ const prodBuild = (conf) =>
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-watch-app", () => {
 | 
			
		||||
export const rspackWatchApp = () => {
 | 
			
		||||
  // This command will run forever because we don't close compiler
 | 
			
		||||
  rspack(
 | 
			
		||||
    process.env.ES5
 | 
			
		||||
      ? bothBuilds(createAppConfig, { isProdBuild: false })
 | 
			
		||||
      : createAppConfig({ isProdBuild: false, latestBuild: true })
 | 
			
		||||
  ).watch({ poll: isWsl }, doneHandler());
 | 
			
		||||
  gulp.watch(
 | 
			
		||||
  watch(
 | 
			
		||||
    path.join(paths.translations_src, "en.json"),
 | 
			
		||||
    gulp.series("build-translations", "copy-translations-app")
 | 
			
		||||
    series(buildTranslations, copyTranslationsApp)
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-prod-app", () =>
 | 
			
		||||
export const rspackProdApp = () =>
 | 
			
		||||
  prodBuild(
 | 
			
		||||
    bothBuilds(createAppConfig, {
 | 
			
		||||
      isProdBuild: true,
 | 
			
		||||
      isStatsBuild: env.isStatsBuild(),
 | 
			
		||||
      isTestBuild: env.isTestBuild(),
 | 
			
		||||
      isStatsBuild: isStatsBuild(),
 | 
			
		||||
      isTestBuild: isTestBuild(),
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-dev-server-demo", () =>
 | 
			
		||||
export const rspackDevServerDemo = () =>
 | 
			
		||||
  runDevServer({
 | 
			
		||||
    compiler: rspack(
 | 
			
		||||
      createDemoConfig({ isProdBuild: false, latestBuild: true })
 | 
			
		||||
    ),
 | 
			
		||||
    contentBase: paths.demo_output_root,
 | 
			
		||||
    port: 8090,
 | 
			
		||||
  })
 | 
			
		||||
);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-prod-demo", () =>
 | 
			
		||||
export const rspackProdDemo = () =>
 | 
			
		||||
  prodBuild(
 | 
			
		||||
    bothBuilds(createDemoConfig, {
 | 
			
		||||
      isProdBuild: true,
 | 
			
		||||
      isStatsBuild: env.isStatsBuild(),
 | 
			
		||||
      isStatsBuild: isStatsBuild(),
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-dev-server-cast", () =>
 | 
			
		||||
export const rspackDevServerCast = () =>
 | 
			
		||||
  runDevServer({
 | 
			
		||||
    compiler: rspack(
 | 
			
		||||
      createCastConfig({ isProdBuild: false, latestBuild: true })
 | 
			
		||||
@@ -148,18 +163,16 @@ gulp.task("rspack-dev-server-cast", () =>
 | 
			
		||||
    port: 8080,
 | 
			
		||||
    // Accessible from the network, because that's how Cast hits it.
 | 
			
		||||
    listenHost: "0.0.0.0",
 | 
			
		||||
  })
 | 
			
		||||
);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-prod-cast", () =>
 | 
			
		||||
export const rspackProdCast = () =>
 | 
			
		||||
  prodBuild(
 | 
			
		||||
    bothBuilds(createCastConfig, {
 | 
			
		||||
      isProdBuild: true,
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-watch-hassio", () => {
 | 
			
		||||
export const rspackWatchHassio = () => {
 | 
			
		||||
  // This command will run forever because we don't close compiler
 | 
			
		||||
  rspack(
 | 
			
		||||
    createHassioConfig({
 | 
			
		||||
@@ -168,23 +181,22 @@ gulp.task("rspack-watch-hassio", () => {
 | 
			
		||||
    })
 | 
			
		||||
  ).watch({ ignored: /build/, poll: isWsl }, doneHandler());
 | 
			
		||||
 | 
			
		||||
  gulp.watch(
 | 
			
		||||
  watch(
 | 
			
		||||
    path.join(paths.translations_src, "en.json"),
 | 
			
		||||
    gulp.series("build-supervisor-translations", "copy-translations-supervisor")
 | 
			
		||||
    series(buildSupervisorTranslations, copyTranslationsSupervisor)
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-prod-hassio", () =>
 | 
			
		||||
export const rspackProdHassio = () =>
 | 
			
		||||
  prodBuild(
 | 
			
		||||
    bothBuilds(createHassioConfig, {
 | 
			
		||||
      isProdBuild: true,
 | 
			
		||||
      isStatsBuild: env.isStatsBuild(),
 | 
			
		||||
      isTestBuild: env.isTestBuild(),
 | 
			
		||||
      isStatsBuild: isStatsBuild(),
 | 
			
		||||
      isTestBuild: isTestBuild(),
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-dev-server-gallery", () =>
 | 
			
		||||
export const rspackDevServerGallery = () =>
 | 
			
		||||
  runDevServer({
 | 
			
		||||
    compiler: rspack(
 | 
			
		||||
      createGalleryConfig({ isProdBuild: false, latestBuild: true })
 | 
			
		||||
@@ -192,19 +204,17 @@ gulp.task("rspack-dev-server-gallery", () =>
 | 
			
		||||
    contentBase: paths.gallery_output_root,
 | 
			
		||||
    port: 8100,
 | 
			
		||||
    listenHost: "0.0.0.0",
 | 
			
		||||
  })
 | 
			
		||||
);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-prod-gallery", () =>
 | 
			
		||||
export const rspackProdGallery = () =>
 | 
			
		||||
  prodBuild(
 | 
			
		||||
    createGalleryConfig({
 | 
			
		||||
      isProdBuild: true,
 | 
			
		||||
      latestBuild: true,
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-watch-landing-page", () => {
 | 
			
		||||
export const rspackWatchLandingPage = () => {
 | 
			
		||||
  // This command will run forever because we don't close compiler
 | 
			
		||||
  rspack(
 | 
			
		||||
    process.env.ES5
 | 
			
		||||
@@ -212,21 +222,17 @@ gulp.task("rspack-watch-landing-page", () => {
 | 
			
		||||
      : createLandingPageConfig({ isProdBuild: false, latestBuild: true })
 | 
			
		||||
  ).watch({ poll: isWsl }, doneHandler());
 | 
			
		||||
 | 
			
		||||
  gulp.watch(
 | 
			
		||||
  watch(
 | 
			
		||||
    path.join(paths.translations_src, "en.json"),
 | 
			
		||||
    gulp.series(
 | 
			
		||||
      "build-landing-page-translations",
 | 
			
		||||
      "copy-translations-landing-page"
 | 
			
		||||
    )
 | 
			
		||||
    series(buildLandingPageTranslations, copyTranslationsLandingPage)
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("rspack-prod-landing-page", () =>
 | 
			
		||||
export const rspackProdLandingPage = () =>
 | 
			
		||||
  prodBuild(
 | 
			
		||||
    bothBuilds(createLandingPageConfig, {
 | 
			
		||||
      isProdBuild: true,
 | 
			
		||||
      isStatsBuild: env.isStatsBuild(),
 | 
			
		||||
      isTestBuild: env.isTestBuild(),
 | 
			
		||||
      isStatsBuild: isStatsBuild(),
 | 
			
		||||
      isTestBuild: isTestBuild(),
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
  );
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
// Generate service workers
 | 
			
		||||
 | 
			
		||||
import { deleteAsync } from "del";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
 | 
			
		||||
import { basename, join, relative } from "node:path";
 | 
			
		||||
import { injectManifest } from "workbox-build";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
 | 
			
		||||
const SW_MAP = {
 | 
			
		||||
  [paths.app_output_latest]: "modern",
 | 
			
		||||
@@ -23,7 +22,7 @@ self.addEventListener('install', (event) => {
 | 
			
		||||
});
 | 
			
		||||
  `.trim() + "\n";
 | 
			
		||||
 | 
			
		||||
gulp.task("gen-service-worker-app-dev", async () => {
 | 
			
		||||
export const genServiceWorkerAppDev = async () => {
 | 
			
		||||
  await mkdir(paths.app_output_root, { recursive: true });
 | 
			
		||||
  await Promise.all(
 | 
			
		||||
    Object.values(SW_MAP).map((build) =>
 | 
			
		||||
@@ -32,9 +31,9 @@ gulp.task("gen-service-worker-app-dev", async () => {
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("gen-service-worker-app-prod", () =>
 | 
			
		||||
export const genServiceWorkerAppProd = () =>
 | 
			
		||||
  Promise.all(
 | 
			
		||||
    Object.entries(SW_MAP).map(async ([outPath, build]) => {
 | 
			
		||||
      const manifest = JSON.parse(
 | 
			
		||||
@@ -83,5 +82,4 @@ gulp.task("gen-service-worker-app-prod", () =>
 | 
			
		||||
        await symlink(basename(swDest), swOld);
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
);
 | 
			
		||||
  );
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
import { deleteAsync } from "del";
 | 
			
		||||
import { glob } from "glob";
 | 
			
		||||
import gulp from "gulp";
 | 
			
		||||
import { src as glupSrc, dest as gulpDest, parallel, series } from "gulp";
 | 
			
		||||
import rename from "gulp-rename";
 | 
			
		||||
import merge from "lodash.merge";
 | 
			
		||||
import { createHash } from "node:crypto";
 | 
			
		||||
@@ -10,9 +10,12 @@ 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 env from "../env.cjs";
 | 
			
		||||
import paths from "../paths.cjs";
 | 
			
		||||
import "./fetch-nightly-translations.js";
 | 
			
		||||
import { isProdBuild } from "../env.ts";
 | 
			
		||||
import paths from "../paths.ts";
 | 
			
		||||
import {
 | 
			
		||||
  allowSetupFetchNightlyTranslations,
 | 
			
		||||
  fetchNightlyTranslations,
 | 
			
		||||
} from "./fetch-nightly-translations.ts";
 | 
			
		||||
 | 
			
		||||
const inFrontendDir = "translations/frontend";
 | 
			
		||||
const inBackendDir = "translations/backend";
 | 
			
		||||
@@ -23,18 +26,20 @@ const TEST_LOCALE = "en-x-test";
 | 
			
		||||
 | 
			
		||||
let mergeBackend = false;
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "translations-enable-merge-backend",
 | 
			
		||||
  gulp.parallel(async () => {
 | 
			
		||||
    mergeBackend = true;
 | 
			
		||||
  }, "allow-setup-fetch-nightly-translations")
 | 
			
		||||
);
 | 
			
		||||
// translations-enable-merge-backend
 | 
			
		||||
export const translationsEnableMergeBackend = parallel(async () => {
 | 
			
		||||
  mergeBackend = true;
 | 
			
		||||
}, allowSetupFetchNightlyTranslations);
 | 
			
		||||
 | 
			
		||||
// 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) {
 | 
			
		||||
  _func: any;
 | 
			
		||||
 | 
			
		||||
  _reviver: any;
 | 
			
		||||
 | 
			
		||||
  constructor(func, reviver: any = null) {
 | 
			
		||||
    super({ objectMode: true });
 | 
			
		||||
    this._func = func;
 | 
			
		||||
    this._reviver = reviver;
 | 
			
		||||
@@ -56,9 +61,17 @@ class CustomJSON extends Transform {
 | 
			
		||||
 | 
			
		||||
// Transform stream to merge Vinyl JSON files (buffer mode only).
 | 
			
		||||
class MergeJSON extends Transform {
 | 
			
		||||
  _objects = [];
 | 
			
		||||
  _objects: any[] = [];
 | 
			
		||||
 | 
			
		||||
  constructor(stem, startObj = {}, reviver = null) {
 | 
			
		||||
  _stem: any;
 | 
			
		||||
 | 
			
		||||
  _startObj: any;
 | 
			
		||||
 | 
			
		||||
  _reviver: any;
 | 
			
		||||
 | 
			
		||||
  _outFile: any;
 | 
			
		||||
 | 
			
		||||
  constructor(stem, startObj = {}, reviver: any = null) {
 | 
			
		||||
    super({ objectMode: true, allowHalfOpen: false });
 | 
			
		||||
    this._stem = stem;
 | 
			
		||||
    this._startObj = structuredClone(startObj);
 | 
			
		||||
@@ -111,11 +124,12 @@ const testReviver = (_key, value) =>
 | 
			
		||||
const KEY_REFERENCE = /\[%key:([^%]+)%\]/;
 | 
			
		||||
const lokaliseTransform = (data, path, original = data) => {
 | 
			
		||||
  const output = {};
 | 
			
		||||
  for (const [key, value] of Object.entries(data)) {
 | 
			
		||||
  for (const entry of Object.entries(data)) {
 | 
			
		||||
    const [key, value] = entry as [string, string];
 | 
			
		||||
    if (typeof value === "object") {
 | 
			
		||||
      output[key] = lokaliseTransform(value, path, original);
 | 
			
		||||
    } else {
 | 
			
		||||
      output[key] = value.replace(KEY_REFERENCE, (_match, lokalise_key) => {
 | 
			
		||||
      output[key] = value?.replace(KEY_REFERENCE, (_match, lokalise_key) => {
 | 
			
		||||
        const replace = lokalise_key.split("::").reduce((tr, k) => {
 | 
			
		||||
          if (!tr) {
 | 
			
		||||
            throw Error(`Invalid key placeholder ${lokalise_key} in ${path}`);
 | 
			
		||||
@@ -132,18 +146,17 @@ const lokaliseTransform = (data, path, original = data) => {
 | 
			
		||||
  return output;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task("clean-translations", () => deleteAsync([workDir]));
 | 
			
		||||
export const cleanTranslations = () => deleteAsync([workDir]);
 | 
			
		||||
 | 
			
		||||
const makeWorkDir = () => mkdir(workDir, { recursive: true });
 | 
			
		||||
 | 
			
		||||
const createTestTranslation = () =>
 | 
			
		||||
  env.isProdBuild()
 | 
			
		||||
  isProdBuild()
 | 
			
		||||
    ? Promise.resolve()
 | 
			
		||||
    : gulp
 | 
			
		||||
        .src(EN_SRC)
 | 
			
		||||
    : glupSrc(EN_SRC)
 | 
			
		||||
        .pipe(new CustomJSON(null, testReviver))
 | 
			
		||||
        .pipe(rename(`${TEST_LOCALE}.json`))
 | 
			
		||||
        .pipe(gulp.dest(workDir));
 | 
			
		||||
        .pipe(gulpDest(workDir));
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This task will build a master translation file, to be used as the base for
 | 
			
		||||
@@ -155,11 +168,10 @@ const createTestTranslation = () =>
 | 
			
		||||
 * the Lokalise update to translations/en.json will not happen immediately.
 | 
			
		||||
 */
 | 
			
		||||
const createMasterTranslation = () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
 | 
			
		||||
  glupSrc([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
 | 
			
		||||
    .pipe(new CustomJSON(lokaliseTransform))
 | 
			
		||||
    .pipe(new MergeJSON("en"))
 | 
			
		||||
    .pipe(gulp.dest(workDir));
 | 
			
		||||
    .pipe(gulpDest(workDir));
 | 
			
		||||
 | 
			
		||||
const FRAGMENTS = ["base"];
 | 
			
		||||
 | 
			
		||||
@@ -186,12 +198,12 @@ const createTranslations = async () => {
 | 
			
		||||
  // each locale, then fragmentizes and flattens the data for final output.
 | 
			
		||||
  const translationFiles = await glob([
 | 
			
		||||
    `${inFrontendDir}/!(en).json`,
 | 
			
		||||
    ...(env.isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
 | 
			
		||||
    ...(isProdBuild() ? [] : [`${workDir}/${TEST_LOCALE}.json`]),
 | 
			
		||||
  ]);
 | 
			
		||||
  const hashStream = new Transform({
 | 
			
		||||
    objectMode: true,
 | 
			
		||||
    transform: async (file, _, callback) => {
 | 
			
		||||
      const hash = env.isProdBuild()
 | 
			
		||||
      const hash = isProdBuild()
 | 
			
		||||
        ? createHash("md5").update(file.contents).digest("hex")
 | 
			
		||||
        : "dev";
 | 
			
		||||
      HASHES.set(file.stem, hash);
 | 
			
		||||
@@ -230,7 +242,7 @@ const createTranslations = async () => {
 | 
			
		||||
        })
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(outDir));
 | 
			
		||||
    .pipe(gulpDest(outDir));
 | 
			
		||||
 | 
			
		||||
  // Send the English master downstream first, then for each other locale
 | 
			
		||||
  // generate merged JSON data to continue piping. It begins with the master
 | 
			
		||||
@@ -240,15 +252,15 @@ const createTranslations = async () => {
 | 
			
		||||
  // 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.
 | 
			
		||||
  const masterStream = gulp
 | 
			
		||||
    .src(`${workDir}/en.json`)
 | 
			
		||||
    .pipe(new PassThrough({ objectMode: true }));
 | 
			
		||||
  const masterStream = glupSrc(`${workDir}/en.json`).pipe(
 | 
			
		||||
    new PassThrough({ objectMode: true })
 | 
			
		||||
  );
 | 
			
		||||
  masterStream.pipe(hashStream, { end: false });
 | 
			
		||||
  const mergesFinished = [finished(masterStream)];
 | 
			
		||||
  for (const translationFile of translationFiles) {
 | 
			
		||||
    const locale = basename(translationFile, ".json");
 | 
			
		||||
    const subtags = locale.split("-");
 | 
			
		||||
    const mergeFiles = [];
 | 
			
		||||
    const mergeFiles: string[] = [];
 | 
			
		||||
    for (let i = 1; i <= subtags.length; i++) {
 | 
			
		||||
      const lang = subtags.slice(0, i).join("-");
 | 
			
		||||
      if (lang === TEST_LOCALE) {
 | 
			
		||||
@@ -260,9 +272,9 @@ const createTranslations = async () => {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    const mergeStream = gulp
 | 
			
		||||
      .src(mergeFiles, { allowEmpty: true })
 | 
			
		||||
      .pipe(new MergeJSON(locale, enMaster, emptyReviver));
 | 
			
		||||
    const mergeStream = glupSrc(mergeFiles, { allowEmpty: true }).pipe(
 | 
			
		||||
      new MergeJSON(locale, enMaster, emptyReviver)
 | 
			
		||||
    );
 | 
			
		||||
    mergesFinished.push(finished(mergeStream));
 | 
			
		||||
    mergeStream.pipe(hashStream, { end: false });
 | 
			
		||||
  }
 | 
			
		||||
@@ -275,12 +287,11 @@ const createTranslations = async () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const writeTranslationMetaData = () =>
 | 
			
		||||
  gulp
 | 
			
		||||
    .src([`${paths.translations_src}/translationMetadata.json`])
 | 
			
		||||
  glupSrc([`${paths.translations_src}/translationMetadata.json`])
 | 
			
		||||
    .pipe(
 | 
			
		||||
      new CustomJSON((meta) => {
 | 
			
		||||
        // Add the test translation in development.
 | 
			
		||||
        if (!env.isProdBuild()) {
 | 
			
		||||
        if (!isProdBuild()) {
 | 
			
		||||
          meta[TEST_LOCALE] = { nativeName: "Translation Test" };
 | 
			
		||||
        }
 | 
			
		||||
        // Filter out locales without a native name, and add the hashes.
 | 
			
		||||
@@ -300,28 +311,22 @@ const writeTranslationMetaData = () =>
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
    )
 | 
			
		||||
    .pipe(gulp.dest(workDir));
 | 
			
		||||
    .pipe(gulpDest(workDir));
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-translations",
 | 
			
		||||
  gulp.series(
 | 
			
		||||
    gulp.parallel(
 | 
			
		||||
      "fetch-nightly-translations",
 | 
			
		||||
      gulp.series("clean-translations", makeWorkDir)
 | 
			
		||||
    ),
 | 
			
		||||
    createTestTranslation,
 | 
			
		||||
    createMasterTranslation,
 | 
			
		||||
    createTranslations,
 | 
			
		||||
    writeTranslationMetaData
 | 
			
		||||
  )
 | 
			
		||||
export const buildTranslations = series(
 | 
			
		||||
  parallel(fetchNightlyTranslations, series(cleanTranslations, makeWorkDir)),
 | 
			
		||||
  createTestTranslation,
 | 
			
		||||
  createMasterTranslation,
 | 
			
		||||
  createTranslations,
 | 
			
		||||
  writeTranslationMetaData
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-supervisor-translations",
 | 
			
		||||
  gulp.series(setFragment("supervisor"), "build-translations")
 | 
			
		||||
export const buildSupervisorTranslations = series(
 | 
			
		||||
  setFragment("supervisor"),
 | 
			
		||||
  buildTranslations
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
gulp.task(
 | 
			
		||||
  "build-landing-page-translations",
 | 
			
		||||
  gulp.series(setFragment("landing-page"), "build-translations")
 | 
			
		||||
export const buildLandingPageTranslations = series(
 | 
			
		||||
  setFragment("landing-page"),
 | 
			
		||||
  buildTranslations
 | 
			
		||||
);
 | 
			
		||||
@@ -5,10 +5,11 @@ import { version as babelVersion } from "@babel/core";
 | 
			
		||||
import presetEnv from "@babel/preset-env";
 | 
			
		||||
import compilationTargets from "@babel/helper-compilation-targets";
 | 
			
		||||
import coreJSCompat from "core-js-compat";
 | 
			
		||||
 | 
			
		||||
import { logPlugin } from "@babel/preset-env/lib/debug.js";
 | 
			
		||||
// eslint-disable-next-line import/no-relative-packages
 | 
			
		||||
import shippedPolyfills from "../node_modules/babel-plugin-polyfill-corejs3/lib/shipped-proposals.js";
 | 
			
		||||
import { babelOptions } from "./bundle.cjs";
 | 
			
		||||
import { babelOptions } from "./bundle.ts";
 | 
			
		||||
 | 
			
		||||
const detailsOpen = (heading) =>
 | 
			
		||||
  `<details>\n<summary><h4>${heading}</h4></summary>\n`;
 | 
			
		||||
@@ -50,6 +51,12 @@ for (const buildType of ["Modern", "Legacy"]) {
 | 
			
		||||
  const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" });
 | 
			
		||||
  const presetEnvOpts = babelOpts.presets[0][1];
 | 
			
		||||
 | 
			
		||||
  if (typeof presetEnvOpts !== "object") {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      "The first preset in babelOptions is not an object. This is unexpected."
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Invoking preset-env in debug mode will log the included plugins
 | 
			
		||||
  console.log(detailsOpen(`${buildType} Build Babel Plugins`));
 | 
			
		||||
  presetEnv.default(dummyAPI, {
 | 
			
		||||
@@ -1,63 +0,0 @@
 | 
			
		||||
const path = require("path");
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  root_dir: path.resolve(__dirname, ".."),
 | 
			
		||||
 | 
			
		||||
  build_dir: path.resolve(__dirname, "../build"),
 | 
			
		||||
  app_output_root: path.resolve(__dirname, "../hass_frontend"),
 | 
			
		||||
  app_output_static: path.resolve(__dirname, "../hass_frontend/static"),
 | 
			
		||||
  app_output_latest: path.resolve(
 | 
			
		||||
    __dirname,
 | 
			
		||||
    "../hass_frontend/frontend_latest"
 | 
			
		||||
  ),
 | 
			
		||||
  app_output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
 | 
			
		||||
 | 
			
		||||
  demo_dir: path.resolve(__dirname, "../demo"),
 | 
			
		||||
  demo_output_root: path.resolve(__dirname, "../demo/dist"),
 | 
			
		||||
  demo_output_static: path.resolve(__dirname, "../demo/dist/static"),
 | 
			
		||||
  demo_output_latest: path.resolve(__dirname, "../demo/dist/frontend_latest"),
 | 
			
		||||
  demo_output_es5: path.resolve(__dirname, "../demo/dist/frontend_es5"),
 | 
			
		||||
 | 
			
		||||
  cast_dir: path.resolve(__dirname, "../cast"),
 | 
			
		||||
  cast_output_root: path.resolve(__dirname, "../cast/dist"),
 | 
			
		||||
  cast_output_static: path.resolve(__dirname, "../cast/dist/static"),
 | 
			
		||||
  cast_output_latest: path.resolve(__dirname, "../cast/dist/frontend_latest"),
 | 
			
		||||
  cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"),
 | 
			
		||||
 | 
			
		||||
  gallery_dir: path.resolve(__dirname, "../gallery"),
 | 
			
		||||
  gallery_build: path.resolve(__dirname, "../gallery/build"),
 | 
			
		||||
  gallery_output_root: path.resolve(__dirname, "../gallery/dist"),
 | 
			
		||||
  gallery_output_latest: path.resolve(
 | 
			
		||||
    __dirname,
 | 
			
		||||
    "../gallery/dist/frontend_latest"
 | 
			
		||||
  ),
 | 
			
		||||
  gallery_output_static: path.resolve(__dirname, "../gallery/dist/static"),
 | 
			
		||||
 | 
			
		||||
  landingPage_dir: path.resolve(__dirname, "../landing-page"),
 | 
			
		||||
  landingPage_build: path.resolve(__dirname, "../landing-page/build"),
 | 
			
		||||
  landingPage_output_root: path.resolve(__dirname, "../landing-page/dist"),
 | 
			
		||||
  landingPage_output_latest: path.resolve(
 | 
			
		||||
    __dirname,
 | 
			
		||||
    "../landing-page/dist/frontend_latest"
 | 
			
		||||
  ),
 | 
			
		||||
  landingPage_output_es5: path.resolve(
 | 
			
		||||
    __dirname,
 | 
			
		||||
    "../landing-page/dist/frontend_es5"
 | 
			
		||||
  ),
 | 
			
		||||
  landingPage_output_static: path.resolve(
 | 
			
		||||
    __dirname,
 | 
			
		||||
    "../landing-page/dist/static"
 | 
			
		||||
  ),
 | 
			
		||||
 | 
			
		||||
  hassio_dir: path.resolve(__dirname, "../hassio"),
 | 
			
		||||
  hassio_output_root: path.resolve(__dirname, "../hassio/build"),
 | 
			
		||||
  hassio_output_static: path.resolve(__dirname, "../hassio/build/static"),
 | 
			
		||||
  hassio_output_latest: path.resolve(
 | 
			
		||||
    __dirname,
 | 
			
		||||
    "../hassio/build/frontend_latest"
 | 
			
		||||
  ),
 | 
			
		||||
  hassio_output_es5: path.resolve(__dirname, "../hassio/build/frontend_es5"),
 | 
			
		||||
  hassio_publicPath: "/api/hassio/app",
 | 
			
		||||
 | 
			
		||||
  translations_src: path.resolve(__dirname, "../src/translations"),
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										63
									
								
								build-scripts/paths.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								build-scripts/paths.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
import path, { dirname as pathDirname } from "node:path";
 | 
			
		||||
import { fileURLToPath } from "node:url";
 | 
			
		||||
 | 
			
		||||
export const dirname = pathDirname(fileURLToPath(import.meta.url));
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  root_dir: path.resolve(dirname, ".."),
 | 
			
		||||
 | 
			
		||||
  build_dir: path.resolve(dirname, "../build"),
 | 
			
		||||
  app_output_root: path.resolve(dirname, "../hass_frontend"),
 | 
			
		||||
  app_output_static: path.resolve(dirname, "../hass_frontend/static"),
 | 
			
		||||
  app_output_latest: path.resolve(dirname, "../hass_frontend/frontend_latest"),
 | 
			
		||||
  app_output_es5: path.resolve(dirname, "../hass_frontend/frontend_es5"),
 | 
			
		||||
 | 
			
		||||
  demo_dir: path.resolve(dirname, "../demo"),
 | 
			
		||||
  demo_output_root: path.resolve(dirname, "../demo/dist"),
 | 
			
		||||
  demo_output_static: path.resolve(dirname, "../demo/dist/static"),
 | 
			
		||||
  demo_output_latest: path.resolve(dirname, "../demo/dist/frontend_latest"),
 | 
			
		||||
  demo_output_es5: path.resolve(dirname, "../demo/dist/frontend_es5"),
 | 
			
		||||
 | 
			
		||||
  cast_dir: path.resolve(dirname, "../cast"),
 | 
			
		||||
  cast_output_root: path.resolve(dirname, "../cast/dist"),
 | 
			
		||||
  cast_output_static: path.resolve(dirname, "../cast/dist/static"),
 | 
			
		||||
  cast_output_latest: path.resolve(dirname, "../cast/dist/frontend_latest"),
 | 
			
		||||
  cast_output_es5: path.resolve(dirname, "../cast/dist/frontend_es5"),
 | 
			
		||||
 | 
			
		||||
  gallery_dir: path.resolve(dirname, "../gallery"),
 | 
			
		||||
  gallery_build: path.resolve(dirname, "../gallery/build"),
 | 
			
		||||
  gallery_output_root: path.resolve(dirname, "../gallery/dist"),
 | 
			
		||||
  gallery_output_latest: path.resolve(
 | 
			
		||||
    dirname,
 | 
			
		||||
    "../gallery/dist/frontend_latest"
 | 
			
		||||
  ),
 | 
			
		||||
  gallery_output_static: path.resolve(dirname, "../gallery/dist/static"),
 | 
			
		||||
 | 
			
		||||
  landingPage_dir: path.resolve(dirname, "../landing-page"),
 | 
			
		||||
  landingPage_build: path.resolve(dirname, "../landing-page/build"),
 | 
			
		||||
  landingPage_output_root: path.resolve(dirname, "../landing-page/dist"),
 | 
			
		||||
  landingPage_output_latest: path.resolve(
 | 
			
		||||
    dirname,
 | 
			
		||||
    "../landing-page/dist/frontend_latest"
 | 
			
		||||
  ),
 | 
			
		||||
  landingPage_output_es5: path.resolve(
 | 
			
		||||
    dirname,
 | 
			
		||||
    "../landing-page/dist/frontend_es5"
 | 
			
		||||
  ),
 | 
			
		||||
  landingPage_output_static: path.resolve(
 | 
			
		||||
    dirname,
 | 
			
		||||
    "../landing-page/dist/static"
 | 
			
		||||
  ),
 | 
			
		||||
 | 
			
		||||
  hassio_dir: path.resolve(dirname, "../hassio"),
 | 
			
		||||
  hassio_output_root: path.resolve(dirname, "../hassio/build"),
 | 
			
		||||
  hassio_output_static: path.resolve(dirname, "../hassio/build/static"),
 | 
			
		||||
  hassio_output_latest: path.resolve(
 | 
			
		||||
    dirname,
 | 
			
		||||
    "../hassio/build/frontend_latest"
 | 
			
		||||
  ),
 | 
			
		||||
  hassio_output_es5: path.resolve(dirname, "../hassio/build/frontend_es5"),
 | 
			
		||||
  hassio_publicPath: "/api/hassio/app",
 | 
			
		||||
 | 
			
		||||
  translations_src: path.resolve(dirname, "../src/translations"),
 | 
			
		||||
};
 | 
			
		||||
@@ -1,20 +1,25 @@
 | 
			
		||||
const { existsSync } = require("fs");
 | 
			
		||||
const path = require("path");
 | 
			
		||||
const rspack = require("@rspack/core");
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/naming-convention
 | 
			
		||||
const { RsdoctorRspackPlugin } = require("@rsdoctor/rspack-plugin");
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/naming-convention
 | 
			
		||||
const { StatsWriterPlugin } = require("webpack-stats-plugin");
 | 
			
		||||
const filterStats = require("@bundle-stats/plugin-webpack-filter");
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/naming-convention
 | 
			
		||||
const TerserPlugin = require("terser-webpack-plugin");
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/naming-convention
 | 
			
		||||
const { WebpackManifestPlugin } = require("rspack-manifest-plugin");
 | 
			
		||||
const log = require("fancy-log");
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/naming-convention
 | 
			
		||||
const WebpackBar = require("webpackbar/rspack");
 | 
			
		||||
const paths = require("./paths.cjs");
 | 
			
		||||
const bundle = require("./bundle.cjs");
 | 
			
		||||
import filterStats from "@bundle-stats/plugin-webpack-filter";
 | 
			
		||||
import { RsdoctorRspackPlugin } from "@rsdoctor/rspack-plugin";
 | 
			
		||||
import { DefinePlugin, NormalModuleReplacementPlugin } from "@rspack/core";
 | 
			
		||||
import { defineConfig } from "@rspack/cli";
 | 
			
		||||
import log from "fancy-log";
 | 
			
		||||
import { existsSync } from "node:fs";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import { WebpackManifestPlugin } from "rspack-manifest-plugin";
 | 
			
		||||
import TerserPlugin from "terser-webpack-plugin";
 | 
			
		||||
import { StatsWriterPlugin } from "webpack-stats-plugin";
 | 
			
		||||
// @ts-ignore
 | 
			
		||||
import WebpackBar from "webpackbar/rspack";
 | 
			
		||||
import {
 | 
			
		||||
  babelOptions,
 | 
			
		||||
  config,
 | 
			
		||||
  definedVars,
 | 
			
		||||
  emptyPackages,
 | 
			
		||||
  sourceMapURL,
 | 
			
		||||
  swcOptions,
 | 
			
		||||
  terserOptions,
 | 
			
		||||
} from "./bundle.ts";
 | 
			
		||||
import paths from "./paths.ts";
 | 
			
		||||
 | 
			
		||||
class LogStartCompilePlugin {
 | 
			
		||||
  ignoredFirst = false;
 | 
			
		||||
@@ -30,7 +35,7 @@ class LogStartCompilePlugin {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const createRspackConfig = ({
 | 
			
		||||
export const createRspackConfig = ({
 | 
			
		||||
  name,
 | 
			
		||||
  entry,
 | 
			
		||||
  outputPath,
 | 
			
		||||
@@ -42,12 +47,23 @@ const createRspackConfig = ({
 | 
			
		||||
  isTestBuild,
 | 
			
		||||
  isHassioBuild,
 | 
			
		||||
  dontHash,
 | 
			
		||||
}: {
 | 
			
		||||
  name: string;
 | 
			
		||||
  entry: any;
 | 
			
		||||
  outputPath: string;
 | 
			
		||||
  publicPath: string;
 | 
			
		||||
  defineOverlay?: Record<string, any>;
 | 
			
		||||
  isProdBuild?: boolean;
 | 
			
		||||
  latestBuild?: boolean;
 | 
			
		||||
  isStatsBuild?: boolean;
 | 
			
		||||
  isTestBuild?: boolean;
 | 
			
		||||
  isHassioBuild?: boolean;
 | 
			
		||||
  dontHash?: Set<string>;
 | 
			
		||||
}) => {
 | 
			
		||||
  if (!dontHash) {
 | 
			
		||||
    dontHash = new Set();
 | 
			
		||||
  }
 | 
			
		||||
  const ignorePackages = bundle.ignorePackages({ latestBuild });
 | 
			
		||||
  return {
 | 
			
		||||
  return defineConfig({
 | 
			
		||||
    name,
 | 
			
		||||
    mode: isProdBuild ? "production" : "development",
 | 
			
		||||
    target: `browserslist:${latestBuild ? "modern" : "legacy"}`,
 | 
			
		||||
@@ -70,7 +86,7 @@ const createRspackConfig = ({
 | 
			
		||||
            {
 | 
			
		||||
              loader: "babel-loader",
 | 
			
		||||
              options: {
 | 
			
		||||
                ...bundle.babelOptions({
 | 
			
		||||
                ...babelOptions({
 | 
			
		||||
                  latestBuild,
 | 
			
		||||
                  isProdBuild,
 | 
			
		||||
                  isTestBuild,
 | 
			
		||||
@@ -82,7 +98,7 @@ const createRspackConfig = ({
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
              loader: "builtin:swc-loader",
 | 
			
		||||
              options: bundle.swcOptions(),
 | 
			
		||||
              options: swcOptions(),
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          resolve: {
 | 
			
		||||
@@ -103,7 +119,7 @@ const createRspackConfig = ({
 | 
			
		||||
        new TerserPlugin({
 | 
			
		||||
          parallel: true,
 | 
			
		||||
          extractComments: true,
 | 
			
		||||
          terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }),
 | 
			
		||||
          terserOptions: terserOptions({ latestBuild, isTestBuild }),
 | 
			
		||||
        }),
 | 
			
		||||
      ],
 | 
			
		||||
      moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
 | 
			
		||||
@@ -122,7 +138,7 @@ const createRspackConfig = ({
 | 
			
		||||
              !chunk.canBeInitial() &&
 | 
			
		||||
              !new RegExp(
 | 
			
		||||
                `^.+-work${latestBuild ? "(?:let|er)" : "let"}$`
 | 
			
		||||
              ).test(chunk.name),
 | 
			
		||||
              ).test(chunk?.name || ""),
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    plugins: [
 | 
			
		||||
@@ -131,44 +147,11 @@ const createRspackConfig = ({
 | 
			
		||||
        // Only include the JS of entrypoints
 | 
			
		||||
        filter: (file) => file.isInitial && !file.name.endsWith(".map"),
 | 
			
		||||
      }),
 | 
			
		||||
      new rspack.DefinePlugin(
 | 
			
		||||
        bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
 | 
			
		||||
      new DefinePlugin(
 | 
			
		||||
        definedVars({ isProdBuild, latestBuild, defineOverlay })
 | 
			
		||||
      ),
 | 
			
		||||
      new rspack.IgnorePlugin({
 | 
			
		||||
        checkResource(resource, context) {
 | 
			
		||||
          // Only use ignore to intercept imports that we don't control
 | 
			
		||||
          // inside node_module dependencies.
 | 
			
		||||
          if (
 | 
			
		||||
            !context.includes("/node_modules/") ||
 | 
			
		||||
            // calling define.amd will call require("!!webpack amd options")
 | 
			
		||||
            resource.startsWith("!!webpack") ||
 | 
			
		||||
            // loaded by webpack dev server but doesn't exist.
 | 
			
		||||
            resource === "webpack/hot" ||
 | 
			
		||||
            resource.startsWith("@swc/helpers")
 | 
			
		||||
          ) {
 | 
			
		||||
            return false;
 | 
			
		||||
          }
 | 
			
		||||
          let fullPath;
 | 
			
		||||
          try {
 | 
			
		||||
            fullPath = resource.startsWith(".")
 | 
			
		||||
              ? path.resolve(context, resource)
 | 
			
		||||
              : require.resolve(resource);
 | 
			
		||||
          } catch (err) {
 | 
			
		||||
            console.error(
 | 
			
		||||
              "Error in Home Assistant ignore plugin",
 | 
			
		||||
              resource,
 | 
			
		||||
              context
 | 
			
		||||
            );
 | 
			
		||||
            throw err;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return ignorePackages.some((toIgnorePath) =>
 | 
			
		||||
            fullPath.startsWith(toIgnorePath)
 | 
			
		||||
          );
 | 
			
		||||
        },
 | 
			
		||||
      }),
 | 
			
		||||
      new rspack.NormalModuleReplacementPlugin(
 | 
			
		||||
        new RegExp(bundle.emptyPackages({ isHassioBuild }).join("|")),
 | 
			
		||||
      new NormalModuleReplacementPlugin(
 | 
			
		||||
        new RegExp(emptyPackages({ isHassioBuild }).join("|")),
 | 
			
		||||
        path.resolve(paths.root_dir, "src/util/empty.js")
 | 
			
		||||
      ),
 | 
			
		||||
      !isProdBuild && new LogStartCompilePlugin(),
 | 
			
		||||
@@ -184,7 +167,9 @@ const createRspackConfig = ({
 | 
			
		||||
      isProdBuild &&
 | 
			
		||||
        isStatsBuild &&
 | 
			
		||||
        new RsdoctorRspackPlugin({
 | 
			
		||||
          reportDir: path.join(paths.build_dir, "rsdoctor"),
 | 
			
		||||
          output: {
 | 
			
		||||
            reportDir: path.join(paths.build_dir, "rsdoctor"),
 | 
			
		||||
          },
 | 
			
		||||
          features: ["plugins", "bundle"],
 | 
			
		||||
          supports: {
 | 
			
		||||
            generateTileGraph: true,
 | 
			
		||||
@@ -219,7 +204,9 @@ const createRspackConfig = ({
 | 
			
		||||
    output: {
 | 
			
		||||
      module: latestBuild,
 | 
			
		||||
      filename: ({ chunk }) =>
 | 
			
		||||
        !isProdBuild || isStatsBuild || dontHash.has(chunk.name)
 | 
			
		||||
        !isProdBuild ||
 | 
			
		||||
        isStatsBuild ||
 | 
			
		||||
        (chunk?.name && dontHash.has(chunk.name))
 | 
			
		||||
          ? "[name].js"
 | 
			
		||||
          : "[name].[contenthash].js",
 | 
			
		||||
      chunkFilename:
 | 
			
		||||
@@ -250,7 +237,7 @@ const createRspackConfig = ({
 | 
			
		||||
                  // dev tools, and they stay happy getting 404s with valid requests.
 | 
			
		||||
                  return `/unknown${path.resolve("/", info.resourcePath)}`;
 | 
			
		||||
                }
 | 
			
		||||
                return new URL(info.resourcePath, bundle.sourceMapURL()).href;
 | 
			
		||||
                return new URL(info.resourcePath, sourceMapURL()).href;
 | 
			
		||||
              }
 | 
			
		||||
            : undefined,
 | 
			
		||||
        ])
 | 
			
		||||
@@ -260,35 +247,51 @@ const createRspackConfig = ({
 | 
			
		||||
      layers: true,
 | 
			
		||||
      outputModule: true,
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const createAppConfig = ({
 | 
			
		||||
export const createAppConfig = ({
 | 
			
		||||
  isProdBuild,
 | 
			
		||||
  latestBuild,
 | 
			
		||||
  isStatsBuild,
 | 
			
		||||
  isTestBuild,
 | 
			
		||||
}: {
 | 
			
		||||
  isProdBuild?: boolean;
 | 
			
		||||
  latestBuild?: boolean;
 | 
			
		||||
  isStatsBuild?: boolean;
 | 
			
		||||
  isTestBuild?: boolean;
 | 
			
		||||
}) =>
 | 
			
		||||
  createRspackConfig(
 | 
			
		||||
    bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild })
 | 
			
		||||
    config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild })
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
 | 
			
		||||
  createRspackConfig(
 | 
			
		||||
    bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
 | 
			
		||||
  );
 | 
			
		||||
export const createDemoConfig = ({
 | 
			
		||||
  isProdBuild,
 | 
			
		||||
  latestBuild,
 | 
			
		||||
  isStatsBuild,
 | 
			
		||||
}: {
 | 
			
		||||
  isProdBuild?: boolean;
 | 
			
		||||
  latestBuild?: boolean;
 | 
			
		||||
  isStatsBuild?: boolean;
 | 
			
		||||
}) =>
 | 
			
		||||
  createRspackConfig(config.demo({ isProdBuild, latestBuild, isStatsBuild }));
 | 
			
		||||
 | 
			
		||||
const createCastConfig = ({ isProdBuild, latestBuild }) =>
 | 
			
		||||
  createRspackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
 | 
			
		||||
export const createCastConfig = ({ isProdBuild, latestBuild }) =>
 | 
			
		||||
  createRspackConfig(config.cast({ isProdBuild, latestBuild }));
 | 
			
		||||
 | 
			
		||||
const createHassioConfig = ({
 | 
			
		||||
export const createHassioConfig = ({
 | 
			
		||||
  isProdBuild,
 | 
			
		||||
  latestBuild,
 | 
			
		||||
  isStatsBuild,
 | 
			
		||||
  isTestBuild,
 | 
			
		||||
}: {
 | 
			
		||||
  isProdBuild?: boolean;
 | 
			
		||||
  latestBuild?: boolean;
 | 
			
		||||
  isStatsBuild?: boolean;
 | 
			
		||||
  isTestBuild?: boolean;
 | 
			
		||||
}) =>
 | 
			
		||||
  createRspackConfig(
 | 
			
		||||
    bundle.config.hassio({
 | 
			
		||||
    config.hassio({
 | 
			
		||||
      isProdBuild,
 | 
			
		||||
      latestBuild,
 | 
			
		||||
      isStatsBuild,
 | 
			
		||||
@@ -296,18 +299,8 @@ const createHassioConfig = ({
 | 
			
		||||
    })
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
 | 
			
		||||
  createRspackConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
 | 
			
		||||
export const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
 | 
			
		||||
  createRspackConfig(config.gallery({ isProdBuild, latestBuild }));
 | 
			
		||||
 | 
			
		||||
const createLandingPageConfig = ({ isProdBuild, latestBuild }) =>
 | 
			
		||||
  createRspackConfig(bundle.config.landingPage({ isProdBuild, latestBuild }));
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  createAppConfig,
 | 
			
		||||
  createDemoConfig,
 | 
			
		||||
  createCastConfig,
 | 
			
		||||
  createHassioConfig,
 | 
			
		||||
  createGalleryConfig,
 | 
			
		||||
  createRspackConfig,
 | 
			
		||||
  createLandingPageConfig,
 | 
			
		||||
};
 | 
			
		||||
export const createLandingPageConfig = ({ isProdBuild, latestBuild }) =>
 | 
			
		||||
  createRspackConfig(config.landingPage({ isProdBuild, latestBuild }));
 | 
			
		||||
							
								
								
									
										42
									
								
								build-scripts/runTask.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								build-scripts/runTask.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
// run-build.ts
 | 
			
		||||
import { series } from "gulp";
 | 
			
		||||
import { availableParallelism } from "node:os";
 | 
			
		||||
import tasks from "./gulp/index.ts";
 | 
			
		||||
 | 
			
		||||
process.env.UV_THREADPOOL_SIZE = availableParallelism().toString();
 | 
			
		||||
 | 
			
		||||
const runGulpTask = async (runTasks: string[]) => {
 | 
			
		||||
  try {
 | 
			
		||||
    for (const taskName of runTasks) {
 | 
			
		||||
      if (tasks[taskName] === undefined) {
 | 
			
		||||
        console.error(`Gulp task "${taskName}" does not exist.`);
 | 
			
		||||
        console.log("Available tasks:");
 | 
			
		||||
        Object.keys(tasks).forEach((task) => {
 | 
			
		||||
          console.log(`  - ${task}`);
 | 
			
		||||
        });
 | 
			
		||||
        process.exit(1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await new Promise((resolve, reject) => {
 | 
			
		||||
      series(...runTasks.map((taskName) => tasks[taskName]))((err?: Error) => {
 | 
			
		||||
        if (err) {
 | 
			
		||||
          reject(err);
 | 
			
		||||
        } else {
 | 
			
		||||
          resolve(null);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    process.exit(0);
 | 
			
		||||
  } catch (error: any) {
 | 
			
		||||
    console.error(`Error running Gulp task "${runTasks}":`, error);
 | 
			
		||||
    process.exit(1);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Get the task name from command line arguments
 | 
			
		||||
// TODO arg validation
 | 
			
		||||
const tasksToRun = process.argv.slice(2);
 | 
			
		||||
 | 
			
		||||
runGulpTask(tasksToRun);
 | 
			
		||||
@@ -14,5 +14,5 @@
 | 
			
		||||
  "name": "Home Assistant Cast",
 | 
			
		||||
  "short_name": "HA Cast",
 | 
			
		||||
  "start_url": "/?homescreen=1",
 | 
			
		||||
  "theme_color": "#009ac7"
 | 
			
		||||
  "theme_color": "#03A9F4"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp build-cast
 | 
			
		||||
yarn run-task build-cast
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp develop-cast
 | 
			
		||||
yarn run-task develop-cast
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
 | 
			
		||||
import type { ActionDetail } from "@material/mwc-list/mwc-list";
 | 
			
		||||
import { mdiCast, mdiCastConnected, mdiViewDashboard } from "@mdi/js";
 | 
			
		||||
import type { Auth, Connection } from "home-assistant-js-websocket";
 | 
			
		||||
@@ -18,7 +20,6 @@ import { atLeastVersion } from "../../../../src/common/config/version";
 | 
			
		||||
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
 | 
			
		||||
import "../../../../src/components/ha-icon";
 | 
			
		||||
import "../../../../src/components/ha-list";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-list-item";
 | 
			
		||||
import "../../../../src/components/ha-svg-icon";
 | 
			
		||||
import {
 | 
			
		||||
@@ -62,20 +63,12 @@ class HcCast extends LitElement {
 | 
			
		||||
              <p class="question action-item">
 | 
			
		||||
                Stay logged in?
 | 
			
		||||
                <span>
 | 
			
		||||
                  <ha-button
 | 
			
		||||
                    appearance="plain"
 | 
			
		||||
                    size="small"
 | 
			
		||||
                    @click=${this._handleSaveTokens}
 | 
			
		||||
                  >
 | 
			
		||||
                  <mwc-button @click=${this._handleSaveTokens}>
 | 
			
		||||
                    YES
 | 
			
		||||
                  </ha-button>
 | 
			
		||||
                  <ha-button
 | 
			
		||||
                    appearance="plain"
 | 
			
		||||
                    size="small"
 | 
			
		||||
                    @click=${this._handleSkipSaveTokens}
 | 
			
		||||
                  >
 | 
			
		||||
                  </mwc-button>
 | 
			
		||||
                  <mwc-button @click=${this._handleSkipSaveTokens}>
 | 
			
		||||
                    NO
 | 
			
		||||
                  </ha-button>
 | 
			
		||||
                  </mwc-button>
 | 
			
		||||
                </span>
 | 
			
		||||
              </p>
 | 
			
		||||
            `
 | 
			
		||||
@@ -85,10 +78,10 @@ class HcCast extends LitElement {
 | 
			
		||||
          : !this.castManager.status
 | 
			
		||||
            ? html`
 | 
			
		||||
                <p class="center-item">
 | 
			
		||||
                  <ha-button @click=${this._handleLaunch}>
 | 
			
		||||
                    <ha-svg-icon slot="start" .path=${mdiCast}></ha-svg-icon>
 | 
			
		||||
                  <mwc-button raised @click=${this._handleLaunch}>
 | 
			
		||||
                    <ha-svg-icon .path=${mdiCast}></ha-svg-icon>
 | 
			
		||||
                    Start Casting
 | 
			
		||||
                  </ha-button>
 | 
			
		||||
                  </mwc-button>
 | 
			
		||||
                </p>
 | 
			
		||||
              `
 | 
			
		||||
            : html`
 | 
			
		||||
@@ -128,22 +121,14 @@ class HcCast extends LitElement {
 | 
			
		||||
        <div class="card-actions">
 | 
			
		||||
          ${this.castManager.status
 | 
			
		||||
            ? html`
 | 
			
		||||
                <ha-button appearance="plain" @click=${this._handleLaunch}>
 | 
			
		||||
                  <ha-svg-icon
 | 
			
		||||
                    slot="start"
 | 
			
		||||
                    .path=${mdiCastConnected}
 | 
			
		||||
                  ></ha-svg-icon>
 | 
			
		||||
                <mwc-button @click=${this._handleLaunch}>
 | 
			
		||||
                  <ha-svg-icon .path=${mdiCastConnected}></ha-svg-icon>
 | 
			
		||||
                  Manage
 | 
			
		||||
                </ha-button>
 | 
			
		||||
                </mwc-button>
 | 
			
		||||
              `
 | 
			
		||||
            : ""}
 | 
			
		||||
          <div class="spacer"></div>
 | 
			
		||||
          <ha-button
 | 
			
		||||
            variant="danger"
 | 
			
		||||
            appearance="plain"
 | 
			
		||||
            @click=${this._handleLogout}
 | 
			
		||||
            >Log out</ha-button
 | 
			
		||||
          >
 | 
			
		||||
          <mwc-button @click=${this._handleLogout}>Log out</mwc-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </hc-layout>
 | 
			
		||||
    `;
 | 
			
		||||
@@ -260,6 +245,13 @@ class HcCast extends LitElement {
 | 
			
		||||
      color: var(--secondary-text-color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mwc-button ha-svg-icon {
 | 
			
		||||
      margin-right: 8px;
 | 
			
		||||
      margin-inline-end: 8px;
 | 
			
		||||
      margin-inline-start: initial;
 | 
			
		||||
      height: 18px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ha-list-item ha-icon,
 | 
			
		||||
    ha-list-item ha-svg-icon {
 | 
			
		||||
      padding: 12px;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import { mdiCastConnected, mdiCast } from "@mdi/js";
 | 
			
		||||
import type {
 | 
			
		||||
  Auth,
 | 
			
		||||
@@ -27,7 +28,6 @@ import "../../../../src/layouts/hass-loading-screen";
 | 
			
		||||
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
 | 
			
		||||
import "./hc-layout";
 | 
			
		||||
import "../../../../src/components/ha-textfield";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
 | 
			
		||||
const seeFAQ = (qid) => html`
 | 
			
		||||
  See <a href="./faq.html${qid ? `#${qid}` : ""}">the FAQ</a> for more
 | 
			
		||||
@@ -83,14 +83,11 @@ export class HcConnect extends LitElement {
 | 
			
		||||
            Unable to connect to ${tokens!.hassUrl}.
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="card-actions">
 | 
			
		||||
            <ha-button appearance="plain" href="/">Retry</ha-button>
 | 
			
		||||
            <a href="/">
 | 
			
		||||
              <mwc-button> Retry </mwc-button>
 | 
			
		||||
            </a>
 | 
			
		||||
            <div class="spacer"></div>
 | 
			
		||||
            <ha-button
 | 
			
		||||
              appearance="plain"
 | 
			
		||||
              variant="danger"
 | 
			
		||||
              @click=${this._handleLogout}
 | 
			
		||||
              >Log out</ha-button
 | 
			
		||||
            >
 | 
			
		||||
            <mwc-button @click=${this._handleLogout}>Log out</mwc-button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </hc-layout>
 | 
			
		||||
      `;
 | 
			
		||||
@@ -131,19 +128,16 @@ export class HcConnect extends LitElement {
 | 
			
		||||
            ${this.error ? html` <p class="error">${this.error}</p> ` : ""}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="card-actions">
 | 
			
		||||
            <ha-button appearance="plain" @click=${this._handleDemo}>
 | 
			
		||||
            <mwc-button @click=${this._handleDemo}>
 | 
			
		||||
              Show Demo
 | 
			
		||||
              <ha-svg-icon
 | 
			
		||||
                slot="end"
 | 
			
		||||
                .path=${this.castManager.castState === "CONNECTED"
 | 
			
		||||
                  ? mdiCastConnected
 | 
			
		||||
                  : mdiCast}
 | 
			
		||||
              ></ha-svg-icon>
 | 
			
		||||
            </ha-button>
 | 
			
		||||
            </mwc-button>
 | 
			
		||||
            <div class="spacer"></div>
 | 
			
		||||
            <ha-button appearance="plain" @click=${this._handleConnect}
 | 
			
		||||
              >Authorize</ha-button
 | 
			
		||||
            >
 | 
			
		||||
            <mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </hc-layout>
 | 
			
		||||
      `;
 | 
			
		||||
@@ -315,6 +309,10 @@ export class HcConnect extends LitElement {
 | 
			
		||||
      color: darkred;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mwc-button ha-svg-icon {
 | 
			
		||||
      margin-left: 8px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .spacer {
 | 
			
		||||
      flex: 1;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -75,5 +75,5 @@
 | 
			
		||||
  "name": "Home Assistant Demo",
 | 
			
		||||
  "short_name": "HA Demo",
 | 
			
		||||
  "start_url": "/?homescreen=1",
 | 
			
		||||
  "theme_color": "#009ac7"
 | 
			
		||||
  "theme_color": "#03A9F4"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp build-demo
 | 
			
		||||
yarn run-task build-demo
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp develop-demo
 | 
			
		||||
yarn run-task develop-demo
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp analyze-demo
 | 
			
		||||
yarn run-task analyze-demo
 | 
			
		||||
@@ -89,14 +89,11 @@ export class HADemoCard extends LitElement implements LovelaceCard {
 | 
			
		||||
          )}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="actions small-hidden">
 | 
			
		||||
          <ha-button
 | 
			
		||||
            appearance="plain"
 | 
			
		||||
            size="small"
 | 
			
		||||
            href="https://www.home-assistant.io"
 | 
			
		||||
            target="_blank"
 | 
			
		||||
          >
 | 
			
		||||
            ${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
 | 
			
		||||
          </ha-button>
 | 
			
		||||
          <a href="https://www.home-assistant.io" target="_blank">
 | 
			
		||||
            <ha-button>
 | 
			
		||||
              ${this.hass.localize("ui.panel.page-demo.cards.demo.learn_more")}
 | 
			
		||||
            </ha-button>
 | 
			
		||||
          </a>
 | 
			
		||||
        </div>
 | 
			
		||||
      </ha-card>
 | 
			
		||||
    `;
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@
 | 
			
		||||
      }
 | 
			
		||||
      #ha-launch-screen .ha-launch-screen-spacer-top {
 | 
			
		||||
        flex: 1;
 | 
			
		||||
        margin-top: calc( 2 * max(var(--safe-area-inset-bottom, 0px), 48px) + 46px );
 | 
			
		||||
        margin-top: calc( 2 * max(var(--safe-area-inset-bottom), 48px) + 46px );
 | 
			
		||||
        padding-top: 48px;
 | 
			
		||||
      }
 | 
			
		||||
      #ha-launch-screen .ha-launch-screen-spacer-bottom {
 | 
			
		||||
@@ -76,7 +76,7 @@
 | 
			
		||||
        padding-top: 48px;
 | 
			
		||||
      }
 | 
			
		||||
      .ohf-logo {
 | 
			
		||||
        margin: max(var(--safe-area-inset-bottom, 0px), 48px) 0;
 | 
			
		||||
        margin: max(var(--safe-area-inset-bottom), 48px) 0;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
 
 | 
			
		||||
@@ -56,15 +56,6 @@ export default tseslint.config(
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    settings: {
 | 
			
		||||
      "import/resolver": {
 | 
			
		||||
        webpack: {
 | 
			
		||||
          config: "./rspack.config.cjs",
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    rules: {
 | 
			
		||||
      "class-methods-use-this": "off",
 | 
			
		||||
      "new-cap": "off",
 | 
			
		||||
@@ -187,5 +178,12 @@ export default tseslint.config(
 | 
			
		||||
      ],
 | 
			
		||||
      "no-use-before-define": "off",
 | 
			
		||||
    },
 | 
			
		||||
    settings: {
 | 
			
		||||
      "import/resolver": {
 | 
			
		||||
        node: {
 | 
			
		||||
          extensions: [".ts", ".js"],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp build-gallery
 | 
			
		||||
yarn run-task build-gallery
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp develop-gallery
 | 
			
		||||
yarn run-task develop-gallery
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
import type { Button } from "@material/mwc-button";
 | 
			
		||||
import type { TemplateResult } from "lit";
 | 
			
		||||
import { html, LitElement, css, nothing } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators";
 | 
			
		||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../src/components/ha-card";
 | 
			
		||||
import "../../../src/components/ha-button";
 | 
			
		||||
import type { HaButton } from "../../../src/components/ha-button";
 | 
			
		||||
 | 
			
		||||
@customElement("demo-black-white-row")
 | 
			
		||||
class DemoBlackWhiteRow extends LitElement {
 | 
			
		||||
@@ -25,9 +25,12 @@ class DemoBlackWhiteRow extends LitElement {
 | 
			
		||||
              <slot name="light"></slot>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="card-actions">
 | 
			
		||||
              <ha-button .disabled=${this.disabled} @click=${this.handleSubmit}>
 | 
			
		||||
              <mwc-button
 | 
			
		||||
                .disabled=${this.disabled}
 | 
			
		||||
                @click=${this.handleSubmit}
 | 
			
		||||
              >
 | 
			
		||||
                Submit
 | 
			
		||||
              </ha-button>
 | 
			
		||||
              </mwc-button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </ha-card>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -37,9 +40,12 @@ class DemoBlackWhiteRow extends LitElement {
 | 
			
		||||
              <slot name="dark"></slot>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="card-actions">
 | 
			
		||||
              <ha-button .disabled=${this.disabled} @click=${this.handleSubmit}>
 | 
			
		||||
              <mwc-button
 | 
			
		||||
                .disabled=${this.disabled}
 | 
			
		||||
                @click=${this.handleSubmit}
 | 
			
		||||
              >
 | 
			
		||||
                Submit
 | 
			
		||||
              </ha-button>
 | 
			
		||||
              </mwc-button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </ha-card>
 | 
			
		||||
          ${this.value
 | 
			
		||||
@@ -68,7 +74,7 @@ class DemoBlackWhiteRow extends LitElement {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleSubmit(ev) {
 | 
			
		||||
    const content = (ev.target as HaButton).closest(".content")!;
 | 
			
		||||
    const content = (ev.target as Button).closest(".content")!;
 | 
			
		||||
    fireEvent(this, "submitted" as any, {
 | 
			
		||||
      slot: content.classList.contains("light") ? "light" : "dark",
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import { HaDeviceAction } from "../../../../src/panels/config/automation/action/
 | 
			
		||||
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
 | 
			
		||||
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
 | 
			
		||||
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
 | 
			
		||||
import { HaPlayMediaAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-play_media";
 | 
			
		||||
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
 | 
			
		||||
import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence";
 | 
			
		||||
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
 | 
			
		||||
@@ -31,6 +32,7 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
 | 
			
		||||
  { name: "Service", actions: [HaServiceAction.defaultConfig] },
 | 
			
		||||
  { name: "Condition", actions: [HaConditionAction.defaultConfig] },
 | 
			
		||||
  { name: "Delay", actions: [HaDelayAction.defaultConfig] },
 | 
			
		||||
  { name: "Play media", actions: [HaPlayMediaAction.defaultConfig] },
 | 
			
		||||
  { name: "Wait", actions: [HaWaitAction.defaultConfig] },
 | 
			
		||||
  { name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
 | 
			
		||||
  { name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
 | 
			
		||||
 
 | 
			
		||||
@@ -147,13 +147,13 @@ The `title ` option should not be used without a description.
 | 
			
		||||
 | 
			
		||||
<ha-alert alert-type="success">
 | 
			
		||||
  This is a success alert — check it out!
 | 
			
		||||
  <ha-button slot="action">Undo</ha-button>
 | 
			
		||||
  <mwc-button slot="action" label="Undo"></mwc-button>
 | 
			
		||||
</ha-alert>
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<ha-alert alert-type="success">
 | 
			
		||||
  This is a success alert — check it out!
 | 
			
		||||
  <ha-button slot="action">Undo</ha-button>
 | 
			
		||||
  <mwc-button slot="action" label="Undo"></mwc-button>
 | 
			
		||||
</ha-alert>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
import type { TemplateResult } from "lit";
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
import { customElement } from "lit/decorators";
 | 
			
		||||
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
 | 
			
		||||
import "../../../../src/components/ha-alert";
 | 
			
		||||
import "../../../../src/components/ha-card";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-logo-svg";
 | 
			
		||||
 | 
			
		||||
const alerts: {
 | 
			
		||||
@@ -78,13 +78,13 @@ const alerts: {
 | 
			
		||||
    title: "Error with action",
 | 
			
		||||
    description: "This is a test error alert with action",
 | 
			
		||||
    type: "error",
 | 
			
		||||
    actionSlot: html`<ha-button size="small" slot="action">restart</ha-button>`,
 | 
			
		||||
    actionSlot: html`<mwc-button slot="action" label="restart"></mwc-button>`,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "Unsaved data",
 | 
			
		||||
    description: "You have unsaved data",
 | 
			
		||||
    type: "warning",
 | 
			
		||||
    actionSlot: html`<ha-button size="small" slot="action">save</ha-button>`,
 | 
			
		||||
    actionSlot: html`<mwc-button slot="action" label="save"></mwc-button>`,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "Slotted icon",
 | 
			
		||||
@@ -108,7 +108,7 @@ const alerts: {
 | 
			
		||||
    title: "Slotted action",
 | 
			
		||||
    description: "Alert with slotted action",
 | 
			
		||||
    type: "info",
 | 
			
		||||
    actionSlot: html`<ha-button slot="action">action</ha-button>`,
 | 
			
		||||
    actionSlot: html`<mwc-button slot="action" label="action"></mwc-button>`,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    description: "Dismissable information (RTL)",
 | 
			
		||||
@@ -120,7 +120,7 @@ const alerts: {
 | 
			
		||||
    title: "Error with action",
 | 
			
		||||
    description: "This is a test error alert with action (RTL)",
 | 
			
		||||
    type: "error",
 | 
			
		||||
    actionSlot: html`<ha-button slot="action">restart</ha-button>`,
 | 
			
		||||
    actionSlot: html`<mwc-button slot="action" label="restart"></mwc-button>`,
 | 
			
		||||
    rtl: true,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
@@ -211,7 +211,7 @@ export class DemoHaAlert extends LitElement {
 | 
			
		||||
      max-height: 24px;
 | 
			
		||||
      width: 24px;
 | 
			
		||||
    }
 | 
			
		||||
    ha-button {
 | 
			
		||||
    mwc-button {
 | 
			
		||||
      --mdc-theme-primary: var(--primary-text-color);
 | 
			
		||||
    }
 | 
			
		||||
  `;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,67 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
title: Button
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
  .wrapper {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 24px;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
# Button `<ha-button>`
 | 
			
		||||
 | 
			
		||||
## Implementation
 | 
			
		||||
 | 
			
		||||
### Example Usage
 | 
			
		||||
 | 
			
		||||
<div class="wrapper">
 | 
			
		||||
  <ha-button>
 | 
			
		||||
    simple button
 | 
			
		||||
  </ha-button>
 | 
			
		||||
  <ha-button appearance="plain">
 | 
			
		||||
    plain button
 | 
			
		||||
  </ha-button>
 | 
			
		||||
  <ha-button appearance="filled">
 | 
			
		||||
    filled button
 | 
			
		||||
  </ha-button>
 | 
			
		||||
 | 
			
		||||
  <ha-button size="small">
 | 
			
		||||
    small
 | 
			
		||||
  </ha-button>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<ha-button> simple button </ha-button>
 | 
			
		||||
 | 
			
		||||
<ha-button size="small"> small </ha-button>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### API
 | 
			
		||||
 | 
			
		||||
This component is based on the webawesome button component.
 | 
			
		||||
Check the [webawesome documentation](https://webawesome.com/docs/components/button/) for more details.
 | 
			
		||||
 | 
			
		||||
**Slots**
 | 
			
		||||
 | 
			
		||||
- default slot: Label of the button
 | 
			
		||||
  ` - no default
 | 
			
		||||
- `start`: The prefix container (usually for icons).
 | 
			
		||||
  ` - no default
 | 
			
		||||
- `end`: The suffix container (usually for icons).
 | 
			
		||||
  ` - no default
 | 
			
		||||
 | 
			
		||||
**Properties/Attributes**
 | 
			
		||||
 | 
			
		||||
| Name       | Type                                           | Default  | Description                                                                       |
 | 
			
		||||
| ---------- | ---------------------------------------------- | -------- | --------------------------------------------------------------------------------- |
 | 
			
		||||
| appearance | "accent"/"filled"/"plain"                      | "accent" | Sets the button appearance.                                                       |
 | 
			
		||||
| variants   | "brand"/"danger"/"neutral"/"warning"/"success" | "brand"  | Sets the button color variant. "brand" is default.                                |
 | 
			
		||||
| size       | "small"/"medium"                               | "medium" | Sets the button size.                                                             |
 | 
			
		||||
| loading    | Boolean                                        | false    | Shows a loading indicator instead of the buttons label and disable buttons click. |
 | 
			
		||||
| disabled   | Boolean                                        | false    | Disables the button and prevents user interaction.                                |
 | 
			
		||||
 | 
			
		||||
**CSS Custom Properties**
 | 
			
		||||
 | 
			
		||||
- `--ha-button-height` - Height of the button.
 | 
			
		||||
- `--ha-button-border-radius` - Border radius of the button. Defaults to `var(--ha-border-radius-pill)`.
 | 
			
		||||
@@ -1,171 +0,0 @@
 | 
			
		||||
import { mdiHome } from "@mdi/js";
 | 
			
		||||
import type { TemplateResult } from "lit";
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
import { customElement } from "lit/decorators";
 | 
			
		||||
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
 | 
			
		||||
import { titleCase } from "../../../../src/common/string/title-case";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-card";
 | 
			
		||||
import "../../../../src/components/ha-svg-icon";
 | 
			
		||||
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
 | 
			
		||||
 | 
			
		||||
const appearances = ["accent", "filled", "plain"];
 | 
			
		||||
const variants = ["brand", "danger", "neutral", "warning", "success"];
 | 
			
		||||
 | 
			
		||||
@customElement("demo-components-ha-button")
 | 
			
		||||
export class DemoHaButton extends LitElement {
 | 
			
		||||
  protected render(): TemplateResult {
 | 
			
		||||
    return html`
 | 
			
		||||
      ${["light", "dark"].map(
 | 
			
		||||
        (mode) => html`
 | 
			
		||||
          <div class=${mode}>
 | 
			
		||||
            <ha-card header="ha-button in ${mode}">
 | 
			
		||||
              <div class="card-content">
 | 
			
		||||
                ${variants.map(
 | 
			
		||||
                  (variant) => html`
 | 
			
		||||
                    <div>
 | 
			
		||||
                      ${appearances.map(
 | 
			
		||||
                        (appearance) => html`
 | 
			
		||||
                          <ha-button
 | 
			
		||||
                            .appearance=${appearance}
 | 
			
		||||
                            .variant=${variant}
 | 
			
		||||
                          >
 | 
			
		||||
                            <ha-svg-icon
 | 
			
		||||
                              .path=${mdiHomeAssistant}
 | 
			
		||||
                              slot="start"
 | 
			
		||||
                            ></ha-svg-icon>
 | 
			
		||||
                            ${titleCase(`${variant} ${appearance}`)}
 | 
			
		||||
                            <ha-svg-icon
 | 
			
		||||
                              .path=${mdiHome}
 | 
			
		||||
                              slot="end"
 | 
			
		||||
                            ></ha-svg-icon>
 | 
			
		||||
                          </ha-button>
 | 
			
		||||
                        `
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      ${appearances.map(
 | 
			
		||||
                        (appearance) => html`
 | 
			
		||||
                          <ha-button
 | 
			
		||||
                            .appearance=${appearance}
 | 
			
		||||
                            .variant=${variant}
 | 
			
		||||
                            size="small"
 | 
			
		||||
                          >
 | 
			
		||||
                            ${titleCase(`${variant} ${appearance}`)}
 | 
			
		||||
                          </ha-button>
 | 
			
		||||
                        `
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      ${appearances.map(
 | 
			
		||||
                        (appearance) => html`
 | 
			
		||||
                          <ha-button
 | 
			
		||||
                            .appearance=${appearance}
 | 
			
		||||
                            .variant=${variant}
 | 
			
		||||
                            loading
 | 
			
		||||
                          >
 | 
			
		||||
                            <ha-svg-icon
 | 
			
		||||
                              .path=${mdiHomeAssistant}
 | 
			
		||||
                              slot="start"
 | 
			
		||||
                            ></ha-svg-icon>
 | 
			
		||||
                            ${titleCase(`${variant} ${appearance}`)}
 | 
			
		||||
                            <ha-svg-icon
 | 
			
		||||
                              .path=${mdiHome}
 | 
			
		||||
                              slot="end"
 | 
			
		||||
                            ></ha-svg-icon>
 | 
			
		||||
                          </ha-button>
 | 
			
		||||
                        `
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  `
 | 
			
		||||
                )}
 | 
			
		||||
                ${variants.map(
 | 
			
		||||
                  (variant) => html`
 | 
			
		||||
                    <div>
 | 
			
		||||
                      ${appearances.map(
 | 
			
		||||
                        (appearance) => html`
 | 
			
		||||
                          <ha-button
 | 
			
		||||
                            .variant=${variant}
 | 
			
		||||
                            .appearance=${appearance}
 | 
			
		||||
                            disabled
 | 
			
		||||
                          >
 | 
			
		||||
                            ${titleCase(`${appearance}`)}
 | 
			
		||||
                          </ha-button>
 | 
			
		||||
                        `
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div>
 | 
			
		||||
                      ${appearances.map(
 | 
			
		||||
                        (appearance) => html`
 | 
			
		||||
                          <ha-button
 | 
			
		||||
                            .variant=${variant}
 | 
			
		||||
                            .appearance=${appearance}
 | 
			
		||||
                            size="small"
 | 
			
		||||
                            disabled
 | 
			
		||||
                          >
 | 
			
		||||
                            ${titleCase(`${appearance}`)}
 | 
			
		||||
                          </ha-button>
 | 
			
		||||
                        `
 | 
			
		||||
                      )}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  `
 | 
			
		||||
                )}
 | 
			
		||||
              </div>
 | 
			
		||||
            </ha-card>
 | 
			
		||||
          </div>
 | 
			
		||||
        `
 | 
			
		||||
      )}
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  firstUpdated(changedProps) {
 | 
			
		||||
    super.firstUpdated(changedProps);
 | 
			
		||||
    applyThemesOnElement(
 | 
			
		||||
      this.shadowRoot!.querySelector(".dark"),
 | 
			
		||||
      {
 | 
			
		||||
        default_theme: "default",
 | 
			
		||||
        default_dark_theme: "default",
 | 
			
		||||
        themes: {},
 | 
			
		||||
        darkMode: true,
 | 
			
		||||
        theme: "default",
 | 
			
		||||
      },
 | 
			
		||||
      undefined,
 | 
			
		||||
      undefined,
 | 
			
		||||
      true
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static styles = css`
 | 
			
		||||
    :host {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
    }
 | 
			
		||||
    .dark,
 | 
			
		||||
    .light {
 | 
			
		||||
      display: block;
 | 
			
		||||
      background-color: var(--primary-background-color);
 | 
			
		||||
      padding: 0 50px;
 | 
			
		||||
    }
 | 
			
		||||
    .button {
 | 
			
		||||
      padding: unset;
 | 
			
		||||
    }
 | 
			
		||||
    ha-card {
 | 
			
		||||
      margin: 24px auto;
 | 
			
		||||
    }
 | 
			
		||||
    .card-content {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      gap: 24px;
 | 
			
		||||
    }
 | 
			
		||||
    .card-content div {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      gap: 8px;
 | 
			
		||||
    }
 | 
			
		||||
  `;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface HTMLElementTagNameMap {
 | 
			
		||||
    "demo-components-ha-button": DemoHaButton;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
/* eslint-disable lit/no-template-arrow */
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import type { TemplateResult } from "lit";
 | 
			
		||||
import { html, LitElement } from "lit";
 | 
			
		||||
import { customElement, state } from "lit/decorators";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
title: Progress Button
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
  .wrapper {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    gap: 24px;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
# Progress Button `<ha-progress-button>`
 | 
			
		||||
 | 
			
		||||
### API
 | 
			
		||||
 | 
			
		||||
This component is a wrapper around `<ha-button>` that adds support for showing progress
 | 
			
		||||
 | 
			
		||||
**Slots**
 | 
			
		||||
 | 
			
		||||
- default slot: Label of the button
 | 
			
		||||
  ` - no default
 | 
			
		||||
 | 
			
		||||
**Properties/Attributes**
 | 
			
		||||
 | 
			
		||||
| Name       | Type                                           | Default   | Description                                        |
 | 
			
		||||
| ---------- | ---------------------------------------------- | --------- | -------------------------------------------------- |
 | 
			
		||||
| label      | string                                         | "accent"  | Sets the button label.                             |
 | 
			
		||||
| disabled   | Boolean                                        | false     | Disables the button if true.                       |
 | 
			
		||||
| progress   | Boolean                                        | false     | Shows a progress indicator on the button.          |
 | 
			
		||||
| appearance | "accent"/"filled"/"plain"                      | "accent"  | Sets the button appearance.                        |
 | 
			
		||||
| variants   | "brand"/"danger"/"neutral"/"warning"/"success" | "brand"   | Sets the button color variant. "brand" is default. |
 | 
			
		||||
| iconPath   | string                                         | undefined | Sets the icon path for the button.                 |
 | 
			
		||||
@@ -1,139 +0,0 @@
 | 
			
		||||
import type { TemplateResult } from "lit";
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
import { customElement } from "lit/decorators";
 | 
			
		||||
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
 | 
			
		||||
import "../../../../src/components/buttons/ha-progress-button";
 | 
			
		||||
import "../../../../src/components/ha-card";
 | 
			
		||||
import "../../../../src/components/ha-svg-icon";
 | 
			
		||||
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
 | 
			
		||||
 | 
			
		||||
@customElement("demo-components-ha-progress-button")
 | 
			
		||||
export class DemoHaProgressButton extends LitElement {
 | 
			
		||||
  protected render(): TemplateResult {
 | 
			
		||||
    return html`
 | 
			
		||||
      ${["light", "dark"].map(
 | 
			
		||||
        (mode) => html`
 | 
			
		||||
          <div class=${mode}>
 | 
			
		||||
            <ha-card header="ha-progress-button in ${mode}">
 | 
			
		||||
              <div class="card-content">
 | 
			
		||||
                <ha-progress-button @click=${this._clickedSuccess}>
 | 
			
		||||
                  Success
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
                <ha-progress-button @click=${this._clickedFail}>
 | 
			
		||||
                  Fail
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
                <ha-progress-button size="small" @click=${this._clickedSuccess}>
 | 
			
		||||
                  small
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
                <ha-progress-button
 | 
			
		||||
                  appearance="filled"
 | 
			
		||||
                  @click=${this._clickedSuccess}
 | 
			
		||||
                >
 | 
			
		||||
                  filled
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
                <ha-progress-button
 | 
			
		||||
                  appearance="plain"
 | 
			
		||||
                  @click=${this._clickedSuccess}
 | 
			
		||||
                >
 | 
			
		||||
                  plain
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
                <ha-progress-button
 | 
			
		||||
                  variant="warning"
 | 
			
		||||
                  @click=${this._clickedSuccess}
 | 
			
		||||
                >
 | 
			
		||||
                  warning
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
                <ha-progress-button
 | 
			
		||||
                  variant="neutral"
 | 
			
		||||
                  @click=${this._clickedSuccess}
 | 
			
		||||
                  label="with icon"
 | 
			
		||||
                  .iconPath=${mdiHomeAssistant}
 | 
			
		||||
                >
 | 
			
		||||
                  With Icon
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
                <ha-progress-button progress @click=${this._clickedSuccess}>
 | 
			
		||||
                  progress
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
                <ha-progress-button disabled @click=${this._clickedSuccess}>
 | 
			
		||||
                  disabled
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </ha-card>
 | 
			
		||||
          </div>
 | 
			
		||||
        `
 | 
			
		||||
      )}
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  firstUpdated(changedProps) {
 | 
			
		||||
    super.firstUpdated(changedProps);
 | 
			
		||||
    applyThemesOnElement(
 | 
			
		||||
      this.shadowRoot!.querySelector(".dark"),
 | 
			
		||||
      {
 | 
			
		||||
        default_theme: "default",
 | 
			
		||||
        default_dark_theme: "default",
 | 
			
		||||
        themes: {},
 | 
			
		||||
        darkMode: true,
 | 
			
		||||
        theme: "default",
 | 
			
		||||
      },
 | 
			
		||||
      undefined,
 | 
			
		||||
      undefined,
 | 
			
		||||
      true
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _clickedSuccess(ev: CustomEvent): Promise<void> {
 | 
			
		||||
    console.log("Clicked success");
 | 
			
		||||
    const button = ev.currentTarget as any;
 | 
			
		||||
    button.progress = true;
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      button.actionSuccess();
 | 
			
		||||
      button.progress = false;
 | 
			
		||||
    }, 1000);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async _clickedFail(ev: CustomEvent): Promise<void> {
 | 
			
		||||
    const button = ev.currentTarget as any;
 | 
			
		||||
    button.progress = true;
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      button.actionError();
 | 
			
		||||
      button.progress = false;
 | 
			
		||||
    }, 1000);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static styles = css`
 | 
			
		||||
    :host {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      justify-content: center;
 | 
			
		||||
    }
 | 
			
		||||
    .dark,
 | 
			
		||||
    .light {
 | 
			
		||||
      display: block;
 | 
			
		||||
      background-color: var(--primary-background-color);
 | 
			
		||||
      padding: 0 50px;
 | 
			
		||||
    }
 | 
			
		||||
    .button {
 | 
			
		||||
      padding: unset;
 | 
			
		||||
    }
 | 
			
		||||
    ha-card {
 | 
			
		||||
      margin: 24px auto;
 | 
			
		||||
    }
 | 
			
		||||
    .card-content {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      gap: 24px;
 | 
			
		||||
    }
 | 
			
		||||
    .card-content div {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      gap: 8px;
 | 
			
		||||
    }
 | 
			
		||||
  `;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface HTMLElementTagNameMap {
 | 
			
		||||
    "demo-components-ha-progress-button": DemoHaProgressButton;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import type { TemplateResult } from "lit";
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
import { customElement, state } from "lit/decorators";
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@ import { provideHass } from "../../../../src/fake_data/provide_hass";
 | 
			
		||||
import "../../components/demo-cards";
 | 
			
		||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
 | 
			
		||||
import { ClimateEntityFeature } from "../../../../src/data/climate";
 | 
			
		||||
import { FanEntityFeature } from "../../../../src/data/fan";
 | 
			
		||||
 | 
			
		||||
const ENTITIES = [
 | 
			
		||||
  getEntity("switch", "tv_outlet", "on", {
 | 
			
		||||
@@ -101,15 +100,6 @@ const ENTITIES = [
 | 
			
		||||
      ClimateEntityFeature.FAN_MODE +
 | 
			
		||||
      ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
 | 
			
		||||
  }),
 | 
			
		||||
  getEntity("fan", "fan_demo", "on", {
 | 
			
		||||
    friendly_name: "Ceiling fan",
 | 
			
		||||
    device_class: "fan",
 | 
			
		||||
    direction: "reverse",
 | 
			
		||||
    supported_features:
 | 
			
		||||
      FanEntityFeature.DIRECTION +
 | 
			
		||||
      FanEntityFeature.SET_SPEED +
 | 
			
		||||
      FanEntityFeature.OSCILLATE,
 | 
			
		||||
  }),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const CONFIGS = [
 | 
			
		||||
@@ -271,33 +261,6 @@ const CONFIGS = [
 | 
			
		||||
  - type: target-temperature
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Fan direction feature",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: tile
 | 
			
		||||
  entity: fan.fan_demo
 | 
			
		||||
  features:
 | 
			
		||||
  - type: fan-direction
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Fan speed feature",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: tile
 | 
			
		||||
  entity: fan.fan_demo
 | 
			
		||||
  features:
 | 
			
		||||
  - type: fan-speed
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    heading: "Fan oscillate feature",
 | 
			
		||||
    config: `
 | 
			
		||||
- type: tile
 | 
			
		||||
  entity: fan.fan_demo
 | 
			
		||||
  features:
 | 
			
		||||
  - type: fan-oscillate
 | 
			
		||||
    `,
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@customElement("demo-lovelace-tile-card")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import type { TemplateResult } from "lit";
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
import { customElement } from "lit/decorators";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-card";
 | 
			
		||||
import type { ActionHandlerEvent } from "../../../../src/data/lovelace/action_handler";
 | 
			
		||||
import { actionHandler } from "../../../../src/panels/lovelace/common/directives/action-handler-directive";
 | 
			
		||||
@@ -13,16 +13,12 @@ export class DemoUtilLongPress extends LitElement {
 | 
			
		||||
      ${[1, 2, 3].map(
 | 
			
		||||
        () => html`
 | 
			
		||||
          <ha-card>
 | 
			
		||||
            <ha-button
 | 
			
		||||
              appearance="plain"
 | 
			
		||||
            <mwc-button
 | 
			
		||||
              @action=${this._handleAction}
 | 
			
		||||
              .actionHandler=${actionHandler({
 | 
			
		||||
                hasHold: true,
 | 
			
		||||
                hasDoubleClick: true,
 | 
			
		||||
              })}
 | 
			
		||||
              .actionHandler=${actionHandler({})}
 | 
			
		||||
            >
 | 
			
		||||
              (long) press me!
 | 
			
		||||
            </ha-button>
 | 
			
		||||
            </mwc-button>
 | 
			
		||||
 | 
			
		||||
            <textarea></textarea>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
---
 | 
			
		||||
title: Fan
 | 
			
		||||
---
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
import type { PropertyValues, TemplateResult } from "lit";
 | 
			
		||||
import { html, LitElement } from "lit";
 | 
			
		||||
import { customElement, property, query } from "lit/decorators";
 | 
			
		||||
import "../../../../src/components/ha-card";
 | 
			
		||||
import "../../../../src/dialogs/more-info/more-info-content";
 | 
			
		||||
import { getEntity } from "../../../../src/fake_data/entity";
 | 
			
		||||
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
 | 
			
		||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
 | 
			
		||||
import "../../components/demo-more-infos";
 | 
			
		||||
import { FanEntityFeature } from "../../../../src/data/fan";
 | 
			
		||||
 | 
			
		||||
const ENTITIES = [
 | 
			
		||||
  getEntity("fan", "fan", "on", {
 | 
			
		||||
    friendly_name: "Fan",
 | 
			
		||||
    device_class: "fan",
 | 
			
		||||
    supported_features:
 | 
			
		||||
      FanEntityFeature.OSCILLATE +
 | 
			
		||||
      FanEntityFeature.DIRECTION +
 | 
			
		||||
      FanEntityFeature.SET_SPEED,
 | 
			
		||||
  }),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@customElement("demo-more-info-fan")
 | 
			
		||||
class DemoMoreInfoFan extends LitElement {
 | 
			
		||||
  @property({ attribute: false }) public hass!: MockHomeAssistant;
 | 
			
		||||
 | 
			
		||||
  @query("demo-more-infos") private _demoRoot!: HTMLElement;
 | 
			
		||||
 | 
			
		||||
  protected render(): TemplateResult {
 | 
			
		||||
    return html`
 | 
			
		||||
      <demo-more-infos
 | 
			
		||||
        .hass=${this.hass}
 | 
			
		||||
        .entities=${ENTITIES.map((ent) => ent.entityId)}
 | 
			
		||||
      ></demo-more-infos>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected firstUpdated(changedProperties: PropertyValues) {
 | 
			
		||||
    super.firstUpdated(changedProperties);
 | 
			
		||||
    const hass = provideHass(this._demoRoot);
 | 
			
		||||
    hass.updateTranslations(null, "en");
 | 
			
		||||
    hass.addEntities(ENTITIES);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface HTMLElementTagNameMap {
 | 
			
		||||
    "demo-more-info-fan": DemoMoreInfoFan;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
import { availableParallelism } from "node:os";
 | 
			
		||||
import "./build-scripts/gulp/index.mjs";
 | 
			
		||||
 | 
			
		||||
process.env.UV_THREADPOOL_SIZE = availableParallelism();
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp build-hassio
 | 
			
		||||
yarn run-task build-hassio
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp develop-hassio
 | 
			
		||||
yarn run-task develop-hassio
 | 
			
		||||
 
 | 
			
		||||
@@ -99,8 +99,7 @@ class HassioAddonNetwork extends LitElement {
 | 
			
		||||
          : nothing}
 | 
			
		||||
        <div class="card-actions">
 | 
			
		||||
          <ha-progress-button
 | 
			
		||||
            variant="danger"
 | 
			
		||||
            appearance="plain"
 | 
			
		||||
            class="warning"
 | 
			
		||||
            .disabled=${this.disabled}
 | 
			
		||||
            @click=${this._resetTapped}
 | 
			
		||||
          >
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,6 @@ import type { CSSResultGroup, TemplateResult } from "lit";
 | 
			
		||||
import { LitElement, css, html, nothing } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import { classMap } from "lit/directives/class-map";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { atLeastVersion } from "../../../../src/common/config/version";
 | 
			
		||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
			
		||||
@@ -188,13 +187,12 @@ class HassioAddonInfo extends LitElement {
 | 
			
		||||
                "addon.dashboard.protection_mode.content"
 | 
			
		||||
              )}
 | 
			
		||||
              <ha-button
 | 
			
		||||
                variant="danger"
 | 
			
		||||
                slot="action"
 | 
			
		||||
                @click=${this._protectionToggled}
 | 
			
		||||
              >
 | 
			
		||||
                ${this.supervisor.localize(
 | 
			
		||||
                .label=${this.supervisor.localize(
 | 
			
		||||
                  "addon.dashboard.protection_mode.enable"
 | 
			
		||||
                )}
 | 
			
		||||
                @click=${this._protectionToggled}
 | 
			
		||||
              >
 | 
			
		||||
              </ha-button>
 | 
			
		||||
            </ha-alert>
 | 
			
		||||
          `
 | 
			
		||||
@@ -694,16 +692,14 @@ class HassioAddonInfo extends LitElement {
 | 
			
		||||
              ? this._computeIsRunning
 | 
			
		||||
                ? html`
 | 
			
		||||
                    <ha-progress-button
 | 
			
		||||
                      variant="danger"
 | 
			
		||||
                      appearance="plain"
 | 
			
		||||
                      class="warning"
 | 
			
		||||
                      @click=${this._stopClicked}
 | 
			
		||||
                      .disabled=${systemManaged && !this.controlEnabled}
 | 
			
		||||
                    >
 | 
			
		||||
                      ${this.supervisor.localize("addon.dashboard.stop")}
 | 
			
		||||
                    </ha-progress-button>
 | 
			
		||||
                    <ha-progress-button
 | 
			
		||||
                      variant="danger"
 | 
			
		||||
                      appearance="plain"
 | 
			
		||||
                      class="warning"
 | 
			
		||||
                      @click=${this._restartClicked}
 | 
			
		||||
                    >
 | 
			
		||||
                      ${this.supervisor.localize("addon.dashboard.restart")}
 | 
			
		||||
@@ -713,60 +709,10 @@ class HassioAddonInfo extends LitElement {
 | 
			
		||||
                    <ha-progress-button
 | 
			
		||||
                      @click=${this._startClicked}
 | 
			
		||||
                      .progress=${this.addon.state === "startup"}
 | 
			
		||||
                      appearance="plain"
 | 
			
		||||
                    >
 | 
			
		||||
                      ${this.supervisor.localize("addon.dashboard.start")}
 | 
			
		||||
                    </ha-progress-button>
 | 
			
		||||
                  `
 | 
			
		||||
              : nothing}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div>
 | 
			
		||||
            ${this.addon.version
 | 
			
		||||
              ? html`
 | 
			
		||||
                  <ha-progress-button
 | 
			
		||||
                    variant="danger"
 | 
			
		||||
                    appearance="plain"
 | 
			
		||||
                    @click=${this._uninstallClicked}
 | 
			
		||||
                    .disabled=${systemManaged && !this.controlEnabled}
 | 
			
		||||
                  >
 | 
			
		||||
                    ${this.supervisor.localize("addon.dashboard.uninstall")}
 | 
			
		||||
                  </ha-progress-button>
 | 
			
		||||
                  ${this.addon.build
 | 
			
		||||
                    ? html`
 | 
			
		||||
                        <ha-progress-button
 | 
			
		||||
                          variant="danger"
 | 
			
		||||
                          appearance="plain"
 | 
			
		||||
                          @click=${this._rebuildClicked}
 | 
			
		||||
                        >
 | 
			
		||||
                          ${this.supervisor.localize("addon.dashboard.rebuild")}
 | 
			
		||||
                        </ha-progress-button>
 | 
			
		||||
                      `
 | 
			
		||||
                    : nothing}
 | 
			
		||||
                  ${this._computeShowWebUI || this._computeShowIngressUI
 | 
			
		||||
                    ? html`
 | 
			
		||||
                        <ha-button
 | 
			
		||||
                          href=${ifDefined(
 | 
			
		||||
                            !this._computeShowIngressUI
 | 
			
		||||
                              ? this._pathWebui!
 | 
			
		||||
                              : nothing
 | 
			
		||||
                          )}
 | 
			
		||||
                          target=${ifDefined(
 | 
			
		||||
                            !this._computeShowIngressUI ? "_blank" : nothing
 | 
			
		||||
                          )}
 | 
			
		||||
                          rel=${ifDefined(
 | 
			
		||||
                            !this._computeShowIngressUI ? "noopener" : nothing
 | 
			
		||||
                          )}
 | 
			
		||||
                          @click=${!this._computeShowWebUI
 | 
			
		||||
                            ? this._openIngress
 | 
			
		||||
                            : undefined}
 | 
			
		||||
                        >
 | 
			
		||||
                          ${this.supervisor.localize(
 | 
			
		||||
                            "addon.dashboard.open_web_ui"
 | 
			
		||||
                          )}
 | 
			
		||||
                        </ha-button>
 | 
			
		||||
                      `
 | 
			
		||||
                    : nothing}
 | 
			
		||||
                `
 | 
			
		||||
              : html`
 | 
			
		||||
                  <ha-progress-button
 | 
			
		||||
                    .disabled=${!this.addon.available}
 | 
			
		||||
@@ -776,6 +722,52 @@ class HassioAddonInfo extends LitElement {
 | 
			
		||||
                  </ha-progress-button>
 | 
			
		||||
                `}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div>
 | 
			
		||||
            ${this.addon.version
 | 
			
		||||
              ? html` ${this._computeShowWebUI
 | 
			
		||||
                    ? html`
 | 
			
		||||
                        <a
 | 
			
		||||
                          href=${this._pathWebui!}
 | 
			
		||||
                          tabindex="-1"
 | 
			
		||||
                          target="_blank"
 | 
			
		||||
                          rel="noopener"
 | 
			
		||||
                        >
 | 
			
		||||
                          <ha-button>
 | 
			
		||||
                            ${this.supervisor.localize(
 | 
			
		||||
                              "addon.dashboard.open_web_ui"
 | 
			
		||||
                            )}
 | 
			
		||||
                          </ha-button>
 | 
			
		||||
                        </a>
 | 
			
		||||
                      `
 | 
			
		||||
                    : nothing}
 | 
			
		||||
                  ${this._computeShowIngressUI
 | 
			
		||||
                    ? html`
 | 
			
		||||
                        <ha-button @click=${this._openIngress}>
 | 
			
		||||
                          ${this.supervisor.localize(
 | 
			
		||||
                            "addon.dashboard.open_web_ui"
 | 
			
		||||
                          )}
 | 
			
		||||
                        </ha-button>
 | 
			
		||||
                      `
 | 
			
		||||
                    : nothing}
 | 
			
		||||
                  <ha-progress-button
 | 
			
		||||
                    class="warning"
 | 
			
		||||
                    @click=${this._uninstallClicked}
 | 
			
		||||
                    .disabled=${systemManaged && !this.controlEnabled}
 | 
			
		||||
                  >
 | 
			
		||||
                    ${this.supervisor.localize("addon.dashboard.uninstall")}
 | 
			
		||||
                  </ha-progress-button>
 | 
			
		||||
                  ${this.addon.build
 | 
			
		||||
                    ? html`
 | 
			
		||||
                        <ha-progress-button
 | 
			
		||||
                          class="warning"
 | 
			
		||||
                          @click=${this._rebuildClicked}
 | 
			
		||||
                        >
 | 
			
		||||
                          ${this.supervisor.localize("addon.dashboard.rebuild")}
 | 
			
		||||
                        </ha-progress-button>
 | 
			
		||||
                      `
 | 
			
		||||
                    : nothing}`
 | 
			
		||||
              : nothing}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </ha-card>
 | 
			
		||||
 | 
			
		||||
@@ -1154,17 +1146,15 @@ class HassioAddonInfo extends LitElement {
 | 
			
		||||
          ),
 | 
			
		||||
          dismissText: this.supervisor.localize("common.cancel"),
 | 
			
		||||
        });
 | 
			
		||||
        button.actionError();
 | 
			
		||||
        button.progress = false;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (err: any) {
 | 
			
		||||
      button.actionError();
 | 
			
		||||
      button.progress = false;
 | 
			
		||||
      showAlertDialog(this, {
 | 
			
		||||
        title: "Failed to validate addon configuration",
 | 
			
		||||
        text: extractApiErrorMessage(err),
 | 
			
		||||
      });
 | 
			
		||||
      button.progress = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -1178,15 +1168,11 @@ class HassioAddonInfo extends LitElement {
 | 
			
		||||
      };
 | 
			
		||||
      fireEvent(this, "hass-api-called", eventdata);
 | 
			
		||||
    } catch (err: any) {
 | 
			
		||||
      button.actionError();
 | 
			
		||||
      button.progress = false;
 | 
			
		||||
      showAlertDialog(this, {
 | 
			
		||||
        title: this.supervisor.localize("addon.dashboard.action_error.start"),
 | 
			
		||||
        text: extractApiErrorMessage(err),
 | 
			
		||||
      });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    button.actionSuccess();
 | 
			
		||||
    button.progress = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -1242,7 +1228,6 @@ class HassioAddonInfo extends LitElement {
 | 
			
		||||
        path: "uninstall",
 | 
			
		||||
      };
 | 
			
		||||
      fireEvent(this, "hass-api-called", eventdata);
 | 
			
		||||
      button.actionSuccess();
 | 
			
		||||
    } catch (err: any) {
 | 
			
		||||
      showAlertDialog(this, {
 | 
			
		||||
        title: this.supervisor.localize(
 | 
			
		||||
@@ -1250,7 +1235,6 @@ class HassioAddonInfo extends LitElement {
 | 
			
		||||
        ),
 | 
			
		||||
        text: extractApiErrorMessage(err),
 | 
			
		||||
      });
 | 
			
		||||
      button.actionError();
 | 
			
		||||
    }
 | 
			
		||||
    button.progress = false;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import type { TemplateResult } from "lit";
 | 
			
		||||
import { LitElement, css, html, nothing } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import type { ActionDetail } from "@material/mwc-list";
 | 
			
		||||
 | 
			
		||||
import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
 | 
			
		||||
@@ -16,7 +17,6 @@ import type {
 | 
			
		||||
} from "../../../src/components/data-table/ha-data-table";
 | 
			
		||||
import "../../../src/components/ha-button-menu";
 | 
			
		||||
import "../../../src/components/ha-fab";
 | 
			
		||||
import "../../../src/components/ha-button";
 | 
			
		||||
import "../../../src/components/ha-icon-button";
 | 
			
		||||
import "../../../src/components/ha-list-item";
 | 
			
		||||
import "../../../src/components/ha-svg-icon";
 | 
			
		||||
@@ -241,13 +241,12 @@ export class HassioBackups extends LitElement {
 | 
			
		||||
              <div class="header-btns">
 | 
			
		||||
                ${!this.narrow
 | 
			
		||||
                  ? html`
 | 
			
		||||
                      <ha-button
 | 
			
		||||
                        appearance="plain"
 | 
			
		||||
                        variant="danger"
 | 
			
		||||
                      <mwc-button
 | 
			
		||||
                        @click=${this._deleteSelected}
 | 
			
		||||
                        class="warning"
 | 
			
		||||
                      >
 | 
			
		||||
                        ${this.supervisor.localize("backup.delete_selected")}
 | 
			
		||||
                      </ha-button>
 | 
			
		||||
                      </mwc-button>
 | 
			
		||||
                    `
 | 
			
		||||
                  : html`
 | 
			
		||||
                      <ha-icon-button
 | 
			
		||||
@@ -409,7 +408,7 @@ export class HassioBackups extends LitElement {
 | 
			
		||||
          margin-inline-end: -12px;
 | 
			
		||||
          margin-inline-start: initial;
 | 
			
		||||
        }
 | 
			
		||||
        .header-btns > ha-button,
 | 
			
		||||
        .header-btns > mwc-button,
 | 
			
		||||
        .header-btns > ha-icon-button {
 | 
			
		||||
          margin: 8px;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import type { CSSResultGroup } from "lit";
 | 
			
		||||
import { css, html, LitElement, nothing } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import "../../../src/components/buttons/ha-progress-button";
 | 
			
		||||
import "../../../src/components/ha-card";
 | 
			
		||||
import "../../../src/components/ha-button";
 | 
			
		||||
import "../../../src/components/ha-settings-row";
 | 
			
		||||
import "../../../src/components/ha-svg-icon";
 | 
			
		||||
import type { HassioHassOSInfo } from "../../../src/data/hassio/host";
 | 
			
		||||
@@ -108,9 +109,10 @@ export class HassioUpdate extends LitElement {
 | 
			
		||||
          </ha-settings-row>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="card-actions">
 | 
			
		||||
          <ha-button appearance="plain" href="/hassio/update-available/${key}">
 | 
			
		||||
            ${this.supervisor.localize("common.show")}
 | 
			
		||||
          </ha-button>
 | 
			
		||||
          <a href="/hassio/update-available/${key}">
 | 
			
		||||
            <mwc-button .label=${this.supervisor.localize("common.show")}>
 | 
			
		||||
            </mwc-button>
 | 
			
		||||
          </a>
 | 
			
		||||
        </div>
 | 
			
		||||
      </ha-card>
 | 
			
		||||
    `;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
import type { CSSResultGroup } from "lit";
 | 
			
		||||
import { css, html, LitElement, nothing } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../../src/components/ha-dialog";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-form/ha-form";
 | 
			
		||||
import type { SchemaUnion } from "../../../../src/components/ha-form/types";
 | 
			
		||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
 | 
			
		||||
@@ -77,21 +77,20 @@ class HassioBackupLocationDialog extends LitElement {
 | 
			
		||||
          @value-changed=${this._valueChanged}
 | 
			
		||||
          dialogInitialFocus
 | 
			
		||||
        ></ha-form>
 | 
			
		||||
        <ha-button
 | 
			
		||||
          appearance="plain"
 | 
			
		||||
        <mwc-button
 | 
			
		||||
          slot="secondaryAction"
 | 
			
		||||
          @click=${this.closeDialog}
 | 
			
		||||
          dialogInitialFocus
 | 
			
		||||
        >
 | 
			
		||||
          ${this._dialogParams.supervisor.localize("common.cancel")}
 | 
			
		||||
        </ha-button>
 | 
			
		||||
        <ha-button
 | 
			
		||||
        </mwc-button>
 | 
			
		||||
        <mwc-button
 | 
			
		||||
          .disabled=${this._waiting || !this._data}
 | 
			
		||||
          slot="primaryAction"
 | 
			
		||||
          @click=${this._changeMount}
 | 
			
		||||
        >
 | 
			
		||||
          ${this._dialogParams.supervisor.localize("common.save")}
 | 
			
		||||
        </ha-button>
 | 
			
		||||
        </mwc-button>
 | 
			
		||||
      </ha-dialog>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import { atLeastVersion } from "../../../../src/common/config/version";
 | 
			
		||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
			
		||||
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
 | 
			
		||||
import { slugify } from "../../../../src/common/string/slugify";
 | 
			
		||||
import "../../../../src/components/buttons/ha-progress-button";
 | 
			
		||||
import "../../../../src/components/ha-alert";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-button-menu";
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
import type { CSSResultGroup } from "lit";
 | 
			
		||||
import { css, html, LitElement, nothing } from "lit";
 | 
			
		||||
import { customElement, property, query, state } from "lit/decorators";
 | 
			
		||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../../src/components/buttons/ha-progress-button";
 | 
			
		||||
import "../../../../src/components/ha-alert";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-spinner";
 | 
			
		||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
 | 
			
		||||
import {
 | 
			
		||||
@@ -68,20 +69,16 @@ class HassioCreateBackupDialog extends LitElement {
 | 
			
		||||
        ${this._error
 | 
			
		||||
          ? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
 | 
			
		||||
          : ""}
 | 
			
		||||
        <ha-button
 | 
			
		||||
          appearance="plain"
 | 
			
		||||
          slot="secondaryAction"
 | 
			
		||||
          @click=${this.closeDialog}
 | 
			
		||||
        >
 | 
			
		||||
        <mwc-button slot="secondaryAction" @click=${this.closeDialog}>
 | 
			
		||||
          ${this._dialogParams.supervisor.localize("common.close")}
 | 
			
		||||
        </ha-button>
 | 
			
		||||
        <ha-button
 | 
			
		||||
        </mwc-button>
 | 
			
		||||
        <mwc-button
 | 
			
		||||
          .disabled=${this._creatingBackup}
 | 
			
		||||
          slot="primaryAction"
 | 
			
		||||
          @click=${this._createBackup}
 | 
			
		||||
        >
 | 
			
		||||
          ${this._dialogParams.supervisor.localize("backup.create")}
 | 
			
		||||
        </ha-button>
 | 
			
		||||
        </mwc-button>
 | 
			
		||||
      </ha-dialog>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import memoizeOne from "memoize-one";
 | 
			
		||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../../src/components/ha-dialog";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-list-item";
 | 
			
		||||
import "../../../../src/components/ha-select";
 | 
			
		||||
import "../../../../src/components/ha-spinner";
 | 
			
		||||
@@ -21,8 +20,8 @@ import type { HomeAssistant } from "../../../../src/types";
 | 
			
		||||
import type { HassioDatatiskDialogParams } from "./show-dialog-hassio-datadisk";
 | 
			
		||||
 | 
			
		||||
const calculateMoveTime = memoizeOne((supervisor: Supervisor): number => {
 | 
			
		||||
  // Assume a speed of 30 MB/s.
 | 
			
		||||
  const moveTime = (supervisor.host.disk_used * 1000) / 60 / 30;
 | 
			
		||||
  const speed = supervisor.host.disk_life_time !== "" ? 30 : 10;
 | 
			
		||||
  const moveTime = (supervisor.host.disk_used * 1000) / 60 / speed;
 | 
			
		||||
  const rebootTime = (supervisor.host.startup_time * 4) / 60;
 | 
			
		||||
  return Math.ceil((moveTime + rebootTime) / 10) * 10;
 | 
			
		||||
});
 | 
			
		||||
@@ -110,18 +109,17 @@ class HassioDatadiskDialog extends LitElement {
 | 
			
		||||
                      "dialog.datadisk_move.no_devices"
 | 
			
		||||
                    )}
 | 
			
		||||
 | 
			
		||||
              <ha-button
 | 
			
		||||
                appearance="plain"
 | 
			
		||||
                slot="primaryAction"
 | 
			
		||||
              <mwc-button
 | 
			
		||||
                slot="secondaryAction"
 | 
			
		||||
                @click=${this.closeDialog}
 | 
			
		||||
                dialogInitialFocus
 | 
			
		||||
              >
 | 
			
		||||
                ${this.dialogParams.supervisor.localize(
 | 
			
		||||
                  "dialog.datadisk_move.cancel"
 | 
			
		||||
                )}
 | 
			
		||||
              </ha-button>
 | 
			
		||||
              </mwc-button>
 | 
			
		||||
 | 
			
		||||
              <ha-button
 | 
			
		||||
              <mwc-button
 | 
			
		||||
                .disabled=${!this.selectedDevice}
 | 
			
		||||
                slot="primaryAction"
 | 
			
		||||
                @click=${this._moveDatadisk}
 | 
			
		||||
@@ -129,7 +127,7 @@ class HassioDatadiskDialog extends LitElement {
 | 
			
		||||
                ${this.dialogParams.supervisor.localize(
 | 
			
		||||
                  "dialog.datadisk_move.move"
 | 
			
		||||
                )}
 | 
			
		||||
              </ha-button>`}
 | 
			
		||||
              </mwc-button>`}
 | 
			
		||||
      </ha-dialog>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
import { mdiClose } from "@mdi/js";
 | 
			
		||||
import type { CSSResultGroup } from "lit";
 | 
			
		||||
import { css, html, LitElement, nothing } from "lit";
 | 
			
		||||
@@ -5,7 +6,6 @@ import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import { cache } from "lit/directives/cache";
 | 
			
		||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../../src/components/ha-alert";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-dialog";
 | 
			
		||||
import "../../../../src/components/ha-expansion-panel";
 | 
			
		||||
import "../../../../src/components/ha-formfield";
 | 
			
		||||
@@ -15,6 +15,7 @@ import "../../../../src/components/ha-list";
 | 
			
		||||
import "../../../../src/components/ha-list-item";
 | 
			
		||||
import "../../../../src/components/ha-password-field";
 | 
			
		||||
import "../../../../src/components/ha-radio";
 | 
			
		||||
import "../../../../src/components/ha-spinner";
 | 
			
		||||
import "../../../../src/components/ha-textfield";
 | 
			
		||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
 | 
			
		||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
 | 
			
		||||
@@ -153,16 +154,16 @@ export class DialogHassioNetwork
 | 
			
		||||
                      )}
 | 
			
		||||
                    </p>`
 | 
			
		||||
                  : ""}
 | 
			
		||||
                <ha-button
 | 
			
		||||
                  appearance="plain"
 | 
			
		||||
                  size="small"
 | 
			
		||||
                <mwc-button
 | 
			
		||||
                  class="scan"
 | 
			
		||||
                  @click=${this._scanForAP}
 | 
			
		||||
                  .disabled=${this._scanning}
 | 
			
		||||
                  .loading=${this._scanning}
 | 
			
		||||
                >
 | 
			
		||||
                  ${this.supervisor.localize("dialog.network.scan_ap")}
 | 
			
		||||
                </ha-button>
 | 
			
		||||
                  ${this._scanning
 | 
			
		||||
                    ? html`<ha-spinner aria-label="Scanning" size="small">
 | 
			
		||||
                      </ha-spinner>`
 | 
			
		||||
                    : this.supervisor.localize("dialog.network.scan_ap")}
 | 
			
		||||
                </mwc-button>
 | 
			
		||||
                ${this._accessPoints &&
 | 
			
		||||
                this._accessPoints.accesspoints &&
 | 
			
		||||
                this._accessPoints.accesspoints.length !== 0
 | 
			
		||||
@@ -269,16 +270,16 @@ export class DialogHassioNetwork
 | 
			
		||||
          : ""}
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="buttons">
 | 
			
		||||
        <ha-button @click=${this.closeDialog} appearance="plain">
 | 
			
		||||
          ${this.supervisor.localize("common.cancel")}
 | 
			
		||||
        </ha-button>
 | 
			
		||||
        <ha-button
 | 
			
		||||
          @click=${this._updateNetwork}
 | 
			
		||||
          .disabled=${!this._dirty}
 | 
			
		||||
          .loading=${this._processing}
 | 
			
		||||
        <mwc-button
 | 
			
		||||
          .label=${this.supervisor.localize("common.cancel")}
 | 
			
		||||
          @click=${this.closeDialog}
 | 
			
		||||
        >
 | 
			
		||||
          ${this.supervisor.localize("common.save")}
 | 
			
		||||
        </ha-button>
 | 
			
		||||
        </mwc-button>
 | 
			
		||||
        <mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
 | 
			
		||||
          ${this._processing
 | 
			
		||||
            ? html`<ha-spinner size="small"> </ha-spinner>`
 | 
			
		||||
            : this.supervisor.localize("common.save")}
 | 
			
		||||
        </mwc-button>
 | 
			
		||||
      </div>`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -583,7 +584,11 @@ export class DialogHassioNetwork
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ha-button.scan {
 | 
			
		||||
        mwc-button.warning {
 | 
			
		||||
          --mdc-theme-primary: var(--error-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mwc-button.scan {
 | 
			
		||||
          margin-left: 8px;
 | 
			
		||||
          margin-inline-start: 8px;
 | 
			
		||||
          margin-inline-end: initial;
 | 
			
		||||
@@ -604,8 +609,8 @@ export class DialogHassioNetwork
 | 
			
		||||
            var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
 | 
			
		||||
          display: flex;
 | 
			
		||||
          justify-content: space-between;
 | 
			
		||||
          padding: 16px;
 | 
			
		||||
          padding-bottom: max(var(--safe-area-inset-bottom), 16px);
 | 
			
		||||
          padding: 8px;
 | 
			
		||||
          padding-bottom: max(var(--safe-area-inset-bottom), 8px);
 | 
			
		||||
          background-color: var(--mdc-theme-surface, #fff);
 | 
			
		||||
        }
 | 
			
		||||
        .warning {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,13 @@
 | 
			
		||||
import { mdiDelete, mdiPlus } from "@mdi/js";
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
import { mdiDelete } from "@mdi/js";
 | 
			
		||||
import type { CSSResultGroup, TemplateResult } from "lit";
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
 | 
			
		||||
import "../../../../src/components/ha-form/ha-form";
 | 
			
		||||
import type { SchemaUnion } from "../../../../src/components/ha-form/types";
 | 
			
		||||
import "../../../../src/components/ha-icon-button";
 | 
			
		||||
import "../../../../src/components/ha-settings-row";
 | 
			
		||||
import "../../../../src/components/ha-svg-icon";
 | 
			
		||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
 | 
			
		||||
import {
 | 
			
		||||
  addHassioDockerRegistry,
 | 
			
		||||
@@ -85,19 +84,16 @@ class HassioRegistriesDialog extends LitElement {
 | 
			
		||||
                dialogInitialFocus
 | 
			
		||||
              ></ha-form>
 | 
			
		||||
              <div class="action">
 | 
			
		||||
                <ha-button
 | 
			
		||||
                <mwc-button
 | 
			
		||||
                  ?disabled=${Boolean(
 | 
			
		||||
                    !this._input.registry ||
 | 
			
		||||
                      !this._input.username ||
 | 
			
		||||
                      !this._input.password
 | 
			
		||||
                  )}
 | 
			
		||||
                  @click=${this._addNewRegistry}
 | 
			
		||||
                  appearance="filled"
 | 
			
		||||
                  size="small"
 | 
			
		||||
                >
 | 
			
		||||
                  <ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
 | 
			
		||||
                  ${this.supervisor.localize("dialog.registries.add_registry")}
 | 
			
		||||
                </ha-button>
 | 
			
		||||
                </mwc-button>
 | 
			
		||||
              </div>
 | 
			
		||||
            `
 | 
			
		||||
          : html`${this._registries?.length
 | 
			
		||||
@@ -130,17 +126,11 @@ class HassioRegistriesDialog extends LitElement {
 | 
			
		||||
                    </ha-alert>
 | 
			
		||||
                  `}
 | 
			
		||||
              <div class="action">
 | 
			
		||||
                <ha-button
 | 
			
		||||
                  @click=${this._addRegistry}
 | 
			
		||||
                  dialogInitialFocus
 | 
			
		||||
                  appearance="filled"
 | 
			
		||||
                  size="small"
 | 
			
		||||
                >
 | 
			
		||||
                  <ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
 | 
			
		||||
                <mwc-button @click=${this._addRegistry} dialogInitialFocus>
 | 
			
		||||
                  ${this.supervisor.localize(
 | 
			
		||||
                    "dialog.registries.add_new_registry"
 | 
			
		||||
                  )}
 | 
			
		||||
                </ha-button>
 | 
			
		||||
                </mwc-button>
 | 
			
		||||
              </div> `}
 | 
			
		||||
      </ha-dialog>
 | 
			
		||||
    `;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import { mdiDelete, mdiDeleteOff, mdiPlus } from "@mdi/js";
 | 
			
		||||
import "@material/mwc-button/mwc-button";
 | 
			
		||||
import { mdiDelete, mdiDeleteOff } from "@mdi/js";
 | 
			
		||||
import type { CSSResultGroup } from "lit";
 | 
			
		||||
import { css, html, LitElement, nothing } from "lit";
 | 
			
		||||
import { customElement, property, query, state } from "lit/decorators";
 | 
			
		||||
@@ -6,15 +7,10 @@ import memoizeOne from "memoize-one";
 | 
			
		||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
 | 
			
		||||
import { caseInsensitiveStringCompare } from "../../../../src/common/string/compare";
 | 
			
		||||
import "../../../../src/components/ha-alert";
 | 
			
		||||
import "../../../../src/components/ha-button";
 | 
			
		||||
import "../../../../src/components/ha-tooltip";
 | 
			
		||||
import "../../../../src/components/ha-spinner";
 | 
			
		||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
 | 
			
		||||
import "../../../../src/components/ha-icon-button";
 | 
			
		||||
import "../../../../src/components/ha-md-list";
 | 
			
		||||
import "../../../../src/components/ha-md-list-item";
 | 
			
		||||
import "../../../../src/components/ha-svg-icon";
 | 
			
		||||
import "../../../../src/components/ha-textfield";
 | 
			
		||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
 | 
			
		||||
import "../../../../src/components/ha-tooltip";
 | 
			
		||||
import type {
 | 
			
		||||
  HassioAddonInfo,
 | 
			
		||||
  HassioAddonRepository,
 | 
			
		||||
@@ -28,6 +24,10 @@ import {
 | 
			
		||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
 | 
			
		||||
import type { HomeAssistant } from "../../../../src/types";
 | 
			
		||||
import type { HassioRepositoryDialogParams } from "./show-dialog-repositories";
 | 
			
		||||
import type { HaTextField } from "../../../../src/components/ha-textfield";
 | 
			
		||||
import "../../../../src/components/ha-textfield";
 | 
			
		||||
import "../../../../src/components/ha-md-list";
 | 
			
		||||
import "../../../../src/components/ha-md-list-item";
 | 
			
		||||
 | 
			
		||||
@customElement("dialog-hassio-repositories")
 | 
			
		||||
class HassioRepositoriesDialog extends LitElement {
 | 
			
		||||
@@ -159,22 +159,18 @@ class HassioRepositoriesDialog extends LitElement {
 | 
			
		||||
              @keydown=${this._handleKeyAdd}
 | 
			
		||||
              dialogInitialFocus
 | 
			
		||||
            ></ha-textfield>
 | 
			
		||||
            <ha-button
 | 
			
		||||
              .loading=${this._processing}
 | 
			
		||||
              @click=${this._addRepository}
 | 
			
		||||
              appearance="filled"
 | 
			
		||||
              size="small"
 | 
			
		||||
            >
 | 
			
		||||
              <ha-svg-icon slot="start" .path=${mdiPlus}></ha-svg-icon>
 | 
			
		||||
              ${this._dialogParams!.supervisor.localize(
 | 
			
		||||
                "dialog.repositories.add"
 | 
			
		||||
              )}
 | 
			
		||||
            </ha-button>
 | 
			
		||||
            <mwc-button @click=${this._addRepository}>
 | 
			
		||||
              ${this._processing
 | 
			
		||||
                ? html`<ha-spinner size="small"></ha-spinner>`
 | 
			
		||||
                : this._dialogParams!.supervisor.localize(
 | 
			
		||||
                    "dialog.repositories.add"
 | 
			
		||||
                  )}
 | 
			
		||||
            </mwc-button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <ha-button slot="primaryAction" @click=${this.closeDialog}>
 | 
			
		||||
        <mwc-button slot="primaryAction" @click=${this.closeDialog}>
 | 
			
		||||
          ${this._dialogParams?.supervisor.localize("common.close")}
 | 
			
		||||
        </ha-button>
 | 
			
		||||
        </mwc-button>
 | 
			
		||||
      </ha-dialog>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
@@ -195,11 +191,16 @@ class HassioRepositoriesDialog extends LitElement {
 | 
			
		||||
          border-radius: 4px;
 | 
			
		||||
          margin-top: 4px;
 | 
			
		||||
        }
 | 
			
		||||
        ha-button {
 | 
			
		||||
        mwc-button {
 | 
			
		||||
          margin-left: 8px;
 | 
			
		||||
          margin-inline-start: 8px;
 | 
			
		||||
          margin-inline-end: initial;
 | 
			
		||||
        }
 | 
			
		||||
        ha-spinner {
 | 
			
		||||
          display: block;
 | 
			
		||||
          margin: 32px;
 | 
			
		||||
          text-align: center;
 | 
			
		||||
        }
 | 
			
		||||
        div.delete ha-icon-button {
 | 
			
		||||
          color: var(--error-color);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,10 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
 | 
			
		||||
import type { CSSResultGroup, TemplateResult } from "lit";
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
import { atLeastVersion } from "../../../src/common/config/version";
 | 
			
		||||
import "../../../src/components/buttons/ha-progress-button";
 | 
			
		||||
import "../../../src/components/ha-button";
 | 
			
		||||
import "../../../src/components/ha-button-menu";
 | 
			
		||||
import "../../../src/components/ha-card";
 | 
			
		||||
import "../../../src/components/ha-settings-row";
 | 
			
		||||
@@ -69,12 +70,12 @@ class HassioCoreInfo extends LitElement {
 | 
			
		||||
              ${!atLeastVersion(this.hass.config.version, 2021, 12) &&
 | 
			
		||||
              this.supervisor.core.update_available
 | 
			
		||||
                ? html`
 | 
			
		||||
                    <ha-button
 | 
			
		||||
                      appearance="plain"
 | 
			
		||||
                      href="/hassio/update-available/core"
 | 
			
		||||
                    >
 | 
			
		||||
                      ${this.supervisor.localize("common.show")}
 | 
			
		||||
                    </ha-button>
 | 
			
		||||
                    <a href="/hassio/update-available/core">
 | 
			
		||||
                      <mwc-button
 | 
			
		||||
                        .label=${this.supervisor.localize("common.show")}
 | 
			
		||||
                      >
 | 
			
		||||
                      </mwc-button>
 | 
			
		||||
                    </a>
 | 
			
		||||
                  `
 | 
			
		||||
                : ""}
 | 
			
		||||
            </ha-settings-row>
 | 
			
		||||
@@ -94,7 +95,7 @@ class HassioCoreInfo extends LitElement {
 | 
			
		||||
        <div class="card-actions">
 | 
			
		||||
          <ha-progress-button
 | 
			
		||||
            slot="primaryAction"
 | 
			
		||||
            variant="danger"
 | 
			
		||||
            class="warning"
 | 
			
		||||
            @click=${this._coreRestart}
 | 
			
		||||
            .title=${this.supervisor.localize("common.restart_name", {
 | 
			
		||||
              name: "Core",
 | 
			
		||||
@@ -187,6 +188,11 @@ class HassioCoreInfo extends LitElement {
 | 
			
		||||
          white-space: normal;
 | 
			
		||||
          color: var(--secondary-text-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .warning {
 | 
			
		||||
          --mdc-theme-primary: var(--error-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ha-button-menu {
 | 
			
		||||
          color: var(--secondary-text-color);
 | 
			
		||||
          --mdc-menu-min-width: 200px;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
 | 
			
		||||
import { mdiDotsVertical } from "@mdi/js";
 | 
			
		||||
import type { CSSResultGroup, TemplateResult } from "lit";
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
@@ -6,11 +8,10 @@ import memoizeOne from "memoize-one";
 | 
			
		||||
import { atLeastVersion } from "../../../src/common/config/version";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../src/components/buttons/ha-progress-button";
 | 
			
		||||
import "../../../src/components/ha-button";
 | 
			
		||||
import "../../../src/components/ha-button-menu";
 | 
			
		||||
import "../../../src/components/ha-card";
 | 
			
		||||
import "../../../src/components/ha-icon-button";
 | 
			
		||||
import "../../../src/components/ha-list-item";
 | 
			
		||||
import "../../../src/components/ha-icon-button";
 | 
			
		||||
import "../../../src/components/ha-settings-row";
 | 
			
		||||
import {
 | 
			
		||||
  extractApiErrorMessage,
 | 
			
		||||
@@ -76,28 +77,24 @@ class HassioHostInfo extends LitElement {
 | 
			
		||||
                  <span slot="description">
 | 
			
		||||
                    ${this.supervisor.host.hostname}
 | 
			
		||||
                  </span>
 | 
			
		||||
                  <ha-button
 | 
			
		||||
                  <mwc-button
 | 
			
		||||
                    .label=${this.supervisor.localize("system.host.change")}
 | 
			
		||||
                    @click=${this._changeHostnameClicked}
 | 
			
		||||
                    appearance="plain"
 | 
			
		||||
                    size="small"
 | 
			
		||||
                  >
 | 
			
		||||
                    ${this.supervisor.localize("system.host.change")}
 | 
			
		||||
                  </ha-button>
 | 
			
		||||
                  </mwc-button>
 | 
			
		||||
                </ha-settings-row>`
 | 
			
		||||
              : ""}
 | 
			
		||||
            ${this.supervisor.host.features.includes("network")
 | 
			
		||||
              ? html`<ha-settings-row>
 | 
			
		||||
              ? html` <ha-settings-row>
 | 
			
		||||
                  <span slot="heading">
 | 
			
		||||
                    ${this.supervisor.localize("system.host.ip_address")}
 | 
			
		||||
                  </span>
 | 
			
		||||
                  <span slot="description"> ${primaryIpAddress} </span>
 | 
			
		||||
                  <ha-button
 | 
			
		||||
                  <mwc-button
 | 
			
		||||
                    .label=${this.supervisor.localize("system.host.change")}
 | 
			
		||||
                    @click=${this._changeNetworkClicked}
 | 
			
		||||
                    appearance="plain"
 | 
			
		||||
                    size="small"
 | 
			
		||||
                  >
 | 
			
		||||
                    ${this.supervisor.localize("system.host.change")}
 | 
			
		||||
                  </ha-button>
 | 
			
		||||
                  </mwc-button>
 | 
			
		||||
                </ha-settings-row>`
 | 
			
		||||
              : ""}
 | 
			
		||||
 | 
			
		||||
@@ -111,13 +108,12 @@ class HassioHostInfo extends LitElement {
 | 
			
		||||
              ${!atLeastVersion(this.hass.config.version, 2021, 12) &&
 | 
			
		||||
              this.supervisor.os.update_available
 | 
			
		||||
                ? html`
 | 
			
		||||
                    <ha-button
 | 
			
		||||
                      appearance="plain"
 | 
			
		||||
                      size="small"
 | 
			
		||||
                      href="/hassio/update-available/os"
 | 
			
		||||
                    >
 | 
			
		||||
                      ${this.supervisor.localize("common.show")}
 | 
			
		||||
                    </ha-button>
 | 
			
		||||
                    <a href="/hassio/update-available/os">
 | 
			
		||||
                      <mwc-button
 | 
			
		||||
                        .label=${this.supervisor.localize("common.show")}
 | 
			
		||||
                      >
 | 
			
		||||
                      </mwc-button>
 | 
			
		||||
                    </a>
 | 
			
		||||
                  `
 | 
			
		||||
                : ""}
 | 
			
		||||
            </ha-settings-row>
 | 
			
		||||
@@ -143,12 +139,16 @@ class HassioHostInfo extends LitElement {
 | 
			
		||||
              : ""}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div>
 | 
			
		||||
            ${this.supervisor.host.disk_life_time !== null
 | 
			
		||||
            ${this.supervisor.host.disk_life_time !== "" &&
 | 
			
		||||
            this.supervisor.host.disk_life_time >= 10
 | 
			
		||||
              ? html` <ha-settings-row>
 | 
			
		||||
                  <span slot="heading">
 | 
			
		||||
                    ${this.supervisor.localize("system.host.lifetime_used")}
 | 
			
		||||
                    ${this.supervisor.localize(
 | 
			
		||||
                      "system.host.emmc_lifetime_used"
 | 
			
		||||
                    )}
 | 
			
		||||
                  </span>
 | 
			
		||||
                  <span slot="description">
 | 
			
		||||
                    ${this.supervisor.host.disk_life_time - 10} % -
 | 
			
		||||
                    ${this.supervisor.host.disk_life_time} %
 | 
			
		||||
                  </span>
 | 
			
		||||
                </ha-settings-row>`
 | 
			
		||||
@@ -167,7 +167,7 @@ class HassioHostInfo extends LitElement {
 | 
			
		||||
        <div class="card-actions">
 | 
			
		||||
          ${this.supervisor.host.features.includes("reboot")
 | 
			
		||||
            ? html`
 | 
			
		||||
                <ha-progress-button variant="danger" @click=${this._hostReboot}>
 | 
			
		||||
                <ha-progress-button class="warning" @click=${this._hostReboot}>
 | 
			
		||||
                  ${this.supervisor.localize("system.host.reboot_host")}
 | 
			
		||||
                </ha-progress-button>
 | 
			
		||||
              `
 | 
			
		||||
@@ -175,7 +175,7 @@ class HassioHostInfo extends LitElement {
 | 
			
		||||
          ${this.supervisor.host.features.includes("shutdown")
 | 
			
		||||
            ? html`
 | 
			
		||||
                <ha-progress-button
 | 
			
		||||
                  variant="danger"
 | 
			
		||||
                  class="warning"
 | 
			
		||||
                  @click=${this._hostShutdown}
 | 
			
		||||
                >
 | 
			
		||||
                  ${this.supervisor.localize("system.host.shutdown_host")}
 | 
			
		||||
@@ -431,6 +431,10 @@ class HassioHostInfo extends LitElement {
 | 
			
		||||
          color: var(--secondary-text-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .warning {
 | 
			
		||||
          --mdc-theme-primary: var(--error-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ha-button-menu {
 | 
			
		||||
          color: var(--secondary-text-color);
 | 
			
		||||
          --mdc-menu-min-width: 200px;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ import { atLeastVersion } from "../../../src/common/config/version";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
import "../../../src/components/buttons/ha-progress-button";
 | 
			
		||||
import "../../../src/components/ha-alert";
 | 
			
		||||
import "../../../src/components/ha-button";
 | 
			
		||||
import "../../../src/components/ha-card";
 | 
			
		||||
import "../../../src/components/ha-settings-row";
 | 
			
		||||
import "../../../src/components/ha-switch";
 | 
			
		||||
@@ -81,13 +80,12 @@ class HassioSupervisorInfo extends LitElement {
 | 
			
		||||
              ${!atLeastVersion(this.hass.config.version, 2021, 12) &&
 | 
			
		||||
              this.supervisor.supervisor.update_available
 | 
			
		||||
                ? html`
 | 
			
		||||
                    <ha-button
 | 
			
		||||
                      appearance="plain"
 | 
			
		||||
                      size="small"
 | 
			
		||||
                      href="/hassio/update-available/supervisor"
 | 
			
		||||
                    >
 | 
			
		||||
                      ${this.supervisor.localize("common.show")}
 | 
			
		||||
                    </ha-button>
 | 
			
		||||
                    <a href="/hassio/update-available/supervisor">
 | 
			
		||||
                      <mwc-button
 | 
			
		||||
                        .label=${this.supervisor.localize("common.show")}
 | 
			
		||||
                      >
 | 
			
		||||
                      </mwc-button>
 | 
			
		||||
                    </a>
 | 
			
		||||
                  `
 | 
			
		||||
                : ""}
 | 
			
		||||
            </ha-settings-row>
 | 
			
		||||
@@ -158,28 +156,24 @@ class HassioSupervisorInfo extends LitElement {
 | 
			
		||||
                  ${this.supervisor.localize(
 | 
			
		||||
                    "system.supervisor.unsupported_title"
 | 
			
		||||
                  )}
 | 
			
		||||
                  <ha-button
 | 
			
		||||
                  <mwc-button
 | 
			
		||||
                    slot="action"
 | 
			
		||||
                    .label=${this.supervisor.localize("common.learn_more")}
 | 
			
		||||
                    @click=${this._unsupportedDialog}
 | 
			
		||||
                    variant="warning"
 | 
			
		||||
                    size="small"
 | 
			
		||||
                  >
 | 
			
		||||
                    ${this.supervisor.localize("common.learn_more")}
 | 
			
		||||
                  </ha-button>
 | 
			
		||||
                  </mwc-button>
 | 
			
		||||
                </ha-alert>`}
 | 
			
		||||
            ${!this.supervisor.supervisor.healthy
 | 
			
		||||
              ? html`<ha-alert alert-type="error">
 | 
			
		||||
                  ${this.supervisor.localize(
 | 
			
		||||
                    "system.supervisor.unhealthy_title"
 | 
			
		||||
                  )}
 | 
			
		||||
                  <ha-button
 | 
			
		||||
                    variant="danger"
 | 
			
		||||
                    size="small"
 | 
			
		||||
                  <mwc-button
 | 
			
		||||
                    slot="action"
 | 
			
		||||
                    .label=${this.supervisor.localize("common.learn_more")}
 | 
			
		||||
                    @click=${this._unhealthyDialog}
 | 
			
		||||
                  >
 | 
			
		||||
                    ${this.supervisor.localize("common.learn_more")}
 | 
			
		||||
                  </ha-button>
 | 
			
		||||
                  </mwc-button>
 | 
			
		||||
                </ha-alert>`
 | 
			
		||||
              : ""}
 | 
			
		||||
          </div>
 | 
			
		||||
@@ -454,6 +448,9 @@ class HassioSupervisorInfo extends LitElement {
 | 
			
		||||
          white-space: normal;
 | 
			
		||||
          color: var(--secondary-text-color);
 | 
			
		||||
        }
 | 
			
		||||
        ha-alert mwc-button {
 | 
			
		||||
          --mdc-theme-primary: var(--primary-text-color);
 | 
			
		||||
        }
 | 
			
		||||
        a {
 | 
			
		||||
          text-decoration: none;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
import "@material/mwc-button";
 | 
			
		||||
 | 
			
		||||
import type { CSSResultGroup, TemplateResult } from "lit";
 | 
			
		||||
import { css, html, LitElement } from "lit";
 | 
			
		||||
import { customElement, property, state } from "lit/decorators";
 | 
			
		||||
 
 | 
			
		||||
@@ -208,16 +208,14 @@ class UpdateAvailableCard extends LitElement {
 | 
			
		||||
              <div class="card-actions">
 | 
			
		||||
                ${changelog
 | 
			
		||||
                  ? html`
 | 
			
		||||
                      <ha-button
 | 
			
		||||
                        href=${changelog}
 | 
			
		||||
                        target="_blank"
 | 
			
		||||
                        rel="noreferrer"
 | 
			
		||||
                        appearance="plain"
 | 
			
		||||
                      >
 | 
			
		||||
                        ${this.supervisor.localize(
 | 
			
		||||
                          "update_available.open_release_notes"
 | 
			
		||||
                        )}
 | 
			
		||||
                      </ha-button>
 | 
			
		||||
                      <a href=${changelog} target="_blank" rel="noreferrer">
 | 
			
		||||
                        <ha-button
 | 
			
		||||
                          .label=${this.supervisor.localize(
 | 
			
		||||
                            "update_available.open_release_notes"
 | 
			
		||||
                          )}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ha-button>
 | 
			
		||||
                      </a>
 | 
			
		||||
                    `
 | 
			
		||||
                  : nothing}
 | 
			
		||||
                <span></span>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp build-landing-page
 | 
			
		||||
yarn run-task build-landing-page
 | 
			
		||||
 
 | 
			
		||||
@@ -6,4 +6,4 @@ set -e
 | 
			
		||||
 | 
			
		||||
cd "$(dirname "$0")/../.."
 | 
			
		||||
 | 
			
		||||
./node_modules/.bin/gulp develop-landing-page
 | 
			
		||||
yarn run-task develop-landing-page
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,29 @@
 | 
			
		||||
import "@material/mwc-linear-progress/mwc-linear-progress";
 | 
			
		||||
import { mdiArrowCollapseDown, mdiDownload } from "@mdi/js";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line import/extensions
 | 
			
		||||
import { IntersectionController } from "@lit-labs/observers/intersection-controller.js";
 | 
			
		||||
import { LitElement, type PropertyValues, css, html, nothing } from "lit";
 | 
			
		||||
import { customElement, property, query, state } from "lit/decorators";
 | 
			
		||||
import { classMap } from "lit/directives/class-map";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
import { customElement, property, query, state } from "lit/decorators";
 | 
			
		||||
import type {
 | 
			
		||||
  LandingPageKeys,
 | 
			
		||||
  LocalizeFunc,
 | 
			
		||||
} from "../../../src/common/translations/localize";
 | 
			
		||||
import { waitForSeconds } from "../../../src/common/util/wait";
 | 
			
		||||
import "../../../src/components/ha-alert";
 | 
			
		||||
import "../../../src/components/ha-ansi-to-html";
 | 
			
		||||
import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html";
 | 
			
		||||
import "../../../src/components/ha-button";
 | 
			
		||||
import "../../../src/components/ha-icon-button";
 | 
			
		||||
import "../../../src/components/ha-svg-icon";
 | 
			
		||||
import { fileDownload } from "../../../src/util/file_download";
 | 
			
		||||
import "../../../src/components/ha-ansi-to-html";
 | 
			
		||||
import "../../../src/components/ha-alert";
 | 
			
		||||
import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html";
 | 
			
		||||
import {
 | 
			
		||||
  getObserverLogs,
 | 
			
		||||
  downloadUrl as observerLogsDownloadUrl,
 | 
			
		||||
} from "../data/observer";
 | 
			
		||||
import { fireEvent } from "../../../src/common/dom/fire_event";
 | 
			
		||||
import { fileDownload } from "../../../src/util/file_download";
 | 
			
		||||
import { getSupervisorLogs, getSupervisorLogsFollow } from "../data/supervisor";
 | 
			
		||||
import { waitForSeconds } from "../../../src/common/util/wait";
 | 
			
		||||
import { ASSUME_CORE_START_SECONDS } from "../ha-landing-page";
 | 
			
		||||
 | 
			
		||||
const ERROR_CHECK = /^[\d\s-:]+(ERROR|CRITICAL)(.*)/gm;
 | 
			
		||||
@@ -64,7 +65,7 @@ class LandingPageLogs extends LitElement {
 | 
			
		||||
  protected render() {
 | 
			
		||||
    return html`
 | 
			
		||||
      <div class="actions">
 | 
			
		||||
        <ha-button appearance="plain" @click=${this._toggleLogDetails}>
 | 
			
		||||
        <ha-button @click=${this._toggleLogDetails}>
 | 
			
		||||
          ${this.localize(this._show ? "hide_details" : "show_details")}
 | 
			
		||||
        </ha-button>
 | 
			
		||||
        ${this._show
 | 
			
		||||
@@ -81,11 +82,7 @@ class LandingPageLogs extends LitElement {
 | 
			
		||||
              alert-type="error"
 | 
			
		||||
              .title=${this.localize("logs.fetch_error")}
 | 
			
		||||
            >
 | 
			
		||||
              <ha-button
 | 
			
		||||
                size="small"
 | 
			
		||||
                variant="danger"
 | 
			
		||||
                @click=${this._startLogStream}
 | 
			
		||||
              >
 | 
			
		||||
              <ha-button @click=${this._startLogStream}>
 | 
			
		||||
                ${this.localize("logs.retry")}
 | 
			
		||||
              </ha-button>
 | 
			
		||||
            </ha-alert>
 | 
			
		||||
@@ -108,13 +105,14 @@ class LandingPageLogs extends LitElement {
 | 
			
		||||
              !this._scrolledToBottomController.value) ||
 | 
			
		||||
            false,
 | 
			
		||||
        })}"
 | 
			
		||||
        size="small"
 | 
			
		||||
        appearance="filled"
 | 
			
		||||
        @click=${this._scrollToBottom}
 | 
			
		||||
      >
 | 
			
		||||
        <ha-svg-icon .path=${mdiArrowCollapseDown} slot="start"></ha-svg-icon>
 | 
			
		||||
        <ha-svg-icon .path=${mdiArrowCollapseDown} slot="icon"></ha-svg-icon>
 | 
			
		||||
        ${this.localize("logs.scroll_down_button")}
 | 
			
		||||
        <ha-svg-icon .path=${mdiArrowCollapseDown} slot="end"></ha-svg-icon>
 | 
			
		||||
        <ha-svg-icon
 | 
			
		||||
          .path=${mdiArrowCollapseDown}
 | 
			
		||||
          slot="trailingIcon"
 | 
			
		||||
        ></ha-svg-icon>
 | 
			
		||||
      </ha-button>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
@@ -311,14 +309,21 @@ class LandingPageLogs extends LitElement {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .new-logs-indicator {
 | 
			
		||||
        --mdc-theme-primary: var(--text-primary-color);
 | 
			
		||||
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        bottom: 4px;
 | 
			
		||||
        left: 4px;
 | 
			
		||||
        bottom: 0;
 | 
			
		||||
        left: 0;
 | 
			
		||||
        right: 0;
 | 
			
		||||
        height: 0;
 | 
			
		||||
        background-color: var(--primary-color);
 | 
			
		||||
        border-radius: 8px;
 | 
			
		||||
 | 
			
		||||
        transition: height 0.4s ease-out;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        justify-content: space-between;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .new-logs-indicator.visible {
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user