Compare commits

..

3 Commits

Author SHA1 Message Date
Paulus Schoutsen
cdf53773c5 Align layout of all cards 2021-04-16 11:47:56 -07:00
Paulus Schoutsen
3174037c54 One more fix 2021-04-16 11:07:17 -07:00
Paulus Schoutsen
4af060890a Fixes for integration cards 2021-04-16 10:49:24 -07:00
1475 changed files with 78482 additions and 152872 deletions

View File

@@ -16,9 +16,6 @@
"runem.lit-plugin", "runem.lit-plugin",
"ms-python.vscode-pylance" "ms-python.vscode-pylance"
], ],
"containerEnv": {
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
},
"settings": { "settings": {
"terminal.integrated.shell.linux": "/bin/bash", "terminal.integrated.shell.linux": "/bin/bash",
"files.eol": "\n", "files.eol": "\n",

View File

@@ -1,11 +1,11 @@
{ {
"extends": [ "extends": [
"airbnb-base",
"airbnb-typescript/base", "airbnb-typescript/base",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:wc/recommended", "plugin:wc/recommended",
"plugin:lit/all", "plugin:lit/recommended",
"prettier" "prettier",
"prettier/@typescript-eslint"
], ],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
@@ -29,91 +29,64 @@
"__BUILD__": false, "__BUILD__": false,
"__VERSION__": false, "__VERSION__": false,
"__STATIC_PATH__": false, "__STATIC_PATH__": false,
"__SUPERVISOR__": false, "Polymer": true,
"Polymer": true "webkitSpeechRecognition": false,
"ResizeObserver": false
}, },
"env": { "env": {
"browser": true, "browser": true,
"es6": true "es6": true
}, },
"rules": { "rules": {
"class-methods-use-this": "off", "class-methods-use-this": 0,
"new-cap": "off", "new-cap": 0,
"prefer-template": "off", "prefer-template": 0,
"object-shorthand": "off", "object-shorthand": 0,
"func-names": "off", "func-names": 0,
"no-underscore-dangle": "off", "prefer-arrow-callback": 0,
"strict": "off", "no-underscore-dangle": 0,
"no-plusplus": "off", "strict": 0,
"no-bitwise": "error", "prefer-spread": 0,
"comma-dangle": "off", "no-plusplus": 0,
"vars-on-top": "off", "no-bitwise": 2,
"no-continue": "off", "comma-dangle": 0,
"no-param-reassign": "off", "vars-on-top": 0,
"no-multi-assign": "off", "no-continue": 0,
"no-console": "error", "no-param-reassign": 0,
"radix": "off", "no-multi-assign": 0,
"no-alert": "off", "no-console": 2,
"no-nested-ternary": "off", "radix": 0,
"prefer-destructuring": "off", "no-alert": 0,
"no-return-await": 0,
"no-nested-ternary": 0,
"prefer-destructuring": 0,
"no-restricted-globals": [2, "event"], "no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": "off", "prefer-promise-reject-errors": 0,
"import/prefer-default-export": "off", "import/order": 0,
"import/no-default-export": "off", "import/prefer-default-export": 0,
"import/no-unresolved": "off", "import/no-unresolved": 0,
"import/no-cycle": "off", "import/no-cycle": 0,
"import/extensions": [ "import/extensions": [
"error", 2,
"ignorePackages", "ignorePackages",
{ "ts": "never", "js": "never" } { "ts": "never", "js": "never" }
], ],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"], "no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": "off", "object-curly-newline": 0,
"default-case": "off", "default-case": 0,
"wc/no-self-class": "off", "wc/no-self-class": 0,
"no-shadow": "off", "no-shadow": 0,
"@typescript-eslint/camelcase": "off", "@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-shadow": ["error"], "@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/naming-convention": [ "lit/attribute-value-entities": 0
"off",
{
"selector": "default",
"format": ["camelCase", "snake_case"],
"leadingUnderscore": "allow",
"trailingUnderscore": "allow"
}, },
{ "plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
"selector": ["variable"],
"format": ["camelCase", "snake_case", "UPPER_CASE"],
"leadingUnderscore": "allow",
"trailingUnderscore": "allow"
},
{
"selector": "typeLike",
"format": ["PascalCase"]
}
],
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-vars": [
"error",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
],
"unused-imports/no-unused-imports": "error",
"lit/attribute-value-entities": "off",
"lit/no-template-map": "off"
},
"plugins": ["disable", "unused-imports"],
"processor": "disable/disable" "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 Provide details about the versions you are using, which helps us reproducing
and finding the issue quicker. Version information is found in the 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 Browser version and operating system is important! Please try to replicate
your issue in a different browser and be sure to include your findings. your issue in a different browser and be sure to include your findings.

View File

@@ -1,6 +1,8 @@
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. about: Report an issue related to the Home Assistant frontend.
labels: bug labels: bug
title: ""
issue_body: true
body: body:
- type: markdown - type: markdown
attributes: attributes:
@@ -9,7 +11,7 @@ body:
If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue. If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue.
**Please not not report issues for custom cards.** **Please not not report issues for custom Lovelace cards.**
[fr]: https://github.com/home-assistant/frontend/discussions [fr]: https://github.com/home-assistant/frontend/discussions
[releases]: https://github.com/home-assistant/home-assistant/releases [releases]: https://github.com/home-assistant/home-assistant/releases
@@ -64,7 +66,7 @@ body:
label: What version of Home Assistant Core has the issue? label: What version of Home Assistant Core has the issue?
placeholder: core- placeholder: core-
description: > description: >
Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/). Can be found in the Configuration panel -> Info.
- type: input - type: input
attributes: attributes:
label: What was the last working version of Home Assistant Core? label: What was the last working version of Home Assistant Core?
@@ -95,7 +97,11 @@ body:
If your issue is about how an entity is shown in the UI, please add the If your issue is about how an entity is shown in the UI, please add the
state and attributes for all situations. You can find this information state and attributes for all situations. You can find this information
at Developer Tools -> States. at Developer Tools -> States.
render: txt value: |
```yaml
# Paste your state here.
```
- type: textarea - type: textarea
attributes: attributes:
label: Problem-relevant frontend configuration label: Problem-relevant frontend configuration
@@ -104,18 +110,29 @@ body:
configuration of the used cards. Fill this out even if it seems configuration of the used cards. Fill this out even if it seems
unimportant to you. Please be sure to remove personal information like unimportant to you. Please be sure to remove personal information like
passwords, private URLs and other credentials. passwords, private URLs and other credentials.
render: yaml value: |
```yaml
# Paste your YAML here.
```
- type: textarea - type: textarea
attributes: attributes:
label: Javascript errors shown in your browser console/inspector label: Javascript errors shown in your browser console/inspector
description: > description: >
If you come across any Javascript or other error logs, e.g., in your If you come across any Javascript or other error logs, e.g., in your
browser console/inspector please provide them. browser console/inspector please provide them.
render: txt value: |
- type: textarea ```txt
# Paste your logs here.
```
- type: markdown
attributes: attributes:
label: Additional information value: |
description: > ## Additional information
- type: markdown
attributes:
value: |
If you have any additional information for us, use the field below. If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here, by Please note, you can attach screenshots or screen recordings here,
dragging and dropping files in the field below. by dragging and dropping files in the field below.

View File

@@ -1,17 +1,17 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: 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 url: https://github.com/home-assistant/frontend/discussions/category_choices
about: Request an new feature for the Home Assistant frontend. 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 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 - name: Report incorrect or missing information on our website
url: https://github.com/home-assistant/home-assistant.io/issues 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. about: Our documentation has its own issue tracker. Please report issues with the website there.
- name: I have a question or need support - name: I have a question or need support
url: https://www.home-assistant.io/help 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 - name: I'm unsure where to go
url: https://www.home-assistant.io/join-chat url: https://www.home-assistant.io/join-chat
about: If you are unsure where to go, then joining our chat is recommended; Just ask! about: If you are unsure where to go, then joining our chat is recommended; Just ask!

View File

@@ -10,64 +10,81 @@ on:
- dev - dev
- master - master
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:
CI: true CI: true
- name: Build resources - name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos
- name: Run eslint - name: Run eslint
run: yarn run lint:eslint run: ./node_modules/.bin/eslint '{**/src,src}/**/*.{js,ts,html}' --ignore-path .gitignore
- name: Run tsc - name: Run tsc
run: yarn run lint:types run: ./node_modules/.bin/tsc
- name: Run prettier
run: yarn run lint:prettier
- name: Check for duplicate dependencies
run: yarn dedupe --check
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:
CI: true CI: true
- name: Build resources - name: Run Mocha
run: ./node_modules/.bin/gulp build-translations build-locale-data run: npm run mocha
- name: Run Tests
run: yarn run test
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint, test] needs: [lint, test]
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:
@@ -82,11 +99,20 @@ jobs:
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:

View File

@@ -4,22 +4,26 @@ on:
push: push:
branches: branches:
- dev - dev
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:

View File

@@ -15,5 +15,5 @@ jobs:
- name: Trigger Demo build - name: Trigger Demo build
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }} run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }}
- name: Trigger Design build - name: Trigger Gallery build
run: curl -X POST -d "NIGHTLY" https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }} run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }}

View File

@@ -6,22 +6,14 @@ on:
- published - published
env: env:
PYTHON_VERSION: 3.8 WHEELS_TAG: 3.7-alpine3.11
NODE_VERSION: 14 PYTHON_VERSION: 3.7
NODE_OPTIONS: --max_old_space_size=6144 NODE_VERSION: 12.1
# 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: jobs:
release: release:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write # Required to upload release assets
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 uses: actions/checkout@v2
@@ -38,30 +30,15 @@ jobs:
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ env.NODE_VERSION }} 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: Build and release package - name: Build and release package
run: | run: |
python3 -m pip install twine build python3 -m pip install twine
export TWINE_USERNAME="__token__" export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}" export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
script/release script/release
- name: Upload release assets
uses: softprops/action-gh-release@v0.1.14
with:
files: |
dist/*.whl
dist/*.tar.gz
wheels-init: wheels-init:
name: Init wheels build name: Init wheels build
needs: release needs: release
@@ -87,8 +64,6 @@ jobs:
strategy: strategy:
matrix: matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"] arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.9-alpine3.14"
steps: steps:
- name: Download requirements.txt - name: Download requirements.txt
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
@@ -98,7 +73,7 @@ jobs:
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@master uses: home-assistant/wheels@master
with: with:
tag: ${{ matrix.tag }} tag: ${{ env.WHEELS_TAG }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }} wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }} wheels-key: ${{ secrets.WHEELS_KEY }}

View File

@@ -1,6 +1,8 @@
name: Translations name: Translations
on: on:
schedule:
- cron: "30 0 * * *"
push: push:
branches: branches:
- dev - dev
@@ -8,7 +10,7 @@ on:
- src/translations/en.json - src/translations/en.json
env: env:
NODE_VERSION: 14 NODE_VERSION: 12
jobs: jobs:
upload: upload:
@@ -18,8 +20,46 @@ jobs:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Upload Translations - name: Upload Translations
run: | run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}" export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base ./script/translations_upload_base
download:
name: Download
needs: upload
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
npm install
./script/translations_download
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
with:
name: GitHub Action
email: github-action@users.noreply.github.com
- name: Update translation
run: |
git add translations
git commit -am "Translation update"
git push

28
.gitignore vendored
View File

@@ -1,22 +1,10 @@
.DS_Store
.reify-cache
# build
build build
hass_frontend/* build-translations/*
dist
# yarn
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
node_modules/* node_modules/*
yarn-error.log
npm-debug.log npm-debug.log
.DS_Store
hass_frontend/*
.reify-cache
# Python stuff # Python stuff
*.py[cod] *.py[cod]
@@ -26,8 +14,11 @@ npm-debug.log
# venv stuff # venv stuff
pyvenv.cfg pyvenv.cfg
pip-selfcheck.json pip-selfcheck.json
venv/* venv
.venv .venv
lib
bin
dist
# vscode # vscode
.vscode/* .vscode/*
@@ -40,8 +31,9 @@ src/cast/dev_const.ts
# Secrets # Secrets
.lokalise_token .lokalise_token
yarn-error.log
# asdf #asdf
.tool-versions .tool-versions
# Home Assistant config # Home Assistant config

2
.nvmrc
View File

@@ -1 +1 @@
14 12.1

View File

@@ -1,4 +1,5 @@
build build
build-translations/*
translations/* translations/*
node_modules/* node_modules/*
hass_frontend/* hass_frontend/*

View File

@@ -1,29 +0,0 @@
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

@@ -1,12 +0,0 @@
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>

View File

@@ -1,34 +0,0 @@
diff --git a/lib/legacy/class.js b/lib/legacy/class.js
index aee2511be1cd9bf900ee552bc98190c1631c57c0..f2f499d68bf52034cac9c28307c99e8ce6b8417d 100644
--- a/lib/legacy/class.js
+++ b/lib/legacy/class.js
@@ -304,17 +304,23 @@ function GenerateClassFromInfo(info, Base, behaviors) {
// only proceed if the generated class' prototype has not been registered.
const generatedProto = PolymerGenerated.prototype;
if (!generatedProto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', generatedProto))) {
- generatedProto.__hasRegisterFinished = true;
+ // make sure legacy lifecycle is called on the *element*'s prototype
+ // and not the generated class prototype; if the element has been
+ // extended, these are *not* the same.
+ const proto = Object.getPrototypeOf(this);
+ // Only set flag when generated prototype itself is registered,
+ // as this element may be extended from, and needs to run `registered`
+ // on all behaviors on the subclass as well.
+ if (proto === generatedProto) {
+ generatedProto.__hasRegisterFinished = true;
+ }
// ensure superclass is registered first.
super._registered();
// copy properties onto the generated class lazily if we're optimizing,
- if (legacyOptimizations) {
+ if (legacyOptimizations && !Object.hasOwnProperty(generatedProto, '__hasCopiedProperties')) {
+ generatedProto.__hasCopiedProperties = true;
copyPropertiesToProto(generatedProto);
}
- // make sure legacy lifecycle is called on the *element*'s prototype
- // and not the generated class prototype; if the element has been
- // extended, these are *not* the same.
- const proto = Object.getPrototypeOf(this);
let list = lifecycle.beforeRegister;
if (list) {
for (let i=0; i < list.length; i++) {

File diff suppressed because one or more lines are too long

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,9 +0,0 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.2.0.cjs

View File

@@ -1,4 +1,5 @@
include README.md include README.md
include LICENSE.md
graft hass_frontend graft hass_frontend
graft hass_frontend_es5 graft hass_frontend_es5
recursive-exclude * *.py[co] recursive-exclude * *.py[co]

View File

@@ -2,7 +2,7 @@
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend. This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
[![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/frontend/master/docs/screenshot.png)](https://demo.home-assistant.io/) [![Screenshot of the frontend](https://raw.githubusercontent.com/home-assistant/home-assistant-polymer/master/docs/screenshot.png)](https://demo.home-assistant.io/)
- [View demo of Home Assistant](https://demo.home-assistant.io/) - [View demo of Home Assistant](https://demo.home-assistant.io/)
- [More information about Home Assistant](https://home-assistant.io) - [More information about Home Assistant](https://home-assistant.io)

View File

@@ -1,170 +0,0 @@
/* 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.
module.exports = function inlineConstants(babel, options, cwd) {
const t = babel.types;
if (!Array.isArray(options.modules)) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `modules` array to be passed"
);
}
if (options.resolveExtensions && !Array.isArray(options.resolveExtensions)) {
throw new TypeError(
"babel-plugin-inline-constants: expected `resolveExtensions` to be an array"
);
}
const ignoreModuleNotFound = options.ignoreModuleNotFound;
const resolveExtensions = options.resolveExtensions;
const hasRelativeModules = options.modules.some(
(module) => module.startsWith(".") || module.startsWith("/")
);
const modules = Object.fromEntries(
options.modules.map((module) => {
const absolute = module.startsWith(".")
? require.resolve(module, { paths: [cwd] })
: module;
// eslint-disable-next-line import/no-dynamic-require
return [absolute, require(absolute)];
})
);
const toLiteral = (value) => {
if (typeof value === "string") {
return t.stringLiteral(value);
}
if (typeof value === "number") {
return t.numericLiteral(value);
}
if (typeof value === "boolean") {
return t.booleanLiteral(value);
}
if (value === null) {
return t.nullLiteral();
}
throw new Error(
"babel-plugin-inline-constants: cannot handle non-literal `" + value + "`"
);
};
const resolveAbsolute = (value, state, resolveExtensionIndex) => {
if (!state.filename) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `filename` to be set for files"
);
}
if (resolveExtensions && resolveExtensionIndex !== undefined) {
value += resolveExtensions[resolveExtensionIndex];
}
try {
return require.resolve(value, { paths: [path.dirname(state.filename)] });
} catch (error) {
if (
error.code === "MODULE_NOT_FOUND" &&
resolveExtensions &&
(resolveExtensionIndex === undefined ||
resolveExtensionIndex < resolveExtensions.length - 1)
) {
const resolveExtensionIdx = (resolveExtensionIndex || -1) + 1;
return resolveAbsolute(value, state, resolveExtensionIdx);
}
if (error.code === "MODULE_NOT_FOUND" && ignoreModuleNotFound) {
return undefined;
}
throw error;
}
};
const importDeclaration = (p, state) => {
if (p.node.type !== "ImportDeclaration") {
return;
}
const absolute =
hasRelativeModules && p.node.source.value.startsWith(".")
? resolveAbsolute(p.node.source.value, state)
: p.node.source.value;
if (!absolute || !(absolute in modules)) {
return;
}
const module = modules[absolute];
for (const specifier of p.node.specifiers) {
if (
specifier.type === "ImportDefaultSpecifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!("default" in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access default export from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module.default);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else if (
specifier.type === "ImportSpecifier" &&
specifier.imported &&
specifier.imported.type === "Identifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!(specifier.imported.name in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access `" +
specifier.imported.name +
"` from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module[specifier.imported.name]);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else {
throw new Error("Cannot handle specifier `" + specifier.type + "`");
}
}
p.remove();
};
return {
visitor: {
ImportDeclaration: importDeclaration,
},
};
};

View File

@@ -1,16 +1,17 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
const env = require("./env.js"); const env = require("./env.js");
const paths = require("./paths.js"); const paths = require("./paths.js");
// Files from NPM Packages that should not be imported // Files from NPM Packages that should not be imported
module.exports.ignorePackages = ({ latestBuild }) => [ module.exports.ignorePackages = ({ latestBuild }) => [
// Bloats bundle and it's not used.
path.resolve(require.resolve("moment"), "../locale"),
// Part of yaml.js and only used for !!js functions that we don't use // Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"), require.resolve("esprima"),
]; ];
// Files from NPM packages that we should replace with empty file // Files from NPM packages that we should replace with empty file
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) => module.exports.emptyPackages = ({ latestBuild }) =>
[ [
// Contains all color definitions for all material color sets. // Contains all color definitions for all material color sets.
// We don't use it // We don't use it
@@ -18,8 +19,7 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
require.resolve("@polymer/paper-styles/default-theme.js"), require.resolve("@polymer/paper-styles/default-theme.js"),
// Loads stuff from a CDN // Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"), require.resolve("@polymer/font-roboto/roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/typography.js"), require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
// Compatibility not needed for latest builds // Compatibility not needed for latest builds
latestBuild && latestBuild &&
// wrapped in require.resolve so it blows up if file no longer exists // wrapped in require.resolve so it blows up if file no longer exists
@@ -28,15 +28,6 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
), ),
// This polyfill is loaded in workers to support ES5, filter it out. // This polyfill is loaded in workers to support ES5, filter it out.
latestBuild && require.resolve("proxy-polyfill/src/index.js"), latestBuild && require.resolve("proxy-polyfill/src/index.js"),
// Icons in supervisor conflict with icons in HA so we don't load.
isHassioBuild &&
require.resolve(
path.resolve(paths.polymer_dir, "src/components/ha-icon.ts")
),
isHassioBuild &&
require.resolve(
path.resolve(paths.polymer_dir, "src/components/ha-icon-picker.ts")
),
].filter(Boolean); ].filter(Boolean);
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
@@ -44,7 +35,6 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(env.version()), __VERSION__: JSON.stringify(env.version()),
__DEMO__: false, __DEMO__: false,
__SUPERVISOR__: false,
__BACKWARDS_COMPAT__: false, __BACKWARDS_COMPAT__: false,
__STATIC_PATH__: "/static/", __STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify( "process.env.NODE_ENV": JSON.stringify(
@@ -61,29 +51,17 @@ module.exports.terserOptions = (latestBuild) => ({
module.exports.babelOptions = ({ latestBuild }) => ({ module.exports.babelOptions = ({ latestBuild }) => ({
babelrc: false, babelrc: false,
compact: false,
presets: [ presets: [
!latestBuild && [ !latestBuild && [
"@babel/preset-env", require("@babel/preset-env").default,
{ {
useBuiltIns: "entry", useBuiltIns: "entry",
corejs: "3.15", corejs: "3.6",
bugfixes: true,
}, },
], ],
"@babel/preset-typescript", require("@babel/preset-typescript").default,
].filter(Boolean), ].filter(Boolean),
plugins: [ plugins: [
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/inline-constants-plugin.js"
),
{
modules: ["@mdi/js"],
ignoreModuleNotFound: true,
},
],
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2}) // Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
!latestBuild && [ !latestBuild && [
"@babel/plugin-proposal-object-rest-spread", "@babel/plugin-proposal-object-rest-spread",
@@ -92,21 +70,25 @@ module.exports.babelOptions = ({ latestBuild }) => ({
// Only support the syntax, Webpack will handle it. // Only support the syntax, Webpack will handle it.
"@babel/plugin-syntax-import-meta", "@babel/plugin-syntax-import-meta",
"@babel/plugin-syntax-dynamic-import", "@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-top-level-await",
"@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], [
["@babel/plugin-proposal-private-methods", { loose: true }], require("@babel/plugin-proposal-decorators").default,
["@babel/plugin-proposal-private-property-in-object", { loose: true }], { decoratorsBeforeExport: 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/,
], ],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
].filter(Boolean),
}); });
// Are already ES5, cause warnings when babelified.
module.exports.babelExclude = () => [
require.resolve("@mdi/js/mdi.js"),
require.resolve("hls.js"),
];
const outputPath = (outputRoot, latestBuild) => const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5"); path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
@@ -174,7 +156,6 @@ module.exports.config = {
cast({ isProdBuild, latestBuild }) { cast({ isProdBuild, latestBuild }) {
const entry = { const entry = {
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"), launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"),
}; };
if (latestBuild) { if (latestBuild) {
@@ -205,10 +186,6 @@ module.exports.config = {
publicPath: publicPath(latestBuild, paths.hassio_publicPath), publicPath: publicPath(latestBuild, paths.hassio_publicPath),
isProdBuild, isProdBuild,
latestBuild, latestBuild,
isHassioBuild: true,
defineOverlay: {
__SUPERVISOR__: true,
},
}; };
}, },
@@ -221,9 +198,6 @@ module.exports.config = {
publicPath: publicPath(latestBuild), publicPath: publicPath(latestBuild),
isProdBuild, isProdBuild,
latestBuild, latestBuild,
defineOverlay: {
__DEMO__: true,
},
}; };
}, },
}; };

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const paths = require("./paths.js"); const paths = require("./paths.js");
@@ -26,11 +25,11 @@ module.exports = {
}, },
version() { version() {
const version = fs const version = fs
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8") .readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
.match(/version\W+=\W"(\d{8}\.\d)"/); .match(/\d{8}\.\d+/);
if (!version) { if (!version) {
throw Error("Version not found"); throw Error("Version not found");
} }
return version[1]; return version[0];
}, },
}; };

View File

@@ -5,7 +5,6 @@ const env = require("../env");
require("./clean.js"); require("./clean.js");
require("./translations.js"); require("./translations.js");
require("./locale-data.js");
require("./gen-icons-json.js"); require("./gen-icons-json.js");
require("./gather-static.js"); require("./gather-static.js");
require("./compress.js"); require("./compress.js");
@@ -27,8 +26,7 @@ gulp.task(
"gen-icons-json", "gen-icons-json",
"gen-pages-dev", "gen-pages-dev",
"gen-index-app-dev", "gen-index-app-dev",
"build-translations", "build-translations"
"build-locale-data"
), ),
"copy-static-app", "copy-static-app",
env.useWDS() env.useWDS()
@@ -46,11 +44,11 @@ gulp.task(
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
"clean", "clean",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-app", "copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app", env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
// Don't compress running tests ...// Don't compress running tests
...(env.isTest() ? [] : ["compress-app"]), (env.isTest() ? [] : ["compress-app"]),
gulp.parallel( gulp.parallel(
"gen-pages-prod", "gen-pages-prod",
"gen-index-app-prod", "gen-index-app-prod",

View File

@@ -18,7 +18,7 @@ gulp.task(
}, },
"clean-cast", "clean-cast",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-cast", "copy-static-cast",
"gen-index-cast-dev", "gen-index-cast-dev",
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast" env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
@@ -33,7 +33,7 @@ gulp.task(
}, },
"clean-cast", "clean-cast",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-cast", "copy-static-cast",
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast", env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
"gen-index-cast-prod" "gen-index-cast-prod"

View File

@@ -5,32 +5,32 @@ require("./translations");
gulp.task( gulp.task(
"clean", "clean",
gulp.parallel("clean-translations", () => gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
del([paths.app_output_root, paths.build_dir]) return del([paths.app_output_root, paths.build_dir]);
) })
); );
gulp.task( gulp.task(
"clean-demo", "clean-demo",
gulp.parallel("clean-translations", () => gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
del([paths.demo_output_root, paths.build_dir]) return del([paths.demo_output_root, paths.build_dir]);
) })
); );
gulp.task( gulp.task(
"clean-cast", "clean-cast",
gulp.parallel("clean-translations", () => gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
del([paths.cast_output_root, paths.build_dir]) return del([paths.cast_output_root, paths.build_dir]);
) })
); );
gulp.task("clean-hassio", () => gulp.task("clean-hassio", function cleanOutputAndBuildDir() {
del([paths.hassio_output_root, paths.build_dir]) return del([paths.hassio_output_root, paths.build_dir]);
); });
gulp.task( gulp.task(
"clean-gallery", "clean-gallery",
gulp.parallel("clean-translations", () => gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
del([paths.gallery_output_root, paths.gallery_build, paths.build_dir]) return del([paths.gallery_output_root, paths.build_dir]);
) })
); );

View File

@@ -20,12 +20,7 @@ gulp.task(
}, },
"clean-demo", "clean-demo",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel( gulp.parallel("gen-icons-json", "gen-index-demo-dev", "build-translations"),
"gen-icons-json",
"gen-index-demo-dev",
"build-translations",
"build-locale-data"
),
"copy-static-demo", "copy-static-demo",
env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo" env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo"
) )
@@ -40,7 +35,7 @@ gulp.task(
"clean-demo", "clean-demo",
// Cast needs to be backwards compatible and older HA has no translations // Cast needs to be backwards compatible and older HA has no translations
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-demo", "copy-static-demo",
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo", env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
"gen-index-demo-prod" "gen-index-demo-prod"

View File

@@ -154,15 +154,6 @@ gulp.task("gen-index-cast-dev", (done) => {
contentReceiver 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", { const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: "/frontend_latest/launcher.js", latestLauncherJS: "/frontend_latest/launcher.js",
es5LauncherJS: "/frontend_es5/launcher.js", es5LauncherJS: "/frontend_es5/launcher.js",
@@ -201,15 +192,6 @@ gulp.task("gen-index-cast-prod", (done) => {
contentReceiver 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", { const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: latestManifest["launcher.js"], latestLauncherJS: latestManifest["launcher.js"],
es5LauncherJS: es5Manifest["launcher.js"], es5LauncherJS: es5Manifest["launcher.js"],
@@ -320,23 +302,15 @@ gulp.task("gen-index-hassio-prod", async () => {
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) { function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true }); 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( fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"), path.resolve(paths.hassio_output_root, "entrypoint.js"),
` `
function loadES5() { try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
var el = document.createElement('script'); var el = document.createElement('script');
el.src = '${es5Entrypoint}'; el.src = '${es5Entrypoint}';
document.body.appendChild(el); 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" } { encoding: "utf-8" }

View File

@@ -1,11 +1,7 @@
/* eslint-disable */
// Run demo develop mode // Run demo develop mode
const gulp = require("gulp"); const gulp = require("gulp");
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const { marked } = require("marked");
const glob = require("glob");
const yaml = require("js-yaml");
const env = require("../env"); const env = require("../env");
const paths = require("../paths"); const paths = require("../paths");
@@ -19,129 +15,26 @@ require("./service-worker.js");
require("./entry-html.js"); require("./entry-html.js");
require("./rollup.js"); require("./rollup.js");
gulp.task("gather-gallery-pages", async function gatherPages() { gulp.task("gather-gallery-demos", async function gatherDemos() {
const pageDir = path.resolve(paths.gallery_dir, "src/pages"); const files = await fs.promises.readdir(
const files = glob.sync(path.resolve(pageDir, "**/*")); path.resolve(paths.gallery_dir, "src/demos")
);
const galleryBuild = path.resolve(paths.gallery_dir, "build"); let content = "export const DEMOS = {\n";
fs.mkdirSync(galleryBuild, { recursive: true });
let content = "export const PAGES = {\n";
const processed = new Set();
for (const file of files) { for (const file of files) {
if (fs.lstatSync(file).isDirectory()) { const demoId = path.basename(file, ".ts");
continue; const demoPath = "../src/demos/" + demoId;
} content += ` "${demoId}": () => import("${demoPath}"),\n`;
const pageId = file.substring(pageDir.length + 1, file.lastIndexOf("."));
if (processed.has(pageId)) {
continue;
}
processed.add(pageId);
const [category, name] = pageId.split("/", 2);
const demoFile = path.resolve(pageDir, `${pageId}.ts`);
const descriptionFile = path.resolve(pageDir, `${pageId}.markdown`);
const hasDemo = fs.existsSync(demoFile);
let hasDescription = fs.existsSync(descriptionFile);
let metadata = {};
if (hasDescription) {
let descriptionContent = fs.readFileSync(descriptionFile, "utf-8");
if (descriptionContent.startsWith("---")) {
const metadataEnd = descriptionContent.indexOf("---", 3);
metadata = yaml.load(descriptionContent.substring(3, metadataEnd));
descriptionContent = descriptionContent
.substring(metadataEnd + 3)
.trim();
} }
// If description is just metadata content += "};";
if (descriptionContent === "") {
hasDescription = false; const galleryBuild = path.resolve(paths.gallery_dir, "build");
} else {
descriptionContent = marked(descriptionContent).replace(/`/g, "\\`"); fs.mkdirSync(galleryBuild, { recursive: true });
fs.mkdirSync(path.resolve(galleryBuild, category), { recursive: true });
fs.writeFileSync( fs.writeFileSync(
path.resolve(galleryBuild, `${pageId}-description.ts`), path.resolve(galleryBuild, "import-demos.ts"),
`
import {html} from "lit";
export default html\`${descriptionContent}\`
`
);
}
}
content += ` "${pageId}": {
metadata: ${JSON.stringify(metadata)},
${
hasDescription
? `description: () => import("./${pageId}-description").then(m => m.default),`
: ""
}
${hasDemo ? `demo: () => import("../src/pages/${pageId}")` : ""}
},\n`;
}
content += "};\n";
// Generate sidebar
const sidebarPath = path.resolve(paths.gallery_dir, "sidebar.js");
// To make watch work during development
delete require.cache[sidebarPath];
const sidebar = require(sidebarPath);
const pagesToProcess = {};
for (const key of processed) {
const [category, page] = key.split("/", 2);
if (!(category in pagesToProcess)) {
pagesToProcess[category] = new Set();
}
pagesToProcess[category].add(page);
}
for (const group of Object.values(sidebar)) {
const toProcess = pagesToProcess[group.category];
delete pagesToProcess[group.category];
if (!toProcess) {
console.error("Unknown category", group.category);
if (!group.pages) {
group.pages = [];
}
continue;
}
// Any pre-defined groups will not be sorted.
if (group.pages) {
for (const page of group.pages) {
if (!toProcess.delete(page)) {
console.error("Found unreferenced demo", page);
}
}
} else {
group.pages = [];
}
for (const page of Array.from(toProcess).sort()) {
group.pages.push(page);
}
}
for (const [category, pages] of Object.entries(pagesToProcess)) {
sidebar.push({
category,
header: category,
pages: Array.from(pages).sort(),
});
}
content += `export const SIDEBAR = ${JSON.stringify(sidebar, null, 2)};\n`;
fs.writeFileSync(
path.resolve(galleryBuild, "import-pages.ts"),
content, content,
"utf-8" "utf-8"
); );
@@ -158,25 +51,11 @@ gulp.task(
gulp.parallel( gulp.parallel(
"gen-icons-json", "gen-icons-json",
"build-translations", "build-translations",
"build-locale-data", "gather-gallery-demos"
"gather-gallery-pages"
), ),
"copy-static-gallery", "copy-static-gallery",
"gen-index-gallery-dev", "gen-index-gallery-dev",
gulp.parallel( env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery"
env.useRollup()
? "rollup-dev-server-gallery"
: "webpack-dev-server-gallery",
async function watchMarkdownFiles() {
gulp.watch(
[
path.resolve(paths.gallery_dir, "src/pages/**/*.markdown"),
path.resolve(paths.gallery_dir, "sidebar.js"),
],
gulp.series("gather-gallery-pages")
);
}
)
) )
); );
@@ -191,8 +70,7 @@ gulp.task(
gulp.parallel( gulp.parallel(
"gen-icons-json", "gen-icons-json",
"build-translations", "build-translations",
"build-locale-data", "gather-gallery-demos"
"gather-gallery-pages"
), ),
"copy-static-gallery", "copy-static-gallery",
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery", env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",

View File

@@ -2,6 +2,7 @@
const gulp = require("gulp"); const gulp = require("gulp");
const path = require("path"); const path = require("path");
const cpx = require("cpx");
const fs = require("fs-extra"); const fs = require("fs-extra");
const paths = require("../paths"); const paths = require("../paths");
@@ -12,9 +13,7 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) => const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile))); fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath = const genStaticPath = (staticDir) => (...parts) =>
(staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts); path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) { function copyTranslations(staticDir) {
@@ -22,18 +21,11 @@ function copyTranslations(staticDir) {
// Translation output // Translation output
fs.copySync( fs.copySync(
polyPath("build/translations/output"), polyPath("build-translations/output"),
staticPath("translations") staticPath("translations")
); );
} }
function copyLocaleData(staticDir) {
const staticPath = genStaticPath(staticDir);
// Locale data output
fs.copySync(polyPath("build/locale-data"), staticPath("locale-data"));
}
function copyMdiIcons(staticDir) { function copyMdiIcons(staticDir) {
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);
@@ -70,20 +62,12 @@ function copyLoaderJS(staticDir) {
function copyFonts(staticDir) { function copyFonts(staticDir) {
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);
// Local fonts // Local fonts
fs.copySync( cpx.copySync(
npmPath("roboto-fontface/fonts/roboto/"), npmPath("roboto-fontface/fonts/roboto/*.woff2"),
staticPath("fonts/roboto/"), staticPath("fonts/roboto")
{
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
}
); );
} }
function copyQrScannerWorker(staticDir) {
const staticPath = genStaticPath(staticDir);
copyFileDir(npmPath("qr-scanner/qr-scanner-worker.min.js"), staticPath("js"));
}
function copyMapPanel(staticDir) { function copyMapPanel(staticDir) {
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);
copyFileDir( copyFileDir(
@@ -96,11 +80,6 @@ function copyMapPanel(staticDir) {
); );
} }
gulp.task("copy-locale-data", async () => {
const staticDir = paths.app_output_static;
copyLocaleData(staticDir);
});
gulp.task("copy-translations-app", async () => { gulp.task("copy-translations-app", async () => {
const staticDir = paths.app_output_static; const staticDir = paths.app_output_static;
copyTranslations(staticDir); copyTranslations(staticDir);
@@ -111,11 +90,6 @@ gulp.task("copy-translations-supervisor", async () => {
copyTranslations(staticDir); copyTranslations(staticDir);
}); });
gulp.task("copy-locale-data-supervisor", async () => {
const staticDir = paths.hassio_output_static;
copyLocaleData(staticDir);
});
gulp.task("copy-static-app", async () => { gulp.task("copy-static-app", async () => {
const staticDir = paths.app_output_static; const staticDir = paths.app_output_static;
// Basic static files // Basic static files
@@ -125,14 +99,10 @@ gulp.task("copy-static-app", async () => {
copyPolyfills(staticDir); copyPolyfills(staticDir);
copyFonts(staticDir); copyFonts(staticDir);
copyTranslations(staticDir); copyTranslations(staticDir);
copyLocaleData(staticDir);
copyMdiIcons(staticDir); copyMdiIcons(staticDir);
// Panel assets // Panel assets
copyMapPanel(staticDir); copyMapPanel(staticDir);
// Qr Scanner assets
copyQrScannerWorker(staticDir);
}); });
gulp.task("copy-static-demo", async () => { gulp.task("copy-static-demo", async () => {
@@ -149,7 +119,6 @@ gulp.task("copy-static-demo", async () => {
copyMapPanel(paths.demo_output_static); copyMapPanel(paths.demo_output_static);
copyFonts(paths.demo_output_static); copyFonts(paths.demo_output_static);
copyTranslations(paths.demo_output_static); copyTranslations(paths.demo_output_static);
copyLocaleData(paths.demo_output_static);
copyMdiIcons(paths.demo_output_static); copyMdiIcons(paths.demo_output_static);
}); });
@@ -164,7 +133,6 @@ gulp.task("copy-static-cast", async () => {
copyMapPanel(paths.cast_output_static); copyMapPanel(paths.cast_output_static);
copyFonts(paths.cast_output_static); copyFonts(paths.cast_output_static);
copyTranslations(paths.cast_output_static); copyTranslations(paths.cast_output_static);
copyLocaleData(paths.cast_output_static);
copyMdiIcons(paths.cast_output_static); copyMdiIcons(paths.cast_output_static);
}); });
@@ -180,6 +148,5 @@ gulp.task("copy-static-gallery", async () => {
copyMapPanel(paths.gallery_output_static); copyMapPanel(paths.gallery_output_static);
copyFonts(paths.gallery_output_static); copyFonts(paths.gallery_output_static);
copyTranslations(paths.gallery_output_static); copyTranslations(paths.gallery_output_static);
copyLocaleData(paths.gallery_output_static);
copyMdiIcons(paths.gallery_output_static); copyMdiIcons(paths.gallery_output_static);
}); });

View File

@@ -22,40 +22,17 @@ const getMeta = () => {
const svg = fs.readFileSync(`${ICON_PATH}/${icon.name}.svg`, { const svg = fs.readFileSync(`${ICON_PATH}/${icon.name}.svg`, {
encoding, encoding,
}); });
return { return { path: svg.match(/ d="([^"]+)"/)[1], name: icon.name };
path: svg.match(/ d="([^"]+)"/)[1],
name: icon.name,
tags: icon.tags,
aliases: icon.aliases,
};
}); });
}; };
const addRemovedMeta = (meta) => { const addRemovedMeta = (meta) => {
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding }); const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
const removed = JSON.parse(file); const removed = JSON.parse(file);
const removedMeta = removed.map((removeIcon) => ({ const combinedMeta = [...meta, ...removed];
path: removeIcon.path,
name: removeIcon.name,
tags: [],
aliases: [],
}));
const combinedMeta = [...meta, ...removedMeta];
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name)); return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
}; };
const homeAutomationTag = "Home Automation";
const orderMeta = (meta) => {
const homeAutomationMeta = meta.filter((icon) =>
icon.tags.includes(homeAutomationTag)
);
const otherMeta = meta.filter(
(icon) => !icon.tags.includes(homeAutomationTag)
);
return [...homeAutomationMeta, ...otherMeta];
};
const splitBySize = (meta) => { const splitBySize = (meta) => {
const chunks = []; const chunks = [];
const CHUNK_SIZE = 50000; const CHUNK_SIZE = 50000;
@@ -100,10 +77,8 @@ const findDifferentiator = (curString, prevString) => {
}; };
gulp.task("gen-icons-json", (done) => { gulp.task("gen-icons-json", (done) => {
const meta = getMeta(); const meta = addRemovedMeta(getMeta());
const split = splitBySize(meta);
const metaAndRemoved = addRemovedMeta(meta);
const split = splitBySize(metaAndRemoved);
if (!fs.existsSync(OUTPUT_DIR)) { if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true }); fs.mkdirSync(OUTPUT_DIR, { recursive: true });
@@ -141,18 +116,5 @@ gulp.task("gen-icons-json", (done) => {
JSON.stringify({ version: package.version, parts }) JSON.stringify({ version: package.version, parts })
); );
fs.writeFileSync(
path.resolve(OUTPUT_DIR, "iconList.json"),
JSON.stringify(
orderMeta(meta).map((icon) => ({
name: icon.name,
keywords: [
...icon.tags.map((t) => t.toLowerCase().replace(/\s\/\s/g, " ")),
...icon.aliases,
],
}))
)
);
done(); done();
}); });

View File

@@ -1,6 +1,9 @@
const gulp = require("gulp"); const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const env = require("../env"); const env = require("../env");
const paths = require("../paths");
require("./clean.js"); require("./clean.js");
require("./gen-icons-json.js"); require("./gen-icons-json.js");
@@ -17,11 +20,10 @@ gulp.task(
process.env.NODE_ENV = "development"; process.env.NODE_ENV = "development";
}, },
"clean-hassio", "clean-hassio",
"gen-icons-json",
"gen-index-hassio-dev", "gen-index-hassio-dev",
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
"build-locale-data",
"copy-locale-data-supervisor",
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio" env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
) )
); );
@@ -33,10 +35,9 @@ gulp.task(
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
"clean-hassio", "clean-hassio",
"gen-icons-json",
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
"build-locale-data",
"copy-locale-data-supervisor",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio", env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
"gen-index-hassio-prod", "gen-index-hassio-prod",
...// Don't compress running tests ...// Don't compress running tests

View File

@@ -1,74 +0,0 @@
/* 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");
const outDir = "build/locale-data";
gulp.task("clean-locale-data", () => del([outDir]));
gulp.task("ensure-locale-data-build-dir", (done) => {
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
}
done();
});
const modules = {
"intl-relativetimeformat": "RelativeTimeFormat",
"intl-datetimeformat": "DateTimeFormat",
"intl-numberformat": "NumberFormat",
};
gulp.task("create-locale-data", (done) => {
const translationMeta = JSON.parse(
fs.readFileSync(
path.join(paths.translations_src, "translationMetadata.json")
)
);
Object.entries(modules).forEach(([module, className]) => {
Object.keys(translationMeta).forEach((lang) => {
try {
const localeData = String(
fs.readFileSync(
require.resolve(`@formatjs/${module}/locale-data/${lang}.js`)
)
)
.replace(
new RegExp(
`\\/\\*\\s*@generated\\s*\\*\\/\\s*\\/\\/\\s*prettier-ignore\\s*if\\s*\\(Intl\\.${className}\\s*&&\\s*typeof\\s*Intl\\.${className}\\.__addLocaleData\\s*===\\s*'function'\\)\\s*{\\s*Intl\\.${className}\\.__addLocaleData\\(`,
"im"
),
""
)
.replace(/\)\s*}/im, "");
// make sure we have valid JSON
JSON.parse(localeData);
if (!fs.existsSync(path.join(outDir, module))) {
fs.mkdirSync(path.join(outDir, module), { recursive: true });
}
fs.writeFileSync(
path.join(outDir, `${module}/${lang}.json`),
localeData
);
} catch (e) {
if (e.code !== "MODULE_NOT_FOUND") {
throw e;
}
}
});
done();
});
});
gulp.task(
"build-locale-data",
gulp.series(
"clean-locale-data",
"ensure-locale-data-build-dir",
"create-locale-data"
)
);

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const crypto = require("crypto"); const crypto = require("crypto");
const del = require("del"); const del = require("del");
const path = require("path"); const path = require("path");
@@ -7,7 +5,7 @@ const source = require("vinyl-source-stream");
const vinylBuffer = require("vinyl-buffer"); const vinylBuffer = require("vinyl-buffer");
const gulp = require("gulp"); const gulp = require("gulp");
const fs = require("fs"); const fs = require("fs");
const flatmap = require("gulp-flatmap"); const foreach = require("gulp-foreach");
const merge = require("gulp-merge-json"); const merge = require("gulp-merge-json");
const rename = require("gulp-rename"); const rename = require("gulp-rename");
const transform = require("gulp-json-transform"); const transform = require("gulp-json-transform");
@@ -17,7 +15,7 @@ const paths = require("../paths");
const inFrontendDir = "translations/frontend"; const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend"; const inBackendDir = "translations/backend";
const workDir = "build/translations"; const workDir = "build-translations";
const fullDir = workDir + "/full"; const fullDir = workDir + "/full";
const coreDir = workDir + "/core"; const coreDir = workDir + "/core";
const outDir = workDir + "/output"; const outDir = workDir + "/output";
@@ -28,6 +26,13 @@ gulp.task("translations-enable-merge-backend", (done) => {
done(); done();
}); });
String.prototype.rsplit = function (sep, maxsplit) {
var split = this.split(sep);
return maxsplit
? [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit))
: split;
};
// Panel translations which should be split from the core translations. // Panel translations which should be split from the core translations.
const TRANSLATION_FRAGMENTS = Object.keys( const TRANSLATION_FRAGMENTS = Object.keys(
require("../../src/translations/en.json").ui.panel require("../../src/translations/en.json").ui.panel
@@ -35,7 +40,7 @@ const TRANSLATION_FRAGMENTS = Object.keys(
function recursiveFlatten(prefix, data) { function recursiveFlatten(prefix, data) {
let output = {}; let output = {};
Object.keys(data).forEach((key) => { Object.keys(data).forEach(function (key) {
if (typeof data[key] === "object") { if (typeof data[key] === "object") {
output = { output = {
...output, ...output,
@@ -96,19 +101,15 @@ function lokaliseTransform(data, original, file) {
if (value instanceof Object) { if (value instanceof Object) {
output[key] = lokaliseTransform(value, original, file); output[key] = lokaliseTransform(value, original, file);
} else { } else {
output[key] = value.replace(re_key_reference, (_match, lokalise_key) => { output[key] = value.replace(re_key_reference, (match, key) => {
const replace = lokalise_key.split("::").reduce((tr, k) => { const replace = key.split("::").reduce((tr, k) => {
if (!tr) { if (!tr) {
throw Error( throw Error(`Invalid key placeholder ${key} in ${file.path}`);
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
} }
return tr[k]; return tr[k];
}, original); }, original);
if (typeof replace !== "string") { if (typeof replace !== "string") {
throw Error( throw Error(`Invalid key placeholder ${key} in ${file.path}`);
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
} }
return replace; return replace;
}); });
@@ -117,16 +118,18 @@ function lokaliseTransform(data, original, file) {
return output; return output;
} }
gulp.task("clean-translations", () => del([workDir])); gulp.task("clean-translations", function () {
return del([workDir]);
});
gulp.task("ensure-translations-build-dir", (done) => { gulp.task("ensure-translations-build-dir", (done) => {
if (!fs.existsSync(workDir)) { if (!fs.existsSync(workDir)) {
fs.mkdirSync(workDir, { recursive: true }); fs.mkdirSync(workDir);
} }
done(); done();
}); });
gulp.task("create-test-metadata", (cb) => { gulp.task("create-test-metadata", function (cb) {
fs.writeFile( fs.writeFile(
workDir + "/testMetadata.json", workDir + "/testMetadata.json",
JSON.stringify({ JSON.stringify({
@@ -140,13 +143,17 @@ gulp.task("create-test-metadata", (cb) => {
gulp.task( gulp.task(
"create-test-translation", "create-test-translation",
gulp.series("create-test-metadata", () => gulp.series("create-test-metadata", function createTestTranslation() {
gulp return gulp
.src(path.join(paths.translations_src, "en.json")) .src(path.join(paths.translations_src, "en.json"))
.pipe(transform((data, _file) => recursiveEmpty(data))) .pipe(
.pipe(rename("test.json")) transform(function (data, file) {
.pipe(gulp.dest(workDir)) return recursiveEmpty(data);
})
) )
.pipe(rename("test.json"))
.pipe(gulp.dest(workDir));
})
); );
/** /**
@@ -158,7 +165,7 @@ gulp.task(
* project is buildable immediately after merging new translation keys, since * project is buildable immediately after merging new translation keys, since
* the Lokalise update to translations/en.json will not happen immediately. * the Lokalise update to translations/en.json will not happen immediately.
*/ */
gulp.task("build-master-translation", () => { gulp.task("build-master-translation", function () {
const src = [path.join(paths.translations_src, "en.json")]; const src = [path.join(paths.translations_src, "en.json")];
if (mergeBackend) { if (mergeBackend) {
@@ -167,7 +174,11 @@ gulp.task("build-master-translation", () => {
return gulp return gulp
.src(src) .src(src)
.pipe(transform((data, file) => lokaliseTransform(data, data, file))) .pipe(
transform(function (data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe( .pipe(
merge({ merge({
fileName: "translationMaster.json", fileName: "translationMaster.json",
@@ -176,14 +187,18 @@ gulp.task("build-master-translation", () => {
.pipe(gulp.dest(workDir)); .pipe(gulp.dest(workDir));
}); });
gulp.task("build-merged-translations", () => gulp.task("build-merged-translations", function () {
gulp return gulp
.src([inFrontendDir + "/*.json", workDir + "/test.json"], { .src([inFrontendDir + "/*.json", workDir + "/test.json"], {
allowEmpty: true, allowEmpty: true,
}) })
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe( .pipe(
flatmap((stream, file) => { transform(function (data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe(
foreach(function (stream, file) {
// For each language generate a merged json file. It begins with the master // For each language generate a merged json file. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent // translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag // tags into one file for each specific subtag
@@ -215,17 +230,17 @@ gulp.task("build-merged-translations", () =>
) )
.pipe(gulp.dest(fullDir)); .pipe(gulp.dest(fullDir));
}) })
) );
); });
let taskName; var taskName;
const splitTasks = []; const splitTasks = [];
TRANSLATION_FRAGMENTS.forEach((fragment) => { TRANSLATION_FRAGMENTS.forEach((fragment) => {
taskName = "build-translation-fragment-" + fragment; taskName = "build-translation-fragment-" + fragment;
gulp.task(taskName, () => gulp.task(taskName, function () {
// Return only the translations for this fragment. // Return only the translations for this fragment.
gulp return gulp
.src(fullDir + "/*.json") .src(fullDir + "/*.json")
.pipe( .pipe(
transform((data) => ({ transform((data) => ({
@@ -236,18 +251,18 @@ TRANSLATION_FRAGMENTS.forEach((fragment) => {
}, },
})) }))
) )
.pipe(gulp.dest(workDir + "/" + fragment)) .pipe(gulp.dest(workDir + "/" + fragment));
); });
splitTasks.push(taskName); splitTasks.push(taskName);
}); });
taskName = "build-translation-core"; taskName = "build-translation-core";
gulp.task(taskName, () => gulp.task(taskName, function () {
// Remove the fragment translations from the core translation. // Remove the fragment translations from the core translation.
gulp return gulp
.src(fullDir + "/*.json") .src(fullDir + "/*.json")
.pipe( .pipe(
transform((data, _file) => { transform((data, file) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => { TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment]; delete data.ui.panel[fragment];
}); });
@@ -255,14 +270,14 @@ gulp.task(taskName, () =>
return data; return data;
}) })
) )
.pipe(gulp.dest(coreDir)) .pipe(gulp.dest(coreDir));
); });
splitTasks.push(taskName); splitTasks.push(taskName);
gulp.task("build-flattened-translations", () => gulp.task("build-flattened-translations", function () {
// Flatten the split versions of our translations, and move them into outDir // Flatten the split versions of our translations, and move them into outDir
gulp return gulp
.src( .src(
TRANSLATION_FRAGMENTS.map( TRANSLATION_FRAGMENTS.map(
(fragment) => workDir + "/" + fragment + "/*.json" (fragment) => workDir + "/" + fragment + "/*.json"
@@ -270,28 +285,26 @@ gulp.task("build-flattened-translations", () =>
{ base: workDir } { base: workDir }
) )
.pipe( .pipe(
transform((data) => transform(function (data) {
// Polymer.AppLocalizeBehavior requires flattened json // Polymer.AppLocalizeBehavior requires flattened json
flatten(data) return flatten(data);
) })
) )
.pipe( .pipe(
rename((filePath) => { rename((filePath) => {
if (filePath.dirname === "core") { if (filePath.dirname === "core") {
filePath.dirname = ""; filePath.dirname = "";
} }
// In dev we create the file with the fake hash in the filename
if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
}) })
) )
.pipe(gulp.dest(outDir)) .pipe(gulp.dest(outDir));
); });
const fingerprints = {}; const fingerprints = {};
gulp.task("build-translation-fingerprints", () => { gulp.task(
"build-translation-fingerprints",
function fingerprintTranslationFiles() {
// Fingerprint full file of each language // Fingerprint full file of each language
const files = fs.readdirSync(fullDir); const files = fs.readdirSync(fullDir);
@@ -307,8 +320,6 @@ gulp.task("build-translation-fingerprints", () => {
}; };
} }
// In dev we create the file with the fake hash in the filename
if (env.isProdBuild()) {
mapFiles(outDir, ".json", (filename) => { mapFiles(outDir, ".json", (filename) => {
const parsed = path.parse(filename); const parsed = path.parse(filename);
@@ -324,43 +335,35 @@ gulp.task("build-translation-fingerprints", () => {
}` }`
); );
}); });
}
const stream = source("translationFingerprints.json"); const stream = source("translationFingerprints.json");
stream.write(JSON.stringify(fingerprints)); stream.write(JSON.stringify(fingerprints));
process.nextTick(() => stream.end()); process.nextTick(() => stream.end());
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir)); return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
}); }
);
gulp.task("build-translation-fragment-supervisor", () => gulp.task("build-translation-fragment-supervisor", function () {
gulp return gulp
.src(fullDir + "/*.json") .src(fullDir + "/*.json")
.pipe(transform((data) => data.supervisor)) .pipe(transform((data) => data.supervisor))
.pipe( .pipe(gulp.dest(workDir + "/supervisor"));
rename((filePath) => { });
// In dev we create the file with the fake hash in the filename
if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
})
)
.pipe(gulp.dest(workDir + "/supervisor"))
);
gulp.task("build-translation-flatten-supervisor", () => gulp.task("build-translation-flatten-supervisor", function () {
gulp return gulp
.src(workDir + "/supervisor/*.json") .src(workDir + "/supervisor/*.json")
.pipe( .pipe(
transform((data) => transform(function (data) {
// Polymer.AppLocalizeBehavior requires flattened json // Polymer.AppLocalizeBehavior requires flattened json
flatten(data) return flatten(data);
})
) )
) .pipe(gulp.dest(outDir));
.pipe(gulp.dest(outDir)) });
);
gulp.task("build-translation-write-metadata", () => gulp.task("build-translation-write-metadata", function writeMetadata() {
gulp return gulp
.src( .src(
[ [
path.join(paths.translations_src, "translationMetadata.json"), path.join(paths.translations_src, "translationMetadata.json"),
@@ -371,14 +374,13 @@ gulp.task("build-translation-write-metadata", () =>
) )
.pipe(merge({})) .pipe(merge({}))
.pipe( .pipe(
transform((data) => { transform(function (data) {
const newData = {}; const newData = {};
Object.entries(data).forEach(([key, value]) => { Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name. // Filter out translations without native name.
if (value.nativeName) { if (value.nativeName) {
newData[key] = value; newData[key] = value;
} else { } else {
// eslint-disable-next-line no-console
console.warn( console.warn(
`Skipping language ${key}. Native name was not translated.` `Skipping language ${key}. Native name was not translated.`
); );
@@ -394,26 +396,19 @@ gulp.task("build-translation-write-metadata", () =>
})) }))
) )
.pipe(rename("translationMetadata.json")) .pipe(rename("translationMetadata.json"))
.pipe(gulp.dest(workDir)) .pipe(gulp.dest(workDir));
); });
gulp.task(
"create-translations",
gulp.series(
env.isProdBuild() ? (done) => done() : "create-test-translation",
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
"build-flattened-translations"
)
);
gulp.task( gulp.task(
"build-translations", "build-translations",
gulp.series( gulp.series(
"clean-translations", "clean-translations",
"ensure-translations-build-dir", "ensure-translations-build-dir",
"create-translations", env.isProdBuild() ? (done) => done() : "create-test-translation",
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
"build-flattened-translations",
"build-translation-fingerprints", "build-translation-fingerprints",
"build-translation-write-metadata" "build-translation-write-metadata"
) )

View File

@@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Tasks to run webpack. // Tasks to run webpack.
const fs = require("fs");
const gulp = require("gulp"); const gulp = require("gulp");
const webpack = require("webpack"); const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server"); const WebpackDevServer = require("webpack-dev-server");
@@ -20,13 +18,6 @@ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: false }), createConfigFunc({ ...params, latestBuild: false }),
]; ];
const isWsl =
fs.existsSync("/proc/version") &&
fs
.readFileSync("/proc/version", "utf-8")
.toLocaleLowerCase()
.includes("microsoft");
/** /**
* @param {{ * @param {{
* compiler: import("webpack").Compiler, * compiler: import("webpack").Compiler,
@@ -35,29 +26,26 @@ const isWsl =
* listenHost?: string * listenHost?: string
* }} * }}
*/ */
const runDevServer = async ({ const runDevServer = ({
compiler, compiler,
contentBase, contentBase,
port, port,
listenHost = "localhost", listenHost = "localhost",
}) => { }) =>
const server = new WebpackDevServer( new WebpackDevServer(compiler, {
{
open: true, open: true,
host: listenHost, watchContentBase: true,
port, contentBase,
static: { }).listen(port, listenHost, function (err) {
directory: contentBase, if (err) {
watch: true, throw err;
}, }
},
compiler
);
await server.start();
// Server listening // Server listening
log("[webpack-dev-server]", `Project is running at http://localhost:${port}`); log(
}; "[webpack-dev-server]",
`Project is running at http://localhost:${port}`
);
});
const doneHandler = (done) => (err, stats) => { const doneHandler = (done) => (err, stats) => {
if (err) { if (err) {
@@ -69,7 +57,6 @@ const doneHandler = (done) => (err, stats) => {
} }
if (stats.hasErrors() || stats.hasWarnings()) { if (stats.hasErrors() || stats.hasWarnings()) {
// eslint-disable-next-line no-console
console.log(stats.toString("minimal")); console.log(stats.toString("minimal"));
} }
@@ -91,14 +78,13 @@ const prodBuild = (conf) =>
gulp.task("webpack-watch-app", () => { gulp.task("webpack-watch-app", () => {
// This command will run forever because we don't close compiler // This command will run forever because we don't close compiler
webpack( webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
process.env.ES5 { ignored: /build-translations/ },
? bothBuilds(createAppConfig, { isProdBuild: false }) doneHandler()
: createAppConfig({ isProdBuild: false, latestBuild: true }) );
).watch({ poll: isWsl }, doneHandler());
gulp.watch( gulp.watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),
gulp.series("create-translations", "copy-translations-app") gulp.series("build-translations", "copy-translations-app")
); );
}); });
@@ -110,13 +96,13 @@ gulp.task("webpack-prod-app", () =>
) )
); );
gulp.task("webpack-dev-server-demo", () => gulp.task("webpack-dev-server-demo", () => {
runDevServer({ runDevServer({
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })), compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
contentBase: paths.demo_output_root, contentBase: paths.demo_output_root,
port: 8090, port: 8090,
}) });
); });
gulp.task("webpack-prod-demo", () => gulp.task("webpack-prod-demo", () =>
prodBuild( prodBuild(
@@ -126,15 +112,15 @@ gulp.task("webpack-prod-demo", () =>
) )
); );
gulp.task("webpack-dev-server-cast", () => gulp.task("webpack-dev-server-cast", () => {
runDevServer({ runDevServer({
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })), compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
contentBase: paths.cast_output_root, contentBase: paths.cast_output_root,
port: 8080, port: 8080,
// Accessible from the network, because that's how Cast hits it. // Accessible from the network, because that's how Cast hits it.
listenHost: "0.0.0.0", listenHost: "0.0.0.0",
}) });
); });
gulp.task("webpack-prod-cast", () => gulp.task("webpack-prod-cast", () =>
prodBuild( prodBuild(
@@ -151,7 +137,7 @@ gulp.task("webpack-watch-hassio", () => {
isProdBuild: false, isProdBuild: false,
latestBuild: true, latestBuild: true,
}) })
).watch({ ignored: /build/, poll: isWsl }, doneHandler()); ).watch({ ignored: /build-translations/ }, doneHandler());
gulp.watch( gulp.watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),
@@ -167,15 +153,14 @@ gulp.task("webpack-prod-hassio", () =>
) )
); );
gulp.task("webpack-dev-server-gallery", () => gulp.task("webpack-dev-server-gallery", () => {
runDevServer({ runDevServer({
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't // We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })), compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
contentBase: paths.gallery_output_root, contentBase: paths.gallery_output_root,
port: 8100, port: 8100,
listenHost: "0.0.0.0", });
}) });
);
gulp.task("webpack-prod-gallery", () => gulp.task("webpack-prod-gallery", () =>
prodBuild( prodBuild(

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
module.exports = { module.exports = {
@@ -26,7 +25,6 @@ module.exports = {
cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"), cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"),
gallery_dir: path.resolve(__dirname, "../gallery"), gallery_dir: path.resolve(__dirname, "../gallery"),
gallery_build: path.resolve(__dirname, "../gallery/build"),
gallery_output_root: path.resolve(__dirname, "../gallery/dist"), gallery_output_root: path.resolve(__dirname, "../gallery/dist"),
gallery_output_latest: path.resolve( gallery_output_latest: path.resolve(
__dirname, __dirname,

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path"); const path = require("path");
const commonjs = require("@rollup/plugin-commonjs"); const commonjs = require("@rollup/plugin-commonjs");
@@ -33,7 +32,8 @@ const createRollupConfig = ({
publicPath, publicPath,
dontHash, dontHash,
isWDS, isWDS,
}) => ({ }) => {
return {
/** /**
* @type { import("rollup").InputOptions } * @type { import("rollup").InputOptions }
*/ */
@@ -52,18 +52,25 @@ const createRollupConfig = ({
browser: true, browser: true,
rootDir: paths.polymer_dir, rootDir: paths.polymer_dir,
}), }),
commonjs(), commonjs({
namedExports: {
"js-yaml": ["safeDump", "safeLoad"],
},
}),
json(), json(),
babel({ babel({
...bundle.babelOptions({ latestBuild }), ...bundle.babelOptions({ latestBuild }),
extensions, extensions,
exclude: bundle.babelExclude(),
babelHelpers: isWDS ? "inline" : "bundled", babelHelpers: isWDS ? "inline" : "bundled",
}), }),
string({ string({
// Import certain extensions as strings // Import certain extensions as strings
include: [path.join(paths.polymer_dir, "node_modules/**/*.css")], include: [path.join(paths.polymer_dir, "node_modules/**/*.css")],
}), }),
replace(bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })), replace(
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
),
!isWDS && !isWDS &&
manifest({ manifest({
publicPath, publicPath,
@@ -95,15 +102,18 @@ const createRollupConfig = ({
// https://rollupjs.org/guide/en/#outputassetfilenames // https://rollupjs.org/guide/en/#outputassetfilenames
entryFileNames: entryFileNames:
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js", isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
chunkFileNames: isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js", chunkFileNames:
assetFileNames: isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js", isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
assetFileNames:
isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
// https://rollupjs.org/guide/en/#outputsourcemap // https://rollupjs.org/guide/en/#outputsourcemap
sourcemap: isProdBuild ? true : "inline", sourcemap: isProdBuild ? true : "inline",
}, },
}); };
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) => {
createRollupConfig( return createRollupConfig(
bundle.config.app({ bundle.config.app({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
@@ -111,24 +121,31 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) =>
isWDS, isWDS,
}) })
); );
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
createRollupConfig( return createRollupConfig(
bundle.config.demo({ bundle.config.demo({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
isStatsBuild, isStatsBuild,
}) })
); );
};
const createCastConfig = ({ isProdBuild, latestBuild }) => const createCastConfig = ({ isProdBuild, latestBuild }) => {
createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild })); return createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) => const createHassioConfig = ({ isProdBuild, latestBuild }) => {
createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild })); return createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
createRollupConfig(bundle.config.gallery({ isProdBuild, latestBuild })); return createRollupConfig(
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = { module.exports = {
createAppConfig, createAppConfig,

View File

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

View File

@@ -1,12 +1,10 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const webpack = require("webpack"); const webpack = require("webpack");
const path = require("path"); const path = require("path");
const TerserPlugin = require("terser-webpack-plugin"); const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin"); const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
const paths = require("./paths.js"); const paths = require("./paths.js");
const bundle = require("./bundle.js"); const bundle = require("./bundle");
const log = require("fancy-log");
class LogStartCompilePlugin { class LogStartCompilePlugin {
ignoredFirst = false; ignoredFirst = false;
@@ -30,7 +28,6 @@ const createWebpackConfig = ({
isProdBuild, isProdBuild,
latestBuild, latestBuild,
isStatsBuild, isStatsBuild,
isHassioBuild,
dontHash, dontHash,
}) => { }) => {
if (!dontHash) { if (!dontHash) {
@@ -49,18 +46,15 @@ const createWebpackConfig = ({
rules: [ rules: [
{ {
test: /\.m?js$|\.ts$/, test: /\.m?js$|\.ts$/,
exclude: bundle.babelExclude(),
use: { use: {
loader: "babel-loader", loader: "babel-loader",
options: { options: bundle.babelOptions({ latestBuild }),
...bundle.babelOptions({ latestBuild }),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
}, },
}, },
{ {
test: /\.css$/, test: /\.css$/,
type: "asset/source", use: "raw-loader",
}, },
], ],
}, },
@@ -72,11 +66,8 @@ const createWebpackConfig = ({
terserOptions: bundle.terserOptions(latestBuild), terserOptions: bundle.terserOptions(latestBuild),
}), }),
], ],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
}, },
plugins: [ plugins: [
new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({ new WebpackManifestPlugin({
// Only include the JS of entrypoints // Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"), filter: (file) => file.isInitial && !file.name.endsWith(".map"),
@@ -103,7 +94,6 @@ const createWebpackConfig = ({
? path.resolve(context, resource) ? path.resolve(context, resource)
: require.resolve(resource); : require.resolve(resource);
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console
console.error( console.error(
"Error in Home Assistant ignore plugin", "Error in Home Assistant ignore plugin",
resource, resource,
@@ -118,68 +108,69 @@ const createWebpackConfig = ({
}, },
}), }),
new webpack.NormalModuleReplacementPlugin( new webpack.NormalModuleReplacementPlugin(
new RegExp( new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
),
path.resolve(paths.polymer_dir, "src/util/empty.js") path.resolve(paths.polymer_dir, "src/util/empty.js")
), ),
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one
new webpack.NormalModuleReplacementPlugin(
new RegExp(
require.resolve(
"lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
)
),
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
),
!isProdBuild && new LogStartCompilePlugin(), !isProdBuild && new LogStartCompilePlugin(),
].filter(Boolean), ].filter(Boolean),
resolve: { resolve: {
extensions: [".ts", ".js", ".json"], extensions: [".ts", ".js", ".json"],
alias: {
"lit/decorators$": "lit/decorators.js",
"lit/directive$": "lit/directive.js",
"lit/directives/until$": "lit/directives/until.js",
"lit/directives/class-map$": "lit/directives/class-map.js",
"lit/directives/style-map$": "lit/directives/style-map.js",
"lit/directives/if-defined$": "lit/directives/if-defined.js",
"lit/directives/guard$": "lit/directives/guard.js",
"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: { output: {
filename: ({ chunk }) => { filename: ({ chunk }) => {
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) { if (!isProdBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`; return `${chunk.name}.js`;
} }
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
}, },
chunkFilename: chunkFilename:
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js", isProdBuild && !isStatsBuild
? "chunk.[chunkhash].js"
: "[name].chunk.js",
path: outputPath, path: outputPath,
publicPath, publicPath,
// To silence warning in worker plugin // To silence warning in worker plugin
globalObject: "self", globalObject: "self",
}, },
experiments: {
topLevelAwait: true,
},
}; };
}; };
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
createWebpackConfig( return createWebpackConfig(
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild }) bundle.config.app({ isProdBuild, latestBuild, isStatsBuild })
); );
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
createWebpackConfig( return createWebpackConfig(
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild }) bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
); );
};
const createCastConfig = ({ isProdBuild, latestBuild }) => const createCastConfig = ({ isProdBuild, latestBuild }) => {
createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild })); return createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) => const createHassioConfig = ({ isProdBuild, latestBuild }) => {
createWebpackConfig(bundle.config.hassio({ isProdBuild, latestBuild })); return createWebpackConfig(
bundle.config.hassio({ isProdBuild, latestBuild })
);
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild })); return createWebpackConfig(
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = { module.exports = {
createAppConfig, createAppConfig,

View File

@@ -139,7 +139,7 @@
Your authentication credentials or Home Assistant url are never sent Your authentication credentials or Home Assistant url are never sent
to the Cloud. You can validate this behavior in to the Cloud. You can validate this behavior in
<a <a
href="https://github.com/home-assistant/frontend/tree/dev/cast" href="https://github.com/home-assistant/home-assistant-polymer/tree/dev/cast"
target="_blank" target="_blank"
>the source code</a >the source code</a
>. >.

View File

@@ -1,46 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
<style>
body {
--logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
--logo-repeat: no-repeat;
--playback-logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
--theme-hue: 200;
--progress-color: #03a9f4;
--splash-image: url('https://home-assistant.io/images/cast/splash.png');
--splash-size: cover;
--background-color: #41bdf5;
}
</style>
<script>
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
s.parentNode.insertBefore(g,s)}(document,'script'));
</script>
</head>
<body>
<%= renderTemplate('_js_base') %>
<cast-media-player></cast-media-player>
<script>
import("<%= latestMediaJS %>");
window.latestJS = true;
</script>
<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,16 @@
import "@material/mwc-button/mwc-button";
import { mdiCast, mdiCastConnected } from "@mdi/js";
import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket"; import { Auth, Connection } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../../src/cast/cast_manager"; import { CastManager } from "../../../../src/cast/cast_manager";
import { import {
castSendShowLovelaceView, castSendShowLovelaceView,
@@ -18,7 +24,6 @@ import {
import { atLeastVersion } from "../../../../src/common/config/version"; import { atLeastVersion } from "../../../../src/common/config/version";
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute"; import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
import "../../../../src/components/ha-icon"; import "../../../../src/components/ha-icon";
import "../../../../src/components/ha-svg-icon";
import { import {
getLegacyLovelaceCollection, getLegacyLovelaceCollection,
getLovelaceCollection, getLovelaceCollection,
@@ -27,6 +32,7 @@ import {
import "../../../../src/layouts/hass-loading-screen"; import "../../../../src/layouts/hass-loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config"; import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout"; import "./hc-layout";
import "@material/mwc-button/mwc-button";
@customElement("hc-cast") @customElement("hc-cast")
class HcCast extends LitElement { class HcCast extends LitElement {
@@ -36,9 +42,9 @@ class HcCast extends LitElement {
@property() public castManager!: CastManager; @property() public castManager!: CastManager;
@state() private askWrite = false; @internalProperty() private askWrite = false;
@state() private lovelaceConfig?: LovelaceConfig | null; @internalProperty() private lovelaceConfig?: LovelaceConfig | null;
protected render(): TemplateResult { protected render(): TemplateResult {
if (this.lovelaceConfig === undefined) { if (this.lovelaceConfig === undefined) {
@@ -48,7 +54,9 @@ class HcCast extends LitElement {
const error = const error =
this.castManager.castState === "NO_DEVICES_AVAILABLE" this.castManager.castState === "NO_DEVICES_AVAILABLE"
? html` ? html`
<p>There were no suitable Chromecast devices to cast to found.</p> <p>
There were no suitable Chromecast devices to cast to found.
</p>
` `
: undefined; : undefined;
@@ -75,7 +83,7 @@ class HcCast extends LitElement {
? html` ? html`
<p class="center-item"> <p class="center-item">
<mwc-button raised @click=${this._handleLaunch}> <mwc-button raised @click=${this._handleLaunch}>
<ha-svg-icon .path=${mdiCast}></ha-svg-icon> <ha-icon icon="hass:cast"></ha-icon>
Start Casting Start Casting
</mwc-button> </mwc-button>
</p> </p>
@@ -113,7 +121,7 @@ class HcCast extends LitElement {
${this.castManager.status ${this.castManager.status
? html` ? html`
<mwc-button @click=${this._handleLaunch}> <mwc-button @click=${this._handleLaunch}>
<ha-svg-icon .path=${mdiCastConnected}></ha-svg-icon> <ha-icon icon="hass:cast-connected"></ha-icon>
Manage Manage
</mwc-button> </mwc-button>
` `
@@ -193,12 +201,12 @@ class HcCast extends LitElement {
} }
this.connection.close(); this.connection.close();
location.reload(); location.reload();
} catch (err: any) { } catch (err) {
alert("Unable to log out!"); alert("Unable to log out!");
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
.center-item { .center-item {
display: flex; display: flex;
@@ -235,7 +243,7 @@ class HcCast extends LitElement {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
mwc-button ha-svg-icon { mwc-button ha-icon {
margin-right: 8px; margin-right: 8px;
height: 18px; height: 18px;
} }

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { mdiCastConnected, mdiCast } from "@mdi/js";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
Auth, Auth,
@@ -12,15 +11,22 @@ import {
getAuth, getAuth,
getAuthOptions, getAuthOptions,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, state } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
TemplateResult,
internalProperty,
} from "lit-element";
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager"; import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../../src/cast/receiver_messages"; import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
import { import {
loadTokens, loadTokens,
saveTokens, saveTokens,
} from "../../../../src/common/auth/token_storage"; } from "../../../../src/common/auth/token_storage";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-icon";
import "../../../../src/layouts/hass-loading-screen"; import "../../../../src/layouts/hass-loading-screen";
import { registerServiceWorker } from "../../../../src/util/register-service-worker"; import { registerServiceWorker } from "../../../../src/util/register-service-worker";
import "./hc-layout"; import "./hc-layout";
@@ -54,19 +60,19 @@ const INTRO = html`
@customElement("hc-connect") @customElement("hc-connect")
export class HcConnect extends LitElement { export class HcConnect extends LitElement {
@state() private loading = false; @internalProperty() private loading = false;
// If we had stored credentials but we cannot connect, // If we had stored credentials but we cannot connect,
// show a screen asking retry or logout. // show a screen asking retry or logout.
@state() private cannotConnect = false; @internalProperty() private cannotConnect = false;
@state() private error?: string | TemplateResult; @internalProperty() private error?: string | TemplateResult;
@state() private auth?: Auth; @internalProperty() private auth?: Auth;
@state() private connection?: Connection; @internalProperty() private connection?: Connection;
@state() private castManager?: CastManager | null; @internalProperty() private castManager?: CastManager | null;
private openDemo = false; private openDemo = false;
@@ -80,7 +86,9 @@ export class HcConnect extends LitElement {
</div> </div>
<div class="card-actions"> <div class="card-actions">
<a href="/"> <a href="/">
<mwc-button> Retry </mwc-button> <mwc-button>
Retry
</mwc-button>
</a> </a>
<div class="spacer"></div> <div class="spacer"></div>
<mwc-button @click=${this._handleLogout}>Log out</mwc-button> <mwc-button @click=${this._handleLogout}>Log out</mwc-button>
@@ -128,11 +136,11 @@ export class HcConnect extends LitElement {
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._handleDemo}> <mwc-button @click=${this._handleDemo}>
Show Demo Show Demo
<ha-svg-icon <ha-icon
.path=${this.castManager.castState === "CONNECTED" .icon=${this.castManager.castState === "CONNECTED"
? mdiCastConnected ? "hass:cast-connected"
: mdiCast} : "hass:cast"}
></ha-svg-icon> ></ha-icon>
</mwc-button> </mwc-button>
<div class="spacer"></div> <div class="spacer"></div>
<mwc-button @click=${this._handleConnect}>Authorize</mwc-button> <mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
@@ -213,7 +221,7 @@ export class HcConnect extends LitElement {
let url: URL; let url: URL;
try { try {
url = new URL(value); url = new URL(value);
} catch (err: any) { } catch (err) {
this.error = "Invalid URL"; this.error = "Invalid URL";
return; return;
} }
@@ -241,7 +249,7 @@ export class HcConnect extends LitElement {
try { try {
this.loading = true; this.loading = true;
auth = await getAuth(options); auth = await getAuth(options);
} catch (err: any) { } catch (err) {
if (init === "saved-tokens" && err === ERR_CANNOT_CONNECT) { if (init === "saved-tokens" && err === ERR_CANNOT_CONNECT) {
this.cannotConnect = true; this.cannotConnect = true;
return; return;
@@ -260,7 +268,7 @@ export class HcConnect extends LitElement {
try { try {
conn = await createConnection({ auth }); conn = await createConnection({ auth });
} catch (err: any) { } catch (err) {
// In case of saved tokens, silently solve problems. // In case of saved tokens, silently solve problems.
if (init === "saved-tokens") { if (init === "saved-tokens") {
if (err === ERR_CANNOT_CONNECT) { if (err === ERR_CANNOT_CONNECT) {
@@ -286,12 +294,12 @@ export class HcConnect extends LitElement {
try { try {
saveTokens(null); saveTokens(null);
location.reload(); location.reload();
} catch (err: any) { } catch (err) {
alert("Unable to log out!"); alert("Unable to log out!");
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
.card-content a { .card-content a {
color: var(--primary-color); color: var(--primary-color);
@@ -308,7 +316,7 @@ export class HcConnect extends LitElement {
color: darkred; color: darkred;
} }
mwc-button ha-svg-icon { mwc-button ha-icon {
margin-left: 8px; margin-left: 8px;
} }

View File

@@ -4,8 +4,15 @@ import {
getUser, getUser,
HassUser, HassUser,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
@customElement("hc-layout") @customElement("hc-layout")
@@ -62,7 +69,7 @@ class HcLayout extends LitElement {
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
:host { :host {
display: flex; display: flex;

View File

@@ -1,22 +0,0 @@
const castContext = cast.framework.CastReceiverContext.getInstance();
const playerManager = castContext.getPlayerManager();
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD,
(loadRequestData) => {
const media = loadRequestData.media;
// Special handling if it came from Google Assistant
if (media.entity) {
media.contentId = media.entity;
media.streamType = cast.framework.messages.StreamType.LIVE;
media.contentType = "application/vnd.apple.mpegurl";
// @ts-ignore
media.hlsVideoSegmentFormat =
cast.framework.messages.HlsVideoSegmentFormat.FMP4;
}
return loadRequestData;
}
);
castContext.start();

View File

@@ -5,8 +5,8 @@ import {
import { castContext } from "../cast_context"; import { castContext } from "../cast_context";
export const castDemoLovelace: () => LovelaceConfig = () => { export const castDemoLovelace: () => LovelaceConfig = () => {
const touchSupported = const touchSupported = castContext.getDeviceCapabilities()
castContext.getDeviceCapabilities().touch_input_supported; .touch_input_supported;
return { return {
views: [ views: [
{ {

View File

@@ -8,9 +8,6 @@ import { ReceivedMessage } from "./types";
const lovelaceController = new HcMain(); const lovelaceController = new HcMain();
document.body.append(lovelaceController); document.body.append(lovelaceController);
lovelaceController.addEventListener("cast-view-changed", (ev) => {
playDummyMedia(ev.detail.title);
});
const mediaPlayer = document.createElement("cast-media-player"); const mediaPlayer = document.createElement("cast-media-player");
mediaPlayer.style.display = "none"; mediaPlayer.style.display = "none";
@@ -31,31 +28,6 @@ const setTouchControlsVisibility = (visible: boolean) => {
} }
}; };
let timeOut: number | undefined;
const playDummyMedia = (viewTitle?: string) => {
const loadRequestData = new cast.framework.messages.LoadRequestData();
loadRequestData.autoplay = true;
loadRequestData.media = new cast.framework.messages.MediaInformation();
loadRequestData.media.contentId =
"https://cast.home-assistant.io/images/google-nest-hub.png";
loadRequestData.media.contentType = "image/jpeg";
loadRequestData.media.streamType = cast.framework.messages.StreamType.NONE;
const metadata = new cast.framework.messages.GenericMediaMetadata();
metadata.title = viewTitle;
loadRequestData.media.metadata = metadata;
loadRequestData.requestId = 0;
playerManager.load(loadRequestData);
if (timeOut) {
clearTimeout(timeOut);
timeOut = undefined;
}
if (castContext.getDeviceCapabilities().touch_input_supported) {
timeOut = window.setTimeout(() => playDummyMedia(viewTitle), 540000); // repeat every 9 minutes to keep it active (gets deactivated after 10 minutes)
}
};
const showLovelaceController = () => { const showLovelaceController = () => {
mediaPlayer.style.display = "none"; mediaPlayer.style.display = "none";
lovelaceController.style.display = "initial"; lovelaceController.style.display = "initial";
@@ -79,7 +51,6 @@ const showMediaPlayer = () => {
--progress-color: #03a9f4; --progress-color: #03a9f4;
--splash-image: url('https://home-assistant.io/images/cast/splash.png'); --splash-image: url('https://home-assistant.io/images/cast/splash.png');
--splash-size: cover; --splash-size: cover;
--background-color: #41bdf5;
} }
`; `;
document.head.appendChild(style); document.head.appendChild(style);
@@ -92,6 +63,22 @@ options.customNamespaces = {
[CAST_NS]: cast.framework.system.MessageType.JSON, [CAST_NS]: cast.framework.system.MessageType.JSON,
}; };
// The docs say we need to set options.touchScreenOptimizeApp = true
// https://developers.google.com/cast/docs/caf_receiver/customize_ui#accessing_ui_controls
// This doesn't work.
// @ts-ignore
options.touchScreenOptimizedApp = true;
// The class reference say we can set a uiConfig in options to set it
// https://developers.google.com/cast/docs/reference/caf_receiver/cast.framework.CastReceiverOptions#uiConfig
// This doesn't work either.
// @ts-ignore
options.uiConfig = new cast.framework.ui.UiConfig();
// @ts-ignore
options.uiConfig.touchScreenOptimizedApp = true;
castContext.setInactivityTimeout(86400); // 1 day
castContext.addCustomMessageListener( castContext.addCustomMessageListener(
CAST_NS, CAST_NS,
// @ts-ignore // @ts-ignore
@@ -116,12 +103,6 @@ const playerManager = castContext.getPlayerManager();
playerManager.setMessageInterceptor( playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD, cast.framework.messages.MessageType.LOAD,
(loadRequestData) => { (loadRequestData) => {
if (
loadRequestData.media.contentId ===
"https://cast.home-assistant.io/images/google-nest-hub.png"
) {
return loadRequestData;
}
// We received a play media command, hide Lovelace and show media player // We received a play media command, hide Lovelace and show media player
showMediaPlayer(); showMediaPlayer();
const media = loadRequestData.media; const media = loadRequestData.media;

View File

@@ -1,5 +1,10 @@
import { html, TemplateResult } from "lit"; import {
import { customElement, property, state } from "lit/decorators"; customElement,
html,
internalProperty,
property,
TemplateResult,
} from "lit-element";
import { mockHistory } from "../../../../demo/src/stubs/history"; import { mockHistory } from "../../../../demo/src/stubs/history";
import { LovelaceConfig } from "../../../../src/data/lovelace"; import { LovelaceConfig } from "../../../../src/data/lovelace";
import { import {
@@ -16,7 +21,7 @@ import "./hc-lovelace";
class HcDemo extends HassElement { class HcDemo extends HassElement {
@property({ attribute: false }) public lovelacePath!: string; @property({ attribute: false }) public lovelacePath!: string;
@state() private _lovelaceConfig?: LovelaceConfig; @internalProperty() private _lovelaceConfig?: LovelaceConfig;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._lovelaceConfig) { if (!this._lovelaceConfig) {
@@ -33,10 +38,10 @@ class HcDemo extends HassElement {
protected firstUpdated(changedProps) { protected firstUpdated(changedProps) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this._initializeHass(); this._initialize();
} }
private async _initializeHass() { private async _initialize() {
const initial: Partial<MockHomeAssistant> = { const initial: Partial<MockHomeAssistant> = {
// Override updateHass so that the correct hass lifecycle methods are called // Override updateHass so that the correct hass lifecycle methods are called
updateHass: (hassUpdate: Partial<HomeAssistant>) => updateHass: (hassUpdate: Partial<HomeAssistant>) =>

View File

@@ -1,5 +1,12 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property } from "lit/decorators"; css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
@customElement("hc-launch-screen") @customElement("hc-launch-screen")
@@ -22,7 +29,7 @@ class HcLaunchScreen extends LitElement {
`; `;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
:host { :host {
display: block; display: block;

View File

@@ -1,15 +1,18 @@
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { customElement, property, query } from "lit/decorators"; css,
import { fireEvent } from "../../../../src/common/dom/fire_event"; CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { LovelaceConfig } from "../../../../src/data/lovelace"; import { LovelaceConfig } from "../../../../src/data/lovelace";
import { Lovelace } from "../../../../src/panels/lovelace/types"; import { Lovelace } from "../../../../src/panels/lovelace/types";
import "../../../../src/panels/lovelace/views/hui-view"; import "../../../../src/panels/lovelace/views/hui-view";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import "./hc-launch-screen"; import "./hc-launch-screen";
(window as any).loadCardHelpers = () =>
import("../../../../src/panels/lovelace/custom-card-helpers");
@customElement("hc-lovelace") @customElement("hc-lovelace")
class HcLovelace extends LitElement { class HcLovelace extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@@ -18,9 +21,7 @@ class HcLovelace extends LitElement {
@property() public viewPath?: string | number; @property() public viewPath?: string | number;
@property() public urlPath: string | null = null; public urlPath?: string | null;
@query("hui-view") private _huiView?: HTMLElement;
protected render(): TemplateResult { protected render(): TemplateResult {
const index = this._viewIndex; const index = this._viewIndex;
@@ -34,9 +35,8 @@ class HcLovelace extends LitElement {
} }
const lovelace: Lovelace = { const lovelace: Lovelace = {
config: this.lovelaceConfig, config: this.lovelaceConfig,
rawConfig: this.lovelaceConfig,
editMode: false, editMode: false,
urlPath: this.urlPath, urlPath: this.urlPath!,
enableFullEditMode: () => undefined, enableFullEditMode: () => undefined,
mode: "storage", mode: "storage",
locale: this.hass.locale, locale: this.hass.locale,
@@ -60,32 +60,17 @@ class HcLovelace extends LitElement {
const index = this._viewIndex; const index = this._viewIndex;
if (index !== undefined) { if (index !== undefined) {
const dashboardTitle = this.lovelaceConfig.title || this.urlPath;
const viewTitle =
this.lovelaceConfig.views[index].title ||
this.lovelaceConfig.views[index].path;
fireEvent(this, "cast-view-changed", {
title:
dashboardTitle || viewTitle
? `${dashboardTitle || ""}${
dashboardTitle && viewTitle ? ": " : ""
}${viewTitle || ""}`
: undefined,
});
const configBackground = const configBackground =
this.lovelaceConfig.views[index].background || this.lovelaceConfig.views[index].background ||
this.lovelaceConfig.background; this.lovelaceConfig.background;
if (configBackground) { if (configBackground) {
this._huiView!.style.setProperty( (this.shadowRoot!.querySelector(
"hui-view"
) as HTMLElement)!.style.setProperty(
"--lovelace-background", "--lovelace-background",
configBackground configBackground
); );
} else {
this._huiView!.style.removeProperty("--lovelace-background");
} }
} }
} }
@@ -105,7 +90,7 @@ class HcLovelace extends LitElement {
return undefined; return undefined;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
:host { :host {
min-height: 100vh; min-height: 100vh;
@@ -118,22 +103,12 @@ class HcLovelace extends LitElement {
:host > * { :host > * {
flex: 1; flex: 1;
} }
hui-view {
background: var(--lovelace-background, var(--primary-background-color));
}
`; `;
} }
} }
export interface CastViewChanged {
title: string | undefined;
}
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"hc-lovelace": HcLovelace; "hc-lovelace": HcLovelace;
} }
interface HASSDomEvents {
"cast-view-changed": CastViewChanged;
}
} }

View File

@@ -3,8 +3,12 @@ import {
getAuth, getAuth,
UnsubscribeFunc, UnsubscribeFunc,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { html, TemplateResult } from "lit"; import {
import { customElement, state } from "lit/decorators"; customElement,
html,
internalProperty,
TemplateResult,
} from "lit-element";
import { CAST_NS } from "../../../../src/cast/const"; import { CAST_NS } from "../../../../src/cast/const";
import { import {
ConnectMessage, ConnectMessage,
@@ -13,11 +17,7 @@ import {
ShowDemoMessage, ShowDemoMessage,
ShowLovelaceViewMessage, ShowLovelaceViewMessage,
} from "../../../../src/cast/receiver_messages"; } from "../../../../src/cast/receiver_messages";
import { import { ReceiverStatusMessage } from "../../../../src/cast/sender_messages";
ReceiverErrorCode,
ReceiverErrorMessage,
ReceiverStatusMessage,
} from "../../../../src/cast/sender_messages";
import { atLeastVersion } from "../../../../src/common/config/version"; import { atLeastVersion } from "../../../../src/common/config/version";
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click"; import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
import { import {
@@ -36,18 +36,18 @@ let resourcesLoaded = false;
@customElement("hc-main") @customElement("hc-main")
export class HcMain extends HassElement { export class HcMain extends HassElement {
@state() private _showDemo = false; @internalProperty() private _showDemo = false;
@state() private _lovelaceConfig?: LovelaceConfig; @internalProperty() private _lovelaceConfig?: LovelaceConfig;
@state() private _lovelacePath: string | number | null = null; @internalProperty() private _lovelacePath: string | number | null = null;
@state() private _error?: string; @internalProperty() private _error?: string;
@state() private _urlPath?: string | null;
private _unsubLovelace?: UnsubscribeFunc; private _unsubLovelace?: UnsubscribeFunc;
private _urlPath?: string | null;
public processIncomingMessage(msg: HassMessage) { public processIncomingMessage(msg: HassMessage) {
if (msg.type === "connect") { if (msg.type === "connect") {
this._handleConnectMessage(msg); this._handleConnectMessage(msg);
@@ -72,10 +72,8 @@ export class HcMain extends HassElement {
!this._lovelaceConfig || !this._lovelaceConfig ||
this._lovelacePath === null || this._lovelacePath === null ||
// Guard against part of HA not being loaded yet. // Guard against part of HA not being loaded yet.
!this.hass || (this.hass &&
!this.hass.states || (!this.hass.states || !this.hass.config || !this.hass.services))
!this.hass.config ||
!this.hass.services
) { ) {
return html` return html`
<hc-launch-screen <hc-launch-screen
@@ -113,7 +111,6 @@ export class HcMain extends HassElement {
this._sendStatus(); this._sendStatus();
} }
}); });
this.addEventListener("dialog-closed", this._dialogClosed);
} }
private _sendStatus(senderId?: string) { private _sendStatus(senderId?: string) {
@@ -125,7 +122,7 @@ export class HcMain extends HassElement {
if (this.hass) { if (this.hass) {
status.hassUrl = this.hass.auth.data.hassUrl; status.hassUrl = this.hass.auth.data.hassUrl;
status.lovelacePath = this._lovelacePath; status.lovelacePath = this._lovelacePath!;
status.urlPath = this._urlPath; status.urlPath = this._urlPath;
} }
@@ -138,30 +135,6 @@ export class HcMain extends HassElement {
} }
} }
private _sendError(
error_code: number,
error_message: string,
senderId?: string
) {
const error: ReceiverErrorMessage = {
type: "receiver_error",
error_code,
error_message,
};
if (senderId) {
this.sendMessage(senderId, error);
} else {
for (const sender of castContext.getSenders()) {
this.sendMessage(sender.id, error);
}
}
}
private _dialogClosed = () => {
document.body.setAttribute("style", "overflow-y: auto !important");
};
private async _handleGetStatusMessage(msg: GetStatusMessage) { private async _handleGetStatusMessage(msg: GetStatusMessage) {
this._sendStatus(msg.senderId!); this._sendStatus(msg.senderId!);
} }
@@ -179,19 +152,15 @@ export class HcMain extends HassElement {
expires_in: 0, expires_in: 0,
}), }),
}); });
} catch (err: any) { } catch (err) {
const errorMessage = this._getErrorMessage(err); this._error = this._getErrorMessage(err);
this._error = errorMessage;
this._sendError(err, errorMessage);
return; return;
} }
let connection; let connection;
try { try {
connection = await createConnection({ auth }); connection = await createConnection({ auth });
} catch (err: any) { } catch (err) {
const errorMessage = this._getErrorMessage(err); this._error = this._getErrorMessage(err);
this._error = errorMessage;
this._sendError(err, errorMessage);
return; return;
} }
if (this.hass) { if (this.hass) {
@@ -203,29 +172,24 @@ export class HcMain extends HassElement {
} }
private async _handleShowLovelaceMessage(msg: ShowLovelaceViewMessage) { private async _handleShowLovelaceMessage(msg: ShowLovelaceViewMessage) {
this._showDemo = false;
// We should not get this command before we are connected. // We should not get this command before we are connected.
// Means a client got out of sync. Let's send status to them. // Means a client got out of sync. Let's send status to them.
if (!this.hass) { if (!this.hass) {
this._sendStatus(msg.senderId!); this._sendStatus(msg.senderId!);
this._error = "Cannot show Lovelace because we're not connected."; this._error = "Cannot show Lovelace because we're not connected.";
this._sendError(ReceiverErrorCode.NOT_CONNECTED, this._error);
return; return;
} }
this._error = undefined;
if (msg.urlPath === "lovelace") { if (msg.urlPath === "lovelace") {
msg.urlPath = null; msg.urlPath = null;
} }
this._lovelacePath = msg.viewPath;
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) { if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
this._urlPath = msg.urlPath; this._urlPath = msg.urlPath;
this._lovelaceConfig = undefined;
if (this._unsubLovelace) { if (this._unsubLovelace) {
this._unsubLovelace(); this._unsubLovelace();
} }
const llColl = atLeastVersion(this.hass.connection.haVersion, 0, 107) const llColl = atLeastVersion(this.hass.connection.haVersion, 0, 107)
? getLovelaceCollection(this.hass.connection, msg.urlPath) ? getLovelaceCollection(this.hass!.connection, msg.urlPath)
: getLegacyLovelaceCollection(this.hass.connection); : getLegacyLovelaceCollection(this.hass!.connection);
// We first do a single refresh because we need to check if there is LL // We first do a single refresh because we need to check if there is LL
// configuration. // configuration.
try { try {
@@ -233,17 +197,9 @@ export class HcMain extends HassElement {
this._unsubLovelace = llColl.subscribe((lovelaceConfig) => this._unsubLovelace = llColl.subscribe((lovelaceConfig) =>
this._handleNewLovelaceConfig(lovelaceConfig) this._handleNewLovelaceConfig(lovelaceConfig)
); );
} catch (err: any) { } catch (err) {
if (
atLeastVersion(this.hass.connection.haVersion, 0, 107) &&
err.code !== "config_not_found"
) {
// eslint-disable-next-line // eslint-disable-next-line
console.log("Error fetching Lovelace configuration", err, msg); console.log("Error fetching Lovelace configuration", err, msg);
this._error = `Error fetching Lovelace configuration: ${err.message}`;
this._sendError(ReceiverErrorCode.FETCH_CONFIG_FAILED, this._error);
return;
}
// Generate a Lovelace config. // Generate a Lovelace config.
this._unsubLovelace = () => undefined; this._unsubLovelace = () => undefined;
await this._generateLovelaceConfig(); await this._generateLovelaceConfig();
@@ -258,27 +214,23 @@ export class HcMain extends HassElement {
loadLovelaceResources(resources, this.hass!.auth.data.hassUrl); loadLovelaceResources(resources, this.hass!.auth.data.hassUrl);
} }
} }
this._showDemo = false;
this._lovelacePath = msg.viewPath;
this._sendStatus(); this._sendStatus();
} }
private async _generateLovelaceConfig() { private async _generateLovelaceConfig() {
const { generateLovelaceDashboardStrategy } = await import( const { generateLovelaceConfigFromHass } = await import(
"../../../../src/panels/lovelace/strategies/get-strategy" "../../../../src/panels/lovelace/common/generate-lovelace-config"
); );
this._handleNewLovelaceConfig( this._handleNewLovelaceConfig(
await generateLovelaceDashboardStrategy( await generateLovelaceConfigFromHass(this.hass!)
{
hass: this.hass!,
narrow: false,
},
"original-states"
)
); );
} }
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) { private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
castContext.setApplicationState(lovelaceConfig.title || ""); castContext.setApplicationState(lovelaceConfig.title!);
this._lovelaceConfig = lovelaceConfig; this._lovelaceConfig = lovelaceConfig;
} }

View File

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

View File

@@ -246,15 +246,11 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
"light.living_room_lights": { "light.living_room_lights": {
entity_id: "light.living_room_lights", entity_id: "light.living_room_lights",
state: "on", state: "off",
attributes: { attributes: {
min_mireds: 111, min_mireds: 111,
max_mireds: 400, max_mireds: 400,
brightness: 175,
color_temp: 300,
supported_color_modes: ["brightness", "color_temp"],
friendly_name: "Living Room Lights", friendly_name: "Living Room Lights",
color_mode: "color_temp",
supported_features: 55, supported_features: 55,
}, },
}, },
@@ -267,27 +263,13 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
}, },
"light.kitchen_lights": { "light.kitchen_lights": {
entity_id: "light.kitchen_lights", entity_id: "light.kitchen_lights",
state: "on",
attributes: {
min_mireds: 111,
max_mireds: 400,
brightness: 200,
rgb_color: [255, 175, 96],
supported_color_modes: ["brightness", "color_temp", "rgb"],
color_mode: "rgb",
friendly_name: "Kitchen Lights",
supported_features: 55,
},
},
"light.lifx5": {
entity_id: "light.lifx5",
state: "off", state: "off",
attributes: { attributes: {
supported_color_modes: ["brightness"], friendly_name: "Kitchen Lights",
friendly_name: "Garage Lights",
supported_features: 1, supported_features: 1,
}, },
}, },
"sensor.plexspy": { "sensor.plexspy": {
entity_id: "sensor.plexspy", entity_id: "sensor.plexspy",
state: "0", state: "0",
@@ -500,6 +482,16 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
icon: "hademo:history", icon: "hademo:history",
}, },
}, },
"light.lifx5": {
entity_id: "light.lifx5",
state: "on",
attributes: {
min_mireds: 111,
max_mireds: 400,
friendly_name: "Garage Lights",
supported_features: 55,
},
},
"sensor.alok_to_home": { "sensor.alok_to_home": {
entity_id: "sensor.alok_to_home", entity_id: "sensor.alok_to_home",
state: "41", state: "41",

View File

@@ -29,11 +29,6 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
}, },
], ],
}, },
{
title: "Energy distribution today",
type: "energy-distribution",
link_dashboard: true,
},
{ {
type: "thermostat", type: "thermostat",
entity: "climate.upstairs", entity: "climate.upstairs",
@@ -118,7 +113,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)", on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {
@@ -200,7 +196,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)", on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {
@@ -280,7 +277,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)", on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {
@@ -317,7 +315,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)", on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {

View File

@@ -1,6 +1,5 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace } from "../../../src/panels/lovelace/types"; import { Lovelace } from "../../../src/panels/lovelace/types";
import { energyEntities } from "../stubs/entities";
import { DemoConfig } from "./types"; import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
@@ -13,8 +12,9 @@ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
// eslint-disable-next-line import/no-mutable-exports // eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfigIndex = 0; export let selectedDemoConfigIndex = 0;
// eslint-disable-next-line import/no-mutable-exports // eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfig: Promise<DemoConfig> = export let selectedDemoConfig: Promise<DemoConfig> = demoConfigs[
demoConfigs[selectedDemoConfigIndex](); selectedDemoConfigIndex
]();
export const setDemoConfig = async ( export const setDemoConfig = async (
hass: MockHomeAssistant, hass: MockHomeAssistant,
@@ -28,7 +28,6 @@ export const setDemoConfig = async (
selectedDemoConfig = confProm; selectedDemoConfig = confProm;
hass.addEntities(config.entities(hass.localize), true); hass.addEntities(config.entities(hass.localize), true);
hass.addEntities(energyEntities());
lovelace.saveConfig(config.lovelace(hass.localize)); lovelace.saveConfig(config.lovelace(hass.localize));
hass.mockTheme(config.theme()); hass.mockTheme(config.theme());
}; };

View File

@@ -194,7 +194,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
type: "state-icon", type: "state-icon",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "group.downstairs_lights", entity_id: "group.downstairs_lights",
}, },
service: "homeassistant.toggle", service: "homeassistant.toggle",

View File

@@ -980,7 +980,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-off", icon: "mdi:account-off",
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
templates: { templates: {
icon: "if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n", icon:
"if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
icon_color: icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n", "if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
}, },
@@ -1004,7 +1005,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-multiple-minus", icon: "mdi:account-multiple-minus",
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
templates: { templates: {
icon: "if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n", icon:
"if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
icon_color: icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n", "if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
}, },
@@ -1112,9 +1114,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
min_mireds: 153, min_mireds: 153,
max_mireds: 500, max_mireds: 500,
brightness: 63, brightness: 63,
color_temp: 200,
supported_color_modes: ["brightness", "color_temp", "rgb"],
color_mode: "color_temp",
friendly_name: "Upstairs lights", friendly_name: "Upstairs lights",
supported_features: 63, supported_features: 63,
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
@@ -1126,7 +1125,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
attributes: { attributes: {
friendly_name: "Walk in closet lights", friendly_name: "Walk in closet lights",
supported_features: 41, supported_features: 41,
supported_color_modes: ["brightness", "color_temp"],
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
icon: "mdi:wall-sconce", icon: "mdi:wall-sconce",
}, },
@@ -1138,8 +1136,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
brightness: 254, brightness: 254,
friendly_name: "Outdoor lights", friendly_name: "Outdoor lights",
supported_features: 41, supported_features: 41,
supported_color_modes: ["brightness"],
color_mode: "brightness",
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
icon: "mdi:wall-sconce", icon: "mdi:wall-sconce",
}, },
@@ -1152,8 +1148,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
max_mireds: 500, max_mireds: 500,
brightness: 128, brightness: 128,
color_temp: 366, color_temp: 366,
supported_color_modes: ["brightness", "color_temp", "rgb"],
color_mode: "color_temp",
effect_list: ["colorloop"], effect_list: ["colorloop"],
friendly_name: "Downstairs lights", friendly_name: "Downstairs lights",
supported_features: 63, supported_features: 63,
@@ -1313,7 +1307,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
attributes: { attributes: {
min_mireds: 153, min_mireds: 153,
max_mireds: 500, max_mireds: 500,
supported_color_modes: ["brightness", "color_temp"],
is_deconz_group: false, is_deconz_group: false,
friendly_name: "Bedside Lamp", friendly_name: "Bedside Lamp",
supported_features: 63, supported_features: 63,
@@ -1327,7 +1320,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
attributes: { attributes: {
min_mireds: 153, min_mireds: 153,
max_mireds: 500, max_mireds: 500,
supported_color_modes: ["brightness", "color_temp"],
is_deconz_group: false, is_deconz_group: false,
friendly_name: "Floorlamp Reading Light", friendly_name: "Floorlamp Reading Light",
supported_features: 43, supported_features: 43,
@@ -1343,8 +1335,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
max_mireds: 500, max_mireds: 500,
brightness: 128, brightness: 128,
color_temp: 366, color_temp: 366,
supported_color_modes: ["brightness", "color_temp", "rgb"],
color_mode: "color_temp",
effect_list: ["colorloop"], effect_list: ["colorloop"],
is_deconz_group: false, is_deconz_group: false,
friendly_name: "Hallway window light", friendly_name: "Hallway window light",
@@ -1359,7 +1349,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
attributes: { attributes: {
brightness: 77, brightness: 77,
is_deconz_group: false, is_deconz_group: false,
supported_color_modes: ["brightness"],
friendly_name: "Isa Ceiling Light", friendly_name: "Isa Ceiling Light",
supported_features: 41, supported_features: 41,
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
@@ -1374,8 +1363,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
max_mireds: 500, max_mireds: 500,
brightness: 150, brightness: 150,
color_temp: 366, color_temp: 366,
supported_color_modes: ["brightness", "color_temp"],
color_mode: "color_temp",
effect_list: ["colorloop"], effect_list: ["colorloop"],
is_deconz_group: false, is_deconz_group: false,
friendly_name: "Floorlamp", friendly_name: "Floorlamp",
@@ -1390,7 +1377,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
attributes: { attributes: {
friendly_name: "Bedroom Ceiling Light", friendly_name: "Bedroom Ceiling Light",
supported_features: 41, supported_features: 41,
supported_color_modes: ["brightness"],
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
icon: "mdi:ceiling-light", icon: "mdi:ceiling-light",
}, },
@@ -1401,7 +1387,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
attributes: { attributes: {
friendly_name: "Nightlight", friendly_name: "Nightlight",
supported_features: 17, supported_features: 17,
supported_color_modes: ["brightness"],
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
icon: "mdi:lamp", icon: "mdi:lamp",
}, },
@@ -1768,7 +1753,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
power_consumption: 2.2, power_consumption: 2.2,
friendly_name: "Upstairs Hallway Light", friendly_name: "Upstairs Hallway Light",
supported_features: 33, supported_features: 33,
supported_color_modes: ["brightness"],
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
icon: "mdi:ceiling-light", icon: "mdi:ceiling-light",
}, },
@@ -1784,7 +1768,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
power_consumption: 0, power_consumption: 0,
friendly_name: "Dining Room Light", friendly_name: "Dining Room Light",
supported_features: 33, supported_features: 33,
supported_color_modes: ["brightness"],
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
icon: "mdi:ceiling-light", icon: "mdi:ceiling-light",
}, },
@@ -1800,7 +1783,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
power_consumption: 0, power_consumption: 0,
friendly_name: "Living room Spotlights", friendly_name: "Living room Spotlights",
supported_features: 33, supported_features: 33,
supported_color_modes: ["brightness"],
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
icon: "mdi:track-light", icon: "mdi:track-light",
}, },
@@ -1817,7 +1799,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
power_consumption: 2.5, power_consumption: 2.5,
friendly_name: "Passage Lights", friendly_name: "Passage Lights",
supported_features: 33, supported_features: 33,
supported_color_modes: ["brightness"],
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
icon: "mdi:track-light", icon: "mdi:track-light",
}, },
@@ -1862,7 +1843,6 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
power_consumption: 37.4, power_consumption: 37.4,
friendly_name: "Kitchen Lights", friendly_name: "Kitchen Lights",
supported_features: 33, supported_features: 33,
supported_color_modes: ["brightness"],
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
icon: "mdi:track-light", icon: "mdi:track-light",
}, },

View File

@@ -377,7 +377,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed", name: "AC bed",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "script.air_cleaner_quiet", entity_id: "script.air_cleaner_quiet",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -390,7 +390,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed", name: "AC bed",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "script.air_cleaner_auto", entity_id: "script.air_cleaner_auto",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -403,7 +403,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed", name: "AC bed",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "script.air_cleaner_turbo", entity_id: "script.air_cleaner_turbo",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -416,7 +416,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC", name: "AC",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "script.ac_off", entity_id: "script.ac_off",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -429,7 +429,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC", name: "AC",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "script.ac_on", entity_id: "script.ac_on",
}, },
service: "script.turn_on", service: "script.turn_on",
@@ -440,8 +440,8 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
type: "horizontal-stack", type: "horizontal-stack",
}, },
{ {
type: "grid", cards: [
columns: 2, {
cards: [ cards: [
{ {
graph: "line", graph: "line",
@@ -454,6 +454,11 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "S's room", name: "S's room",
entity: "sensor.temperature_stefan", entity: "sensor.temperature_stefan",
}, },
],
type: "horizontal-stack",
},
{
cards: [
{ {
graph: "line", graph: "line",
type: "sensor", type: "sensor",
@@ -465,6 +470,11 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "Bathroom", name: "Bathroom",
entity: "sensor.temperature_downstairs_bathroom", entity: "sensor.temperature_downstairs_bathroom",
}, },
],
type: "horizontal-stack",
},
{
cards: [
{ {
graph: "line", graph: "line",
type: "sensor", type: "sensor",
@@ -477,6 +487,10 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "sensor.refrigerator", entity: "sensor.refrigerator",
}, },
], ],
type: "horizontal-stack",
},
],
type: "vertical-stack",
}, },
{ {
entities: [ entities: [
@@ -629,7 +643,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.morning_lights", entity: "scene.morning_lights",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "scene.morning_lights", entity_id: "scene.morning_lights",
}, },
service: "scene.turn_on", service: "scene.turn_on",
@@ -641,7 +655,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.movie_time", entity: "scene.movie_time",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "scene.movie_time", entity_id: "scene.movie_time",
}, },
service: "scene.turn_on", service: "scene.turn_on",
@@ -702,7 +716,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.downstairs_lights", entity: "light.downstairs_lights",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "light.downstairs_lights", entity_id: "light.downstairs_lights",
}, },
service: "light.toggle", service: "light.toggle",
@@ -714,7 +728,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.upstairs_lights", entity: "light.upstairs_lights",
tap_action: { tap_action: {
action: "call-service", action: "call-service",
data: { service_data: {
entity_id: "light.upstairs_lights", entity_id: "light.upstairs_lights",
}, },
service: "light.toggle", service: "light.toggle",

View File

@@ -1,5 +1,5 @@
/* eslint-disable */ /* eslint-disable */
import { LitElement } from "lit"; import { LitElement } from "lit-element";
import "./card-tools"; import "./card-tools";
class CardModder extends LitElement { class CardModder extends LitElement {

View File

@@ -1,5 +1,5 @@
/* eslint-disable */ /* eslint-disable */
import { html, LitElement } from "lit"; import { html, LitElement } from "lit-element";
if (!window.cardTools) { if (!window.cardTools) {
const version = 0.2; const version = 0.2;

View File

@@ -1,6 +1,12 @@
import { mdiTelevision } from "@mdi/js"; import {
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; css,
import { customElement, state } from "lit/decorators"; CSSResult,
customElement,
html,
internalProperty,
LitElement,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../src/cast/cast_manager"; import { CastManager } from "../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../src/cast/receiver_messages"; import { castSendShowDemo } from "../../../src/cast/receiver_messages";
import "../../../src/components/ha-icon"; import "../../../src/components/ha-icon";
@@ -14,7 +20,7 @@ import { HomeAssistant } from "../../../src/types";
class CastDemoRow extends LitElement implements LovelaceRow { class CastDemoRow extends LitElement implements LovelaceRow {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@state() private _castManager?: CastManager | null; @internalProperty() private _castManager?: CastManager | null;
public setConfig(_config: CastConfig): void { public setConfig(_config: CastConfig): void {
// No config possible. // No config possible.
@@ -28,7 +34,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
return html``; return html``;
} }
return html` return html`
<ha-svg-icon .path=${mdiTelevision}></ha-svg-icon> <ha-icon icon="hademo:television"></ha-icon>
<div class="flex"> <div class="flex">
<div class="name">Show Chromecast interface</div> <div class="name">Show Chromecast interface</div>
<google-cast-launcher></google-cast-launcher> <google-cast-launcher></google-cast-launcher>
@@ -67,13 +73,13 @@ class CastDemoRow extends LitElement implements LovelaceRow {
this.style.display = this._castManager ? "" : "none"; this.style.display = this._castManager ? "" : "none";
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult {
return css` return css`
:host { :host {
display: flex; display: flex;
align-items: center; align-items: center;
} }
ha-svg-icon { ha-icon {
padding: 8px; padding: 8px;
color: var(--paper-item-icon-color); color: var(--paper-item-icon-color);
} }

View File

@@ -1,7 +1,14 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import {
import { property, state } from "lit/decorators"; css,
import { until } from "lit/directives/until"; CSSResult,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { until } from "lit-html/directives/until";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-circular-progress"; import "../../../src/components/ha-circular-progress";
import { LovelaceCardConfig } from "../../../src/data/lovelace"; import { LovelaceCardConfig } from "../../../src/data/lovelace";
@@ -19,7 +26,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: MockHomeAssistant; @property({ attribute: false }) public hass!: MockHomeAssistant;
@state() private _switching = false; @internalProperty() private _switching?: boolean;
private _hidden = localStorage.hide_demo_card; private _hidden = localStorage.hide_demo_card;
@@ -27,7 +34,12 @@ export class HADemoCard extends LitElement implements LovelaceCard {
return this._hidden ? 0 : 2; return this._hidden ? 0 : 2;
} }
public setConfig(_config: LovelaceCardConfig) {} public setConfig(
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
config: LovelaceCardConfig
// eslint-disable-next-line @typescript-eslint/no-empty-function
) {}
protected render(): TemplateResult { protected render(): TemplateResult {
if (this._hidden) { if (this._hidden) {
@@ -44,7 +56,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
(conf) => html` (conf) => html`
${conf.name} ${conf.name}
<small> <small>
<a target="_blank" href=${conf.authorUrl}> <a target="_blank" href="${conf.authorUrl}">
${this.hass.localize( ${this.hass.localize(
"ui.panel.page-demo.cards.demo.demo_by", "ui.panel.page-demo.cards.demo.demo_by",
"name", "name",
@@ -94,14 +106,14 @@ export class HADemoCard extends LitElement implements LovelaceCard {
this._switching = true; this._switching = true;
try { try {
await setDemoConfig(this.hass, this.lovelace!, index); await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err: any) { } catch (err) {
alert("Failed to switch config :-("); alert("Failed to switch config :-(");
} finally { } finally {
this._switching = false; this._switching = false;
} }
} }
static get styles(): CSSResultGroup { static get styles(): CSSResult[] {
return [ return [
css` css`
a { a {

View File

@@ -1,4 +1,11 @@
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style"; import "../../src/resources/ha-style";
import "../../src/resources/roboto"; import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch"; import "../../src/resources/safari-14-attachshadow-patch";
import "./ha-demo"; import "./ha-demo";
/* polyfill for paper-dropdown */
setTimeout(() => {
import("web-animations-js/web-animations-next-lite.min");
}, 1000);

View File

@@ -20,14 +20,11 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log"; import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template"; import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations"; import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl { class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() { protected async _initialize() {
const initial: Partial<MockHomeAssistant> = { const initial: Partial<MockHomeAssistant> = {
panelUrl: (this as any)._panelUrl, panelUrl: (this as any).panelUrl,
// Override updateHass so that the correct hass lifecycle methods are called // Override updateHass so that the correct hass lifecycle methods are called
updateHass: (hassUpdate: Partial<HomeAssistant>) => updateHass: (hassUpdate: Partial<HomeAssistant>) =>
this._updateHass(hassUpdate), this._updateHass(hassUpdate),
@@ -50,12 +47,8 @@ class HaDemo extends HomeAssistantAppEl {
mockEvents(hass); mockEvents(hass);
mockMediaPlayer(hass); mockMediaPlayer(hass);
mockFrontend(hass); mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass); mockPersistentNotification(hass);
hass.addEntities(energyEntities());
// Once config is loaded AND localize, set entities and apply theme. // Once config is loaded AND localize, set entities and apply theme.
Promise.all([selectedDemoConfig, localizePromise]).then( Promise.all([selectedDemoConfig, localizePromise]).then(
([conf, localize]) => { ([conf, localize]) => {
@@ -77,7 +70,7 @@ class HaDemo extends HomeAssistantAppEl {
} }
e.preventDefault(); e.preventDefault();
navigate(href); navigate(this, href);
}, },
{ capture: true } { capture: true }
); );

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +0,0 @@
import { AreaRegistryEntry } from "../../../src/data/area_registry";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockAreaRegistry = (
hass: MockHomeAssistant,
data: AreaRegistryEntry[] = []
) => hass.mockWS("config/area_registry/list", () => data);

View File

@@ -1,41 +0,0 @@
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,7 +0,0 @@
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockDeviceRegistry = (
hass: MockHomeAssistant,
data: DeviceRegistryEntry[] = []
) => hass.mockWS("config/device_registry/list", () => data);

View File

@@ -1,137 +0,0 @@
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
import { EnergySolarForecasts } from "../../../src/data/energy";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEnergy = (hass: MockHomeAssistant) => {
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(
"energy/solar_forecast",
(): EnergySolarForecasts => ({
solar_forecast: {
wh_hours: {
[`${todayString}T06:00:00`]: 0,
[`${todayString}T06:23:00`]: 6,
[`${todayString}T06:45:00`]: 39,
[`${todayString}T07:00:00`]: 28,
[`${todayString}T08:00:00`]: 208,
[`${todayString}T09:00:00`]: 352,
[`${todayString}T10:00:00`]: 544,
[`${todayString}T11:00:00`]: 748,
[`${todayString}T12:00:00`]: 1259,
[`${todayString}T13:00:00`]: 1361,
[`${todayString}T14:00:00`]: 1373,
[`${todayString}T15:00:00`]: 1370,
[`${todayString}T16:00:00`]: 1186,
[`${todayString}T17:00:00`]: 937,
[`${todayString}T18:00:00`]: 652,
[`${todayString}T19:00:00`]: 370,
[`${todayString}T20:00:00`]: 155,
[`${todayString}T21:48:00`]: 24,
[`${todayString}T22:36:00`]: 0,
[`${tomorrowString}T06:01:00`]: 0,
[`${tomorrowString}T06:23:00`]: 9,
[`${tomorrowString}T06:45:00`]: 47,
[`${tomorrowString}T07:00:00`]: 48,
[`${tomorrowString}T08:00:00`]: 473,
[`${tomorrowString}T09:00:00`]: 827,
[`${tomorrowString}T10:00:00`]: 1153,
[`${tomorrowString}T11:00:00`]: 1413,
[`${tomorrowString}T12:00:00`]: 1590,
[`${tomorrowString}T13:00:00`]: 1652,
[`${tomorrowString}T14:00:00`]: 1612,
[`${tomorrowString}T15:00:00`]: 1438,
[`${tomorrowString}T16:00:00`]: 1149,
[`${tomorrowString}T17:00:00`]: 830,
[`${tomorrowString}T18:00:00`]: 542,
[`${tomorrowString}T19:00:00`]: 311,
[`${tomorrowString}T20:00:00`]: 140,
[`${tomorrowString}T21:47:00`]: 22,
[`${tomorrowString}T22:34:00`]: 0,
},
},
})
);
};

View File

@@ -1,178 +0,0 @@
import { convertEntities } from "../../../src/fake_data/entity";
export const energyEntities = () =>
convertEntities({
"sensor.grid_fossil_fuel_percentage": {
entity_id: "sensor.grid_fossil_fuel_percentage",
state: "88.6",
attributes: {
unit_of_measurement: "%",
},
},
"sensor.solar_production": {
entity_id: "sensor.solar_production",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Solar",
unit_of_measurement: "kWh",
},
},
"sensor.battery_input": {
entity_id: "sensor.battery_input",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Input",
unit_of_measurement: "kWh",
},
},
"sensor.battery_output": {
entity_id: "sensor.battery_output",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Output",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1": {
entity_id: "sensor.energy_consumption_tarif_1 ",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_2": {
entity_id: "sensor.energy_consumption_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_1": {
entity_id: "sensor.energy_production_tarif_1",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_2": {
entity_id: "sensor.energy_production_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1_cost": {
entity_id: "sensor.energy_consumption_tarif_1_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_consumption_tarif_2_cost": {
entity_id: "sensor.energy_consumption_tarif_2_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_1_compensation": {
entity_id: "sensor.energy_production_tarif_1_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_2_compensation": {
entity_id: "sensor.energy_production_tarif_2_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas_cost": {
entity_id: "sensor.energy_gas_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas": {
entity_id: "sensor.energy_gas",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Gas",
unit_of_measurement: "m³",
},
},
"sensor.energy_car": {
entity_id: "sensor.energy_car",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Electric car",
unit_of_measurement: "kWh",
},
},
"sensor.energy_ac": {
entity_id: "sensor.energy_ac",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Air conditioning",
unit_of_measurement: "kWh",
},
},
"sensor.energy_washing_machine": {
entity_id: "sensor.energy_washing_machine",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Washing machine",
unit_of_measurement: "kWh",
},
},
"sensor.energy_dryer": {
entity_id: "sensor.energy_dryer",
state: "5.5",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Dryer",
unit_of_measurement: "kWh",
},
},
"sensor.energy_heat_pump": {
entity_id: "sensor.energy_heat_pump",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Heat pump",
unit_of_measurement: "kWh",
},
},
"sensor.energy_boiler": {
entity_id: "sensor.energy_boiler",
state: "7",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Boiler",
unit_of_measurement: "kWh",
},
},
});

View File

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

View File

@@ -1,59 +0,0 @@
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
hass.config.components.push("hassio");
hass.mockWS("supervisor/api", (msg) => {
if (msg.endpoint === "/supervisor/info") {
const data: HassioSupervisorInfo = {
version: "2021.10.dev0805",
version_latest: "2021.10.dev0806",
update_available: true,
channel: "dev",
arch: "aarch64",
supported: true,
healthy: true,
ip_address: "172.30.32.2",
wait_boot: 5,
timezone: "America/Los_Angeles",
logging: "info",
debug: false,
debug_block: false,
diagnostics: true,
addons: [
{
name: "Visual Studio Code",
slug: "a0d7b954_vscode",
description:
"Fully featured VSCode experience, to edit your HA config in the browser, including auto-completion!",
state: "started",
version: "3.6.2",
version_latest: "3.6.2",
update_available: false,
repository: "a0d7b954",
icon: false,
logo: true,
},
{
name: "Z-Wave JS",
slug: "core_zwave_js",
description:
"Control a ZWave network with Home Assistant Z-Wave JS",
state: "started",
version: "0.1.45",
version_latest: "0.1.45",
update_available: false,
repository: "core",
icon: true,
logo: true,
},
] as any,
addons_repositories: [
"https://github.com/hassio-addons/repository",
] as any,
};
return data;
}
return Promise.reject(`${msg.method} ${msg.endpoint} is not implemented`);
});
};

View File

@@ -1,12 +1,4 @@
import {
addDays,
addHours,
addMonths,
differenceInHours,
endOfDay,
} from "date-fns/esm";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
interface HistoryQueryParams { interface HistoryQueryParams {
@@ -72,335 +64,17 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"]; const incrementalUnits = ["clients", "queries", "ads"];
const generateMeanStatistics = (
id: string,
start: Date,
end: Date,
period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let lastVal = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const delta = Math.random() * maxDiff;
const mean = lastVal + delta;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
end: currentDate.toISOString(),
mean,
min: mean - Math.random() * maxDiff,
max: mean + Math.random() * maxDiff,
last_reset: "1970-01-01T00:00:00+00:00",
state: mean,
sum: null,
});
lastVal = mean;
currentDate =
period === "day"
? addDays(currentDate, 1)
: period === "month"
? addMonths(currentDate, 1)
: addHours(currentDate, 1);
}
return statistics;
};
const generateSumStatistics = (
id: string,
start: Date,
end: Date,
period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
end: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum,
});
currentDate =
period === "day"
? addDays(currentDate, 1)
: period === "month"
? addMonths(currentDate, 1)
: addHours(currentDate, 1);
}
return statistics;
};
const generateCurvedStatistics = (
id: string,
start: Date,
end: Date,
_period: "5minute" | "hour" | "day" | "month" = "hour",
initValue: number,
maxDiff: number,
metered: boolean
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const hours = differenceInHours(end, start) - 1;
let i = 0;
let half = false;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += i * add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
end: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum: metered ? sum : null,
});
currentDate = addHours(currentDate, 1);
if (!half && i > hours / 2) {
half = true;
}
i += half ? -1 : 1;
}
return statistics;
};
const statisticsFunctions: Record<
string,
(
id: string,
start: Date,
end: Date,
period: "5minute" | "hour" | "day" | "month"
) => StatisticValue[]
> = {
"sensor.energy_consumption_tarif_1": (
id: string,
start: Date,
end: Date,
period = "hour"
) => {
if (period !== "hour") {
return generateSumStatistics(
id,
start,
end,
period,
0,
period === "day" ? 17 : 504
);
}
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
const morningLow = generateSumStatistics(
id,
start,
morningEnd,
period,
0,
0.7
);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const morningFinalVal = morningLow.length
? morningLow[morningLow.length - 1].sum!
: 0;
const empty = generateSumStatistics(
id,
morningEnd,
eveningStart,
period,
morningFinalVal,
0
);
const eveningLow = generateSumStatistics(
id,
eveningStart,
end,
period,
morningFinalVal,
0.7
);
return [...morningLow, ...empty, ...eveningLow];
},
"sensor.energy_consumption_tarif_2": (
id: string,
start: Date,
end: Date,
period = "hour"
) => {
if (period !== "hour") {
return generateSumStatistics(
id,
start,
end,
period,
0,
period === "day" ? 17 : 504
);
}
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const highTarif = generateSumStatistics(
id,
morningEnd,
eveningStart,
period,
0,
0.3
);
const highTarifFinalVal = highTarif.length
? highTarif[highTarif.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, morningEnd, period, 0, 0);
const evening = generateSumStatistics(
id,
eveningStart,
end,
period,
highTarifFinalVal,
0
);
return [...morning, ...highTarif, ...evening];
},
"sensor.energy_production_tarif_1": (id, start, end, period = "hour") =>
generateSumStatistics(id, start, end, period, 0, 0),
"sensor.energy_production_tarif_1_compensation": (
id,
start,
end,
period = "hour"
) => generateSumStatistics(id, start, end, period, 0, 0),
"sensor.energy_production_tarif_2": (id, start, end, period = "hour") => {
if (period !== "hour") {
return generateSumStatistics(
id,
start,
end,
period,
0,
period === "day" ? 17 : 504
);
}
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
period,
0,
0.15,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(
id,
start,
productionStart,
period,
0,
0
);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
period,
productionFinalVal,
0
);
const rest = generateSumStatistics(
id,
dayEnd,
end,
period,
productionFinalVal,
1
);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.solar_production": (id, start, end, period = "hour") => {
if (period !== "hour") {
return generateSumStatistics(
id,
start,
end,
period,
0,
period === "day" ? 17 : 504
);
}
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
period,
0,
0.3,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(
id,
start,
productionStart,
period,
0,
0
);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
period,
productionFinalVal,
0
);
const rest = generateSumStatistics(
id,
dayEnd,
end,
period,
productionFinalVal,
2
);
return [...morning, ...production, ...evening, ...rest];
},
};
export const mockHistory = (mockHass: MockHomeAssistant) => { export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI( mockHass.mockAPI(
new RegExp("history/period/.+"), new RegExp("history/period/.+"),
(hass, _method, path, _parameters) => { (
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]); const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(","); const entities = params.filter_entity_id.split(",");
@@ -421,7 +95,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
const numberState = Number(state.state); const numberState = Number(state.state);
if (isNaN(numberState)) { if (isNaN(numberState)) {
// eslint-disable-next-line no-console // eslint-disable-next-line
console.log( console.log(
"Ignoring state with unparsable state but with a unit", "Ignoring state with unparsable state but with a unit",
entityId, entityId,
@@ -466,42 +140,4 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results; return results;
} }
); );
mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS(
"history/statistics_during_period",
({ statistic_ids, start_time, end_time, period }, hass) => {
const start = new Date(start_time);
const end = end_time ? new Date(end_time) : new Date();
const statistics: Record<string, StatisticValue[]> = {};
statistic_ids.forEach((id: string) => {
if (id in statisticsFunctions) {
statistics[id] = statisticsFunctions[id](id, start, end, period);
} else {
const entityState = hass.states[id];
const state = entityState ? Number(entityState.state) : 1;
statistics[id] =
entityState && "last_reset" in entityState.attributes
? generateSumStatistics(
id,
start,
end,
period,
state,
state * (state > 80 ? 0.01 : 0.05)
)
: generateMeanStatistics(
id,
start,
end,
period,
state,
state * (state > 80 ? 0.05 : 0.1)
);
}
});
return statistics;
}
);
}; };

View File

@@ -10,9 +10,10 @@ export const mockLovelace = (
localizePromise: Promise<LocalizeFunc> localizePromise: Promise<LocalizeFunc>
) => { ) => {
hass.mockWS("lovelace/config", () => hass.mockWS("lovelace/config", () =>
Promise.all([selectedDemoConfig, localizePromise]).then( Promise.all([
([config, localize]) => config.lovelace(localize) selectedDemoConfig,
) localizePromise,
]).then(([config, localize]) => config.lovelace(localize))
); );
hass.mockWS("lovelace/config/save", () => Promise.resolve()); hass.mockWS("lovelace/config/save", () => Promise.resolve());
@@ -23,9 +24,9 @@ customElements.whenDefined("hui-view").then(() => {
// eslint-disable-next-line // eslint-disable-next-line
const HUIView = customElements.get("hui-view"); const HUIView = customElements.get("hui-view");
// Patch HUI-VIEW to make the lovelace object available to the demo card // Patch HUI-VIEW to make the lovelace object available to the demo card
const oldCreateCard = HUIView!.prototype.createCardElement; const oldCreateCard = HUIView.prototype.createCardElement;
HUIView!.prototype.createCardElement = function (config) { HUIView.prototype.createCardElement = function (config) {
const el = oldCreateCard.call(this, config); const el = oldCreateCard.call(this, config);
if (el.tagName === "HA-DEMO-CARD") { if (el.tagName === "HA-DEMO-CARD") {
(el as HADemoCard).lovelace = this.lovelace; (el as HADemoCard).lovelace = this.lovelace;

View File

@@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
body: { message: "Template dev tool does not work in the demo." }, body: { message: "Template dev tool does not work in the demo." },
}) })
); );
hass.mockWS("render_template", (msg, _hass, onChange) => { hass.mockWS("render_template", (msg, onChange) => {
onChange!({ onChange!({
result: msg.template, result: msg.template,
listeners: { all: false, domains: [], entities: [], time: false }, listeners: { all: false, domains: [], entities: [], time: false },

View File

@@ -3,6 +3,8 @@ import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockTranslations = (hass: MockHomeAssistant) => { export const mockTranslations = (hass: MockHomeAssistant) => {
hass.mockWS( hass.mockWS(
"frontend/get_translations", "frontend/get_translations",
(/* msg: {language: string, category: string} */) => ({ resources: {} }) (/* msg: {language: string, category: string} */) => {
return { resources: {} };
}
); );
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,35 +0,0 @@
#!/bin/bash
TARGET_LABEL="needs design preview"
if [[ "$NETLIFY" != "true" ]]; then
echo "This script can only be run on Netlify"
exit 1
fi
function createStatus() {
state="$1"
description="$2"
target_url="$3"
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/home-assistant/frontend/statuses/$COMMIT_REF" \
-d '{"state": "'"${state}"'", "context": "Netlify/Design Preview Build", "description": "'"$description"'", "target_url": "'"$target_url"'"}'
}
if [[ "${PULL_REQUEST}" == "true" ]]; then
if [[ "$(curl -sSLf -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/home-assistant/frontend/pulls/${REVIEW_ID}" | jq '.labels[].name' -r)" =~ "$TARGET_LABEL" ]]; then
createStatus "pending" "Building design preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
gulp build-gallery
if [ $? -eq 0 ]; then
createStatus "success" "Build complete" "$DEPLOY_PRIME_URL"
else
createStatus "error" "Build failed" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
fi
else
createStatus "success" "Build was not requested by PR label"
fi
elif [[ "$INCOMING_HOOK_BODY" == "NIGHTLY" ]]; then
gulp build-gallery
fi

View File

@@ -1,52 +0,0 @@
module.exports = [
{
// This section has no header and so all page links are shown directly in the sidebar
category: "concepts",
pages: ["home"],
},
{
category: "lovelace",
// Label for in the sidebar
header: "Lovelace",
// Specify order of pages. Any pages in the category folder but not listed here will
// automatically be added after the pages listed here.
pages: ["introduction"],
},
{
category: "automation",
header: "Automation",
pages: [
"editor-trigger",
"editor-condition",
"editor-action",
"trace",
"trace-timeline",
],
},
{
category: "components",
header: "Components",
},
{
category: "more-info",
header: "More Info dialogs",
},
{
category: "misc",
header: "Miscelaneous",
},
{
category: "brand",
header: "Brand",
},
{
category: "user-test",
header: "Users",
pages: ["user-types", "configuration-menu"],
},
{
category: "design.home-assistant.io",
header: "About",
},
];

View File

@@ -1,146 +0,0 @@
import { Button } from "@material/mwc-button";
import { html, LitElement, css, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-card";
@customElement("demo-black-white-row")
class DemoBlackWhiteRow extends LitElement {
@property() title!: string;
@property() value!: any;
@property() disabled = false;
protected render(): TemplateResult {
return html`
<div class="row">
<div class="content light">
<ha-card .header=${this.title}>
<div class="card-content">
<slot name="light"></slot>
</div>
<div class="card-actions">
<mwc-button
.disabled=${this.disabled}
@click=${this.handleSubmit}
>
Submit
</mwc-button>
</div>
</ha-card>
</div>
<div class="content dark">
<ha-card .header=${this.title}>
<div class="card-content">
<slot name="dark"></slot>
</div>
<div class="card-actions">
<mwc-button
.disabled=${this.disabled}
@click=${this.handleSubmit}
>
Submit
</mwc-button>
</div>
</ha-card>
<pre>${JSON.stringify(this.value, undefined, 2)}</pre>
</div>
</div>
`;
}
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
applyThemesOnElement(
this.shadowRoot!.querySelector(".dark"),
{
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: true,
theme: "default",
},
undefined,
undefined,
true
);
}
handleSubmit(ev) {
const content = (ev.target as Button).closest(".content")!;
fireEvent(this, "submitted" as any, {
slot: content.classList.contains("light") ? "light" : "dark",
});
}
static styles = css`
.row {
display: flex;
}
.content {
padding: 50px 0;
background-color: var(--primary-background-color);
}
.light {
flex: 1;
padding-left: 50px;
padding-right: 50px;
box-sizing: border-box;
}
.light ha-card {
margin-left: auto;
}
.dark {
display: flex;
flex: 1;
padding-left: 50px;
box-sizing: border-box;
flex-wrap: wrap;
}
ha-card {
width: 400px;
}
pre {
width: 300px;
margin: 0 16px 0;
overflow: auto;
color: var(--primary-text-color);
}
.card-actions {
display: flex;
flex-direction: row-reverse;
border-top: none;
}
@media only screen and (max-width: 1500px) {
.light {
flex: initial;
}
}
@media only screen and (max-width: 1000px) {
.light,
.dark {
padding: 16px;
}
.row,
.dark {
flex-direction: column;
}
ha-card {
margin: 0 auto;
width: 100%;
max-width: 400px;
}
pre {
margin: 16px auto;
}
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"demo-black-white-row": DemoBlackWhiteRow;
}
}

View File

@@ -0,0 +1,106 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { safeLoad } from "js-yaml";
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
class DemoCard extends PolymerElement {
static get template() {
return html`
<style>
.root {
display: flex;
}
h2 {
margin: 0 0 20px;
color: var(--primary-color);
}
#card {
max-width: 400px;
width: 100vw;
}
pre {
width: 400px;
margin: 0 16px;
overflow: auto;
color: var(--primary-text-color);
}
@media only screen and (max-width: 800px) {
.root {
flex-direction: column;
}
pre {
margin: 16px 0;
}
}
</style>
<h2>[[config.heading]]</h2>
<div class="root">
<div id="card"></div>
<template is="dom-if" if="[[showConfig]]">
<pre>[[_trim(config.config)]]</pre>
</template>
</div>
`;
}
static get properties() {
return {
hass: {
type: Object,
observer: "_hassChanged",
},
config: {
type: Object,
observer: "_configChanged",
},
showConfig: Boolean,
};
}
ready() {
super.ready();
}
_configChanged(config) {
const card = this.$.card;
while (card.lastChild) {
card.removeChild(card.lastChild);
}
const el = this._createCardElement(safeLoad(config.config)[0]);
card.appendChild(el);
}
_createCardElement(cardConfig) {
const element = createCardElement(cardConfig);
if (this.hass) {
element.hass = this.hass;
}
element.addEventListener(
"ll-rebuild",
(ev) => {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
},
{ once: true }
);
return element;
}
_rebuildCard(cardElToReplace, config) {
const newCardEl = this._createCardElement(config);
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
}
_hassChanged(hass) {
const card = this.$.card.lastChild;
if (card) card.hass = hass;
}
_trim(config) {
return config.trim();
}
}
customElements.define("demo-card", DemoCard);

View File

@@ -1,129 +0,0 @@
import { load } from "js-yaml";
import { html, css, LitElement, PropertyValues } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { createCardElement } from "../../../src/panels/lovelace/create-element/create-card-element";
import { HomeAssistant } from "../../../src/types";
export interface DemoCardConfig {
heading: string;
config: string;
}
@customElement("demo-card")
class DemoCard extends LitElement {
@property() public hass!: HomeAssistant;
@property() public config!: DemoCardConfig;
@property() public showConfig = false;
@state() private _size?: number;
@query("#card") private _card!: HTMLElement;
render() {
return html`
<h2>
${this.config.heading}
${this._size !== undefined
? html`<small>(size ${this._size})</small>`
: ""}
</h2>
<div class="root">
<div id="card"></div>
${this.showConfig ? html`<pre>${this.config.config.trim()}</pre>` : ""}
</div>
`;
}
updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("config")) {
const card = this._card;
while (card.lastChild) {
card.removeChild(card.lastChild);
}
const el = this._createCardElement((load(this.config.config) as any)[0]);
card.appendChild(el);
this._getSize(el);
}
if (changedProps.has("hass")) {
const card = this._card.lastChild;
if (card) {
(card as any).hass = this.hass;
}
}
}
async _getSize(el) {
await customElements.whenDefined(el.localName);
if (!("getCardSize" in el)) {
this._size = undefined;
return;
}
this._size = await el.getCardSize();
}
_createCardElement(cardConfig) {
const element = createCardElement(cardConfig);
if (this.hass) {
element.hass = this.hass;
}
element.addEventListener(
"ll-rebuild",
(ev) => {
ev.stopPropagation();
this._rebuildCard(element, cardConfig);
},
{ once: true }
);
return element;
}
_rebuildCard(cardElToReplace, config) {
const newCardEl = this._createCardElement(config);
cardElToReplace.parentElement.replaceChild(newCardEl, cardElToReplace);
}
static styles = css`
.root {
display: flex;
}
h2 {
margin: 0 0 20px;
color: var(--primary-color);
}
h2 small {
font-size: 0.5em;
color: var(--primary-text-color);
}
#card {
max-width: 400px;
width: 100vw;
}
pre {
width: 400px;
margin: 0 16px;
overflow: auto;
color: var(--primary-text-color);
}
@media only screen and (max-width: 800px) {
.root {
flex-direction: column;
}
pre {
margin: 16px 0;
}
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"demo-card": DemoCard;
}
}

View File

@@ -0,0 +1,83 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch";
import "./demo-card";
class DemoCards extends PolymerElement {
static get template() {
return html`
<style>
#container {
min-height: calc(100vh - 128px);
background: var(--primary-background-color);
}
.cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
demo-card {
margin: 16px 16px 32px;
}
app-toolbar {
background-color: var(--light-primary-color);
}
.filters {
margin-left: 60px;
}
ha-formfield {
margin-right: 16px;
}
</style>
<app-toolbar>
<div class="filters">
<ha-formfield label="Show config">
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
</ha-switch>
</ha-formfield>
<ha-formfield label="Dark theme">
<ha-switch on-change="_darkThemeToggled"> </ha-switch>
</ha-formfield>
</div>
</app-toolbar>
<div id="container">
<div class="cards">
<template is="dom-repeat" items="[[configs]]">
<demo-card
config="[[item]]"
show-config="[[_showConfig]]"
hass="[[hass]]"
></demo-card>
</template>
</div>
</div>
`;
}
static get properties() {
return {
configs: Object,
hass: Object,
_showConfig: {
type: Boolean,
value: false,
},
};
}
_showConfigToggled(ev) {
this._showConfig = ev.target.checked;
}
_darkThemeToggled(ev) {
applyThemesOnElement(this.$.container, { themes: {} }, "default", {
dark: ev.target.checked,
});
}
}
customElements.define("demo-cards", DemoCards);

View File

@@ -1,91 +0,0 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import { html, css, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch";
import { HomeAssistant } from "../../../src/types";
import "./demo-card";
import type { DemoCardConfig } from "./demo-card";
@customElement("demo-cards")
class DemoCards extends LitElement {
@property() public configs!: DemoCardConfig[];
@property() public hass!: HomeAssistant;
@state() private _showConfig = false;
@query("#container") private _container!: HTMLElement;
render() {
return html`
<app-toolbar>
<div class="filters">
<ha-formfield label="Show config">
<ha-switch
.checked=${this._showConfig}
@change=${this._showConfigToggled}
>
</ha-switch>
</ha-formfield>
<ha-formfield label="Dark theme">
<ha-switch @change=${this._darkThemeToggled}> </ha-switch>
</ha-formfield>
</div>
</app-toolbar>
<div id="container">
<div class="cards">
${this.configs.map(
(config) => html`
<demo-card
.config=${config}
.showConfig=${this._showConfig}
.hass=${this.hass}
></demo-card>
`
)}
</div>
</div>
`;
}
_showConfigToggled(ev) {
this._showConfig = ev.target.checked;
}
_darkThemeToggled(ev) {
applyThemesOnElement(this._container, { themes: {} } as any, "default", {
dark: ev.target.checked,
});
}
static styles = css`
.cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
demo-card {
margin: 16px 16px 32px;
}
app-toolbar {
background-color: var(--light-primary-color);
}
.filters {
margin-left: 60px;
}
ha-formfield {
margin-right: 16px;
}
#container {
background-color: var(--primary-background-color);
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"demo-cards": DemoCards;
}
}

View File

@@ -1,66 +0,0 @@
import { html, css } from "lit";
import { customElement, property } from "lit/decorators";
import { until } from "lit/directives/until";
import { HaMarkdown } from "../../../src/components/ha-markdown";
import { PAGES } from "../../build/import-pages";
@customElement("page-description")
class PageDescription extends HaMarkdown {
@property() public page!: string;
render() {
if (!PAGES[this.page].description) {
return html``;
}
return html`
<div class="heading">
<div class="title">
${PAGES[this.page].metadata.title || this.page.split("/")[1]}
</div>
<div class="subtitle">${PAGES[this.page].metadata.subtitle}</div>
</div>
${until(
PAGES[this.page]
.description()
.then((content) => html`<div class="root">${content}</div>`),
""
)}
`;
}
static styles = [
HaMarkdown.styles,
css`
.heading {
padding: 16px;
border-bottom: 1px solid var(--secondary-background-color);
}
.title {
font-size: 42px;
line-height: 56px;
padding-bottom: 8px;
}
.subtitle {
font-size: 18px;
line-height: 24px;
}
.root {
max-width: 800px;
margin: 16px auto;
}
.root > *:first-child {
margin-top: 0;
}
.root > *:last-child {
margin-bottom: 0;
}
`,
];
}
declare global {
interface HTMLElementTagNameMap {
"page-description": PageDescription;
}
}

View File

@@ -1,11 +0,0 @@
export const LONG_TEXT = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc laoreet velit ut elit volutpat, eget ultrices odio lacinia. In imperdiet malesuada est, nec sagittis metus ultricies quis. Sed nisl ex, convallis porttitor ante quis, hendrerit tristique justo. Mauris pharetra venenatis augue, eu maximus sem cursus in. Quisque sed consequat risus. Suspendisse facilisis ligula a odio consectetur condimentum. Curabitur vehicula elit nec augue mollis, et volutpat massa dictum.
Nam pellentesque auctor rutrum. Suspendisse elit est, sodales vel diam nec, porttitor faucibus massa. Ut pretium ac orci eu pharetra. Praesent in nibh at magna viverra rutrum eu vitae tortor. Etiam eget sem ex. Fusce tristique odio nec lacus mattis, vitae tempor nunc malesuada. Maecenas faucibus magna vel libero maximus egestas. Vestibulum luctus semper velit, in lobortis risus tempus non. Curabitur bibendum ornare commodo. Quisque commodo neque sit amet tincidunt lacinia. Proin elementum ante velit, eu congue nulla semper quis. Pellentesque consequat vel nunc at scelerisque. Mauris sit amet venenatis diam, blandit viverra leo. Integer commodo laoreet orci.
Curabitur ipsum tortor, sodales ut augue sed, commodo porttitor libero. Pellentesque molestie vitae mi consectetur tempor. In sed lectus consequat, lobortis neque non, semper ipsum. Etiam eget ex et nibh sagittis pulvinar lacinia ac mauris. Aenean ligula eros, viverra ac nibh at, venenatis semper quam. Sed interdum ligula sit amet massa tincidunt tincidunt. Suspendisse potenti. Aliquam egestas facilisis est, sed faucibus erat scelerisque id. Duis dolor quam, viverra vitae orci euismod, laoreet pellentesque justo. Nunc malesuada non erat at ullamcorper. Mauris eget posuere odio. Vestibulum turpis nunc, pharetra eget ante in, feugiat mollis justo. Proin porttitor, diam nec vulputate pretium, tellus arcu rhoncus turpis, a blandit nisi nulla quis arcu. Nunc ac ullamcorper ligula, nec facilisis leo.
In vitae eros sollicitudin, iaculis ex eget, egestas orci. Etiam sed pretium lorem. Nam nisi enim, consectetur sit amet semper ac, semper pharetra diam. In pulvinar neque sapien, ac ullamcorper est lacinia a. Etiam tincidunt velit sed diam malesuada, eu ornare ex consectetur. Phasellus in imperdiet tellus. Sed bibendum, dui sit amet fringilla aliquet, enim odio sollicitudin lorem, vel semper turpis mauris vel mauris. Aenean congue magna ac massa cursus, in dictum orci commodo. Pellentesque mollis velit in sollicitudin tincidunt. Vestibulum et efficitur nulla.
Quisque posuere, velit sed porttitor dapibus, neque augue fringilla felis, eu luctus nisi nisl nec ipsum. Curabitur pellentesque ac lectus eget ultricies. Vestibulum est dolor, lacinia pharetra vulputate a, facilisis a magna. Nam vitae arcu nibh. Praesent finibus blandit ante, ac gravida ex mollis eget. Donec quam est, pulvinar vitae neque ut, bibendum aliquam erat. Nullam mollis arcu at sem tincidunt, in tristique lectus facilisis. Aenean ut lacus vel nisl finibus iaculis non a turpis. Integer eget ipsum ante. Donec nunc neque, vestibulum ac magna ac, posuere scelerisque dui. Pellentesque massa nibh, rhoncus id dolor quis, placerat posuere turpis. Donec aliquet augue nisi, eu finibus dui auctor et. Vestibulum eu varius lorem. Quisque lectus ante, malesuada pretium risus eget, interdum mattis enim.
`;

View File

@@ -119,7 +119,7 @@ export const basicTrace: DemoTrace = {
params: { params: {
domain: "input_boolean", domain: "input_boolean",
service: "toggle", service: "toggle",
data: {}, service_data: {},
target: { target: {
entity_id: ["input_boolean.toggle_4"], entity_id: ["input_boolean.toggle_4"],
}, },
@@ -164,7 +164,7 @@ export const basicTrace: DemoTrace = {
params: { params: {
domain: "input_boolean", domain: "input_boolean",
service: "toggle", service: "toggle",
data: {}, service_data: {},
target: { target: {
entity_id: ["input_boolean.toggle_2"], entity_id: ["input_boolean.toggle_2"],
}, },
@@ -182,7 +182,7 @@ export const basicTrace: DemoTrace = {
params: { params: {
domain: "input_boolean", domain: "input_boolean",
service: "toggle", service: "toggle",
data: {}, service_data: {},
target: { target: {
entity_id: ["input_boolean.toggle_3"], entity_id: ["input_boolean.toggle_3"],
}, },
@@ -200,7 +200,7 @@ export const basicTrace: DemoTrace = {
params: { params: {
domain: "input_boolean", domain: "input_boolean",
service: "toggle", service: "toggle",
data: {}, service_data: {},
target: { target: {
entity_id: ["input_boolean.toggle_4"], entity_id: ["input_boolean.toggle_4"],
}, },
@@ -298,11 +298,11 @@ export const basicTrace: DemoTrace = {
source: "state of input_boolean.toggle_1", source: "state of input_boolean.toggle_1",
entity_id: "automation.toggle_toggles", entity_id: "automation.toggle_toggles",
context_id: "6cfcae368e7b3686fad6c59e83ae76c9", context_id: "6cfcae368e7b3686fad6c59e83ae76c9",
when: 1616647011.240832, when: "2021-03-25T04:36:51.240832+00:00",
domain: "automation", domain: "automation",
}, },
{ {
when: 1616647011.249828, when: "2021-03-25T04:36:51.249828+00:00",
name: "Toggle 4", name: "Toggle 4",
state: "on", state: "on",
entity_id: "input_boolean.toggle_4", entity_id: "input_boolean.toggle_4",
@@ -313,7 +313,7 @@ export const basicTrace: DemoTrace = {
context_name: "Ensure Party mode", context_name: "Ensure Party mode",
}, },
{ {
when: 1616647011.258947, when: "2021-03-25T04:36:51.258947+00:00",
name: "Toggle 2", name: "Toggle 2",
state: "on", state: "on",
entity_id: "input_boolean.toggle_2", entity_id: "input_boolean.toggle_2",
@@ -324,7 +324,7 @@ export const basicTrace: DemoTrace = {
context_name: "Ensure Party mode", context_name: "Ensure Party mode",
}, },
{ {
when: 1616647011.261806, when: "2021-03-25T04:36:51.261806+00:00",
name: "Toggle 3", name: "Toggle 3",
state: "off", state: "off",
entity_id: "input_boolean.toggle_3", entity_id: "input_boolean.toggle_3",
@@ -335,7 +335,7 @@ export const basicTrace: DemoTrace = {
context_name: "Ensure Party mode", context_name: "Ensure Party mode",
}, },
{ {
when: 1616647011.265246, when: "2021-03-25T04:36:51.265246+00:00",
name: "Toggle 4", name: "Toggle 4",
state: "off", state: "off",
entity_id: "input_boolean.toggle_4", entity_id: "input_boolean.toggle_4",

View File

@@ -185,11 +185,11 @@ export const motionLightTrace: DemoTrace = {
"has been triggered by state of binary_sensor.pauluss_macbook_pro_camera_in_use", "has been triggered by state of binary_sensor.pauluss_macbook_pro_camera_in_use",
source: "state of binary_sensor.pauluss_macbook_pro_camera_in_use", source: "state of binary_sensor.pauluss_macbook_pro_camera_in_use",
entity_id: "automation.auto_elgato", entity_id: "automation.auto_elgato",
when: 1615702021.768492, when: "2021-03-14T06:07:01.768492+00:00",
domain: "automation", domain: "automation",
}, },
{ {
when: 1615702021.872187, when: "2021-03-14T06:07:01.872187+00:00",
name: "Elgato Key Light Air", name: "Elgato Key Light Air",
state: "on", state: "on",
entity_id: "light.elgato_key_light_air", entity_id: "light.elgato_key_light_air",
@@ -200,7 +200,7 @@ export const motionLightTrace: DemoTrace = {
context_name: "Auto Elgato", context_name: "Auto Elgato",
}, },
{ {
when: 1615702073.284505, when: "2021-03-14T06:07:53.284505+00:00",
name: "Elgato Key Light Air", name: "Elgato Key Light Air",
state: "off", state: "off",
entity_id: "light.elgato_key_light_air", entity_id: "light.elgato_key_light_air",

View File

@@ -0,0 +1,102 @@
import { safeDump } from "js-yaml";
import {
customElement,
html,
css,
LitElement,
TemplateResult,
property,
} from "lit-element";
import "../../../src/components/ha-card";
import { describeAction } from "../../../src/data/script_i18n";
import { provideHass } from "../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../src/types";
const actions = [
{ wait_template: "{{ true }}", alias: "Something with an alias" },
{ delay: "0:05" },
{ wait_template: "{{ true }}" },
{
condition: "template",
value_template: "{{ true }}",
},
{ event: "happy_event" },
{
device_id: "abcdefgh",
domain: "plex",
entity_id: "media_player.kitchen",
},
{ scene: "scene.kitchen_morning" },
{
wait_for_trigger: [
{
platform: "state",
entity_id: "input_boolean.toggle_1",
},
],
},
{
variables: {
hello: "world",
},
},
{
service: "input_boolean.toggle",
target: {
entity_id: "input_boolean.toggle_4",
},
},
];
@customElement("demo-automation-describe-action")
export class DemoAutomationDescribeAction extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
protected render(): TemplateResult {
if (!this.hass) {
return html``;
}
return html`
<ha-card header="Actions">
${actions.map(
(conf) => html`
<div class="action">
<span>${describeAction(this.hass, conf as any)}</span>
<pre>${safeDump(conf)}</pre>
</div>
`
)}
</ha-card>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
const hass = provideHass(this);
hass.updateTranslations(null, "en");
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
.action {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
span {
margin-right: 16px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-automation-describe-action": DemoAutomationDescribeAction;
}
}

View File

@@ -1,8 +1,13 @@
import { dump } from "js-yaml"; import { safeDump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit"; import {
import { customElement } from "lit/decorators"; customElement,
import "../../../../src/components/ha-card"; html,
import { describeCondition } from "../../../../src/data/automation_i18n"; css,
LitElement,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card";
import { describeCondition } from "../../../src/data/automation_i18n";
const conditions = [ const conditions = [
{ condition: "and" }, { condition: "and" },
@@ -26,7 +31,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
(conf) => html` (conf) => html`
<div class="condition"> <div class="condition">
<span>${describeCondition(conf as any)}</span> <span>${describeCondition(conf as any)}</span>
<pre>${dump(conf)}</pre> <pre>${safeDump(conf)}</pre>
</div> </div>
` `
)} )}

View File

@@ -1,8 +1,13 @@
import { dump } from "js-yaml"; import { safeDump } from "js-yaml";
import { html, css, LitElement, TemplateResult } from "lit"; import {
import { customElement } from "lit/decorators"; customElement,
import "../../../../src/components/ha-card"; html,
import { describeTrigger } from "../../../../src/data/automation_i18n"; css,
LitElement,
TemplateResult,
} from "lit-element";
import "../../../src/components/ha-card";
import { describeTrigger } from "../../../src/data/automation_i18n";
const triggers = [ const triggers = [
{ platform: "state" }, { platform: "state" },
@@ -29,7 +34,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
(conf) => html` (conf) => html`
<div class="trigger"> <div class="trigger">
<span>${describeTrigger(conf as any)}</span> <span>${describeTrigger(conf as any)}</span>
<pre>${dump(conf)}</pre> <pre>${safeDump(conf)}</pre>
</div> </div>
` `
)} )}

View File

@@ -1,13 +1,18 @@
/* eslint-disable lit/no-template-arrow */ import {
import { html, css, LitElement, TemplateResult } from "lit"; customElement,
import { customElement, property } from "lit/decorators"; html,
import "../../../../src/components/ha-card"; css,
import "../../../../src/components/trace/hat-script-graph"; LitElement,
import "../../../../src/components/trace/hat-trace-timeline"; TemplateResult,
import { provideHass } from "../../../../src/fake_data/provide_hass"; property,
import { HomeAssistant } from "../../../../src/types"; } from "lit-element";
import { mockDemoTrace } from "../../data/traces/mock-demo-trace"; import "../../../src/components/ha-card";
import { DemoTrace } from "../../data/traces/types"; import "../../../src/components/trace/hat-script-graph";
import "../../../src/components/trace/hat-trace-timeline";
import { provideHass } from "../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../src/types";
import { mockDemoTrace } from "../data/traces/mock-demo-trace";
import { DemoTrace } from "../data/traces/types";
const traces: DemoTrace[] = [ const traces: DemoTrace[] = [
mockDemoTrace({ state: "running" }), mockDemoTrace({ state: "running" }),

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