Compare commits

..

3 Commits

Author SHA1 Message Date
Zack Arnett
39200b62d5 Fix custom vlaue 2020-11-17 13:22:28 -06:00
Zack Arnett
0eb28ea733 comment 2020-11-17 11:58:32 -06:00
Zack Arnett
62d471888f Only allow selectable attributes in dropdown 2020-11-15 16:18:58 -06:00
1605 changed files with 93757 additions and 213091 deletions

View File

@@ -1,13 +0,0 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9
ENV \
DEBIAN_FRONTEND=noninteractive \
DEVCONTAINER=true \
PATH=$PATH:./node_modules/.bin
# Install nvm
COPY .nvmrc /tmp/.nvmrc
RUN \
su vscode -c \
"source /usr/local/share/nvm/nvm.sh && nvm install $(cat /tmp/.nvmrc) 2>&1"

View File

@@ -1,37 +0,0 @@
{
"name": "Home Assistant Frontend",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"appPort": "8124:8123",
"context": "..",
"postCreateCommand": "script/bootstrap",
"extensions": [
"github.vscode-pull-request-github",
"dbaeumer.vscode-eslint",
"ms-vscode.vscode-typescript-tslint-plugin",
"esbenp.prettier-vscode",
"bierner.lit-html",
"runem.lit-plugin",
"ms-python.vscode-pylance"
],
"containerEnv": {
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
},
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"files.eol": "\n",
"editor.tabSize": 2,
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.trimTrailingWhitespace": true
}
}

View File

@@ -1,11 +1,11 @@
{
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"plugin:@typescript-eslint/recommended",
"plugin:wc/recommended",
"plugin:lit/all",
"prettier"
"plugin:lit/recommended",
"prettier",
"prettier/@typescript-eslint"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
@@ -29,91 +29,63 @@
"__BUILD__": false,
"__VERSION__": false,
"__STATIC_PATH__": false,
"__SUPERVISOR__": false,
"Polymer": true
"Polymer": true,
"webkitSpeechRecognition": false,
"ResizeObserver": false
},
"env": {
"browser": true,
"es6": true
},
"rules": {
"class-methods-use-this": "off",
"new-cap": "off",
"prefer-template": "off",
"object-shorthand": "off",
"func-names": "off",
"no-underscore-dangle": "off",
"strict": "off",
"no-plusplus": "off",
"no-bitwise": "error",
"comma-dangle": "off",
"vars-on-top": "off",
"no-continue": "off",
"no-param-reassign": "off",
"no-multi-assign": "off",
"no-console": "error",
"radix": "off",
"no-alert": "off",
"no-nested-ternary": "off",
"prefer-destructuring": "off",
"class-methods-use-this": 0,
"new-cap": 0,
"prefer-template": 0,
"object-shorthand": 0,
"func-names": 0,
"prefer-arrow-callback": 0,
"no-underscore-dangle": 0,
"strict": 0,
"prefer-spread": 0,
"no-plusplus": 0,
"no-bitwise": 2,
"comma-dangle": 0,
"vars-on-top": 0,
"no-continue": 0,
"no-param-reassign": 0,
"no-multi-assign": 0,
"no-console": 2,
"radix": 0,
"no-alert": 0,
"no-return-await": 0,
"no-nested-ternary": 0,
"prefer-destructuring": 0,
"no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": "off",
"import/prefer-default-export": "off",
"import/no-default-export": "off",
"import/no-unresolved": "off",
"import/no-cycle": "off",
"prefer-promise-reject-errors": 0,
"import/order": 0,
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"import/no-cycle": 0,
"import/extensions": [
"error",
2,
"ignorePackages",
{ "ts": "never", "js": "never" }
],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": "off",
"default-case": "off",
"wc/no-self-class": "off",
"no-shadow": "off",
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/naming-convention": [
"off",
{
"selector": "default",
"format": ["camelCase", "snake_case"],
"leadingUnderscore": "allow",
"trailingUnderscore": "allow"
},
{
"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"
"object-curly-newline": 0,
"default-case": 0,
"wc/no-self-class": 0,
"no-shadow": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-shadow": ["error"]
},
"plugins": ["disable", "unused-imports"],
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
"processor": "disable/disable"
}

View File

@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
<!--
Provide details about the versions you are using, which helps us reproducing
and finding the issue quicker. Version information is found in the
Home Assistant frontend: Settings -> About.
Home Assistant frontend: Configuration -> Info.
Browser version and operating system is important! Please try to replicate
your issue in a different browser and be sure to include your findings.
@@ -74,12 +74,12 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
```
## Problem-relevant frontend configuration
## Problem-relevant configuration
<!--
An example configuration that caused the problem for you, e.g. the YAML configuration
of the used cards. Fill this out even if it seems unimportant to you. Please be sure
to remove personal information like passwords, private URLs and other credentials.
An example configuration that caused the problem for you. Fill this out even
if it seems unimportant to you. Please be sure to remove personal information
like passwords, private URLs and other credentials.
-->
```yaml
@@ -89,7 +89,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
## Javascript errors shown in your browser console/inspector
<!--
If you come across any Javascript or other error logs, e.g. in your browser
If you come across any javascript or other error logs, e.g., in your browser
console/inspector please provide them.
-->

View File

@@ -1,121 +0,0 @@
name: Report a bug with the UI / Dashboards
description: Report an issue related to the Home Assistant frontend.
labels: bug
body:
- type: markdown
attributes:
value: |
Make sure you are running the [latest version of Home Assistant][releases] before reporting 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.**
[fr]: https://github.com/home-assistant/frontend/discussions
[releases]: https://github.com/home-assistant/home-assistant/releases
- type: checkboxes
attributes:
label: Checklist
description: Please verify that you've followed these steps
options:
- label: I have updated to the latest available Home Assistant version.
required: true
- label: I have cleared the cache of my browser.
required: true
- label: I have tried a different browser to see if it is related to my browser.
required: true
- type: markdown
attributes:
value: |
## The problem
- type: textarea
validations:
required: true
attributes:
label: Describe the issue you are experiencing
description: Provide a clear and concise description of what the bug is.
- type: textarea
validations:
required: true
attributes:
label: Describe the behavior you expected
description: Describe what you expected to happen or it should look/behave.
- type: textarea
validations:
required: true
attributes:
label: Steps to reproduce the issue
description: |
Please tell us exactly how to reproduce your issue.
Provide clear and concise step by step instructions and add code snippets if needed.
value: |
1.
2.
3.
...
- type: markdown
attributes:
value: |
## Environment
- type: input
validations:
required: true
attributes:
label: What version of Home Assistant Core has the issue?
placeholder: core-
description: >
Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).
- type: input
attributes:
label: What was the last working version of Home Assistant Core?
placeholder: core-
description: >
If known, otherwise leave blank.
- type: input
attributes:
label: In which browser are you experiencing the issue with?
placeholder: Google Chrome 88.0.4324.150
description: >
Provide the full name and don't forget to add the version!
- type: input
attributes:
label: Which operating system are you using to run this browser?
placeholder: macOS Big Sur (1.11)
description: >
Don't forget to add the version!
- type: markdown
attributes:
value: |
# Details
- type: textarea
attributes:
label: State of relevant entities
description: >
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
at Developer Tools -> States.
render: txt
- type: textarea
attributes:
label: Problem-relevant frontend configuration
description: >
An example configuration that caused the problem for you, e.g., the YAML
configuration of the used cards. Fill this out even if it seems
unimportant to you. Please be sure to remove personal information like
passwords, private URLs and other credentials.
render: yaml
- type: textarea
attributes:
label: Javascript errors shown in your browser console/inspector
description: >
If you come across any Javascript or other error logs, e.g., in your
browser console/inspector please provide them.
render: txt
- type: textarea
attributes:
label: Additional information
description: >
If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here, by
dragging and dropping files in the field below.

View File

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

View File

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

View File

@@ -10,64 +10,81 @@ on:
- dev
- master
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- 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
run: yarn install
env:
CI: true
- name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos
- name: Run eslint
run: yarn run lint:eslint
run: ./node_modules/.bin/eslint '{**/src,src}/**/*.{js,ts,html}' --ignore-path .gitignore
- name: Run tsc
run: yarn run lint:types
- name: Run prettier
run: yarn run lint:prettier
- name: Check for duplicate dependencies
run: yarn dedupe --check
run: ./node_modules/.bin/tsc
test:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- 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
run: yarn install
env:
CI: true
- name: Build resources
run: ./node_modules/.bin/gulp build-translations build-locale-data
- name: Run Tests
run: yarn run test
- name: Run Mocha
run: npm run mocha
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- 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
run: yarn install
env:
@@ -81,12 +98,21 @@ jobs:
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- 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
run: yarn install
env:

View File

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

View File

@@ -4,22 +4,26 @@ on:
push:
branches:
- dev
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v3
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
uses: actions/checkout@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- 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
run: yarn install
env:

View File

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

View File

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

View File

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

View File

@@ -1,84 +0,0 @@
name: Release
on:
release:
types:
- published
env:
PYTHON_VERSION: 3.8
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions
# All scopes not mentioned here are set to no access
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
permissions:
actions: none
jobs:
release:
name: Release
runs-on: ubuntu-latest
permissions:
contents: write # Required to upload release assets
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
- name: Download Translations
run: ./script/translations_download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Build and release package
run: |
python3 -m pip install twine build
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
script/release
- name: Upload release assets
uses: softprops/action-gh-release@v0.1.14
with:
files: |
dist/*.whl
dist/*.tar.gz
wheels-init:
name: Init wheels build
needs: release
runs-on: ubuntu-latest
steps:
- name: Generate requirements.txt
run: |
# Sleep to give pypi time to populate the new version across mirrors
sleep 240
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels
uses: home-assistant/wheels@2022.06.7
with:
abi: cp310
tag: musllinux_1_2
arch: amd64
wheels-key: ${{ secrets.WHEELS_KEY }}
requirements: "requirements.txt"

View File

@@ -1,25 +0,0 @@
name: Translations
on:
push:
branches:
- dev
paths:
- src/translations/en.json
env:
NODE_VERSION: 14
jobs:
upload:
name: Upload
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Upload Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base

31
.gitignore vendored
View File

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

6
.hound.yml Normal file
View File

@@ -0,0 +1,6 @@
jshint:
enabled: false
eslint:
enabled: true
config_file: .eslintrc-hound.json

2
.nvmrc
View File

@@ -1 +1 @@
14
12.1

View File

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

71
.vscode/tasks.json vendored
View File

@@ -37,37 +37,6 @@
"instanceLimit": 1
}
},
{
"label": "Develop Supervisor panel",
"type": "gulp",
"task": "develop-hassio",
"problemMatcher": {
"owner": "ha-build",
"source": "ha-build",
"fileLocation": "absolute",
"severity": "error",
"pattern": [
{
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
"severity": 1,
"file": 2,
"message": 3,
"line": 4,
"column": 5
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "Changes detected. Starting compilation",
"endsPattern": "Build done @"
}
},
"isBackground": true,
"group": "build",
"runOptions": {
"instanceLimit": 1
}
},
{
"label": "Develop Gallery",
"type": "gulp",
@@ -164,45 +133,5 @@
"instanceLimit": 1
}
},
{
"label": "Run HA Core in devcontainer",
"type": "shell",
"command": "script/core",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [],
"runOptions": {
"instanceLimit": 1
}
},
{
"label": "Run HA Core for Supervisor in devcontainer",
"type": "shell",
"command": "SUPERVISOR=${input:supervisorHost} SUPERVISOR_TOKEN=${input:supervisorToken} script/core",
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [],
"runOptions": {
"instanceLimit": 1
}
}
],
"inputs": [
{
"id": "supervisorHost",
"type": "promptString",
"description": "The IP of the Supervisor host running the Remote API proxy add-on"
},
{
"id": "supervisorToken",
"type": "promptString",
"description": "The token for the Remote API proxy add-on"
}
]
}

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 LICENSE.md
graft hass_frontend
graft hass_frontend_es5
recursive-exclude * *.py[co]

View File

@@ -2,7 +2,7 @@
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/)
- [More information about Home Assistant](https://home-assistant.io)
@@ -14,7 +14,7 @@ This is the repository for the official [Home Assistant](https://home-assistant.
- Development: [Instructions](https://developers.home-assistant.io/docs/frontend/development/)
- Production build: `script/build_frontend`
- Gallery: `cd gallery && script/develop_gallery`
- Supervisor: [Instructions](https://developers.home-assistant.io/docs/supervisor/developing)
- Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html)
## Frontend development

View File

@@ -0,0 +1,30 @@
# https://dev.azure.com/home-assistant
trigger: none
pr: none
schedules:
- cron: "0 0 * * *"
displayName: "build preview"
branches:
include:
- dev
always: true
variables:
- group: netlify
jobs:
- job: 'Netlify_preview'
pool:
vmImage: 'ubuntu-latest'
steps:
- script: |
# Cast
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_CAST}
# Demo
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_DEMO}
# Gallery
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_GALLERY}
displayName: 'Trigger netlify build preview'

View File

@@ -0,0 +1,59 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
tags:
include:
- "*"
pr: none
variables:
- name: versionWheels
value: '1.10.1-3.7-alpine3.11'
- name: versionNode
value: '12.1'
- group: twine
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
stages:
- stage: "Validate"
jobs:
- template: templates/azp-job-version.yaml@azure
- stage: "Build"
jobs:
- job: "ReleasePython"
pool:
vmImage: "ubuntu-latest"
steps:
- task: UsePythonVersion@0
displayName: "Use Python 3.7"
inputs:
versionSpec: "3.7"
- task: NodeTool@0
displayName: "Use Node $(versionNode)"
inputs:
versionSpec: "$(versionNode)"
- script: pip install twine wheel
displayName: "Install tools"
- script: |
export TWINE_USERNAME="$(twineUser)"
export TWINE_PASSWORD="$(twinePassword)"
script/release
displayName: "Build and release package"
- stage: "Wheels"
jobs:
- template: templates/azp-job-wheels.yaml@azure
parameters:
builderVersion: '$(versionWheels)'
wheelsRequirement: 'requirement.txt'
preBuild:
- script: |
sleep 240
echo "home-assistant-frontend==$(Build.SourceBranchName)" > requirement.txt

View File

@@ -0,0 +1,70 @@
# https://dev.azure.com/home-assistant
trigger:
batch: true
branches:
include:
- dev
paths:
include:
- translations/en.json
pr: none
schedules:
- cron: "30 0 * * *"
displayName: "frontend translation update"
branches:
include:
- dev
always: true
variables:
- group: translation
resources:
repositories:
- repository: azure
type: github
name: 'home-assistant/ci-azure'
endpoint: 'home-assistant'
jobs:
- job: 'Upload'
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: '12.x'
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
./script/translations_upload_base
displayName: 'Upload Translation'
- job: 'Download'
dependsOn:
- 'Upload'
condition: or(eq(variables['Build.Reason'], 'Schedule'), eq(variables['Build.Reason'], 'Manual'))
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: '12.x'
- template: templates/azp-step-git-init.yaml@azure
- script: |
export LOKALISE_TOKEN="$(lokaliseToken)"
export AZURE_BRANCH="$(Build.SourceBranchName)"
npm install
./script/translations_download
displayName: 'Download Translation'
- script: |
git checkout dev
git add translation
git commit -am "[ci skip] Translation update"
git push
displayName: 'Update translation'

View File

@@ -1,39 +0,0 @@
# Bundling Home Assistant Frontend
The Home Assistant build pipeline contains various steps to prepare a build.
- Generating icon files to be included
- Generating translation files to be included
- Converting TypeScript, CSS and JSON files to JavaScript
- Bundling
- Minifying the files
- Generating the HTML entrypoint files
- Generating the service worker
- Compressing the files
## Converting files
Currently in Home Assistant we use a bundler to convert TypeScript, CSS and JSON files to JavaScript files that the browser understands.
We currently rely on Webpack but also have experimental Rollup support. Both of these programs bundle the converted files in both production and development.
For development, bundling is optional. We just want to get the right files in the browser.
Responsibilities of the converter during development:
- Convert TypeScript to JavaScript
- Convert CSS to JavaScript that sets the content as the default export
- Convert JSON to JavaScript that sets the content as the default export
- Make sure import, dynamic import and web worker references work
- Add extensions where missing
- Resolve absolute package imports
- Filter out specific imports/packages
- Replace constants with values
In production, the following responsibilities are added:
- Minify HTML
- Bundle multiple imports so that the browser can fetch less files
- Generate a second version that is ES5 compatible
Configuration for all these steps are specified in [bundle.js](bundle.js).

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 env = require("./env.js");
const paths = require("./paths.js");
// Files from NPM Packages that should not be imported
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
require.resolve("esprima"),
];
// 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.
// We don't use it
@@ -18,8 +19,7 @@ module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
require.resolve("@polymer/paper-styles/default-theme.js"),
// Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
// Compatibility not needed for latest builds
latestBuild &&
// 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.
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);
module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
@@ -44,7 +35,6 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(env.version()),
__DEMO__: false,
__SUPERVISOR__: false,
__BACKWARDS_COMPAT__: false,
__STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify(
@@ -54,36 +44,24 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
});
module.exports.terserOptions = (latestBuild) => ({
safari10: !latestBuild,
safari10: true,
ecma: latestBuild ? undefined : 5,
output: { comments: false },
});
module.exports.babelOptions = ({ latestBuild }) => ({
babelrc: false,
compact: false,
presets: [
!latestBuild && [
"@babel/preset-env",
require("@babel/preset-env").default,
{
useBuiltIns: "entry",
corejs: "3.15",
bugfixes: true,
corejs: "3.6",
},
],
"@babel/preset-typescript",
require("@babel/preset-typescript").default,
].filter(Boolean),
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})
!latestBuild && [
"@babel/plugin-proposal-object-rest-spread",
@@ -92,21 +70,25 @@ module.exports.babelOptions = ({ latestBuild }) => ({
// Only support the syntax, Webpack will handle it.
"@babel/plugin-syntax-import-meta",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-top-level-await",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
[
require("@babel/plugin-proposal-decorators").default,
{ decoratorsBeforeExport: true },
],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
].filter(Boolean),
exclude: [
// \\ for Windows, / for Mac OS and Linux
/node_modules[\\/]core-js/,
/node_modules[\\/]webpack[\\/]buildin/,
],
});
// Are already ES5, cause warnings when babelified.
module.exports.babelExclude = () => [
require.resolve("@mdi/js/mdi.js"),
require.resolve("hls.js"),
];
const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
@@ -135,7 +117,7 @@ BundleConfig {
*/
module.exports.config = {
app({ isProdBuild, latestBuild, isStatsBuild, isWDS }) {
app({ isProdBuild, latestBuild, isStatsBuild }) {
return {
entry: {
service_worker: "./src/entrypoints/service_worker.ts",
@@ -150,7 +132,6 @@ module.exports.config = {
isProdBuild,
latestBuild,
isStatsBuild,
isWDS,
};
},
@@ -174,7 +155,6 @@ module.exports.config = {
cast({ isProdBuild, latestBuild }) {
const entry = {
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"),
};
if (latestBuild) {
@@ -205,10 +185,6 @@ module.exports.config = {
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
isProdBuild,
latestBuild,
isHassioBuild: true,
defineOverlay: {
__SUPERVISOR__: true,
},
};
},
@@ -221,9 +197,6 @@ module.exports.config = {
publicPath: publicPath(latestBuild),
isProdBuild,
latestBuild,
defineOverlay: {
__DEMO__: true,
},
};
},
};

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const fs = require("fs");
const path = require("path");
const paths = require("./paths.js");
@@ -7,9 +6,6 @@ module.exports = {
useRollup() {
return process.env.ROLLUP === "1";
},
useWDS() {
return process.env.WDS === "1";
},
isProdBuild() {
return (
process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
@@ -26,11 +22,11 @@ module.exports = {
},
version() {
const version = fs
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
.match(/\d{8}\.\d+/);
if (!version) {
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("./translations.js");
require("./locale-data.js");
require("./gen-icons-json.js");
require("./gather-static.js");
require("./compress.js");
@@ -13,7 +12,6 @@ require("./webpack.js");
require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
require("./wds.js");
gulp.task(
"develop-app",
@@ -27,15 +25,10 @@ gulp.task(
"gen-icons-json",
"gen-pages-dev",
"gen-index-app-dev",
"build-translations",
"build-locale-data"
"build-translations"
),
"copy-static-app",
env.useWDS()
? "wds-watch-app"
: env.useRollup()
? "rollup-watch-app"
: "webpack-watch-app"
env.useRollup() ? "rollup-watch-app" : "webpack-watch-app"
)
);
@@ -46,11 +39,11 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
// Don't compress running tests
...(env.isTest() ? [] : ["compress-app"]),
...// Don't compress running tests
(env.isTest() ? [] : ["compress-app"]),
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,6 @@ const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
return compiled({
...data,
useRollup: env.useRollup(),
useWDS: env.useWDS(),
renderTemplate,
});
};
@@ -91,23 +90,10 @@ gulp.task("gen-pages-prod", (done) => {
});
gulp.task("gen-index-app-dev", (done) => {
let latestAppJS, latestCoreJS, latestCustomPanelJS;
if (env.useWDS()) {
latestAppJS = "http://localhost:8000/src/entrypoints/app.ts";
latestCoreJS = "http://localhost:8000/src/entrypoints/core.ts";
latestCustomPanelJS =
"http://localhost:8000/src/entrypoints/custom-panel.ts";
} else {
latestAppJS = "/frontend_latest/app.js";
latestCoreJS = "/frontend_latest/core.js";
latestCustomPanelJS = "/frontend_latest/custom-panel.js";
}
const content = renderTemplate("index", {
latestAppJS,
latestCoreJS,
latestCustomPanelJS,
latestAppJS: "/frontend_latest/app.js",
latestCoreJS: "/frontend_latest/core.js",
latestCustomPanelJS: "/frontend_latest/custom-panel.js",
es5AppJS: "/frontend_es5/app.js",
es5CoreJS: "/frontend_es5/core.js",
@@ -154,15 +140,6 @@ gulp.task("gen-index-cast-dev", (done) => {
contentReceiver
);
const contentMedia = renderCastTemplate("media", {
latestMediaJS: "/frontend_latest/media.js",
es5MediaJS: "/frontend_es5/media.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "media.html"),
contentMedia
);
const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: "/frontend_latest/launcher.js",
es5LauncherJS: "/frontend_es5/launcher.js",
@@ -201,15 +178,6 @@ gulp.task("gen-index-cast-prod", (done) => {
contentReceiver
);
const contentMedia = renderCastTemplate("media", {
latestMediaJS: latestManifest["media.js"],
es5MediaJS: es5Manifest["media.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "media.html"),
contentMedia
);
const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: latestManifest["launcher.js"],
es5LauncherJS: es5Manifest["launcher.js"],
@@ -320,23 +288,15 @@ gulp.task("gen-index-hassio-prod", async () => {
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
function loadES5() {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
var el = document.createElement('script');
el.src = '${es5Entrypoint}';
document.body.appendChild(el);
}
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
loadES5();
} else {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
loadES5();
}
}
`,
{ encoding: "utf-8" }

View File

@@ -1,11 +1,7 @@
/* eslint-disable */
// Run demo develop mode
const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const { marked } = require("marked");
const glob = require("glob");
const yaml = require("js-yaml");
const env = require("../env");
const paths = require("../paths");
@@ -19,129 +15,26 @@ require("./service-worker.js");
require("./entry-html.js");
require("./rollup.js");
gulp.task("gather-gallery-pages", async function gatherPages() {
const pageDir = path.resolve(paths.gallery_dir, "src/pages");
const files = glob.sync(path.resolve(pageDir, "**/*"));
gulp.task("gather-gallery-demos", async function gatherDemos() {
const files = await fs.promises.readdir(
path.resolve(paths.gallery_dir, "src/demos")
);
const galleryBuild = path.resolve(paths.gallery_dir, "build");
fs.mkdirSync(galleryBuild, { recursive: true });
let content = "export const PAGES = {\n";
const processed = new Set();
let content = "export const DEMOS = {\n";
for (const file of files) {
if (fs.lstatSync(file).isDirectory()) {
continue;
}
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
if (descriptionContent === "") {
hasDescription = false;
} else {
descriptionContent = marked(descriptionContent).replace(/`/g, "\\`");
fs.mkdirSync(path.resolve(galleryBuild, category), { recursive: true });
fs.writeFileSync(
path.resolve(galleryBuild, `${pageId}-description.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`;
const demoId = path.basename(file, ".ts");
const demoPath = "../src/demos/" + demoId;
content += ` "${demoId}": () => import("${demoPath}"),\n`;
}
content += "};\n";
content += "};";
// 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`;
const galleryBuild = path.resolve(paths.gallery_dir, "build");
fs.mkdirSync(galleryBuild, { recursive: true });
fs.writeFileSync(
path.resolve(galleryBuild, "import-pages.ts"),
path.resolve(galleryBuild, "import-demos.ts"),
content,
"utf-8"
);
@@ -158,25 +51,11 @@ gulp.task(
gulp.parallel(
"gen-icons-json",
"build-translations",
"build-locale-data",
"gather-gallery-pages"
"gather-gallery-demos"
),
"copy-static-gallery",
"gen-index-gallery-dev",
gulp.parallel(
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")
);
}
)
env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery"
)
);
@@ -191,8 +70,7 @@ gulp.task(
gulp.parallel(
"gen-icons-json",
"build-translations",
"build-locale-data",
"gather-gallery-pages"
"gather-gallery-demos"
),
"copy-static-gallery",
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",

View File

@@ -2,6 +2,7 @@
const gulp = require("gulp");
const path = require("path");
const cpx = require("cpx");
const fs = require("fs-extra");
const paths = require("../paths");
@@ -12,28 +13,19 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath =
(staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts);
const genStaticPath = (staticDir) => (...parts) =>
path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) {
const staticPath = genStaticPath(staticDir);
// Translation output
fs.copySync(
polyPath("build/translations/output"),
polyPath("build-translations/output"),
staticPath("translations")
);
}
function copyLocaleData(staticDir) {
const staticPath = genStaticPath(staticDir);
// Locale data output
fs.copySync(polyPath("build/locale-data"), staticPath("locale-data"));
}
function copyMdiIcons(staticDir) {
const staticPath = genStaticPath(staticDir);
@@ -70,20 +62,12 @@ function copyLoaderJS(staticDir) {
function copyFonts(staticDir) {
const staticPath = genStaticPath(staticDir);
// Local fonts
fs.copySync(
npmPath("roboto-fontface/fonts/roboto/"),
staticPath("fonts/roboto/"),
{
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
}
cpx.copySync(
npmPath("roboto-fontface/fonts/roboto/*.woff2"),
staticPath("fonts/roboto")
);
}
function copyQrScannerWorker(staticDir) {
const staticPath = genStaticPath(staticDir);
copyFileDir(npmPath("qr-scanner/qr-scanner-worker.min.js"), staticPath("js"));
}
function copyMapPanel(staticDir) {
const staticPath = genStaticPath(staticDir);
copyFileDir(
@@ -96,26 +80,11 @@ function copyMapPanel(staticDir) {
);
}
gulp.task("copy-locale-data", async () => {
const staticDir = paths.app_output_static;
copyLocaleData(staticDir);
});
gulp.task("copy-translations-app", async () => {
const staticDir = paths.app_output_static;
copyTranslations(staticDir);
});
gulp.task("copy-translations-supervisor", async () => {
const staticDir = paths.hassio_output_static;
copyTranslations(staticDir);
});
gulp.task("copy-locale-data-supervisor", async () => {
const staticDir = paths.hassio_output_static;
copyLocaleData(staticDir);
});
gulp.task("copy-static-app", async () => {
const staticDir = paths.app_output_static;
// Basic static files
@@ -125,14 +94,10 @@ gulp.task("copy-static-app", async () => {
copyPolyfills(staticDir);
copyFonts(staticDir);
copyTranslations(staticDir);
copyLocaleData(staticDir);
copyMdiIcons(staticDir);
// Panel assets
copyMapPanel(staticDir);
// Qr Scanner assets
copyQrScannerWorker(staticDir);
});
gulp.task("copy-static-demo", async () => {
@@ -149,7 +114,6 @@ gulp.task("copy-static-demo", async () => {
copyMapPanel(paths.demo_output_static);
copyFonts(paths.demo_output_static);
copyTranslations(paths.demo_output_static);
copyLocaleData(paths.demo_output_static);
copyMdiIcons(paths.demo_output_static);
});
@@ -164,7 +128,6 @@ gulp.task("copy-static-cast", async () => {
copyMapPanel(paths.cast_output_static);
copyFonts(paths.cast_output_static);
copyTranslations(paths.cast_output_static);
copyLocaleData(paths.cast_output_static);
copyMdiIcons(paths.cast_output_static);
});
@@ -180,6 +143,5 @@ gulp.task("copy-static-gallery", async () => {
copyMapPanel(paths.gallery_output_static);
copyFonts(paths.gallery_output_static);
copyTranslations(paths.gallery_output_static);
copyLocaleData(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`, {
encoding,
});
return {
path: svg.match(/ d="([^"]+)"/)[1],
name: icon.name,
tags: icon.tags,
aliases: icon.aliases,
};
return { path: svg.match(/ d="([^"]+)"/)[1], name: icon.name };
});
};
const addRemovedMeta = (meta) => {
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
const removed = JSON.parse(file);
const removedMeta = removed.map((removeIcon) => ({
path: removeIcon.path,
name: removeIcon.name,
tags: [],
aliases: [],
}));
const combinedMeta = [...meta, ...removedMeta];
const combinedMeta = [...meta, ...removed];
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 chunks = [];
const CHUNK_SIZE = 50000;
@@ -100,10 +77,8 @@ const findDifferentiator = (curString, prevString) => {
};
gulp.task("gen-icons-json", (done) => {
const meta = getMeta();
const metaAndRemoved = addRemovedMeta(meta);
const split = splitBySize(metaAndRemoved);
const meta = addRemovedMeta(getMeta());
const split = splitBySize(meta);
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
@@ -141,27 +116,5 @@ gulp.task("gen-icons-json", (done) => {
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();
});
gulp.task("gen-dummy-icons-json", (done) => {
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
fs.writeFileSync(path.resolve(OUTPUT_DIR, "iconList.json"), "[]");
done();
});

View File

@@ -1,15 +1,15 @@
const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const env = require("../env");
const paths = require("../paths");
require("./clean.js");
require("./gen-icons-json.js");
require("./webpack.js");
require("./compress.js");
require("./rollup.js");
require("./gather-static.js");
require("./translations.js");
require("./gen-icons-json.js");
gulp.task(
"develop-hassio",
@@ -18,12 +18,8 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-hassio",
"gen-dummy-icons-json",
"gen-icons-json",
"gen-index-hassio-dev",
"build-supervisor-translations",
"copy-translations-supervisor",
"build-locale-data",
"copy-locale-data-supervisor",
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
)
);
@@ -35,11 +31,7 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-hassio",
"gen-dummy-icons-json",
"build-supervisor-translations",
"copy-translations-supervisor",
"build-locale-data",
"copy-locale-data-supervisor",
"gen-icons-json",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
"gen-index-hassio-prod",
...// 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 del = require("del");
const path = require("path");
@@ -7,7 +5,7 @@ const source = require("vinyl-source-stream");
const vinylBuffer = require("vinyl-buffer");
const gulp = require("gulp");
const fs = require("fs");
const flatmap = require("gulp-flatmap");
const foreach = require("gulp-foreach");
const merge = require("gulp-merge-json");
const rename = require("gulp-rename");
const transform = require("gulp-json-transform");
@@ -17,7 +15,7 @@ const paths = require("../paths");
const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend";
const workDir = "build/translations";
const workDir = "build-translations";
const fullDir = workDir + "/full";
const coreDir = workDir + "/core";
const outDir = workDir + "/output";
@@ -28,14 +26,32 @@ gulp.task("translations-enable-merge-backend", (done) => {
done();
});
// Panel translations which should be split from the core translations.
const TRANSLATION_FRAGMENTS = Object.keys(
require("../../src/translations/en.json").ui.panel
);
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. These
// should mirror the fragment definitions in polymer.json, so that we load
// additional resources at equivalent points.
const TRANSLATION_FRAGMENTS = [
"config",
"history",
"logbook",
"mailbox",
"profile",
"shopping-list",
"page-authorize",
"page-demo",
"page-onboarding",
"developer-tools",
];
function recursiveFlatten(prefix, data) {
let output = {};
Object.keys(data).forEach((key) => {
Object.keys(data).forEach(function (key) {
if (typeof data[key] === "object") {
output = {
...output,
@@ -96,19 +112,15 @@ function lokaliseTransform(data, original, file) {
if (value instanceof Object) {
output[key] = lokaliseTransform(value, original, file);
} else {
output[key] = value.replace(re_key_reference, (_match, lokalise_key) => {
const replace = lokalise_key.split("::").reduce((tr, k) => {
output[key] = value.replace(re_key_reference, (match, key) => {
const replace = key.split("::").reduce((tr, k) => {
if (!tr) {
throw Error(
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
}
return tr[k];
}, original);
if (typeof replace !== "string") {
throw Error(
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
}
return replace;
});
@@ -117,16 +129,18 @@ function lokaliseTransform(data, original, file) {
return output;
}
gulp.task("clean-translations", () => del([workDir]));
gulp.task("clean-translations", function () {
return del([workDir]);
});
gulp.task("ensure-translations-build-dir", (done) => {
if (!fs.existsSync(workDir)) {
fs.mkdirSync(workDir, { recursive: true });
fs.mkdirSync(workDir);
}
done();
});
gulp.task("create-test-metadata", (cb) => {
gulp.task("create-test-metadata", function (cb) {
fs.writeFile(
workDir + "/testMetadata.json",
JSON.stringify({
@@ -140,13 +154,17 @@ gulp.task("create-test-metadata", (cb) => {
gulp.task(
"create-test-translation",
gulp.series("create-test-metadata", () =>
gulp
gulp.series("create-test-metadata", function createTestTranslation() {
return gulp
.src(path.join(paths.translations_src, "en.json"))
.pipe(transform((data, _file) => recursiveEmpty(data)))
.pipe(
transform(function (data, file) {
return recursiveEmpty(data);
})
)
.pipe(rename("test.json"))
.pipe(gulp.dest(workDir))
)
.pipe(gulp.dest(workDir));
})
);
/**
@@ -158,7 +176,7 @@ gulp.task(
* project is buildable immediately after merging new translation keys, since
* 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")];
if (mergeBackend) {
@@ -167,7 +185,11 @@ gulp.task("build-master-translation", () => {
return gulp
.src(src)
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe(
transform(function (data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe(
merge({
fileName: "translationMaster.json",
@@ -176,14 +198,18 @@ gulp.task("build-master-translation", () => {
.pipe(gulp.dest(workDir));
});
gulp.task("build-merged-translations", () =>
gulp
gulp.task("build-merged-translations", function () {
return gulp
.src([inFrontendDir + "/*.json", workDir + "/test.json"], {
allowEmpty: true,
})
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.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
// translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag
@@ -215,17 +241,17 @@ gulp.task("build-merged-translations", () =>
)
.pipe(gulp.dest(fullDir));
})
)
);
);
});
let taskName;
var taskName;
const splitTasks = [];
TRANSLATION_FRAGMENTS.forEach((fragment) => {
taskName = "build-translation-fragment-" + fragment;
gulp.task(taskName, () =>
gulp.task(taskName, function () {
// Return only the translations for this fragment.
gulp
return gulp
.src(fullDir + "/*.json")
.pipe(
transform((data) => ({
@@ -236,33 +262,32 @@ TRANSLATION_FRAGMENTS.forEach((fragment) => {
},
}))
)
.pipe(gulp.dest(workDir + "/" + fragment))
);
.pipe(gulp.dest(workDir + "/" + fragment));
});
splitTasks.push(taskName);
});
taskName = "build-translation-core";
gulp.task(taskName, () =>
gulp.task(taskName, function () {
// Remove the fragment translations from the core translation.
gulp
return gulp
.src(fullDir + "/*.json")
.pipe(
transform((data, _file) => {
transform((data, file) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment];
});
delete data.supervisor;
return data;
})
)
.pipe(gulp.dest(coreDir))
);
.pipe(gulp.dest(coreDir));
});
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
gulp
return gulp
.src(
TRANSLATION_FRAGMENTS.map(
(fragment) => workDir + "/" + fragment + "/*.json"
@@ -270,45 +295,41 @@ gulp.task("build-flattened-translations", () =>
{ base: workDir }
)
.pipe(
transform((data) =>
transform(function (data) {
// Polymer.AppLocalizeBehavior requires flattened json
flatten(data)
)
return flatten(data);
})
)
.pipe(
rename((filePath) => {
if (filePath.dirname === "core") {
filePath.dirname = "";
}
// In dev we create the file with the fake hash in the filename
if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
})
)
.pipe(gulp.dest(outDir))
);
.pipe(gulp.dest(outDir));
});
const fingerprints = {};
gulp.task("build-translation-fingerprints", () => {
// Fingerprint full file of each language
const files = fs.readdirSync(fullDir);
gulp.task(
"build-translation-fingerprints",
function fingerprintTranslationFiles() {
// Fingerprint full file of each language
const files = fs.readdirSync(fullDir);
for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes
hash: env.isProdBuild()
? crypto
.createHash("md5")
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
.digest("hex")
: "dev",
};
}
for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes
hash: env.isProdBuild()
? crypto
.createHash("md5")
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
.digest("hex")
: "dev",
};
}
// In dev we create the file with the fake hash in the filename
if (env.isProdBuild()) {
mapFiles(outDir, ".json", (filename) => {
const parsed = path.parse(filename);
@@ -324,88 +345,12 @@ gulp.task("build-translation-fingerprints", () => {
}`
);
});
const stream = source("translationFingerprints.json");
stream.write(JSON.stringify(fingerprints));
process.nextTick(() => stream.end());
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
}
const stream = source("translationFingerprints.json");
stream.write(JSON.stringify(fingerprints));
process.nextTick(() => stream.end());
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
});
gulp.task("build-translation-fragment-supervisor", () =>
gulp
.src(fullDir + "/*.json")
.pipe(transform((data) => data.supervisor))
.pipe(
rename((filePath) => {
// In dev we create the file with the fake hash in the filename
if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
})
)
.pipe(gulp.dest(workDir + "/supervisor"))
);
gulp.task("build-translation-flatten-supervisor", () =>
gulp
.src(workDir + "/supervisor/*.json")
.pipe(
transform((data) =>
// Polymer.AppLocalizeBehavior requires flattened json
flatten(data)
)
)
.pipe(gulp.dest(outDir))
);
gulp.task("build-translation-write-metadata", () =>
gulp
.src(
[
path.join(paths.translations_src, "translationMetadata.json"),
workDir + "/testMetadata.json",
workDir + "/translationFingerprints.json",
],
{ allowEmpty: true }
)
.pipe(merge({}))
.pipe(
transform((data) => {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (value.nativeName) {
newData[key] = value;
} else {
// eslint-disable-next-line no-console
console.warn(
`Skipping language ${key}. Native name was not translated.`
);
}
});
return newData;
})
)
.pipe(
transform((data) => ({
fragments: TRANSLATION_FRAGMENTS,
translations: data,
}))
)
.pipe(rename("translationMetadata.json"))
.pipe(gulp.dest(workDir))
);
gulp.task(
"create-translations",
gulp.series(
env.isProdBuild() ? (done) => done() : "create-test-translation",
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
"build-flattened-translations"
)
);
gulp.task(
@@ -413,22 +358,48 @@ gulp.task(
gulp.series(
"clean-translations",
"ensure-translations-build-dir",
"create-translations",
"build-translation-fingerprints",
"build-translation-write-metadata"
)
);
gulp.task(
"build-supervisor-translations",
gulp.series(
"clean-translations",
"ensure-translations-build-dir",
env.isProdBuild() ? (done) => done() : "create-test-translation",
"build-master-translation",
"build-merged-translations",
"build-translation-fragment-supervisor",
"build-translation-flatten-supervisor",
gulp.parallel(...splitTasks),
"build-flattened-translations",
"build-translation-fingerprints",
"build-translation-write-metadata"
function writeMetadata() {
return gulp
.src(
[
path.join(paths.translations_src, "translationMetadata.json"),
workDir + "/testMetadata.json",
workDir + "/translationFingerprints.json",
],
{ allowEmpty: true }
)
.pipe(merge({}))
.pipe(
transform(function (data) {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (data[key].nativeName) {
newData[key] = data[key];
} else {
console.warn(
`Skipping language ${key}. Native name was not translated.`
);
}
if (data[key]) newData[key] = value;
});
return newData;
})
)
.pipe(
transform((data) => ({
fragments: TRANSLATION_FRAGMENTS,
translations: data,
}))
)
.pipe(rename("translationMetadata.json"))
.pipe(gulp.dest(workDir));
}
)
);

View File

@@ -1,11 +0,0 @@
// Tasks to run Rollup
const gulp = require("gulp");
const { startDevServer } = require("@web/dev-server");
gulp.task("wds-watch-app", () => {
startDevServer({
config: {
watch: true,
},
});
});

View File

@@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Tasks to run webpack.
const fs = require("fs");
const gulp = require("gulp");
const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
@@ -20,13 +18,6 @@ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: false }),
];
const isWsl =
fs.existsSync("/proc/version") &&
fs
.readFileSync("/proc/version", "utf-8")
.toLocaleLowerCase()
.includes("microsoft");
/**
* @param {{
* compiler: import("webpack").Compiler,
@@ -35,31 +26,28 @@ const isWsl =
* listenHost?: string
* }}
*/
const runDevServer = async ({
const runDevServer = ({
compiler,
contentBase,
port,
listenHost = "localhost",
}) => {
const server = new WebpackDevServer(
{
open: true,
host: listenHost,
port,
static: {
directory: contentBase,
watch: true,
},
},
compiler
);
}) =>
new WebpackDevServer(compiler, {
open: true,
watchContentBase: true,
contentBase,
}).listen(port, listenHost, function (err) {
if (err) {
throw err;
}
// Server listening
log(
"[webpack-dev-server]",
`Project is running at http://localhost:${port}`
);
});
await server.start();
// Server listening
log("[webpack-dev-server]", `Project is running at http://localhost:${port}`);
};
const doneHandler = (done) => (err, stats) => {
const handler = (done) => (err, stats) => {
if (err) {
log.error(err.stack || err);
if (err.details) {
@@ -69,7 +57,6 @@ const doneHandler = (done) => (err, stats) => {
}
if (stats.hasErrors() || stats.hasWarnings()) {
// eslint-disable-next-line no-console
console.log(stats.toString("minimal"));
}
@@ -80,108 +67,117 @@ const doneHandler = (done) => (err, stats) => {
}
};
const prodBuild = (conf) =>
new Promise((resolve) => {
webpack(
conf,
// Resolve promise when done. Because we pass a callback, webpack closes itself
doneHandler(resolve)
);
});
gulp.task("webpack-watch-app", () => {
// This command will run forever because we don't close compiler
webpack(
process.env.ES5
? bothBuilds(createAppConfig, { isProdBuild: false })
: createAppConfig({ isProdBuild: false, latestBuild: true })
).watch({ poll: isWsl }, doneHandler());
// we are not calling done, so this command will run forever
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
{ ignored: /build-translations/ },
handler()
);
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series("create-translations", "copy-translations-app")
gulp.series("build-translations", "copy-translations-app")
);
});
gulp.task("webpack-prod-app", () =>
prodBuild(
bothBuilds(createAppConfig, {
isProdBuild: true,
})
)
gulp.task(
"webpack-prod-app",
() =>
new Promise((resolve) =>
webpack(
bothBuilds(createAppConfig, { isProdBuild: true }),
handler(resolve)
)
)
);
gulp.task("webpack-dev-server-demo", () =>
gulp.task("webpack-dev-server-demo", () => {
runDevServer({
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
contentBase: paths.demo_output_root,
port: 8090,
})
});
});
gulp.task(
"webpack-prod-demo",
() =>
new Promise((resolve) =>
webpack(
bothBuilds(createDemoConfig, {
isProdBuild: true,
}),
handler(resolve)
)
)
);
gulp.task("webpack-prod-demo", () =>
prodBuild(
bothBuilds(createDemoConfig, {
isProdBuild: true,
})
)
);
gulp.task("webpack-dev-server-cast", () =>
gulp.task("webpack-dev-server-cast", () => {
runDevServer({
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
contentBase: paths.cast_output_root,
port: 8080,
// Accessible from the network, because that's how Cast hits it.
listenHost: "0.0.0.0",
})
);
});
});
gulp.task("webpack-prod-cast", () =>
prodBuild(
bothBuilds(createCastConfig, {
isProdBuild: true,
})
)
gulp.task(
"webpack-prod-cast",
() =>
new Promise((resolve) =>
webpack(
bothBuilds(createCastConfig, {
isProdBuild: true,
}),
handler(resolve)
)
)
);
gulp.task("webpack-watch-hassio", () => {
// This command will run forever because we don't close compiler
// we are not calling done, so this command will run forever
webpack(
createHassioConfig({
isProdBuild: false,
latestBuild: true,
})
).watch({ ignored: /build/, poll: isWsl }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series("build-supervisor-translations", "copy-translations-supervisor")
);
).watch({}, handler());
});
gulp.task("webpack-prod-hassio", () =>
prodBuild(
bothBuilds(createHassioConfig, {
isProdBuild: true,
})
)
gulp.task(
"webpack-prod-hassio",
() =>
new Promise((resolve) =>
webpack(
bothBuilds(createHassioConfig, {
isProdBuild: true,
}),
handler(resolve)
)
)
);
gulp.task("webpack-dev-server-gallery", () =>
gulp.task("webpack-dev-server-gallery", () => {
runDevServer({
// 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 })),
contentBase: paths.gallery_output_root,
port: 8100,
listenHost: "0.0.0.0",
})
);
});
});
gulp.task("webpack-prod-gallery", () =>
prodBuild(
createGalleryConfig({
isProdBuild: true,
latestBuild: true,
})
)
gulp.task(
"webpack-prod-gallery",
() =>
new Promise((resolve) =>
webpack(
createGalleryConfig({
isProdBuild: true,
latestBuild: true,
}),
handler(resolve)
)
)
);

View File

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

View File

@@ -1,3 +1,5 @@
const path = require("path");
module.exports = function (userOptions = {}) {
// Files need to be absolute paths.
// This only works if the file has no exports

View File

@@ -1,10 +1,9 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
const commonjs = require("@rollup/plugin-commonjs");
const resolve = require("@rollup/plugin-node-resolve");
const json = require("@rollup/plugin-json");
const babel = require("@rollup/plugin-babel").babel;
const babel = require("rollup-plugin-babel");
const replace = require("@rollup/plugin-replace");
const visualizer = require("rollup-plugin-visualizer");
const { string } = require("rollup-plugin-string");
@@ -32,103 +31,116 @@ const createRollupConfig = ({
isStatsBuild,
publicPath,
dontHash,
isWDS,
}) => ({
/**
* @type { import("rollup").InputOptions }
*/
inputOptions: {
input: entry,
// Some entry points contain no JavaScript. This setting silences a warning about that.
// https://rollupjs.org/guide/en/#preserveentrysignatures
preserveEntrySignatures: false,
plugins: [
ignore({
files: bundle.emptyPackages({ latestBuild }),
}),
resolve({
extensions,
preferBuiltins: false,
browser: true,
rootDir: paths.polymer_dir,
}),
commonjs(),
json(),
babel({
...bundle.babelOptions({ latestBuild }),
extensions,
babelHelpers: isWDS ? "inline" : "bundled",
}),
string({
// Import certain extensions as strings
include: [path.join(paths.polymer_dir, "node_modules/**/*.css")],
}),
replace(bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })),
!isWDS &&
}) => {
return {
/**
* @type { import("rollup").InputOptions }
*/
inputOptions: {
input: entry,
// Some entry points contain no JavaScript. This setting silences a warning about that.
// https://rollupjs.org/guide/en/#preserveentrysignatures
preserveEntrySignatures: false,
plugins: [
ignore({
files: bundle.emptyPackages({ latestBuild }),
}),
resolve({
extensions,
preferBuiltins: false,
browser: true,
rootDir: paths.polymer_dir,
}),
commonjs({
namedExports: {
"js-yaml": ["safeDump", "safeLoad"],
},
}),
json(),
babel({
...bundle.babelOptions({ latestBuild }),
extensions,
exclude: bundle.babelExclude(),
}),
string({
// Import certain extensions as strings
include: [path.join(paths.polymer_dir, "node_modules/**/*.css")],
}),
replace(
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
),
manifest({
publicPath,
}),
!isWDS && worker(),
!isWDS && dontHashPlugin({ dontHash }),
!isWDS && isProdBuild && terser(bundle.terserOptions(latestBuild)),
!isWDS &&
worker(),
dontHashPlugin({ dontHash }),
isProdBuild && terser(bundle.terserOptions(latestBuild)),
isStatsBuild &&
visualizer({
// https://github.com/btd/rollup-plugin-visualizer#options
open: true,
sourcemap: true,
}),
].filter(Boolean),
},
/**
* @type { import("rollup").OutputOptions }
*/
outputOptions: {
// https://rollupjs.org/guide/en/#outputdir
dir: outputPath,
// https://rollupjs.org/guide/en/#outputformat
format: latestBuild ? "es" : "systemjs",
// https://rollupjs.org/guide/en/#outputexternallivebindings
externalLiveBindings: false,
// https://rollupjs.org/guide/en/#outputentryfilenames
// https://rollupjs.org/guide/en/#outputchunkfilenames
// https://rollupjs.org/guide/en/#outputassetfilenames
entryFileNames:
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
chunkFileNames: isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
assetFileNames: isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
// https://rollupjs.org/guide/en/#outputsourcemap
sourcemap: isProdBuild ? true : "inline",
},
});
visualizer({
// https://github.com/btd/rollup-plugin-visualizer#options
open: true,
sourcemap: true,
}),
],
},
/**
* @type { import("rollup").OutputOptions }
*/
outputOptions: {
// https://rollupjs.org/guide/en/#outputdir
dir: outputPath,
// https://rollupjs.org/guide/en/#outputformat
format: latestBuild ? "es" : "systemjs",
// https://rollupjs.org/guide/en/#outputexternallivebindings
externalLiveBindings: false,
// https://rollupjs.org/guide/en/#outputentryfilenames
// https://rollupjs.org/guide/en/#outputchunkfilenames
// https://rollupjs.org/guide/en/#outputassetfilenames
entryFileNames:
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
chunkFileNames:
isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
assetFileNames:
isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
// https://rollupjs.org/guide/en/#outputsourcemap
sourcemap: isProdBuild ? true : "inline",
},
};
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) =>
createRollupConfig(
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
return createRollupConfig(
bundle.config.app({
isProdBuild,
latestBuild,
isStatsBuild,
isWDS,
})
);
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
createRollupConfig(
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
return createRollupConfig(
bundle.config.demo({
isProdBuild,
latestBuild,
isStatsBuild,
})
);
};
const createCastConfig = ({ isProdBuild, latestBuild }) =>
createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild }));
const createCastConfig = ({ isProdBuild, latestBuild }) => {
return createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) =>
createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
const createHassioConfig = ({ isProdBuild, latestBuild }) => {
return createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
createRollupConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
return createRollupConfig(
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = {
createAppConfig,

View File

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

View File

@@ -1,12 +1,10 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const webpack = require("webpack");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
const ManifestPlugin = require("webpack-manifest-plugin");
const paths = require("./paths.js");
const bundle = require("./bundle.js");
const bundle = require("./bundle");
const log = require("fancy-log");
class LogStartCompilePlugin {
ignoredFirst = false;
@@ -30,7 +28,6 @@ const createWebpackConfig = ({
isProdBuild,
latestBuild,
isStatsBuild,
isHassioBuild,
dontHash,
}) => {
if (!dontHash) {
@@ -39,7 +36,6 @@ const createWebpackConfig = ({
const ignorePackages = bundle.ignorePackages({ latestBuild });
return {
mode: isProdBuild ? "production" : "development",
target: ["web", latestBuild ? "es2017" : "es5"],
devtool: isProdBuild
? "cheap-module-source-map"
: "eval-cheap-module-source-map",
@@ -49,18 +45,15 @@ const createWebpackConfig = ({
rules: [
{
test: /\.m?js$|\.ts$/,
exclude: bundle.babelExclude(),
use: {
loader: "babel-loader",
options: {
...bundle.babelOptions({ latestBuild }),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
options: bundle.babelOptions({ latestBuild }),
},
},
{
test: /\.css$/,
type: "asset/source",
use: "raw-loader",
},
],
},
@@ -72,12 +65,9 @@ const createWebpackConfig = ({
terserOptions: bundle.terserOptions(latestBuild),
}),
],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
},
plugins: [
new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({
new ManifestPlugin({
// Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
}),
@@ -103,7 +93,6 @@ const createWebpackConfig = ({
? path.resolve(context, resource)
: require.resolve(resource);
} catch (err) {
// eslint-disable-next-line no-console
console.error(
"Error in Home Assistant ignore plugin",
resource,
@@ -118,68 +107,85 @@ const createWebpackConfig = ({
},
}),
new webpack.NormalModuleReplacementPlugin(
new RegExp(
bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
),
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
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(),
].filter(Boolean),
resolve: {
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: {
filename: ({ chunk }) => {
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
if (!isProdBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`;
}
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
},
environment: {
// The environment supports arrow functions ('() => { ... }').
arrowFunction: latestBuild,
// The environment supports BigInt as literal (123n).
bigIntLiteral: false,
// The environment supports const and let for variable declarations.
const: latestBuild,
// The environment supports destructuring ('{ a, b } = obj').
destructuring: latestBuild,
// The environment supports an async import() function to import EcmaScript modules.
dynamicImport: latestBuild,
// The environment supports 'for of' iteration ('for (const x of array) { ... }').
forOf: latestBuild,
// The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...').
module: latestBuild,
},
chunkFilename:
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
isProdBuild && !isStatsBuild
? "chunk.[chunkhash].js"
: "[name].chunk.js",
path: outputPath,
publicPath,
// To silence warning in worker plugin
globalObject: "self",
},
experiments: {
topLevelAwait: true,
},
};
};
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
createWebpackConfig(
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
return createWebpackConfig(
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild })
);
};
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
createWebpackConfig(
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
return createWebpackConfig(
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
);
};
const createCastConfig = ({ isProdBuild, latestBuild }) =>
createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
const createCastConfig = ({ isProdBuild, latestBuild }) => {
return createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
};
const createHassioConfig = ({ isProdBuild, latestBuild }) =>
createWebpackConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
const createHassioConfig = ({ isProdBuild, latestBuild }) => {
return createWebpackConfig(
bundle.config.hassio({ isProdBuild, latestBuild })
);
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
return createWebpackConfig(
bundle.config.gallery({ isProdBuild, latestBuild })
);
};
module.exports = {
createAppConfig,

View File

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

View File

@@ -139,7 +139,7 @@
Your authentication credentials or Home Assistant url are never sent
to the Cloud. You can validate this behavior in
<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"
>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-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { CastManager } from "../../../../src/cast/cast_manager";
import {
castSendShowLovelaceView,
@@ -18,7 +24,6 @@ import {
import { atLeastVersion } from "../../../../src/common/config/version";
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
import "../../../../src/components/ha-icon";
import "../../../../src/components/ha-svg-icon";
import {
getLegacyLovelaceCollection,
getLovelaceCollection,
@@ -27,6 +32,7 @@ import {
import "../../../../src/layouts/hass-loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout";
import "@material/mwc-button/mwc-button";
@customElement("hc-cast")
class HcCast extends LitElement {
@@ -36,19 +42,21 @@ class HcCast extends LitElement {
@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 {
if (this.lovelaceConfig === undefined) {
return html`<hass-loading-screen no-toolbar></hass-loading-screen>`;
return html` <hass-loading-screen no-toolbar></hass-loading-screen>> `;
}
const error =
this.castManager.castState === "NO_DEVICES_AVAILABLE"
? 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;
@@ -75,7 +83,7 @@ class HcCast extends LitElement {
? html`
<p class="center-item">
<mwc-button raised @click=${this._handleLaunch}>
<ha-svg-icon .path=${mdiCast}></ha-svg-icon>
<ha-icon icon="hass:cast"></ha-icon>
Start Casting
</mwc-button>
</p>
@@ -113,7 +121,7 @@ class HcCast extends LitElement {
${this.castManager.status
? html`
<mwc-button @click=${this._handleLaunch}>
<ha-svg-icon .path=${mdiCastConnected}></ha-svg-icon>
<ha-icon icon="hass:cast-connected"></ha-icon>
Manage
</mwc-button>
`
@@ -193,12 +201,12 @@ class HcCast extends LitElement {
}
this.connection.close();
location.reload();
} catch (err: any) {
} catch (err) {
alert("Unable to log out!");
}
}
static get styles(): CSSResultGroup {
static get styles(): CSSResult {
return css`
.center-item {
display: flex;
@@ -235,7 +243,7 @@ class HcCast extends LitElement {
color: var(--secondary-text-color);
}
mwc-button ha-svg-icon {
mwc-button ha-icon {
margin-right: 8px;
height: 18px;
}

View File

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

View File

@@ -4,8 +4,15 @@ import {
getUser,
HassUser,
} from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-card";
@customElement("hc-layout")
@@ -62,7 +69,7 @@ class HcLayout extends LitElement {
}
}
static get styles(): CSSResultGroup {
static get styles(): CSSResult {
return css`
:host {
display: flex;
@@ -91,12 +98,8 @@ class HcLayout extends LitElement {
line-height: 32px;
padding: 24px 16px 16px;
display: block;
margin: 0;
}
.hero {
border-radius: 4px 4px 0 0;
}
.subtitle {
font-size: 14px;
color: var(--secondary-text-color);

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";
export const castDemoLovelace: () => LovelaceConfig = () => {
const touchSupported =
castContext.getDeviceCapabilities().touch_input_supported;
const touchSupported = castContext.getDeviceCapabilities()
.touch_input_supported;
return {
views: [
{

View File

@@ -8,9 +8,6 @@ import { ReceivedMessage } from "./types";
const lovelaceController = new HcMain();
document.body.append(lovelaceController);
lovelaceController.addEventListener("cast-view-changed", (ev) => {
playDummyMedia(ev.detail.title);
});
const mediaPlayer = document.createElement("cast-media-player");
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 = () => {
mediaPlayer.style.display = "none";
lovelaceController.style.display = "initial";
@@ -79,7 +51,6 @@ const showMediaPlayer = () => {
--progress-color: #03a9f4;
--splash-image: url('https://home-assistant.io/images/cast/splash.png');
--splash-size: cover;
--background-color: #41bdf5;
}
`;
document.head.appendChild(style);
@@ -92,6 +63,22 @@ options.customNamespaces = {
[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(
CAST_NS,
// @ts-ignore
@@ -116,12 +103,6 @@ const playerManager = castContext.getPlayerManager();
playerManager.setMessageInterceptor(
cast.framework.messages.MessageType.LOAD,
(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
showMediaPlayer();
const media = loadRequestData.media;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -54,8 +54,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "21",
attributes: {
friendly_name: "Living room temperature",
device_class: "temperature",
unit_of_measurement: "°C",
},
},
"sensor.study_temp_rounded": {
@@ -63,8 +61,6 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "23",
attributes: {
friendly_name: "Study temperature",
device_class: "temperature",
unit_of_measurement: "°C",
},
},
"sensor.living_room": {
@@ -246,15 +242,11 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
"light.living_room_lights": {
entity_id: "light.living_room_lights",
state: "on",
state: "off",
attributes: {
min_mireds: 111,
max_mireds: 400,
brightness: 175,
color_temp: 300,
supported_color_modes: ["brightness", "color_temp"],
friendly_name: "Living Room Lights",
color_mode: "color_temp",
supported_features: 55,
},
},
@@ -267,27 +259,13 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
},
"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",
attributes: {
supported_color_modes: ["brightness"],
friendly_name: "Garage Lights",
friendly_name: "Kitchen lights",
supported_features: 1,
},
},
"sensor.plexspy": {
entity_id: "sensor.plexspy",
state: "0",
@@ -500,6 +478,16 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
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": {
entity_id: "sensor.alok_to_home",
state: "41",

View File

@@ -12,7 +12,6 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
{
type: "entities",
title: localize("ui.panel.page-demo.config.arsaboo.labels.lights"),
state_color: true,
entities: [
{
entity: "light.kitchen_lights",
@@ -29,11 +28,6 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
},
],
},
{
title: "Energy distribution today",
type: "energy-distribution",
link_dashboard: true,
},
{
type: "thermostat",
entity: "climate.upstairs",
@@ -118,7 +112,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
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)",
},
style: {
@@ -200,7 +195,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
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)",
},
style: {
@@ -280,7 +276,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
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)",
},
style: {
@@ -317,7 +314,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
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)",
},
style: {

View File

@@ -1,20 +1,32 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace } from "../../../src/panels/lovelace/types";
import { energyEntities } from "../stubs/entities";
import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
() => import("./arsaboo").then((mod) => mod.demoArsaboo),
() => import("./teachingbirds").then((mod) => mod.demoTeachingbirds),
() => import("./kernehed").then((mod) => mod.demoKernehed),
() => import("./jimpower").then((mod) => mod.demoJimpower),
() =>
import(/* webpackChunkName: "arsaboo" */ "./arsaboo").then(
(mod) => mod.demoArsaboo
),
() =>
import(/* webpackChunkName: "teachingbirds" */ "./teachingbirds").then(
(mod) => mod.demoTeachingbirds
),
() =>
import(/* webpackChunkName: "kernehed" */ "./kernehed").then(
(mod) => mod.demoKernehed
),
() =>
import(/* webpackChunkName: "jimpower" */ "./jimpower").then(
(mod) => mod.demoJimpower
),
];
// eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfigIndex = 0;
// eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfig: Promise<DemoConfig> =
demoConfigs[selectedDemoConfigIndex]();
export let selectedDemoConfig: Promise<DemoConfig> = demoConfigs[
selectedDemoConfigIndex
]();
export const setDemoConfig = async (
hass: MockHomeAssistant,
@@ -28,7 +40,6 @@ export const setDemoConfig = async (
selectedDemoConfig = confProm;
hass.addEntities(config.entities(hass.localize), true);
hass.addEntities(energyEntities());
lovelace.saveConfig(config.lovelace(hass.localize));
hass.mockTheme(config.theme());
};

View File

@@ -653,7 +653,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.smoke_sensor_158d0001b8ddc7",
state: "off",
attributes: {
density: 0,
Density: 0,
battery_level: 59,
friendly_name: "Downstairs Smoke Detector",
device_class: "smoke",
@@ -663,7 +663,7 @@ export const demoEntitiesJimpower: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.smoke_sensor_158d0001b8deba",
state: "off",
attributes: {
density: 0,
Density: 0,
battery_level: 65,
friendly_name: "Upstairs Smoke Detector",
device_class: "smoke",

View File

@@ -3,7 +3,49 @@ import { DemoConfig } from "../types";
export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
name: "Kingia Castle",
resources: [],
resources: [
// {
// url: "/local/custom_ui/dark-sky-weather-card.js?v=4",
// type: "js",
// },
// {
// url: "/local/custom_ui/mini-media-player-bundle.js?v=0.9.8",
// type: "module",
// },
// {
// url: "/local/custom_ui/tracker-card.js?v=0.1.5",
// type: "js",
// },
// {
// url: "/local/custom_ui/surveillance-card.js?v=0.0.1",
// type: "module",
// },
// {
// url: "/local/custom_ui/mini-graph-card-bundle.js?v=0.1.0",
// type: "module",
// },
// {
// url: "/local/custom_ui/slider-entity-row.js?v=d6da75",
// type: "js",
// },
// {
// url:
// "/local/custom_ui/compact-custom-header/compact-custom-header.js?v=0.2.7",
// type: "js",
// },
// {
// url: "/local/custom_ui/waze-card.js?v=1.1.1",
// type: "js",
// },
// {
// url: "/local/custom_ui/circle-sensor-card.js?v=1.2.0",
// type: "module",
// },
// {
// url: "/local/custom_ui/monster-card.js?v=0.2.3",
// type: "js",
// },
],
views: [
{
cards: [
@@ -194,7 +236,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
type: "state-icon",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "group.downstairs_lights",
},
service: "homeassistant.toggle",
@@ -561,6 +603,89 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
},
{
cards: [
// {
// style: {
// "background-image": 'url("/assets/jimpower/cardbackK.png")',
// "background-size": "100% 400px",
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// "background-repeat": "no-repeat",
// color: "#999999",
// "border-radius": "20px",
// border: "solid 1px rgba(100,100,100,0.3)",
// "background-color": "rgba(50,50,50,0.3)",
// },
// type: "custom:card-modder",
// card: {
// entity_visibility: "sensor.dark_sky_visibility",
// entity_sun: "sun.sun",
// entity_daily_summary:
// "sensor.bom_gc_forecast_detailed_summary_0",
// entity_temperature: "sensor.bom_temp",
// entity_forecast_high_temp_3:
// "sensor.bom_gc_forecast_max_temp_c_3",
// entity_forecast_high_temp_2:
// "sensor.bom_gc_forecast_max_temp_c_2",
// entity_forecast_high_temp_5:
// "sensor.bom_gc_forecast_max_temp_c_5",
// entity_forecast_high_temp_4:
// "sensor.bom_gc_forecast_max_temp_c_4",
// entity_wind_speed: "sensor.bom_wind_sp",
// entity_forecast_icon_4: "sensor.dark_sky_icon_4",
// entity_forecast_icon_5: "sensor.dark_sky_icon_5",
// entity_forecast_icon_2: "sensor.dark_sky_icon_2",
// entity_forecast_icon_3: "sensor.dark_sky_icon_3",
// entity_forecast_icon_1: "sensor.dark_sky_icon_1",
// entity_forecast_high_temp_1:
// "sensor.bom_gc_forecast_max_temp_c_1",
// entity_wind_bearing: "sensor.bom_wind_bear",
// entity_forecast_low_temp_2:
// "sensor.bom_gc_forecast_min_temp_c_2",
// entity_forecast_low_temp_3:
// "sensor.bom_gc_forecast_min_temp_c_3",
// entity_pressure: "sensor.bom_pres",
// entity_forecast_low_temp_1:
// "sensor.bom_gc_forecast_min_temp_c_1",
// entity_forecast_low_temp_4:
// "sensor.bom_gc_forecast_min_temp_c_4",
// entity_forecast_low_temp_5:
// "sensor.bom_gc_forecast_min_temp_c_5",
// entity_humidity: "sensor.bom_humd",
// type: "custom:dark-sky-weather-card",
// entity_current_conditions: "sensor.dark_sky_icon",
// },
// },
// {
// style: {
// "background-image": 'url("/assets/jimpower/home/waze_5.png")',
// "background-size": "100% 400px",
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// "background-repeat": "no-repeat",
// "border-radius": "20px",
// border: "solid 1px rgba(100,100,100,0.3)",
// "background-color": "rgba(50,50,50,0.3)",
// },
// type: "custom:card-modder",
// card: {
// entities: [
// {
// name: "James",
// zone: "zone.home",
// entity: "sensor.james_to_home",
// },
// {
// name: "Tina",
// zone: "zone.home",
// entity: "sensor.tina_to_home",
// },
// {
// name: "Work",
// zone: "zone.powertec",
// entity: "sensor.commute_to_work",
// },
// ],
// type: "custom:waze-card",
// },
// },
{
style: {
"border-radius": "20px",
@@ -597,8 +722,46 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
],
type: "vertical-stack",
},
// {
// cards: [
// {
// style: {
// "border-radius": "20px",
// color: "#999999",
// "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// border: "solid 1px rgba(100,100,100,0.3)",
// },
// type: "custom:card-modder",
// card: {
// type: "picture-entity",
// entity: "camera.bom_radar",
// },
// },
// // {
// // style: {
// // "background-image": 'url("/assets/jimpower/cardbackK.png")',
// // "background-size": "100% 525px",
// // "box-shadow": "3px 3px rgba(0,0,0,0.4)",
// // "background-repeat": "no-repeat",
// // color: "#999999",
// // "border-radius": "20px",
// // border: "solid 1px rgba(100,100,100,0.3)",
// // "background-color": "rgba(50,50,50,0.3)",
// // },
// // type: "custom:card-modder",
// // card: {
// // title: null,
// // type: "custom:tracker-card",
// // trackers: [
// // "sensor.custom_card_tracker",
// // "sensor.custom_component_tracker",
// // ],
// // },
// // },
// ],
// type: "vertical-stack",
// },
],
path: "home",
icon: "mdi:castle",
name: "Home",
background:
@@ -718,13 +881,26 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
card: {
image: "/assets/jimpower/security/air_8.jpg",
elements: [
{
image:
"https://www.airvisual.com/assets/aqi/ic-face-1-green.svg",
type: "image",
style: {
width: "80px",
top: "30%",
left: "12%",
transform: "none",
height: "80px",
},
entity: "sensor.us_air_pollution_level_2",
},
{
style: {
color: "hsl(120, 41%, 39%)",
top: "50%",
"font-weight": 600,
"font-size": "50px",
left: "30%",
"font-size": "20px",
left: "44%",
},
type: "state-label",
entity: "sensor.us_air_pollution_level_2",
@@ -744,7 +920,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
style: {
color: "white",
top: "80%",
left: "48%",
left: "52%",
},
type: "state-icon",
entity: "sensor.us_main_pollutant_2",
@@ -1235,7 +1411,6 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
type: "vertical-stack",
},
],
path: "security",
icon: "hass:shield-home",
name: "Security",
background:

View File

@@ -59,7 +59,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
hidden: true,
radius: 50,
friendly_name: "School",
friendly_name: "Skolan",
icon: "mdi:school",
},
},
@@ -101,12 +101,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
"sensor.zwave_battery_front_door": {
entity_id: "sensor.zwave_battery_front_door",
state: "63",
attributes: {
friendly_name: "Battery",
icon: "mdi:battery-60",
unit_of_measurement: "%",
device_class: "battery",
},
attributes: { friendly_name: "Battery", icon: "mdi:battery-60" },
},
"sensor.oskar_devices": {
entity_id: "sensor.oskar_devices",
@@ -137,7 +132,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "73",
attributes: {
unit_of_measurement: "%",
friendly_name: "Oskar battery",
friendly_name: "oskar batteri",
device_class: "battery",
},
},
@@ -146,7 +141,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "88",
attributes: {
unit_of_measurement: "%",
friendly_name: "Bella battery",
friendly_name: "bella batteri",
device_class: "battery",
},
},
@@ -154,7 +149,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
entity_id: "binary_sensor.unifi_camera",
state: "off",
attributes: {
friendly_name: "Motion sensor camera",
friendly_name: "R\u00f6relsesensor kamera",
icon: "mdi:walk",
},
},
@@ -169,7 +164,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
},
"input_select.christmas_pattern": {
entity_id: "input_select.christmas_pattern",
state: "Rainbow",
state: "None",
attributes: {
options: [
"None",
@@ -191,7 +186,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
},
"input_select.christmas_palette": {
entity_id: "input_select.christmas_palette",
state: "Party",
state: "None",
attributes: {
options: [
"None",
@@ -462,7 +457,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "0.0",
attributes: {
unit_of_measurement: "kB/s",
friendly_name: "Downloading",
friendly_name: "Nedladdning",
icon: "mdi:file-download",
},
},
@@ -476,7 +471,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
state: "0.0",
attributes: {
unit_of_measurement: "kB/s",
friendly_name: "Uploading",
friendly_name: "Uppladdning",
icon: "mdi:file-upload",
},
},
@@ -707,7 +702,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
},
],
cloudiness: 25,
friendly_name: "Weather",
friendly_name: "V\u00e4der",
},
},
"binary_sensor.ubiquiti_switch": {
@@ -731,7 +726,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
round_trip_time_max: "0.626",
round_trip_time_mdev: "",
round_trip_time_min: "0.358",
friendly_name: "Entrance camera",
friendly_name: "Entr\u00e9 kamera",
device_class: "connectivity",
icon: "mdi:cctv",
},
@@ -797,7 +792,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 34,
on: true,
friendly_name: "Porch motion sensor",
friendly_name: "altan_motion_sensor",
device_class: "motion",
},
},
@@ -807,7 +802,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 88,
on: true,
friendly_name: "Back door sensor",
friendly_name: "Altand\u00f6rren sensor",
device_class: "opening",
icon: "mdi:door",
},
@@ -818,7 +813,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 74,
on: true,
friendly_name: "Bathroom motion sensor",
friendly_name: "badrumssensor",
device_class: "motion",
},
},
@@ -829,7 +824,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47,
on: true,
dark: true,
friendly_name: "Basement motion sensor",
friendly_name: "R\u00f6relsesensor k\u00e4llaren 1",
device_class: "motion",
icon: "mdi:walk",
},
@@ -841,7 +836,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60,
on: true,
dark: true,
friendly_name: "Laundy room motion sensor",
friendly_name: "R\u00f6relsesensor tv\u00e4ttstugan",
device_class: "motion",
icon: "mdi:walk",
},
@@ -863,7 +858,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
attributes: {
battery_level: 60,
on: true,
friendly_name: "Pantry motion sensor",
friendly_name: "R\u00f6relsesensor skafferiet",
device_class: "motion",
icon: "mdi:walk",
},
@@ -875,7 +870,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 60,
on: true,
dark: true,
friendly_name: "Stair motion sensor",
friendly_name: "R\u00f6relsesensor k\u00e4llaren 2",
device_class: "motion",
icon: "mdi:walk",
},
@@ -887,7 +882,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
battery_level: 47,
on: true,
dark: true,
friendly_name: "Bench sensor",
friendly_name: "B\u00e4nksensor",
device_class: "motion",
},
},

View File

@@ -2,7 +2,44 @@ import { DemoConfig } from "../types";
export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
name: "Hem",
resources: [],
resources: [
// {
// url: "/local/custom-lovelace/monster-card.js",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/mini-media-player-bundle.js?v=0.9.8",
// type: "module",
// },
// {
// url: "/local/custom-lovelace/slideshow-card.js?=1.1.0",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/fold-entity-row.js?v=3ae2c4",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/swipe-card/swipe-card.js?v=2.0.0",
// type: "module",
// },
// {
// url: "/local/custom-lovelace/upcoming-media-card/upcoming-media-card.js",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/tracker-card.js?v=0.1.5",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/card-tools.js?v=6ce5d0",
// type: "js",
// },
// {
// url: "/local/custom-lovelace/krisinfo.js?=0.0.1",
// type: "js",
// },
],
views: [
{
cards: [
@@ -27,7 +64,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
style: {
color: "white",
top: "93%",
left: "85%",
left: "90%",
},
type: "state-label",
entity: "sensor.battery_oskar",
@@ -50,7 +87,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
{
style: {
color: "white",
top: "93%",
top: "92%",
left: "20%",
},
type: "state-label",
@@ -59,8 +96,8 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
{
style: {
color: "white",
top: "93%",
left: "85%",
top: "92%",
left: "90%",
},
type: "state-label",
entity: "sensor.battery_bella",
@@ -68,7 +105,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
{
style: {
color: "white",
top: "93%",
top: "92%",
left: "55%",
},
type: "state-label",
@@ -94,6 +131,78 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
type: "entities",
title: "Lock",
},
// {
// filter: {
// exclude: [
// {
// state: "not_home",
// },
// ],
// include: [
// {
// entity_id: "device_tracker.annasiphone",
// },
// {
// entity_id: "device_tracker.iphone_2",
// },
// ],
// },
// type: "custom:monster-card",
// card: {
// show_header_toggle: false,
// type: "entities",
// title: "G\u00e4ster",
// },
// show_empty: false,
// },
// {
// filter: {
// exclude: [
// {
// state: "Inget",
// },
// {
// state: "i.u.",
// },
// ],
// include: [
// {
// entity_id: "sensor.pollen_al",
// },
// {
// entity_id: "sensor.pollen_alm",
// },
// {
// entity_id: "sensor.pollen_salg_vide",
// },
// {
// entity_id: "sensor.pollen_bjork",
// },
// {
// entity_id: "sensor.pollen_bok",
// },
// {
// entity_id: "sensor.pollen_ek",
// },
// {
// entity_id: "sensor.pollen_grabo",
// },
// {
// entity_id: "sensor.pollen_gras",
// },
// {
// entity_id: "sensor.pollen_hassel",
// },
// ],
// },
// type: "custom:monster-card",
// card: {
// show_header_toggle: false,
// type: "entities",
// title: "Pollenniv\u00e5er",
// },
// show_empty: false,
// },
{
cards: [
{
@@ -117,6 +226,10 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
],
type: "vertical-stack",
},
// {
// url: "https://embed.windy.com/embed2.html",
// type: "iframe",
// },
{
entities: [
{
@@ -150,7 +263,6 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
],
type: "glance",
show_state: false,
columns: 4,
},
{
entities: ["sensor.oskar_bluetooth"],
@@ -158,6 +270,32 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
type: "entities",
title: "Occupancy",
},
// {
// filter: {
// exclude: [
// {
// state: false,
// },
// ],
// include: [
// {
// entity_id:
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_2",
// },
// {
// entity_id:
// "binary_sensor.fibaro_system_unknown_type0c02_id1003_sensor_3",
// },
// ],
// },
// type: "custom:monster-card",
// card: {
// show_header_toggle: false,
// type: "entities",
// title: "Brandvarnare",
// },
// show_empty: false,
// },
{
type: "weather-forecast",
entity: "weather.smhi_vader",
@@ -240,9 +378,41 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
"binary_sensor.windows_server",
"binary_sensor.teamspeak",
"binary_sensor.harmony_hub",
// {
// style: {
// height: "1px",
// width: "85%",
// "margin-left": "auto",
// background: "#62717b",
// "margin-right": "auto",
// },
// type: "divider",
// },
// {
// items: ["sensor.uptime_router", "sensor.installerad_routeros"],
// head: {
// entity: "binary_sensor.router",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "mdi:router",
// },
// },
// {
// items: [
// "sensor.uptime_router_server",
// "sensor.installerad_routeros_server",
// ],
// head: {
// entity: "binary_sensor.router_server",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "mdi:router",
// },
// },
],
show_header_toggle: false,
state_color: true,
type: "entities",
title: "Network",
},
@@ -252,10 +422,29 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
"binary_sensor.ubiquiti_switch",
"binary_sensor.ubiquiti_nvr",
"binary_sensor.entre_kamera",
// {
// items: ["sensor.uptime_ap_1"],
// head: {
// entity: "binary_sensor.accesspunkt_1",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "router-wireless",
// },
// },
// {
// items: ["sensor.uptime_ap_2"],
// head: {
// entity: "binary_sensor.accesspunkt_2",
// },
// type: "custom:fold-entity-row",
// group_config: {
// icon: "router-wireless",
// },
// },
"sensor.total_clients_wireless",
],
show_header_toggle: false,
state_color: true,
type: "entities",
title: "Ubiquiti",
},
@@ -277,7 +466,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
],
show_header_toggle: false,
type: "entities",
title: "Bandwidth",
title: "Bandbredd",
},
// {
// title: "Updater",

View File

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

View File

@@ -215,7 +215,6 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
card: {
type: "glance",
show_state: false,
columns: 4,
},
state_filter: ["on"],
},
@@ -377,7 +376,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.air_cleaner_quiet",
},
service: "script.turn_on",
@@ -390,7 +389,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.air_cleaner_auto",
},
service: "script.turn_on",
@@ -403,7 +402,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC bed",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.air_cleaner_turbo",
},
service: "script.turn_on",
@@ -416,7 +415,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.ac_off",
},
service: "script.turn_on",
@@ -429,7 +428,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "AC",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "script.ac_on",
},
service: "script.turn_on",
@@ -440,43 +439,57 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
type: "horizontal-stack",
},
{
type: "grid",
columns: 2,
cards: [
{
graph: "line",
type: "sensor",
entity: "sensor.temperature_bedroom",
cards: [
{
graph: "line",
type: "sensor",
entity: "sensor.temperature_bedroom",
},
{
graph: "line",
type: "sensor",
name: "S's room",
entity: "sensor.temperature_stefan",
},
],
type: "horizontal-stack",
},
{
graph: "line",
type: "sensor",
name: "S's room",
entity: "sensor.temperature_stefan",
cards: [
{
graph: "line",
type: "sensor",
entity: "sensor.temperature_passage",
},
{
graph: "line",
type: "sensor",
name: "Bathroom",
entity: "sensor.temperature_downstairs_bathroom",
},
],
type: "horizontal-stack",
},
{
graph: "line",
type: "sensor",
entity: "sensor.temperature_passage",
},
{
graph: "line",
type: "sensor",
name: "Bathroom",
entity: "sensor.temperature_downstairs_bathroom",
},
{
graph: "line",
type: "sensor",
entity: "sensor.temperature_storage",
},
{
graph: "line",
type: "sensor",
name: "Refrigerator",
entity: "sensor.refrigerator",
cards: [
{
graph: "line",
type: "sensor",
entity: "sensor.temperature_storage",
},
{
graph: "line",
type: "sensor",
name: "Refrigerator",
entity: "sensor.refrigerator",
},
],
type: "horizontal-stack",
},
],
type: "vertical-stack",
},
{
entities: [
@@ -629,7 +642,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.morning_lights",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "scene.morning_lights",
},
service: "scene.turn_on",
@@ -641,7 +654,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "scene.movie_time",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "scene.movie_time",
},
service: "scene.turn_on",
@@ -702,7 +715,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.downstairs_lights",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "light.downstairs_lights",
},
service: "light.toggle",
@@ -714,7 +727,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
entity: "light.upstairs_lights",
tap_action: {
action: "call-service",
data: {
service_data: {
entity_id: "light.upstairs_lights",
},
service: "light.toggle",
@@ -795,6 +808,67 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
],
type: "vertical-stack",
},
// {
// cards: [
// {
// entities: [
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Bedside",
// entity: "light.bedside_lamp",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Bedroom",
// entity: "light.bedroom_ceiling_light",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Isa",
// entity: "light.isa_ceiling_light",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Upstairs hallway",
// entity: "light.upstairs_hallway_ceiling_light_level",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Nightlight",
// entity: "light.gateway_light_34ce008bfc4b",
// },
// {
// hide_when_off: true,
// toggle: true,
// type: "custom:slider-entity-row",
// name: "Walk in closet",
// entity: "light.walk_in_closet_lights",
// },
// {
// hide_when_off: true,
// toggle: false,
// type: "custom:slider-entity-row",
// name: "Stefan",
// entity: "light.stefan_lightstrip",
// },
// ],
// show_header_toggle: false,
// type: "entities",
// title: "Upstairs",
// },
// ],
// type: "vertical-stack",
// },
],
path: "lights",
title: "Lights",
@@ -844,6 +918,10 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
name: "Dafang",
icon: "mdi:webcam",
},
{
name: "IR Hallway",
entity: "sensor.system_ir_blaster",
},
{
name: "IR Bedroom",
entity: "sensor.system_ir_blaster_bedroom",
@@ -862,7 +940,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
"sensor.system_ring_chime",
],
type: "glance",
columns: 4,
columns: 5,
show_state: false,
},
{

View File

@@ -9,5 +9,5 @@ export interface DemoConfig {
authorUrl: string;
lovelace: (localize: LocalizeFunc) => LovelaceConfig;
entities: (localize: LocalizeFunc) => Entity[];
theme: () => Record<string, string> | null;
theme: () => { [key: string]: string } | null;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
// Compat needs to be first import
import "../../src/resources/compatibility";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate";
@@ -20,14 +19,11 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
protected async _initialize() {
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
updateHass: (hassUpdate: Partial<HomeAssistant>) =>
this._updateHass(hassUpdate),
@@ -50,12 +46,8 @@ class HaDemo extends HomeAssistantAppEl {
mockEvents(hass);
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass);
hass.addEntities(energyEntities());
// Once config is loaded AND localize, set entities and apply theme.
Promise.all([selectedDemoConfig, localizePromise]).then(
([conf, localize]) => {
@@ -77,7 +69,7 @@ class HaDemo extends HomeAssistantAppEl {
}
e.preventDefault();
navigate(href);
navigate(this, href);
},
{ 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?domain=co2signal", () => [
{
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 { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
interface HistoryQueryParams {
@@ -72,335 +64,17 @@ const generateHistory = (state, deltas) => {
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) => {
mockHass.mockAPI(
new RegExp("history/period/.+"),
(hass, _method, path, _parameters) => {
(
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
@@ -421,7 +95,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
const numberState = Number(state.state);
if (isNaN(numberState)) {
// eslint-disable-next-line no-console
// eslint-disable-next-line
console.log(
"Ignoring state with unparsable state but with a unit",
entityId,
@@ -466,43 +140,4 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results;
}
);
mockHass.mockWS("recorder/get_statistics_metadata", () => []);
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>
) => {
hass.mockWS("lovelace/config", () =>
Promise.all([selectedDemoConfig, localizePromise]).then(
([config, localize]) => config.lovelace(localize)
)
Promise.all([
selectedDemoConfig,
localizePromise,
]).then(([config, localize]) => config.lovelace(localize))
);
hass.mockWS("lovelace/config/save", () => Promise.resolve());
@@ -23,9 +24,9 @@ customElements.whenDefined("hui-view").then(() => {
// eslint-disable-next-line
const HUIView = customElements.get("hui-view");
// Patch HUI-VIEW to make the lovelace object available to the demo card
const oldCreateCard = HUIView!.prototype.createCardElement;
const oldCreateCard = HUIView.prototype.createCardElement;
HUIView!.prototype.createCardElement = function (config) {
HUIView.prototype.createCardElement = function (config) {
const el = oldCreateCard.call(this, config);
if (el.tagName === "HA-DEMO-CARD") {
(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." },
})
);
hass.mockWS("render_template", (msg, _hass, onChange) => {
hass.mockWS("render_template", (msg, onChange) => {
onChange!({
result: msg.template,
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) => {
hass.mockWS(
"frontend/get_translations",
(/* msg: {language: string, category: string} */) => ({ resources: {} })
(/* msg: {language: string, category: string} */) => {
return { resources: {} };
}
);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

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