Compare commits

..

2 Commits

Author SHA1 Message Date
Ludeeus
3703ffc42d Remove data/supervisor/root 2022-03-04 09:47:21 +00:00
Ludeeus
9ea8e13c87 Use the update integration to provide updates 2022-03-04 09:45:20 +00:00
1479 changed files with 241919 additions and 94503 deletions

View File

@@ -1,21 +0,0 @@
[modern]
# Support for dynamic import is the main litmus test for serving modern builds.
# Although officially a ES2020 feature, browsers implemented it early, so this
# enables all of ES2017 and some features in ES2018.
supports es6-module-dynamic-import
# Exclude Safari 11-12 because of a bug in tagged template literals
# https://bugs.webkit.org/show_bug.cgi?id=190756
# Note: Dropping version 11 also enables several more ES2018 features
not Safari < 13
not iOS < 13
# Exclude unsupported browsers
not dead
[legacy]
# Legacy builds are transpiled to ES5 (strict mode) but also must support some features that cannot be polyfilled:
# - web sockets to communicate with backend
# - inline SVG used widely in buttons, widgets, etc.
# - custom events used for most user interactions
supports use-strict and supports websockets and supports svg-html5 and supports customevent

View File

@@ -1,5 +1,5 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.10
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9
ENV \
DEBIAN_FRONTEND=noninteractive \

View File

@@ -5,39 +5,33 @@
"context": ".."
},
"appPort": "8124:8123",
"context": "..",
"postCreateCommand": "script/bootstrap",
"extensions": [
"github.vscode-pull-request-github",
"dbaeumer.vscode-eslint",
"ms-vscode.vscode-typescript-tslint-plugin",
"esbenp.prettier-vscode",
"bierner.lit-html",
"runem.lit-plugin",
"ms-python.vscode-pylance"
],
"containerEnv": {
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
},
"customizations": {
"vscode": {
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"runem.lit-plugin",
"github.vscode-pull-request-github",
"eamodio.gitlens"
],
"settings": {
"files.eol": "\n",
"editor.tabSize": 2,
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"editor.renderWhitespace": "boundary",
"editor.rulers": [80],
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.trimTrailingWhitespace": true,
"terminal.integrated.shell.linux": "/usr/bin/zsh",
"gitlens.showWelcomeOnInstall": false,
"gitlens.showWhatsNewAfterUpgrades": false,
"workbench.startupEditor": "none"
}
}
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"files.eol": "\n",
"editor.tabSize": 2,
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.trimTrailingWhitespace": true
}
}

View File

@@ -5,7 +5,6 @@
"plugin:@typescript-eslint/recommended",
"plugin:wc/recommended",
"plugin:lit/all",
"plugin:lit-a11y/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
@@ -20,7 +19,7 @@
"settings": {
"import/resolver": {
"webpack": {
"config": "./webpack.config.cjs"
"config": "./webpack.config.js"
}
}
},
@@ -66,10 +65,7 @@
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"js": "never"
}
{ "ts": "never", "js": "never" }
],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": "off",
@@ -116,14 +112,7 @@
],
"unused-imports/no-unused-imports": "error",
"lit/attribute-value-entities": "off",
"lit/no-template-map": "off",
"lit/no-native-attributes": "warn",
"lit/no-this-assign-in-render": "warn",
"lit-a11y/click-events-have-key-events": ["off"],
"lit-a11y/no-autofocus": "off",
"lit-a11y/alt-text": "warn",
"lit-a11y/anchor-is-valid": "warn",
"lit-a11y/role-has-required-aria-attrs": "warn"
"lit/no-template-map": "off"
},
"plugins": ["disable", "unused-imports"],
"processor": "disable/disable"

View File

@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
<!--
Provide details about the versions you are using, which helps us reproducing
and finding the issue quicker. Version information is found in the
Home Assistant frontend: Settings -> About.
Home Assistant frontend: Configuration -> Info.
Browser version and operating system is important! Please try to replicate
your issue in a different browser and be sure to include your findings.

View File

@@ -1,4 +1,4 @@
name: Report a bug with the UI / Dashboards
name: Report a bug with the UI, Frontend or Lovelace
description: Report an issue related to the Home Assistant frontend.
labels: bug
body:
@@ -9,7 +9,7 @@ body:
If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue.
**Please not not report issues for custom cards.**
**Please not not report issues for custom Lovelace cards.**
[fr]: https://github.com/home-assistant/frontend/discussions
[releases]: https://github.com/home-assistant/home-assistant/releases
@@ -64,7 +64,7 @@ body:
label: What version of Home Assistant Core has the issue?
placeholder: core-
description: >
Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).
Can be found in the Configuration panel -> Info.
- type: input
attributes:
label: What was the last working version of Home Assistant Core?

View File

@@ -1,17 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: Request a feature for the UI / Dashboards
- name: Request a feature for the UI, Frontend or Lovelace
url: https://github.com/home-assistant/frontend/discussions/category_choices
about: Request an new feature for the Home Assistant frontend.
- name: Report a bug that is NOT related to the UI / Dashboards
- name: Report a bug that is NOT related to the UI, Frontend or Lovelace
url: https://github.com/home-assistant/core/issues
about: This is the issue tracker for our frontend. Please report other issues in the backend ("core") repository.
about: This is the issue tracker for our frontend. Please report other issues with the backend repository.
- name: Report incorrect or missing information on our website
url: https://github.com/home-assistant/home-assistant.io/issues
about: Our documentation has its own issue tracker. Please report issues with the website there.
- name: I have a question or need support
url: https://www.home-assistant.io/help
about: We use GitHub for tracking bugs. Check our website for resources on getting help.
about: We use GitHub for tracking bugs, check our website for resources on getting help.
- name: I'm unsure where to go
url: https://www.home-assistant.io/join-chat
about: If you are unsure where to go, then joining our chat is recommended; Just ask!

View File

@@ -1,8 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: weekly
time: "06:00"
open-pull-requests-limit: 10

View File

@@ -1,86 +0,0 @@
name: Cast deployment
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
push:
branches:
- master
env:
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
jobs:
deploy_dev:
runs-on: ubuntu-latest
name: Deploy Development
if: github.event_name != 'push'
environment:
name: Cast Development
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.5.2
with:
ref: dev
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Build Cast
run: ./node_modules/.bin/gulp build-cast
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to Netlify
id: deploy
uses: netlify/actions/cli@master
with:
args: deploy --dir=cast/dist --alias dev
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
deploy_master:
runs-on: ubuntu-latest
name: Deploy Production
if: github.event_name == 'push'
environment:
name: Cast Production
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.5.2
with:
ref: master
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Build Cast
run: ./node_modules/.bin/gulp build-cast
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to Netlify
id: deploy
uses: netlify/actions/cli@master
with:
args: deploy --dir=cast/dist --prod
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}

View File

@@ -11,87 +11,86 @@ on:
- master
env:
NODE_VERSION: 16
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint and check format
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.5.2
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Check for duplicate dependencies
run: yarn dedupe --check
run: yarn install
env:
CI: true
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
- name: Run eslint
run: yarn run lint:eslint --quiet
run: yarn run lint:eslint
- name: Run tsc
run: yarn run lint:types
- name: Run prettier
run: yarn run lint:prettier
- name: Check for duplicate dependencies
run: yarn dedupe --check
test:
name: Run tests
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.5.2
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
run: yarn install
env:
CI: true
- name: Build resources
run: ./node_modules/.bin/gulp build-translations build-locale-data
- name: Run Tests
run: yarn run test
build:
name: Build frontend
needs: [lint, test]
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.5.2
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
run: yarn install
env:
CI: true
- name: Build Application
run: ./node_modules/.bin/gulp build-app
env:
IS_TEST: "true"
supervisor:
name: Build supervisor
needs: [lint, test]
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.5.2
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
run: yarn install
env:
CI: true
- name: Build Application
run: ./node_modules/.bin/gulp build-hassio
env:

View File

@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3.5.2
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
@@ -36,14 +36,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -57,4 +57,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v1

35
.github/workflows/demo.yaml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Demo
on:
push:
branches:
- dev
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
env:
CI: true
- name: Build Demo
run: ./node_modules/.bin/gulp build-demo
- name: Deploy to Netlify
uses: netlify/actions/cli@master
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
with:
args: deploy --dir=demo/dist --prod

View File

@@ -1,87 +0,0 @@
name: Demo deployment
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
push:
branches:
- dev
- master
env:
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
jobs:
deploy_dev:
runs-on: ubuntu-latest
name: Demo Development
if: github.event_name != 'push' || github.ref_name != 'master'
environment:
name: Demo Development
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.5.2
with:
ref: dev
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Build Demo
run: ./node_modules/.bin/gulp build-demo
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to Netlify
id: deploy
uses: netlify/actions/cli@master
with:
args: deploy --dir=demo/dist --prod
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
deploy_master:
runs-on: ubuntu-latest
name: Demo Production
if: github.event_name == 'push' && github.ref_name == 'master'
environment:
name: Demo Production
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.5.2
with:
ref: master
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Build Demo
run: ./node_modules/.bin/gulp build-demo
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to Netlify
id: deploy
uses: netlify/actions/cli@master
with:
args: deploy --dir=demo/dist --prod
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }}

View File

@@ -1,43 +0,0 @@
name: Design deployment
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
env:
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: Design
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3.5.2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Build Gallery
run: ./node_modules/.bin/gulp build-gallery
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to Netlify
id: deploy
uses: netlify/actions/cli@master
with:
args: deploy --dir=gallery/dist --prod
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}

View File

@@ -1,52 +0,0 @@
name: Design preview
on:
pull_request:
types:
- opened
- synchronize
- reopened
- labeled
branches:
- dev
env:
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
jobs:
preview:
runs-on: ubuntu-latest
# Skip running on forks since it won't have access to secrets
# Skip running PRs without 'needs design preview' label
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@v3.5.2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install --immutable
- name: Build Gallery
run: ./node_modules/.bin/gulp build-gallery
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy preview to Netlify
id: deploy
uses: netlify/actions/cli@master
with:
args: deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}"
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
- name: Generate summary
run: |
echo "${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}" >> "$GITHUB_STEP_SUMMARY"

View File

@@ -9,7 +9,7 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4.0.0
- uses: dessant/lock-threads@v2.0.1
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: "30"

19
.github/workflows/netflify.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Netlify
on:
schedule:
- cron: "0 0 * * *"
jobs:
trigger_builds:
name: Trigger netlify build preview
runs-on: "ubuntu-latest"
steps:
- name: Trigger Cast build
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_CAST_DEV_BUILD_HOOK }}
- name: Trigger Demo build
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }}
- name: Trigger Design build
run: curl -X POST -d "NIGHTLY" https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }}

View File

@@ -1,72 +0,0 @@
name: Nightly
on:
workflow_dispatch:
schedule:
- cron: "0 1 * * *"
env:
PYTHON_VERSION: "3.10"
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144
permissions:
actions: none
jobs:
nightly:
name: Nightly
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.2
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
- name: Download translations
run: ./script/translations_download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Bump version
run: script/version_bump.cjs nightly
- name: Build nightly Python wheels
run: |
pip install build
yarn install
export SKIP_FETCH_NIGHTLY_TRANSLATIONS=1
script/build_frontend
rm -rf dist home_assistant_frontend.egg-info
python3 -m build
- name: Archive translations
run: tar -czvf translations.tar.gz translations
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist/home_assistant_frontend*.whl
if-no-files-found: error
- name: Upload translations
uses: actions/upload-artifact@v3
with:
name: translations
path: translations.tar.gz
if-no-files-found: error

View File

@@ -6,36 +6,28 @@ on:
- published
env:
PYTHON_VERSION: "3.10"
NODE_VERSION: 16
PYTHON_VERSION: 3.8
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions
# All scopes not mentioned here are set to no access
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
permissions:
actions: none
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.2
uses: actions/checkout@v2
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
uses: actions/setup-python@v2
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
@@ -52,15 +44,8 @@ jobs:
python3 -m pip install twine build
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
export SKIP_FETCH_NIGHTLY_TRANSLATIONS=1
script/release
- name: Upload release assets
uses: softprops/action-gh-release@v0.1.15
with:
files: |
dist/*.whl
dist/*.tar.gz
script/release
wheels-init:
name: Init wheels build
@@ -74,11 +59,33 @@ jobs:
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels
uses: home-assistant/wheels@2023.04.0
- name: Upload requirements.txt
uses: actions/upload-artifact@v2
with:
abi: cp310
tag: musllinux_1_2
arch: amd64
name: requirements
path: ./requirements.txt
build-wheels:
name: Build wheels for ${{ matrix.arch }}
needs: wheels-init
runs-on: ubuntu-latest
strategy:
matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.9-alpine3.14"
steps:
- name: Download requirements.txt
uses: actions/download-artifact@v2
with:
name: requirements
- name: Build wheels
uses: home-assistant/wheels@master
with:
tag: ${{ matrix.tag }}
arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-user: wheels
requirements: "requirements.txt"

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 90 days stale policy
uses: actions/stale@v8.0.0
uses: actions/stale@v3.0.13
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90

View File

@@ -8,7 +8,7 @@ on:
- src/translations/en.json
env:
NODE_VERSION: 16
NODE_VERSION: 14
jobs:
upload:
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3.5.2
uses: actions/checkout@v2
- name: Upload Translations
run: |

13
.gitignore vendored
View File

@@ -2,10 +2,9 @@
.reify-cache
# build
build/
dist/
/hass_frontend/
/translations/
build
hass_frontend/*
dist
# yarn
.yarn/*
@@ -15,7 +14,7 @@ dist/
!.yarn/sdks
!.yarn/versions
.pnp.*
/node_modules/
node_modules/*
yarn-error.log
npm-debug.log
@@ -27,7 +26,7 @@ npm-debug.log
# venv stuff
pyvenv.cfg
pip-selfcheck.json
/venv/
venv/*
.venv
# vscode
@@ -46,4 +45,4 @@ src/cast/dev_const.ts
.tool-versions
# Home Assistant config
/config/
/config

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
yarn run lint-staged --relative --shell "/bin/bash"

2
.nvmrc
View File

@@ -1 +1 @@
16
14

View File

@@ -2,8 +2,7 @@
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"runem.lit-plugin",
"github.vscode-pull-request-github",
"eamodio.gitlens"
"bierner.lit-html",
"runem.lit-plugin"
]
}

10
.vscode/tasks.json vendored
View File

@@ -181,7 +181,7 @@
{
"label": "Run HA Core for Supervisor in devcontainer",
"type": "shell",
"command": "SUPERVISOR=${input:supervisorHost} SUPERVISOR_TOKEN=${input:supervisorToken} script/core",
"command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core",
"isBackground": true,
"group": {
"kind": "build",
@@ -191,13 +191,7 @@
"runOptions": {
"instanceLimit": 1
}
},
{
"label": "Setup and fetch nightly translations",
"type": "gulp",
"task": "setup-and-fetch-nightly-translations",
"problemMatcher": []
}
}
],
"inputs": [
{

View File

@@ -0,0 +1,29 @@
diff --git a/polyfillLoaders/EventTarget.js b/polyfillLoaders/EventTarget.js
index 4e18ade7ba485849f17f28c94c42f0e0e01ac387..8f34f4f646c7f7becc208fb5a546c96034fc74dc 100644
--- a/polyfillLoaders/EventTarget.js
+++ b/polyfillLoaders/EventTarget.js
@@ -6,16 +6,15 @@
let _ET;
let ET;
export default async function EventTarget() {
- return ET || init();
+ return ET || init();
}
async function init() {
- _ET = window.EventTarget;
- try {
- new _ET();
- }
- catch (_a) {
- _ET = (await import('event-target-shim')).EventTarget;
- }
- return (ET = _ET);
+ _ET = window.EventTarget;
+ try {
+ new _ET();
+ } catch (_a) {
+ _ET = (await import("event-target-shim")).default.EventTarget;
+ }
+ return (ET = _ET);
}
//# sourceMappingURL=EventTarget.js.map

View File

@@ -0,0 +1,12 @@
diff --git a/mwc-icon-button-base.js b/mwc-icon-button-base.js
index 45cdaab93ccc0a6daaaaabc01266dcdc32e46bfd..b3ea5b541597308d85f86ce6c23fd00785fda835 100644
--- a/mwc-icon-button-base.js
+++ b/mwc-icon-button-base.js
@@ -63,7 +63,6 @@ export class IconButtonBase extends LitElement {
@touchend="${this.handleRippleDeactivate}"
@touchcancel="${this.handleRippleDeactivate}"
>${this.renderRipple()}
- <i class="material-icons">${this.icon}</i>
<span
><slot></slot
></span>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

785
.yarn/releases/yarn-3.2.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,3 @@
defaultSemverRangePrefix: ""
nodeLinker: node-modules
plugins:
@@ -8,4 +6,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.5.0.cjs
yarnPath: .yarn/releases/yarn-3.2.0.cjs

7
build-scripts/.eslintrc Normal file
View File

@@ -0,0 +1,7 @@
{
"rules": {
"import/no-extraneous-dependencies": 0,
"no-restricted-syntax": 0,
"no-console": 0
}
}

View File

@@ -1,12 +1,7 @@
{
"extends": "../.eslintrc.json",
"rules": {
"no-console": "off",
"import/no-extraneous-dependencies": "off",
"import/extensions": "off",
"import/no-dynamic-require": "off",
"global-require": "off",
"@typescript-eslint/no-var-requires": "off",
"prefer-arrow-callback": "off"
"import/no-extraneous-dependencies": 0,
"global-require": 0
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
@@ -28,6 +29,7 @@ module.exports = function inlineConstants(babel, options, cwd) {
const absolute = module.startsWith(".")
? require.resolve(module, { paths: [cwd] })
: module;
// eslint-disable-next-line import/no-dynamic-require
return [absolute, require(absolute)];
})
);

View File

@@ -1,18 +1,9 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const env = require("./env.cjs");
const paths = require("./paths.cjs");
// 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")
? process.env.GITHUB_SHA || "dev"
: env.version();
return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}`;
};
const env = require("./env.js");
const paths = require("./paths.js");
// Files from NPM Packages that should not be imported
// eslint-disable-next-line unused-imports/no-unused-vars
module.exports.ignorePackages = ({ latestBuild }) => [
// Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"),
@@ -62,86 +53,60 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
...defineOverlay,
});
module.exports.htmlMinifierOptions = {
caseSensitive: true,
collapseWhitespace: true,
conservativeCollapse: true,
decodeEntities: true,
removeComments: true,
removeRedundantAttributes: true,
minifyCSS: {
compatibility: "*,-properties.zeroUnits",
},
};
module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
module.exports.terserOptions = (latestBuild) => ({
safari10: !latestBuild,
ecma: latestBuild ? undefined : 5,
format: { comments: false },
sourceMap: !isTestBuild,
output: { comments: false },
});
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
module.exports.babelOptions = ({ latestBuild }) => ({
babelrc: false,
compact: false,
assumptions: {
privateFieldsAsProperties: true,
setPublicClassFields: true,
setSpreadProperties: true,
},
browserslistEnv: latestBuild ? "modern" : "legacy",
presets: [
[
!latestBuild && [
"@babel/preset-env",
{
useBuiltIns: latestBuild ? false : "entry",
corejs: latestBuild ? false : { version: "3.30", proposals: true },
useBuiltIns: "entry",
corejs: "3.15",
bugfixes: true,
},
],
"@babel/preset-typescript",
],
].filter(Boolean),
plugins: [
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/inline-constants-plugin.cjs"
"build-scripts/babel-plugins/inline-constants-plugin.js"
),
{
modules: ["@mdi/js"],
ignoreModuleNotFound: true,
},
],
// Support some proposals still in TC39 process
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
// Minify template literals for production
isProdBuild && [
"template-html-minifier",
{
modules: {
lit: [
"html",
{ name: "svg", encapsulation: "svg" },
{ name: "css", encapsulation: "style" },
],
"@polymer/polymer/lib/utils/html-tag": ["html"],
},
strictCSS: true,
htmlMinifier: module.exports.htmlMinifierOptions,
failOnError: true, // we can turn this off in case of false positives
},
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
!latestBuild && [
"@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true },
],
// Only support the syntax, Webpack will handle it.
"@babel/plugin-syntax-import-meta",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-top-level-await",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
].filter(Boolean),
exclude: [
// \\ for Windows, / for Mac OS and Linux
/node_modules[\\/]core-js/,
/node_modules[\\/]webpack[\\/]buildin/,
],
sourceMaps: !isTestBuild,
});
const nameSuffix = (latestBuild) => (latestBuild ? "-latest" : "-es5");
const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
@@ -164,17 +129,14 @@ BundleConfig {
latestBuild: boolean,
// If we're doing a stats build (create nice chunk names)
isStatsBuild: boolean,
// If it's just a test build in CI, skip time on source map generation
isTestBuild: boolean,
// Names of entrypoints that should not be hashed
dontHash: Set<string>
}
*/
module.exports.config = {
app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
app({ isProdBuild, latestBuild, isStatsBuild, isWDS }) {
return {
name: "app" + nameSuffix(latestBuild),
entry: {
service_worker: "./src/entrypoints/service_worker.ts",
app: "./src/entrypoints/app.ts",
@@ -188,14 +150,12 @@ module.exports.config = {
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
isWDS,
};
},
demo({ isProdBuild, latestBuild, isStatsBuild }) {
return {
name: "demo" + nameSuffix(latestBuild),
entry: {
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
},
@@ -225,7 +185,6 @@ module.exports.config = {
}
return {
name: "cast" + nameSuffix(latestBuild),
entry,
outputPath: outputPath(paths.cast_output_root, latestBuild),
publicPath: publicPath(latestBuild),
@@ -237,9 +196,8 @@ module.exports.config = {
};
},
hassio({ isProdBuild, latestBuild, isStatsBuild, isTestBuild }) {
hassio({ isProdBuild, latestBuild }) {
return {
name: "supervisor" + nameSuffix(latestBuild),
entry: {
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
},
@@ -247,8 +205,6 @@ module.exports.config = {
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
isHassioBuild: true,
defineOverlay: {
__SUPERVISOR__: true,
@@ -258,7 +214,6 @@ module.exports.config = {
gallery({ isProdBuild, latestBuild }) {
return {
name: "gallery" + nameSuffix(latestBuild),
entry: {
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
},

View File

@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require("fs");
const path = require("path");
const paths = require("./paths.cjs");
const paths = require("./paths.js");
module.exports = {
useRollup() {
@@ -17,7 +18,7 @@ module.exports = {
isStatsBuild() {
return process.env.STATS === "1";
},
isTestBuild() {
isTest() {
return process.env.IS_TEST === "true";
},
isNetlify() {
@@ -25,8 +26,8 @@ module.exports = {
},
version() {
const version = fs
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
.readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8")
.match(/version\W+=\W(\d{8}\.\d)/);
if (!version) {
throw Error("Version not found");
}

View File

@@ -1,18 +1,19 @@
// Run HA develop mode
const gulp = require("gulp");
const env = require("../env.cjs");
require("./clean.cjs");
require("./translations.cjs");
require("./locale-data.cjs");
require("./gen-icons-json.cjs");
require("./gather-static.cjs");
require("./compress.cjs");
require("./webpack.cjs");
require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
require("./wds.cjs");
const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./locale-data.js");
require("./gen-icons-json.js");
require("./gather-static.js");
require("./compress.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
require("./wds.js");
gulp.task(
"develop-app",
@@ -24,7 +25,8 @@ gulp.task(
gulp.parallel(
"gen-service-worker-app-dev",
"gen-icons-json",
"gen-pages-app-dev",
"gen-pages-dev",
"gen-index-app-dev",
"build-translations",
"build-locale-data"
),
@@ -48,7 +50,11 @@ gulp.task(
"copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
// Don't compress running tests
...(env.isTestBuild() ? [] : ["compress-app"]),
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod")
...(env.isTest() ? [] : ["compress-app"]),
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",
"gen-service-worker-app-prod"
)
)
);

View File

@@ -1,13 +1,14 @@
const gulp = require("gulp");
const env = require("../env.cjs");
require("./clean.cjs");
require("./translations.cjs");
require("./gather-static.cjs");
require("./webpack.cjs");
require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
gulp.task(
"develop-cast",
@@ -19,7 +20,7 @@ gulp.task(
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast",
"gen-pages-cast-dev",
"gen-index-cast-dev",
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
)
);
@@ -35,6 +36,6 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast",
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
"gen-pages-cast-prod"
"gen-index-cast-prod"
)
);

View File

@@ -1,40 +0,0 @@
const del = import("del");
const gulp = require("gulp");
const paths = require("../paths.cjs");
require("./translations.cjs");
gulp.task(
"clean",
gulp.parallel("clean-translations", async () =>
(await del).deleteSync([paths.app_output_root, paths.build_dir])
)
);
gulp.task(
"clean-demo",
gulp.parallel("clean-translations", async () =>
(await del).deleteSync([paths.demo_output_root, paths.build_dir])
)
);
gulp.task(
"clean-cast",
gulp.parallel("clean-translations", async () =>
(await del).deleteSync([paths.cast_output_root, paths.build_dir])
)
);
gulp.task("clean-hassio", async () =>
(await del).deleteSync([paths.hassio_output_root, paths.build_dir])
);
gulp.task(
"clean-gallery",
gulp.parallel("clean-translations", async () =>
(await del).deleteSync([
paths.gallery_output_root,
paths.gallery_build,
paths.build_dir,
])
)
);

View File

@@ -0,0 +1,36 @@
const del = require("del");
const gulp = require("gulp");
const paths = require("../paths");
require("./translations");
gulp.task(
"clean",
gulp.parallel("clean-translations", () =>
del([paths.app_output_root, paths.build_dir])
)
);
gulp.task(
"clean-demo",
gulp.parallel("clean-translations", () =>
del([paths.demo_output_root, paths.build_dir])
)
);
gulp.task(
"clean-cast",
gulp.parallel("clean-translations", () =>
del([paths.cast_output_root, paths.build_dir])
)
);
gulp.task("clean-hassio", () =>
del([paths.hassio_output_root, paths.build_dir])
);
gulp.task(
"clean-gallery",
gulp.parallel("clean-translations", () =>
del([paths.gallery_output_root, paths.gallery_build, paths.build_dir])
)
);

View File

@@ -4,7 +4,7 @@ const gulp = require("gulp");
const zopfli = require("gulp-zopfli-green");
const merge = require("merge-stream");
const path = require("path");
const paths = require("../paths.cjs");
const paths = require("../paths");
const zopfliOptions = { threshold: 150 };

View File

@@ -1,15 +1,16 @@
// Run demo develop mode
const gulp = require("gulp");
const env = require("../env.cjs");
require("./clean.cjs");
require("./translations.cjs");
require("./gen-icons-json.cjs");
require("./gather-static.cjs");
require("./webpack.cjs");
require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
const env = require("../env");
require("./clean.js");
require("./translations.js");
require("./gen-icons-json.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
gulp.task(
"develop-demo",
@@ -21,7 +22,7 @@ gulp.task(
"translations-enable-merge-backend",
gulp.parallel(
"gen-icons-json",
"gen-pages-demo-dev",
"gen-index-demo-dev",
"build-translations",
"build-locale-data"
),
@@ -42,6 +43,6 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-demo",
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
"gen-pages-demo-prod"
"gen-index-demo-prod"
)
);

View File

@@ -1,69 +0,0 @@
const gulp = require("gulp");
const fs = require("fs/promises");
const mapStream = require("map-stream");
const inDirFrontend = "translations/frontend";
const inDirBackend = "translations/backend";
const srcMeta = "src/translations/translationMetadata.json";
const encoding = "utf8";
function hasHtml(data) {
return /<[a-z][\s\S]*>/i.test(data);
}
function recursiveCheckHasHtml(file, data, errors, recKey) {
Object.keys(data).forEach(function (key) {
if (typeof data[key] === "object") {
const nextRecKey = recKey ? `${recKey}.${key}` : key;
recursiveCheckHasHtml(file, data[key], errors, nextRecKey);
} else if (hasHtml(data[key])) {
errors.push(`HTML found in ${file.path} at key ${recKey}.${key}`);
}
});
}
function checkHtml() {
const errors = [];
return mapStream(function (file, cb) {
const content = file.contents;
let error;
if (content) {
if (hasHtml(String(content))) {
const data = JSON.parse(String(content));
recursiveCheckHasHtml(file, data, errors);
if (errors.length > 0) {
error = errors.join("\r\n");
}
}
}
cb(error, file);
});
}
// Backend translations do not currently pass HTML check so are excluded here for now
gulp.task("check-translations-html", function () {
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
});
gulp.task("check-all-files-exist", async function () {
const file = await fs.readFile(srcMeta, { encoding });
const meta = JSON.parse(file);
const writings = [];
Object.keys(meta).forEach((lang) => {
writings.push(
fs.writeFile(`${inDirFrontend}/${lang}.json`, JSON.stringify({}), {
flag: "wx",
}),
fs.writeFile(`${inDirBackend}/${lang}.json`, JSON.stringify({}), {
flag: "wx",
})
);
});
await Promise.allSettled(writings);
});
gulp.task(
"check-downloaded-translations",
gulp.series("check-translations-html", "check-all-files-exist")
);

View File

@@ -0,0 +1,95 @@
const del = require("del");
const gulp = require("gulp");
const fs = require("fs");
const mapStream = require("map-stream");
const inDirFrontend = "translations/frontend";
const inDirBackend = "translations/backend";
const downloadDir = "translations/downloads";
const srcMeta = "src/translations/translationMetadata.json";
const encoding = "utf8";
const tasks = [];
function hasHtml(data) {
return /<[a-z][\s\S]*>/i.test(data);
}
function recursiveCheckHasHtml(file, data, errors, recKey) {
Object.keys(data).forEach(function (key) {
if (typeof data[key] === "object") {
const nextRecKey = recKey ? `${recKey}.${key}` : key;
recursiveCheckHasHtml(file, data[key], errors, nextRecKey);
} else if (hasHtml(data[key])) {
errors.push(`HTML found in ${file.path} at key ${recKey}.${key}`);
}
});
}
function checkHtml() {
const errors = [];
return mapStream(function (file, cb) {
const content = file.contents;
let error;
if (content) {
if (hasHtml(String(content))) {
const data = JSON.parse(String(content));
recursiveCheckHasHtml(file, data, errors);
if (errors.length > 0) {
error = errors.join("\r\n");
}
}
}
cb(error, file);
});
}
let taskName = "clean-downloaded-translations";
gulp.task(taskName, function () {
return del([`${downloadDir}/**`]);
});
tasks.push(taskName);
taskName = "check-translations-html";
gulp.task(taskName, function () {
return gulp.src(`${downloadDir}/*.json`).pipe(checkHtml());
});
tasks.push(taskName);
taskName = "check-all-files-exist";
gulp.task(taskName, function () {
const file = fs.readFileSync(srcMeta, { encoding });
const meta = JSON.parse(file);
Object.keys(meta).forEach((lang) => {
if (!fs.existsSync(`${inDirFrontend}/${lang}.json`)) {
fs.writeFileSync(`${inDirFrontend}/${lang}.json`, JSON.stringify({}));
}
if (!fs.existsSync(`${inDirBackend}/${lang}.json`)) {
fs.writeFileSync(`${inDirBackend}/${lang}.json`, JSON.stringify({}));
}
});
return Promise.resolve();
});
tasks.push(taskName);
taskName = "move-downloaded-translations";
gulp.task(taskName, function () {
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDirFrontend));
});
tasks.push(taskName);
taskName = "check-downloaded-translations";
gulp.task(
taskName,
gulp.series(
"check-translations-html",
"move-downloaded-translations",
"check-all-files-exist",
"clean-downloaded-translations"
)
);
tasks.push(taskName);
module.exports = tasks;

View File

@@ -1,230 +0,0 @@
// Tasks to generate entry HTML
const gulp = require("gulp");
const fs = require("fs-extra");
const path = require("path");
const template = require("lodash.template");
const { minify } = require("html-minifier-terser");
const paths = require("../paths.cjs");
const env = require("../env.cjs");
const { htmlMinifierOptions, terserOptions } = require("../bundle.cjs");
const renderTemplate = (templateFile, data = {}) => {
const compiled = template(
fs.readFileSync(templateFile, { encoding: "utf-8" })
);
return compiled({
...data,
useRollup: env.useRollup(),
useWDS: env.useWDS(),
// Resolve any child/nested templates relative to the parent and pass the same data
renderTemplate: (childTemplate) =>
renderTemplate(
path.resolve(path.dirname(templateFile), childTemplate),
data
),
});
};
const WRAP_TAGS = { ".js": "script", ".css": "style" };
const minifyHtml = (content, ext) => {
const wrapTag = WRAP_TAGS[ext] || "";
const begTag = wrapTag && `<${wrapTag}>`;
const endTag = wrapTag && `</${wrapTag}>`;
return minify(begTag + content + endTag, {
...htmlMinifierOptions,
conservativeCollapse: false,
minifyJS: terserOptions({
latestBuild: false, // Shared scripts should be ES5
isTestBuild: true, // Don't need source maps
}),
}).then((wrapped) =>
wrapTag ? wrapped.slice(begTag.length, -endTag.length) : wrapped
);
};
// Function to generate a dev task for each project's configuration
// Note Currently WDS paths are hard-coded to only work for app
const genPagesDevTask =
(
pageEntries,
inputRoot,
outputRoot,
useWDS = false,
inputSub = "src/html",
publicRoot = ""
) =>
async () => {
for (const [page, entries] of Object.entries(pageEntries)) {
const content = renderTemplate(
path.resolve(inputRoot, inputSub, `${page}.template`),
{
latestEntryJS: entries.map((entry) =>
useWDS
? `http://localhost:8000/src/entrypoints/${entry}.ts`
: `${publicRoot}/frontend_latest/${entry}.js`
),
es5EntryJS: entries.map(
(entry) => `${publicRoot}/frontend_es5/${entry}.js`
),
latestCustomPanelJS: useWDS
? "http://localhost:8000/src/entrypoints/custom-panel.ts"
: `${publicRoot}/frontend_latest/custom-panel.js`,
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
}
);
fs.outputFileSync(path.resolve(outputRoot, page), content);
}
};
// Same as previous but for production builds
// (includes minification and hashed file names from manifest)
const genPagesProdTask =
(
pageEntries,
inputRoot,
outputRoot,
outputLatest,
outputES5,
inputSub = "src/html"
) =>
async () => {
const latestManifest = require(path.resolve(outputLatest, "manifest.json"));
const es5Manifest = outputES5
? require(path.resolve(outputES5, "manifest.json"))
: {};
const minifiedHTML = [];
for (const [page, entries] of Object.entries(pageEntries)) {
const content = renderTemplate(
path.resolve(inputRoot, inputSub, `${page}.template`),
{
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
latestCustomPanelJS: latestManifest["custom-panel.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
}
);
minifiedHTML.push(
minifyHtml(content, path.extname(page)).then((minified) =>
fs.outputFileSync(path.resolve(outputRoot, page), minified)
)
);
}
await Promise.all(minifiedHTML);
};
// Map HTML pages to their required entrypoints
const APP_PAGE_ENTRIES = {
"authorize.html": ["authorize"],
"onboarding.html": ["onboarding"],
"index.html": ["core", "app"],
};
gulp.task(
"gen-pages-app-dev",
genPagesDevTask(
APP_PAGE_ENTRIES,
paths.polymer_dir,
paths.app_output_root,
env.useWDS()
)
);
gulp.task(
"gen-pages-app-prod",
genPagesProdTask(
APP_PAGE_ENTRIES,
paths.polymer_dir,
paths.app_output_root,
paths.app_output_latest,
paths.app_output_es5
)
);
const CAST_PAGE_ENTRIES = {
"faq.html": ["launcher"],
"index.html": ["launcher"],
"media.html": ["media"],
"receiver.html": ["receiver"],
};
gulp.task(
"gen-pages-cast-dev",
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
)
);
const DEMO_PAGE_ENTRIES = { "index.html": ["main"] };
gulp.task(
"gen-pages-demo-dev",
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
)
);
const GALLERY_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
gulp.task(
"gen-pages-gallery-dev",
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
)
);
const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] };
gulp.task(
"gen-pages-hassio-dev",
genPagesDevTask(
HASSIO_PAGE_ENTRIES,
paths.hassio_dir,
paths.hassio_output_root,
undefined,
"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"
)
);

View File

@@ -0,0 +1,344 @@
// Tasks to generate entry HTML
/* eslint-disable import/no-dynamic-require */
/* eslint-disable global-require */
const gulp = require("gulp");
const fs = require("fs-extra");
const path = require("path");
const template = require("lodash.template");
const minify = require("html-minifier").minify;
const paths = require("../paths.js");
const env = require("../env.js");
const templatePath = (tpl) =>
path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`);
const readFile = (pth) => fs.readFileSync(pth).toString();
const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
const compiled = template(readFile(pathFunc(pth)));
return compiled({
...data,
useRollup: env.useRollup(),
useWDS: env.useWDS(),
renderTemplate,
});
};
const renderDemoTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(paths.demo_dir, "src/html/", `${tpl}.html.template`)
);
const renderCastTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(paths.cast_dir, "src/html/", `${tpl}.html.template`)
);
const renderGalleryTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(paths.gallery_dir, "src/html/", `${tpl}.html.template`)
);
const minifyHtml = (content) =>
minify(content, {
collapseWhitespace: true,
minifyJS: true,
minifyCSS: true,
removeComments: true,
});
const PAGES = ["onboarding", "authorize"];
gulp.task("gen-pages-dev", (done) => {
for (const page of PAGES) {
const content = renderTemplate(page, {
latestPageJS: `/frontend_latest/${page}.js`,
es5PageJS: `/frontend_es5/${page}.js`,
});
fs.outputFileSync(
path.resolve(paths.app_output_root, `${page}.html`),
content
);
}
done();
});
gulp.task("gen-pages-prod", (done) => {
const latestManifest = require(path.resolve(
paths.app_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.app_output_es5,
"manifest.json"
));
for (const page of PAGES) {
const content = renderTemplate(page, {
latestPageJS: latestManifest[`${page}.js`],
es5PageJS: es5Manifest[`${page}.js`],
});
fs.outputFileSync(
path.resolve(paths.app_output_root, `${page}.html`),
minifyHtml(content)
);
}
done();
});
gulp.task("gen-index-app-dev", (done) => {
let latestAppJS, latestCoreJS, latestCustomPanelJS;
if (env.useWDS()) {
latestAppJS = "http://localhost:8000/src/entrypoints/app.ts";
latestCoreJS = "http://localhost:8000/src/entrypoints/core.ts";
latestCustomPanelJS =
"http://localhost:8000/src/entrypoints/custom-panel.ts";
} else {
latestAppJS = "/frontend_latest/app.js";
latestCoreJS = "/frontend_latest/core.js";
latestCustomPanelJS = "/frontend_latest/custom-panel.js";
}
const content = renderTemplate("index", {
latestAppJS,
latestCoreJS,
latestCustomPanelJS,
es5AppJS: "/frontend_es5/app.js",
es5CoreJS: "/frontend_es5/core.js",
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
}).replace(/#THEMEC/g, "{{ theme_color }}");
fs.outputFileSync(path.resolve(paths.app_output_root, "index.html"), content);
done();
});
gulp.task("gen-index-app-prod", (done) => {
const latestManifest = require(path.resolve(
paths.app_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.app_output_es5,
"manifest.json"
));
const content = renderTemplate("index", {
latestAppJS: latestManifest["app.js"],
latestCoreJS: latestManifest["core.js"],
latestCustomPanelJS: latestManifest["custom-panel.js"],
es5AppJS: es5Manifest["app.js"],
es5CoreJS: es5Manifest["core.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
});
const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}");
fs.outputFileSync(
path.resolve(paths.app_output_root, "index.html"),
minified
);
done();
});
gulp.task("gen-index-cast-dev", (done) => {
const contentReceiver = renderCastTemplate("receiver", {
latestReceiverJS: "/frontend_latest/receiver.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "receiver.html"),
contentReceiver
);
const contentMedia = renderCastTemplate("media", {
latestMediaJS: "/frontend_latest/media.js",
es5MediaJS: "/frontend_es5/media.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "media.html"),
contentMedia
);
const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: "/frontend_latest/launcher.js",
es5LauncherJS: "/frontend_es5/launcher.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "faq.html"),
contentFAQ
);
const contentLauncher = renderCastTemplate("launcher", {
latestLauncherJS: "/frontend_latest/launcher.js",
es5LauncherJS: "/frontend_es5/launcher.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "index.html"),
contentLauncher
);
done();
});
gulp.task("gen-index-cast-prod", (done) => {
const latestManifest = require(path.resolve(
paths.cast_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.cast_output_es5,
"manifest.json"
));
const contentReceiver = renderCastTemplate("receiver", {
latestReceiverJS: latestManifest["receiver.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "receiver.html"),
contentReceiver
);
const contentMedia = renderCastTemplate("media", {
latestMediaJS: latestManifest["media.js"],
es5MediaJS: es5Manifest["media.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "media.html"),
contentMedia
);
const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: latestManifest["launcher.js"],
es5LauncherJS: es5Manifest["launcher.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "faq.html"),
contentFAQ
);
const contentLauncher = renderCastTemplate("launcher", {
latestLauncherJS: latestManifest["launcher.js"],
es5LauncherJS: es5Manifest["launcher.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "index.html"),
contentLauncher
);
done();
});
gulp.task("gen-index-demo-dev", (done) => {
const content = renderDemoTemplate("index", {
latestDemoJS: "/frontend_latest/main.js",
es5DemoJS: "/frontend_es5/main.js",
});
fs.outputFileSync(
path.resolve(paths.demo_output_root, "index.html"),
content
);
done();
});
gulp.task("gen-index-demo-prod", (done) => {
const latestManifest = require(path.resolve(
paths.demo_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.demo_output_es5,
"manifest.json"
));
const content = renderDemoTemplate("index", {
latestDemoJS: latestManifest["main.js"],
es5DemoJS: es5Manifest["main.js"],
});
const minified = minifyHtml(content);
fs.outputFileSync(
path.resolve(paths.demo_output_root, "index.html"),
minified
);
done();
});
gulp.task("gen-index-gallery-dev", (done) => {
const content = renderGalleryTemplate("index", {
latestGalleryJS: "./frontend_latest/entrypoint.js",
});
fs.outputFileSync(
path.resolve(paths.gallery_output_root, "index.html"),
content
);
done();
});
gulp.task("gen-index-gallery-prod", (done) => {
const latestManifest = require(path.resolve(
paths.gallery_output_latest,
"manifest.json"
));
const content = renderGalleryTemplate("index", {
latestGalleryJS: latestManifest["entrypoint.js"],
});
const minified = minifyHtml(content);
fs.outputFileSync(
path.resolve(paths.gallery_output_root, "index.html"),
minified
);
done();
});
gulp.task("gen-index-hassio-dev", async () => {
writeHassioEntrypoint(
`${paths.hassio_publicPath}/frontend_latest/entrypoint.js`,
`${paths.hassio_publicPath}/frontend_es5/entrypoint.js`
);
});
gulp.task("gen-index-hassio-prod", async () => {
const latestManifest = require(path.resolve(
paths.hassio_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.hassio_output_es5,
"manifest.json"
));
writeHassioEntrypoint(
latestManifest["entrypoint.js"],
es5Manifest["entrypoint.js"]
);
});
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
function loadES5() {
var el = document.createElement('script');
el.src = '${es5Entrypoint}';
document.body.appendChild(el);
}
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
loadES5();
} else {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
loadES5();
}
}
`,
{ encoding: "utf-8" }
);
}

View File

@@ -1,175 +0,0 @@
// Task to download the latest Lokalise translations from the nightly workflow artifacts
const del = import("del");
const fs = require("fs/promises");
const path = require("path");
const process = require("process");
const gulp = require("gulp");
const jszip = require("jszip");
const tar = require("tar");
const { Octokit } = require("@octokit/rest");
const { retry } = require("@octokit/plugin-retry");
const { createOAuthDeviceAuth } = require("@octokit/auth-oauth-device");
const MAX_AGE = 24; // hours
const OWNER = "home-assistant";
const REPO = "frontend";
const WORKFLOW_NAME = "nightly.yaml";
const ARTIFACT_NAME = "translations";
const CLIENT_ID = "Iv1.3914e28cb27834d1";
const EXTRACT_DIR = "translations";
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) => {
allowTokenSetup = true;
done();
});
gulp.task("fetch-nightly-translations", async function () {
// 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");
return;
}
// Read current translations artifact info if it exists,
// and stop if they are not old enough
let currentArtifact;
try {
currentArtifact = JSON.parse(await fs.readFile(ARTIFACT_FILE, "utf-8"));
const currentAge =
(Date.now() - Date.parse(currentArtifact.created_at)) / 3600000;
if (currentAge < MAX_AGE) {
console.log(
"Keeping current translations (only %s hours old)",
currentAge.toFixed(1)
);
return;
}
} catch {
currentArtifact = null;
}
// To store file writing promises
const createExtractDir = fs.mkdir(EXTRACT_DIR, { recursive: true });
const writings = [];
// Authenticate to GitHub using GitHub action token if it exists,
// otherwise look for a saved user token or generate a new one if none
let tokenAuth;
if (process.env.GITHUB_TOKEN) {
tokenAuth = { token: process.env.GITHUB_TOKEN };
} else {
try {
tokenAuth = JSON.parse(await fs.readFile(TOKEN_FILE, "utf-8"));
} catch {
if (!allowTokenSetup) {
console.log("No token found so build wil continue with English only");
return;
}
const auth = createOAuthDeviceAuth({
clientType: "github-app",
clientId: CLIENT_ID,
onVerification: (verification) => {
console.log(
"Task needs to authenticate to GitHub to fetch the translations from nightly workflow\n" +
"Please go to %s to authorize this task\n" +
"\nEnter user code: %s\n\n" +
"This code will expire in %s minutes\n" +
"Task will automatically continue after authorization and token will be saved for future use",
verification.verification_uri,
verification.user_code,
(verification.expires_in / 60).toFixed(0)
);
},
});
tokenAuth = await auth({ type: "oauth" });
writings.push(
createExtractDir.then(
fs.writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2))
)
);
}
}
// Authenticate with token and request workflow runs from GitHub
console.log("Fetching new translations...");
const octokit = new (Octokit.plugin(retry))({
userAgent: "Fetch Nightly Translations",
auth: tokenAuth.token,
});
const workflowRunsResponse = await octokit.rest.actions.listWorkflowRuns({
owner: OWNER,
repo: REPO,
workflow_id: WORKFLOW_NAME,
status: "success",
event: "schedule",
per_page: 1,
exclude_pull_requests: true,
});
if (workflowRunsResponse.data.total_count === 0) {
throw Error("No successful nightly workflow runs found");
}
const latestNightlyRun = workflowRunsResponse.data.workflow_runs[0];
// Stop if current is already the latest, otherwise Find the translations artifact
if (currentArtifact?.workflow_run.id === latestNightlyRun.id) {
console.log("Stopping because current translations are still the latest");
return;
}
const latestArtifact = (
await octokit.actions.listWorkflowRunArtifacts({
owner: OWNER,
repo: REPO,
run_id: latestNightlyRun.id,
})
).data.artifacts.find((artifact) => artifact.name === ARTIFACT_NAME);
if (!latestArtifact) {
throw Error("Latest nightly workflow run has no translations artifact");
}
writings.push(
createExtractDir.then(
fs.writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2))
)
);
// Remove the current translations
const deleteCurrent = Promise.all(writings).then(
(await del).deleteAsync([
`${EXTRACT_DIR}/*`,
`!${ARTIFACT_FILE}`,
`!${TOKEN_FILE}`,
])
);
// Get the download URL and follow the redirect to download (stored as ArrayBuffer)
const downloadResponse = await octokit.actions.downloadArtifact({
owner: OWNER,
repo: REPO,
artifact_id: latestArtifact.id,
archive_format: "zip",
});
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);
await deleteCurrent;
const extractStream = zip.file(/.*/)[0].nodeStream().pipe(tar.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"
)
);

View File

@@ -1,26 +1,27 @@
/* eslint-disable */
// Run demo develop mode
const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const { marked } = require("marked");
const { glob } = require("glob");
const glob = require("glob");
const yaml = require("js-yaml");
const env = require("../env.cjs");
const paths = require("../paths.cjs");
const env = require("../env");
const paths = require("../paths");
require("./clean.cjs");
require("./translations.cjs");
require("./gen-icons-json.cjs");
require("./gather-static.cjs");
require("./webpack.cjs");
require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
require("./clean.js");
require("./translations.js");
require("./gen-icons-json.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
gulp.task("gather-gallery-pages", async function gatherPages() {
const pageDir = path.resolve(paths.gallery_dir, "src/pages");
const files = await glob(path.resolve(pageDir, "**/*"));
const files = glob.sync(path.resolve(pageDir, "**/*"));
const galleryBuild = path.resolve(paths.gallery_dir, "build");
fs.mkdirSync(galleryBuild, { recursive: true });
@@ -40,7 +41,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
}
processed.add(pageId);
const [category] = pageId.split("/", 2);
const [category, name] = pageId.split("/", 2);
const demoFile = path.resolve(pageDir, `${pageId}.ts`);
const descriptionFile = path.resolve(pageDir, `${pageId}.markdown`);
@@ -89,7 +90,9 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
// Generate sidebar
const sidebarPath = path.resolve(paths.gallery_dir, "sidebar.js");
const sidebar = (await import(sidebarPath)).default;
// To make watch work during development
delete require.cache[sidebarPath];
const sidebar = require(sidebarPath);
const pagesToProcess = {};
for (const key of processed) {
@@ -159,7 +162,7 @@ gulp.task(
"gather-gallery-pages"
),
"copy-static-gallery",
"gen-pages-gallery-dev",
"gen-index-gallery-dev",
gulp.parallel(
env.useRollup()
? "rollup-dev-server-gallery"
@@ -193,6 +196,6 @@ gulp.task(
),
"copy-static-gallery",
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
"gen-pages-gallery-prod"
"gen-index-gallery-prod"
)
);

View File

@@ -3,7 +3,7 @@
const gulp = require("gulp");
const path = require("path");
const fs = require("fs-extra");
const paths = require("../paths.cjs");
const paths = require("../paths");
const npmPath = (...parts) =>
path.resolve(paths.polymer_dir, "node_modules", ...parts);

View File

@@ -134,11 +134,11 @@ gulp.task("gen-icons-json", (done) => {
});
const file = fs.readFileSync(PACKAGE_PATH, { encoding });
const packageMeta = JSON.parse(file);
const package = JSON.parse(file);
fs.writeFileSync(
path.resolve(OUTPUT_DIR, "iconMetadata.json"),
JSON.stringify({ version: packageMeta.version, parts })
JSON.stringify({ version: package.version, parts })
);
fs.writeFileSync(
@@ -156,12 +156,3 @@ gulp.task("gen-icons-json", (done) => {
done();
});
gulp.task("gen-dummy-icons-json", (done) => {
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
fs.writeFileSync(path.resolve(OUTPUT_DIR, "iconList.json"), "[]");
done();
});

View File

@@ -1,13 +1,14 @@
const gulp = require("gulp");
const env = require("../env.cjs");
require("./clean.cjs");
require("./compress.cjs");
require("./entry-html.cjs");
require("./gather-static.cjs");
require("./gen-icons-json.cjs");
require("./rollup.cjs");
require("./translations.cjs");
require("./webpack.cjs");
const env = require("../env");
require("./clean.js");
require("./gen-icons-json.js");
require("./webpack.js");
require("./compress.js");
require("./rollup.js");
require("./gather-static.js");
require("./translations.js");
gulp.task(
"develop-hassio",
@@ -16,8 +17,7 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-hassio",
"gen-dummy-icons-json",
"gen-pages-hassio-dev",
"gen-index-hassio-dev",
"build-supervisor-translations",
"copy-translations-supervisor",
"build-locale-data",
@@ -33,14 +33,13 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-hassio",
"gen-dummy-icons-json",
"build-supervisor-translations",
"copy-translations-supervisor",
"build-locale-data",
"copy-locale-data-supervisor",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
"gen-pages-hassio-prod",
"gen-index-hassio-prod",
...// Don't compress running tests
(env.isTestBuild() ? [] : ["compress-hassio"])
(env.isTest() ? [] : ["compress-hassio"])
)
);

View File

@@ -1,12 +1,14 @@
const del = import("del");
/* eslint-disable @typescript-eslint/no-var-requires */
const del = require("del");
const path = require("path");
const gulp = require("gulp");
const fs = require("fs");
const paths = require("../paths.cjs");
const paths = require("../paths");
const outDir = "build/locale-data";
gulp.task("clean-locale-data", async () => (await del).deleteSync([outDir]));
gulp.task("clean-locale-data", () => del([outDir]));
gulp.task("ensure-locale-data-build-dir", (done) => {
if (!fs.existsSync(outDir)) {
@@ -19,7 +21,6 @@ const modules = {
"intl-relativetimeformat": "RelativeTimeFormat",
"intl-datetimeformat": "DateTimeFormat",
"intl-numberformat": "NumberFormat",
"intl-displaynames": "DisplayNames",
};
gulp.task("create-locale-data", (done) => {

View File

@@ -5,9 +5,9 @@ const rollup = require("rollup");
const handler = require("serve-handler");
const http = require("http");
const log = require("fancy-log");
const rollupConfig = require("../rollup");
const paths = require("../paths");
const open = require("open");
const rollupConfig = require("../rollup.cjs");
const paths = require("../paths.cjs");
const bothBuilds = (createConfigFunc, params) =>
gulp.series(
@@ -30,11 +30,11 @@ const bothBuilds = (createConfigFunc, params) =>
);
function createServer(serveOptions) {
const server = http.createServer((request, response) =>
handler(request, response, {
const server = http.createServer((request, response) => {
return handler(request, response, {
public: serveOptions.root,
})
);
});
});
server.listen(
serveOptions.port,
@@ -46,7 +46,7 @@ function createServer(serveOptions) {
);
}
function watchRollup(createConfig, extraWatchSrc = [], serveOptions = null) {
function watchRollup(createConfig, extraWatchSrc = [], serveOptions) {
const { inputOptions, outputOptions } = createConfig({
isProdBuild: false,
latestBuild: true,

View File

@@ -1,11 +1,13 @@
// Generate service worker.
// Based on manifest, create a file with the content as service_worker.js
/* eslint-disable import/no-dynamic-require */
/* eslint-disable global-require */
const gulp = require("gulp");
const path = require("path");
const fs = require("fs-extra");
const workboxBuild = require("workbox-build");
const sourceMapUrl = require("source-map-url");
const paths = require("../paths.cjs");
const paths = require("../paths.js");
const swDest = path.resolve(paths.app_output_root, "service_worker.js");

View File

@@ -1,5 +1,7 @@
const del = import("del");
/* eslint-disable @typescript-eslint/no-var-requires */
const crypto = require("crypto");
const del = require("del");
const path = require("path");
const source = require("vinyl-source-stream");
const vinylBuffer = require("vinyl-buffer");
@@ -9,11 +11,9 @@ const flatmap = require("gulp-flatmap");
const merge = require("gulp-merge-json");
const rename = require("gulp-rename");
const transform = require("gulp-json-transform");
const { mapFiles } = require("../util.cjs");
const env = require("../env.cjs");
const paths = require("../paths.cjs");
require("./fetch-nightly-translations.cjs");
const { mapFiles } = require("../util");
const env = require("../env");
const paths = require("../paths");
const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend";
@@ -23,13 +23,10 @@ const coreDir = workDir + "/core";
const outDir = workDir + "/output";
let mergeBackend = false;
gulp.task(
"translations-enable-merge-backend",
gulp.parallel((done) => {
mergeBackend = true;
done();
}, "allow-setup-fetch-nightly-translations")
);
gulp.task("translations-enable-merge-backend", (done) => {
mergeBackend = true;
done();
});
// Panel translations which should be split from the core translations.
const TRANSLATION_FRAGMENTS = Object.keys(
@@ -120,7 +117,7 @@ function lokaliseTransform(data, original, file) {
return output;
}
gulp.task("clean-translations", async () => (await del).deleteSync([workDir]));
gulp.task("clean-translations", () => del([workDir]));
gulp.task("ensure-translations-build-dir", (done) => {
if (!fs.existsSync(workDir)) {
@@ -173,24 +170,17 @@ gulp.task("build-master-translation", () => {
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe(
merge({
fileName: "en.json",
fileName: "translationMaster.json",
})
)
.pipe(gulp.dest(fullDir));
.pipe(gulp.dest(workDir));
});
gulp.task("build-merged-translations", () =>
gulp
.src(
[
inFrontendDir + "/*.json",
"!" + inFrontendDir + "/en.json",
workDir + "/test.json",
],
{
allowEmpty: true,
}
)
.src([inFrontendDir + "/*.json", workDir + "/test.json"], {
allowEmpty: true,
})
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe(
flatmap((stream, file) => {
@@ -203,7 +193,7 @@ gulp.task("build-merged-translations", () =>
// than a base translation + region.
const tr = path.basename(file.history[0], ".json");
const subtags = tr.split("-");
const src = [fullDir + "/en.json"];
const src = [workDir + "/translationMaster.json"];
for (let i = 1; i <= subtags.length; i++) {
const lang = subtags.slice(0, i).join("-");
if (lang === "test") {
@@ -388,6 +378,7 @@ gulp.task("build-translation-write-metadata", () =>
if (value.nativeName) {
newData[key] = value;
} else {
// eslint-disable-next-line no-console
console.warn(
`Skipping language ${key}. Native name was not translated.`
);
@@ -420,10 +411,8 @@ gulp.task(
gulp.task(
"build-translations",
gulp.series(
gulp.parallel(
"fetch-nightly-translations",
gulp.series("clean-translations", "ensure-translations-build-dir")
),
"clean-translations",
"ensure-translations-build-dir",
"create-translations",
"build-translation-fingerprints",
"build-translation-write-metadata"
@@ -433,10 +422,8 @@ gulp.task(
gulp.task(
"build-supervisor-translations",
gulp.series(
gulp.parallel(
"fetch-nightly-translations",
gulp.series("clean-translations", "ensure-translations-build-dir")
),
"clean-translations",
"ensure-translations-build-dir",
"build-master-translation",
"build-merged-translations",
"build-translation-fragment-supervisor",

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Tasks to run webpack.
const fs = require("fs");
const gulp = require("gulp");
@@ -5,15 +6,14 @@ const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
const log = require("fancy-log");
const path = require("path");
const env = require("../env.cjs");
const paths = require("../paths.cjs");
const paths = require("../paths");
const {
createAppConfig,
createDemoConfig,
createCastConfig,
createHassioConfig,
createGalleryConfig,
} = require("../webpack.cjs");
} = require("../webpack");
const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: true }),
@@ -69,6 +69,7 @@ const doneHandler = (done) => (err, stats) => {
}
if (stats.hasErrors() || stats.hasWarnings()) {
// eslint-disable-next-line no-console
console.log(stats.toString("minimal"));
}
@@ -105,8 +106,6 @@ gulp.task("webpack-prod-app", () =>
prodBuild(
bothBuilds(createAppConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isTestBuild: env.isTestBuild(),
})
)
);
@@ -164,8 +163,6 @@ gulp.task("webpack-prod-hassio", () =>
prodBuild(
bothBuilds(createHassioConfig, {
isProdBuild: true,
isStatsBuild: env.isStatsBuild(),
isTestBuild: env.isTestBuild(),
})
)
);

View File

@@ -1,59 +0,0 @@
#!/usr/bin/env node
// Script to print Babel plugins and Core JS polyfills that will be used by browserslist environments
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";
import { babelOptions } from "./bundle.cjs";
const detailsOpen = (heading) =>
`<details>\n<summary><h4>${heading}</h4></summary>\n`;
const detailsClose = "</details>\n";
const dummyAPI = {
version: babelVersion,
assertVersion: () => {},
caller: (callback) =>
callback({
name: "Dummy Bundler",
supportsStaticESM: true,
supportsDynamicImport: true,
supportsTopLevelAwait: true,
supportsExportNamespaceFrom: true,
}),
targets: () => ({}),
};
for (const buildType of ["Modern", "Legacy"]) {
const browserslistEnv = buildType.toLowerCase();
const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" });
const presetEnvOpts = babelOpts.presets[0][1];
// Invoking preset-env in debug mode will log the included plugins
console.log(detailsOpen(`${buildType} Build Babel Plugins`));
presetEnv.default(dummyAPI, {
...presetEnvOpts,
browserslistEnv,
debug: true,
});
console.log(detailsClose);
// Manually log the Core-JS polyfills using the same technique
if (presetEnvOpts.useBuiltIns) {
console.log(detailsOpen(`${buildType} Build Core-JS Polyfills`));
const targets = compilationTargets.default(babelOpts?.targets, {
browserslistEnv,
});
const polyfillList = coreJSCompat({ targets }).list;
console.log(
"The following %i polyfills may be injected by Babel:\n",
polyfillList.length
);
for (const polyfill of polyfillList) {
logPlugin(polyfill, targets, coreJSCompat.data);
}
console.log(detailsClose);
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
module.exports = {

View File

@@ -81,13 +81,13 @@ module.exports = function (opts = {}) {
opts.workerRegexp.flags
);
if (!workerRegexp.test(code)) {
return undefined;
return;
}
const ms = new MagicString(code);
// Reset the regexp
workerRegexp.lastIndex = 0;
for (;;) {
while (true) {
const match = workerRegexp.exec(code);
if (!match) {
break;
@@ -98,12 +98,11 @@ module.exports = function (opts = {}) {
// Parse the optional options object
if (match[3] && match[3].length > 0) {
// FIXME: ooooof!
// eslint-disable-next-line @typescript-eslint/no-implied-eval
optionsObject = new Function(`return ${match[3].slice(1)};`)();
}
delete optionsObject.type;
if (!/^.*\//.test(workerFile)) {
if (!new RegExp("^.*/").test(workerFile)) {
this.warn(
`Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`
);
@@ -111,14 +110,12 @@ module.exports = function (opts = {}) {
}
// Find worker file and store it as a chunk with ID prefixed for our loader
// eslint-disable-next-line no-await-in-loop
const resolvedWorkerFile = (await this.resolve(workerFile, id)).id;
let chunkRefId;
if (resolvedWorkerFile in refIds) {
chunkRefId = refIds[resolvedWorkerFile];
} else {
this.addWatchFile(resolvedWorkerFile);
// eslint-disable-next-line no-await-in-loop
const source = await getBundledWorker(
resolvedWorkerFile,
rollupOptions

View File

@@ -1,20 +1,21 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const commonjs = require("@rollup/plugin-commonjs");
const resolve = require("@rollup/plugin-node-resolve");
const json = require("@rollup/plugin-json");
const { babel } = require("@rollup/plugin-babel");
const babel = require("@rollup/plugin-babel").babel;
const replace = require("@rollup/plugin-replace");
const visualizer = require("rollup-plugin-visualizer");
const { string } = require("rollup-plugin-string");
const { terser } = require("rollup-plugin-terser");
const manifest = require("./rollup-plugins/manifest-plugin.cjs");
const worker = require("./rollup-plugins/worker-plugin.cjs");
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin.cjs");
const ignore = require("./rollup-plugins/ignore-plugin.cjs");
const manifest = require("./rollup-plugins/manifest-plugin");
const worker = require("./rollup-plugins/worker-plugin");
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin");
const ignore = require("./rollup-plugins/ignore-plugin");
const bundle = require("./bundle.cjs");
const paths = require("./paths.cjs");
const bundle = require("./bundle");
const paths = require("./paths");
const extensions = [".js", ".ts"];
@@ -39,18 +40,11 @@ const createRollupConfig = ({
inputOptions: {
input: entry,
// Some entry points contain no JavaScript. This setting silences a warning about that.
// https://rollupjs.org/configuration-options/#preserveentrysignatures
// https://rollupjs.org/guide/en/#preserveentrysignatures
preserveEntrySignatures: false,
plugins: [
ignore({
files: bundle
.emptyPackages({ latestBuild })
// TEMP HACK: Makes Rollup build work again
.concat(
require.resolve(
"@webcomponents/scoped-custom-element-registry/scoped-custom-element-registry.min"
)
),
files: bundle.emptyPackages({ latestBuild }),
}),
resolve({
extensions,
@@ -61,7 +55,7 @@ const createRollupConfig = ({
commonjs(),
json(),
babel({
...bundle.babelOptions({ latestBuild, isProdBuild }),
...bundle.babelOptions({ latestBuild }),
extensions,
babelHelpers: isWDS ? "inline" : "bundled",
}),
@@ -76,7 +70,7 @@ const createRollupConfig = ({
}),
!isWDS && worker(),
!isWDS && dontHashPlugin({ dontHash }),
!isWDS && isProdBuild && terser(bundle.terserOptions({ latestBuild })),
!isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)),
!isWDS &&
isStatsBuild &&
visualizer({
@@ -90,20 +84,20 @@ const createRollupConfig = ({
* @type { import("rollup").OutputOptions }
*/
outputOptions: {
// https://rollupjs.org/configuration-options/#output-dir
// https://rollupjs.org/guide/en/#outputdir
dir: outputPath,
// https://rollupjs.org/configuration-options/#output-format
// https://rollupjs.org/guide/en/#outputformat
format: latestBuild ? "es" : "systemjs",
// https://rollupjs.org/configuration-options/#output-externallivebindings
// https://rollupjs.org/guide/en/#outputexternallivebindings
externalLiveBindings: false,
// https://rollupjs.org/configuration-options/#output-entryfilenames
// https://rollupjs.org/configuration-options/#output-chunkfilenames
// https://rollupjs.org/configuration-options/#output-assetfilenames
// https://rollupjs.org/guide/en/#outputentryfilenames
// https://rollupjs.org/guide/en/#outputchunkfilenames
// https://rollupjs.org/guide/en/#outputassetfilenames
entryFileNames:
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
chunkFileNames: isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
assetFileNames: isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
// https://rollupjs.org/configuration-options/#output-sourcemap
// https://rollupjs.org/guide/en/#outputsourcemap
sourcemap: isProdBuild ? true : "inline",
},
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const fs = require("fs");

View File

@@ -1,11 +1,12 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const webpack = require("webpack");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const paths = require("./paths.js");
const bundle = require("./bundle.js");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
const paths = require("./paths.cjs");
const bundle = require("./bundle.cjs");
class LogStartCompilePlugin {
ignoredFirst = false;
@@ -22,7 +23,6 @@ class LogStartCompilePlugin {
}
const createWebpackConfig = ({
name,
entry,
outputPath,
publicPath,
@@ -30,7 +30,6 @@ const createWebpackConfig = ({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
isHassioBuild,
dontHash,
}) => {
@@ -39,16 +38,10 @@ const createWebpackConfig = ({
}
const ignorePackages = bundle.ignorePackages({ latestBuild });
return {
name,
mode: isProdBuild ? "production" : "development",
target: ["web", latestBuild ? "es2017" : "es5"],
// For tests/CI, source maps are skipped to gain build speed
// For production, generate source maps for accurate stack traces without source code
// For development, generate "cheap" versions that can map to original line numbers
devtool: isTestBuild
? false
: isProdBuild
? "nosources-source-map"
devtool: isProdBuild
? "cheap-module-source-map"
: "eval-cheap-module-source-map",
entry,
node: false,
@@ -59,14 +52,11 @@ const createWebpackConfig = ({
use: {
loader: "babel-loader",
options: {
...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }),
...bundle.babelOptions({ latestBuild }),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
},
resolve: {
fullySpecified: false,
},
},
{
test: /\.css$/,
@@ -79,14 +69,14 @@ const createWebpackConfig = ({
new TerserPlugin({
parallel: true,
extractComments: true,
terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }),
terserOptions: bundle.terserOptions(latestBuild),
}),
],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
},
plugins: [
!isStatsBuild && new WebpackBar({ fancy: !isProdBuild }),
new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({
// Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
@@ -113,6 +103,7 @@ const createWebpackConfig = ({
? path.resolve(context, resource)
: require.resolve(resource);
} catch (err) {
// eslint-disable-next-line no-console
console.error(
"Error in Home Assistant ignore plugin",
resource,
@@ -147,42 +138,21 @@ const createWebpackConfig = ({
"lit/directives/cache$": "lit/directives/cache.js",
"lit/directives/repeat$": "lit/directives/repeat.js",
"lit/polyfill-support$": "lit/polyfill-support.js",
"@lit-labs/virtualizer/layouts/grid":
"@lit-labs/virtualizer/layouts/grid.js",
},
},
output: {
filename: ({ chunk }) =>
!isProdBuild || isStatsBuild || dontHash.has(chunk.name)
? "[name].js"
: "[name]-[contenthash].js",
filename: ({ chunk }) => {
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`;
}
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
},
chunkFilename:
isProdBuild && !isStatsBuild ? "[id]-[contenthash].js" : "[name].js",
assetModuleFilename:
isProdBuild && !isStatsBuild ? "[id]-[contenthash][ext]" : "[id][ext]",
hashFunction: "xxhash64",
hashDigest: "base64url",
hashDigestLength: 11, // full length of 64 bit base64url
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
path: outputPath,
publicPath,
// To silence warning in worker plugin
globalObject: "self",
// Since production source maps don't include sources, we need to point to them elsewhere
// For dependencies, just provide the path (no source in browser)
// Otherwise, point to the raw code on GitHub for browser to load
devtoolModuleFilenameTemplate:
!isTestBuild && isProdBuild
? (info) => {
const sourcePath = info.resourcePath.replace(/^\.\//, "");
if (
sourcePath.startsWith("node_modules") ||
sourcePath.startsWith("webpack")
) {
return `no-source/${sourcePath}`;
}
return `${bundle.sourceMapURL()}/${sourcePath}`;
}
: undefined,
},
experiments: {
topLevelAwait: true,
@@ -190,14 +160,9 @@ const createWebpackConfig = ({
};
};
const createAppConfig = ({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
}) =>
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
createWebpackConfig(
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild })
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild })
);
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
@@ -208,20 +173,8 @@ const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
const createCastConfig = ({ isProdBuild, latestBuild }) =>
createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
const createHassioConfig = ({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
}) =>
createWebpackConfig(
bundle.config.hassio({
isProdBuild,
latestBuild,
isStatsBuild,
isTestBuild,
})
);
const createHassioConfig = ({ isProdBuild, latestBuild }) =>
createWebpackConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild }));

View File

@@ -1,9 +0,0 @@
# These redirects are handled by Netlify
#
# Some custom cards are not prefixing the instance URL when fetching data
# and can end up fetching the data from the Cast domain instead of HA.
# This will make sure that some common ones are replaced with a placeholder.
/api/camera_proxy/* /images/google-nest-hub.png
/api/camera_proxy_stream/* /images/google-nest-hub.png
/api/media_player_proxy/* /images/google-nest-hub.png

View File

@@ -1,5 +1,5 @@
import rollup from "../build-scripts/rollup.cjs";
import env from "../build-scripts/env.cjs";
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
const config = rollup.createCastConfig({
isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createCastConfig({
isStatsBuild: env.isStatsBuild(),
});
export default { ...config.inputOptions, output: config.outputOptions };
module.exports = { ...config.inputOptions, output: config.outputOptions };

View File

@@ -1,24 +0,0 @@
<meta property="fb:app_id" content="338291289691179" />
<meta property="og:title" content="Home Assistant Cast" />
<meta property="og:site_name" content="Home Assistant Cast" />
<meta property="og:url" content="https://cast.home-assistant.io/" />
<meta property="og:type" content="website" />
<meta
property="og:description"
content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen."
/>
<meta
property="og:image"
content="https://cast.home-assistant.io/images/google-nest-hub.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@home_assistant" />
<meta name="twitter:title" content="Home Assistant Cast" />
<meta
name="twitter:description"
content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen."
/>
<meta
name="twitter:image"
content="https://cast.home-assistant.io/images/google-nest-hub.png"
/>

View File

@@ -1,35 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Home Assistant Cast</title>
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
<%= renderTemplate("../../../src/html/_style_base.html.template") %>
<style>
body {
background-color: #e5e5e5;
}
</style>
<%= renderTemplate("_social_meta.html.template") %>
</head>
<body>
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
<hc-connect></hc-connect>
<script>
<% for (const entry of latestEntryJS) { %>
import("<%= entry %>");
<% } %>
window.latestJS = true;
</script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-57927901-9', 'auto');
ga('send', 'pageview', location.pathname.includes("auth_callback") === -1 ? location.pathname : "/");
</script>
</body>
</html>

View File

@@ -3,7 +3,7 @@
<head>
<title>Home Assistant Cast - FAQ</title>
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
<%= renderTemplate("../../../src/html/_style_base.html.template") %>
<%= renderTemplate('_style_base') %>
<style>
body {
background-color: #e5e5e5;
@@ -35,14 +35,25 @@
/>
</head>
<body>
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
<%= renderTemplate('_js_base') %>
<script>
<% for (const entry of latestEntryJS) { %>
import("<%= entry %>");
<% } %>
import("<%= latestLauncherJS %>");
window.latestJS = true;
</script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script>
if (!window.latestJS) {
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>");
};
<% } else { %>
_ls("<%= es5LauncherJS %>");
<% } %>
}
</script>
<hc-layout subtitle="FAQ">
<style>
a {
@@ -202,7 +213,7 @@
</p>
<ul>
<li>Google Chrome (all platforms except iOS)</li>
<li>Microsoft Edge (all platforms except iOS)</li>
<li>Microsoft Edge (all platforms)</li>
</ul>
</div>

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>Home Assistant Cast</title>
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
<%= renderTemplate('_style_base') %>
<style>
body {
background-color: #e5e5e5;
}
</style>
<meta property="fb:app_id" content="338291289691179">
<meta property="og:title" content="Home Assistant Cast">
<meta property="og:site_name" content="Home Assistant Cast">
<meta property="og:url" content="https://cast.home-assistant.io/">
<meta property="og:type" content="website">
<meta property="og:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen.">
<meta property="og:image" content="https://cast.home-assistant.io/images/google-nest-hub.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@home_assistant">
<meta name="twitter:title" content="Home Assistant Cast">
<meta name="twitter:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen.">
<meta name="twitter:image" content="https://cast.home-assistant.io/images/google-nest-hub.png">
</head>
<body>
<%= renderTemplate('_js_base') %>
<hc-connect></hc-connect>
<script>
import("<%= latestLauncherJS %>");
window.latestJS = true;
</script>
<script>
if (!window.latestJS) {
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>");
};
<% } else { %>
_ls("<%= es5LauncherJS %>");
<% } %>
}
</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-57927901-9', 'auto');
ga('send', 'pageview', location.pathname.includes("auth_callback") === -1 ? location.pathname : "/");
</script>
</body>
</html>

View File

@@ -22,14 +22,25 @@
</script>
</head>
<body>
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
<%= renderTemplate('_js_base') %>
<cast-media-player></cast-media-player>
<script>
<% for (const entry of latestEntryJS) { %>
import("<%= entry %>");
<% } %>
import("<%= latestMediaJS %>");
window.latestJS = true;
</script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script>
if (!window.latestJS) {
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5MediaJS %>");
};
<% } else { %>
_ls("<%= es5MediaJS %>");
<% } %>
}
</script>
</body>
</html>

View File

@@ -1,10 +1,8 @@
<!DOCTYPE html>
<html>
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
<% for (const entry of latestEntryJS) { %>
<script type="module" src="<%= entry %>"></script>
<% } %>
<%= renderTemplate("../../../src/html/_style_base.html.template") %>
<script type="module" src="<%= latestReceiverJS %>"></script>
<%= renderTemplate('_style_base') %>
<style>
body {
background-color: white;

View File

@@ -88,7 +88,7 @@ class HcCast extends LitElement {
>
${(this.lovelaceConfig
? this.lovelaceConfig.views
: [generateDefaultViewConfig({}, {}, {}, {}, () => "")]
: [generateDefaultViewConfig([], [], [], {}, () => "")]
).map(
(view, idx) => html`
<paper-icon-item
@@ -181,7 +181,7 @@ class HcCast extends LitElement {
private async _handlePickView(ev: Event) {
const path = (ev.currentTarget as any).getAttribute("data-path");
await ensureConnectedCastSession(this.castManager!, this.auth!);
castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
castSendShowLovelaceView(this.castManager, path);
}
private async _handleLogout() {

View File

@@ -22,11 +22,7 @@ class HcLayout extends LitElement {
return html`
<ha-card>
<div class="layout">
<img
class="hero"
alt="A Google Nest Hub with a Home Assistant dashboard on its screen"
src="/images/google-nest-hub.png"
/>
<img class="hero" src="/images/google-nest-hub.png" />
<h1 class="card-header">
Home Assistant Cast${this.subtitle ? ` ${this.subtitle}` : ""}
${this.auth
@@ -48,7 +44,7 @@ class HcLayout extends LitElement {
<div class="footer">
<a href="./faq.html">Frequently Asked Questions</a> Found a bug?
<a
href="https://github.com/home-assistant/frontend/issues"
href="https://github.com/home-assistant/home-assistant-polymer/issues"
target="_blank"
>Let us know!</a
>

View File

@@ -1,4 +1,4 @@
import { html, nothing } from "lit";
import { html, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { mockHistory } from "../../../../demo/src/stubs/history";
import { LovelaceConfig } from "../../../../src/data/lovelace";
@@ -18,9 +18,9 @@ class HcDemo extends HassElement {
@state() private _lovelaceConfig?: LovelaceConfig;
protected render() {
protected render(): TemplateResult {
if (!this._lovelaceConfig) {
return nothing;
return html``;
}
return html`
<hc-lovelace

View File

@@ -12,7 +12,6 @@ class HcLaunchScreen extends LitElement {
return html`
<div class="container">
<img
alt="Home Assistant logo on left, Nabu Casa logo on right, and red heart in center"
src="https://www.home-assistant.io/images/blog/2018-09-thinking-big/social.png"
/>
<div class="status">

View File

@@ -33,6 +33,7 @@ import { castContext } from "../cast_context";
import "./hc-launch-screen";
let resourcesLoaded = false;
@customElement("hc-main")
export class HcMain extends HassElement {
@state() private _showDemo = false;
@@ -45,8 +46,6 @@ export class HcMain extends HassElement {
@state() private _urlPath?: string | null;
private _hassUUID?: string;
private _unsubLovelace?: UnsubscribeFunc;
public processIncomingMessage(msg: HassMessage) {
@@ -126,7 +125,6 @@ export class HcMain extends HassElement {
if (this.hass) {
status.hassUrl = this.hass.auth.data.hassUrl;
status.hassUUID = this._hassUUID;
status.lovelacePath = this._lovelacePath;
status.urlPath = this._urlPath;
}
@@ -165,18 +163,6 @@ export class HcMain extends HassElement {
};
private async _handleGetStatusMessage(msg: GetStatusMessage) {
if (
(this.hass && msg.hassUUID && msg.hassUUID !== this._hassUUID) ||
(this.hass && msg.hassUrl && msg.hassUrl !== this.hass.auth.data.hassUrl)
) {
this._error = "Not connected to the same Home Assistant instance.";
this._sendError(
ReceiverErrorCode.WRONG_INSTANCE,
this._error,
msg.senderId!
);
}
this._sendStatus(msg.senderId!);
}
@@ -193,7 +179,6 @@ export class HcMain extends HassElement {
expires_in: 0,
}),
});
this._hassUUID = msg.hassUUID;
} catch (err: any) {
const errorMessage = this._getErrorMessage(err);
this._error = errorMessage;
@@ -224,50 +209,14 @@ export class HcMain extends HassElement {
if (!this.hass) {
this._sendStatus(msg.senderId!);
this._error = "Cannot show Lovelace because we're not connected.";
this._sendError(
ReceiverErrorCode.NOT_CONNECTED,
this._error,
msg.senderId!
);
this._sendError(ReceiverErrorCode.NOT_CONNECTED, this._error);
return;
}
if (
(msg.hassUUID && msg.hassUUID !== this._hassUUID) ||
(msg.hassUrl && msg.hassUrl !== this.hass.auth.data.hassUrl)
) {
this._sendStatus(msg.senderId!);
this._error =
"Cannot show Lovelace because we're not connected to the same Home Assistant instance.";
this._sendError(
ReceiverErrorCode.WRONG_INSTANCE,
this._error,
msg.senderId!
);
return;
}
this._error = undefined;
if (msg.urlPath === "lovelace") {
msg.urlPath = null;
}
this._lovelacePath = msg.viewPath;
if (msg.urlPath === "energy") {
this._lovelaceConfig = {
views: [
{
strategy: {
type: "energy",
options: { show_date_selection: true },
},
},
],
};
this._urlPath = "energy";
this._lovelacePath = 0;
this._sendStatus();
return;
}
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
this._urlPath = msg.urlPath;
this._lovelaceConfig = undefined;

View File

@@ -1,3 +1,4 @@
import "web-animations-js/web-animations-next-lite.min";
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto";
import "./layout/hc-lovelace";

View File

@@ -1,8 +1,8 @@
import webpack from "../build-scripts/webpack.cjs";
import env from "../build-scripts/env.cjs";
const { createCastConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
export default webpack.createCastConfig({
isProdBuild: env.isProdBuild(),
isStatsBuild: env.isStatsBuild(),
module.exports = createCastConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
latestBuild: true,
});

View File

@@ -1,5 +1,5 @@
import rollup from "../build-scripts/rollup.cjs";
import env from "../build-scripts/env.cjs";
const rollup = require("../build-scripts/rollup.js");
const env = require("../build-scripts/env.js");
const config = rollup.createDemoConfig({
isProdBuild: env.isProdBuild(),
@@ -7,4 +7,4 @@ const config = rollup.createDemoConfig({
isStatsBuild: env.isStatsBuild(),
});
export default { ...config.inputOptions, output: config.outputOptions };
module.exports = { ...config.inputOptions, output: config.outputOptions };

View File

@@ -6,9 +6,6 @@ set -e
cd "$(dirname "$0")/.."
export STATS=1
statsfile="compilation-stats-demo.json"
./node_modules/.bin/webpack-cli --profile --node-env=production --json=$statsfile
npx webpack-bundle-analyzer $statsfile dist/frontend_latest
rm -f $statsfile
STATS=1 NODE_ENV=production ../node_modules/.bin/webpack --profile --json > compilation-stats.json
npx webpack-bundle-analyzer compilation-stats.json dist/frontend_latest
rm compilation-stats.json

View File

@@ -508,7 +508,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
origin_addresses: ["XYZ"],
status: "OK",
mode: "driving",
units: "us_customary",
units: "imperial",
duration_in_traffic: "41 mins",
duration: "44 mins",
distance: "34.3 mi",
@@ -527,7 +527,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
origin_addresses: ["XYZ"],
status: "OK",
mode: "driving",
units: "us_customary",
units: "imperial",
duration_in_traffic: "37 mins",
duration: "37 mins",
distance: "30.2 mi",

View File

@@ -194,7 +194,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
type: "state-icon",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "group.downstairs_lights",
},
service: "homeassistant.toggle",
@@ -1196,7 +1196,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
left: "15%",
},
type: "state-icon",
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
entity: "binary_sensor.water_leak_sensor_158d0002338651",
},
{
prefix: "Kitchen: ",
@@ -1206,7 +1206,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
top: "89%",
left: "32%",
},
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
entity: "binary_sensor.water_leak_sensor_158d0002338651",
},
{
style: {
@@ -1215,7 +1215,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
left: "60%",
},
type: "state-icon",
entity: "binary_sensor.water_leak_sensor_158d0002338651",
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
},
{
prefix: "Bathroom: ",
@@ -1225,7 +1225,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
top: "89%",
left: "77%",
},
entity: "binary_sensor.water_leak_sensor_158d0002338651",
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
},
],
type: "picture-elements",

View File

@@ -59,7 +59,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
hidden: true,
radius: 50,
friendly_name: "School",
friendly_name: "Skolan",
icon: "mdi:school",
},
},
@@ -137,7 +137,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "73",
attributes: {
unit_of_measurement: "%",
friendly_name: "Oskar battery",
friendly_name: "oskar batteri",
device_class: "battery",
},
},
@@ -146,7 +146,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "88",
attributes: {
unit_of_measurement: "%",
friendly_name: "Bella battery",
friendly_name: "bella batteri",
device_class: "battery",
},
},
@@ -154,7 +154,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.unifi_camera",
state: "off",
attributes: {
friendly_name: "Motion sensor camera",
friendly_name: "R\u00f6relsesensor kamera",
icon: "mdi:walk",
},
},
@@ -707,7 +707,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
},
],
cloudiness: 25,
friendly_name: "Weather",
friendly_name: "V\u00e4der",
},
},
"binary_sensor.ubiquiti_switch": {
@@ -731,7 +731,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
round_trip_time_max: "0.626",
round_trip_time_mdev: "",
round_trip_time_min: "0.358",
friendly_name: "Entrance camera",
friendly_name: "Entr\u00e9 kamera",
device_class: "connectivity",
icon: "mdi:cctv",
},
@@ -797,7 +797,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 34,
on: true,
friendly_name: "Porch motion sensor",
friendly_name: "altan_motion_sensor",
device_class: "motion",
},
},
@@ -807,7 +807,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 88,
on: true,
friendly_name: "Back door sensor",
friendly_name: "Altand\u00f6rren sensor",
device_class: "opening",
icon: "mdi:door",
},
@@ -818,7 +818,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 74,
on: true,
friendly_name: "Bathroom motion sensor",
friendly_name: "badrumssensor",
device_class: "motion",
},
},
@@ -829,7 +829,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47,
on: true,
dark: true,
friendly_name: "Basement motion sensor",
friendly_name: "R\u00f6relsesensor k\u00e4llaren 1",
device_class: "motion",
icon: "mdi:walk",
},
@@ -841,7 +841,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60,
on: true,
dark: true,
friendly_name: "Laundy room motion sensor",
friendly_name: "R\u00f6relsesensor tv\u00e4ttstugan",
device_class: "motion",
icon: "mdi:walk",
},
@@ -863,7 +863,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 60,
on: true,
friendly_name: "Pantry motion sensor",
friendly_name: "R\u00f6relsesensor skafferiet",
device_class: "motion",
icon: "mdi:walk",
},
@@ -875,7 +875,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60,
on: true,
dark: true,
friendly_name: "Stair motion sensor",
friendly_name: "R\u00f6relsesensor k\u00e4llaren 2",
device_class: "motion",
icon: "mdi:walk",
},
@@ -887,7 +887,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47,
on: true,
dark: true,
friendly_name: "Bench sensor",
friendly_name: "B\u00e4nksensor",
device_class: "motion",
},
},

View File

@@ -277,7 +277,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
],
show_header_toggle: false,
type: "entities",
title: "Bandwidth",
title: "Bandbredd",
},
// {
// title: "Updater",

View File

@@ -377,7 +377,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.air_cleaner_quiet",
},
service: "script.turn_on",
@@ -390,7 +390,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.air_cleaner_auto",
},
service: "script.turn_on",
@@ -403,7 +403,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.air_cleaner_turbo",
},
service: "script.turn_on",
@@ -416,7 +416,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.ac_off",
},
service: "script.turn_on",
@@ -429,7 +429,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.ac_on",
},
service: "script.turn_on",
@@ -629,7 +629,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.morning_lights",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "scene.morning_lights",
},
service: "scene.turn_on",
@@ -641,7 +641,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.movie_time",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "scene.movie_time",
},
service: "scene.turn_on",
@@ -702,7 +702,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.downstairs_lights",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "light.downstairs_lights",
},
service: "light.toggle",
@@ -714,7 +714,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.upstairs_lights",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "light.upstairs_lights",
},
service: "light.toggle",

View File

@@ -138,7 +138,7 @@ if (!window.cardTools) {
return cardTools.createThing("row", config);
const domain = config.entity.split(".", 1)[0];
Object.assign(config, { type: DEFAULT_ROWS[domain] || "simple" });
Object.assign(config, { type: DEFAULT_ROWS[domain] || "text" });
return cardTools.createThing("entity-row", config);
};

View File

@@ -1,5 +1,5 @@
import { mdiTelevision } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { CastManager } from "../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../src/cast/receiver_messages";
@@ -20,12 +20,12 @@ class CastDemoRow extends LitElement implements LovelaceRow {
// No config possible.
}
protected render() {
protected render(): TemplateResult {
if (
!this._castManager ||
this._castManager.castState === "NO_DEVICES_AVAILABLE"
) {
return nothing;
return html``;
}
return html`
<ha-svg-icon .path=${mdiTelevision}></ha-svg-icon>

View File

@@ -1,6 +1,6 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { property, state } from "lit/decorators";
import { until } from "lit/directives/until";
import "../../../src/components/ha-card";
import "../../../src/components/ha-circular-progress";
@@ -14,7 +14,6 @@ import {
setDemoConfig,
} from "../configs/demo-configs";
@customElement("ha-demo-card")
export class HADemoCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public lovelace?: Lovelace;
@@ -30,9 +29,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
public setConfig(_config: LovelaceCardConfig) {}
protected render() {
protected render(): TemplateResult {
if (this._hidden) {
return nothing;
return html``;
}
return html`
<ha-card>
@@ -155,3 +154,5 @@ declare global {
"ha-demo-card": HADemoCard;
}
}
customElements.define("ha-demo-card", HADemoCard);

View File

@@ -1,6 +1,5 @@
// Compat needs to be first import
import "../../src/resources/compatibility";
import { customElement } from "lit/decorators";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate";
import {
@@ -11,24 +10,21 @@ import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
import { HomeAssistant } from "../../src/types";
import { selectedDemoConfig } from "./configs/demo-configs";
import { mockAuth } from "./stubs/auth";
import { mockConfigEntries } from "./stubs/config_entries";
import { mockEnergy } from "./stubs/energy";
import { energyEntities } from "./stubs/entities";
import { mockEntityRegistry } from "./stubs/entity_registry";
import { mockEvents } from "./stubs/events";
import { mockFrontend } from "./stubs/frontend";
import { mockHistory } from "./stubs/history";
import { mockLovelace } from "./stubs/lovelace";
import { mockMediaPlayer } from "./stubs/media_player";
import { mockPersistentNotification } from "./stubs/persistent_notification";
import { mockRecorder } from "./stubs/recorder";
import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
@customElement("ha-demo")
export class HaDemo extends HomeAssistantAppEl {
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
const initial: Partial<MockHomeAssistant> = {
panelUrl: (this as any)._panelUrl,
@@ -48,7 +44,6 @@ export class HaDemo extends HomeAssistantAppEl {
mockAuth(hass);
mockTranslations(hass);
mockHistory(hass);
mockRecorder(hass);
mockShoppingList(hass);
mockSystemLog(hass);
mockTemplate(hass);
@@ -56,42 +51,8 @@ export class HaDemo extends HomeAssistantAppEl {
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass);
mockConfigEntries(hass);
mockEntityRegistry(hass, [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
unique_id: "co2_intensity",
options: null,
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
hidden_by: null,
entity_category: null,
has_entity_name: false,
unique_id: "grid_fossil_fuel_percentage",
options: null,
},
]);
hass.addEntities(energyEntities());
@@ -125,8 +86,4 @@ export class HaDemo extends HomeAssistantAppEl {
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-demo": HaDemo;
}
}
customElements.define("ha-demo", HaDemo);

View File

@@ -1,26 +0,0 @@
<meta property="fb:app_id" content="338291289691179" />
<meta property="og:title" content="Home Assistant Demo" />
<meta property="og:site_name" content="Home Assistant" />
<meta property="og:url" content="https://demo.home-assistant.io/" />
<meta property="og:type" content="website" />
<meta
property="og:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
property="og:image"
content="https://www.home-assistant.io/images/default-social.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@home_assistant" />
<meta name="twitter:title" content="Home Assistant" />
<meta
name="twitter:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
name="twitter:image"
content="https://www.home-assistant.io/images/default-social.png"
/>

View File

@@ -1,8 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>Home Assistant Demo</title>
<%= renderTemplate("../../../src/html/_header.html.template") %>
<meta charset="utf-8" />
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="icon" href="/static/icons/favicon.ico" />
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" />
<link
rel="apple-touch-icon"
@@ -34,7 +35,33 @@
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#03a9f4" />
<%= renderTemplate("_social_meta.html.template") %>
<meta property="fb:app_id" content="338291289691179" />
<meta property="og:title" content="Home Assistant Demo" />
<meta property="og:site_name" content="Home Assistant" />
<meta property="og:url" content="https://demo.home-assistant.io/" />
<meta property="og:type" content="website" />
<meta
property="og:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
property="og:image"
content="https://www.home-assistant.io/images/default-social.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@home_assistant" />
<meta name="twitter:title" content="Home Assistant" />
<meta
name="twitter:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
name="twitter:image"
content="https://www.home-assistant.io/images/default-social.png"
/>
<title>Home Assistant Demo</title>
<style>
html {
background-color: var(--primary-background-color, #fafafa);
@@ -80,22 +107,29 @@
</svg>
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
</div>
<ha-demo></ha-demo>
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %>
<%= renderTemplate('_js_base') %>
<%= renderTemplate('_preload_roboto') %>
<script>
if (!window.globalThis) {
window.globalThis = window;
}
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
if (!isS11_12) {
<% for (const entry of latestEntryJS) { %>
import("<%= entry %>");
import("<%= latestDemoJS %>");
window.latestJS = true;
</script>
<script>
if (!window.latestJS) {
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5DemoJS %>");
};
<% } else { %>
_ls("<%= es5DemoJS %>");
<% } %>
window.latestJS = true;
}
</script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script>
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
(function (d, t) {

41
demo/src/stubs/config.ts Normal file
View File

@@ -0,0 +1,41 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
hass.mockWS("config/entity_registry/list", () => [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
},
]);
};

View File

@@ -1,20 +0,0 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfigEntries = (hass: MockHomeAssistant) => {
hass.mockWS("config_entries/get", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_remove_device: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
};

View File

@@ -1,101 +1,90 @@
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
import {
EnergyInfo,
EnergyPreferences,
EnergySolarForecasts,
FossilEnergyConsumption,
} from "../../../src/data/energy";
import { format, startOfToday, startOfTomorrow } from "date-fns";
import { EnergySolarForecasts } from "../../../src/data/energy";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEnergy = (hass: MockHomeAssistant) => {
hass.mockWS(
"energy/get_prefs",
(): EnergyPreferences => ({
energy_sources: [
{
type: "grid",
flow_from: [
{
stat_energy_from: "sensor.energy_consumption_tarif_1",
stat_cost: "sensor.energy_consumption_tarif_1_cost",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_from: "sensor.energy_consumption_tarif_2",
stat_cost: "sensor.energy_consumption_tarif_2_cost",
entity_energy_price: null,
number_energy_price: null,
},
],
flow_to: [
{
stat_energy_to: "sensor.energy_production_tarif_1",
stat_compensation:
"sensor.energy_production_tarif_1_compensation",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_to: "sensor.energy_production_tarif_2",
stat_compensation:
"sensor.energy_production_tarif_2_compensation",
entity_energy_price: null,
number_energy_price: null,
},
],
cost_adjustment_day: 0,
},
{
type: "solar",
stat_energy_from: "sensor.solar_production",
config_entry_solar_forecast: ["solar_forecast"],
},
/* {
type: "battery",
stat_energy_from: "sensor.battery_output",
stat_energy_to: "sensor.battery_input",
}, */
{
type: "gas",
stat_energy_from: "sensor.energy_gas",
stat_cost: "sensor.energy_gas_cost",
entity_energy_price: null,
number_energy_price: null,
},
],
device_consumption: [
{
stat_consumption: "sensor.energy_car",
},
{
stat_consumption: "sensor.energy_ac",
},
{
stat_consumption: "sensor.energy_washing_machine",
},
{
stat_consumption: "sensor.energy_dryer",
},
{
stat_consumption: "sensor.energy_heat_pump",
},
{
stat_consumption: "sensor.energy_boiler",
},
],
})
);
hass.mockWS(
"energy/info",
(): EnergyInfo => ({ cost_sensors: {}, solar_forecast_domains: [] })
);
hass.mockWS(
"energy/fossil_energy_consumption",
({ period }): FossilEnergyConsumption => ({
start: period === "month" ? 250 : period === "day" ? 10 : 2,
})
);
hass.mockWS("energy/get_prefs", () => ({
energy_sources: [
{
type: "grid",
flow_from: [
{
stat_energy_from: "sensor.energy_consumption_tarif_1",
stat_cost: "sensor.energy_consumption_tarif_1_cost",
entity_energy_from: "sensor.energy_consumption_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_from: "sensor.energy_consumption_tarif_2",
stat_cost: "sensor.energy_consumption_tarif_2_cost",
entity_energy_from: "sensor.energy_consumption_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
flow_to: [
{
stat_energy_to: "sensor.energy_production_tarif_1",
stat_compensation: "sensor.energy_production_tarif_1_compensation",
entity_energy_to: "sensor.energy_production_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_to: "sensor.energy_production_tarif_2",
stat_compensation: "sensor.energy_production_tarif_2_compensation",
entity_energy_to: "sensor.energy_production_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
cost_adjustment_day: 0,
},
{
type: "solar",
stat_energy_from: "sensor.solar_production",
config_entry_solar_forecast: ["solar_forecast"],
},
/* {
type: "battery",
stat_energy_from: "sensor.battery_output",
stat_energy_to: "sensor.battery_input",
}, */
{
type: "gas",
stat_energy_from: "sensor.energy_gas",
stat_cost: "sensor.energy_gas_cost",
entity_energy_from: "sensor.energy_gas",
entity_energy_price: null,
number_energy_price: null,
},
],
device_consumption: [
{
stat_consumption: "sensor.energy_car",
},
{
stat_consumption: "sensor.energy_ac",
},
{
stat_consumption: "sensor.energy_washing_machine",
},
{
stat_consumption: "sensor.energy_dryer",
},
{
stat_consumption: "sensor.energy_heat_pump",
},
{
stat_consumption: "sensor.energy_boiler",
},
],
}));
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
hass.mockWS("energy/fossil_energy_consumption", ({ period }) => ({
start: period === "month" ? 250 : period === "day" ? 10 : 2,
}));
const todayString = format(startOfToday(), "yyyy-MM-dd");
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
hass.mockWS(

View File

@@ -4,6 +4,4 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEntityRegistry = (
hass: MockHomeAssistant,
data: EntityRegistryEntry[] = []
) => {
hass.mockWS("config/entity_registry/list", () => data);
};
) => hass.mockWS("config/entity_registry/list", () => data);

Some files were not shown because too many files have changed in this diff Show More