mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-14 19:59:26 +00:00
Compare commits
2 Commits
add-suppor
...
20171104.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5154d19006 | ||
![]() |
046c364552 |
@@ -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.10
|
|
||||||
|
|
||||||
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"
|
|
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Home Assistant Frontend",
|
|
||||||
"build": {
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"context": ".."
|
|
||||||
},
|
|
||||||
"appPort": "8124:8123",
|
|
||||||
"postCreateCommand": "script/bootstrap",
|
|
||||||
"containerEnv": {
|
|
||||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
|
||||||
},
|
|
||||||
"customizations": {
|
|
||||||
"vscode": {
|
|
||||||
"extensions": [
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"esbenp.prettier-vscode",
|
|
||||||
"runem.lit-plugin",
|
|
||||||
"github.vscode-pull-request-github",
|
|
||||||
"eamodio.gitlens"
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"files.eol": "\n",
|
|
||||||
"editor.tabSize": 2,
|
|
||||||
"editor.formatOnPaste": false,
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.formatOnType": true,
|
|
||||||
"editor.renderWhitespace": "boundary",
|
|
||||||
"editor.rulers": [80],
|
|
||||||
"[typescript]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
||||||
},
|
|
||||||
"[javascript]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
||||||
},
|
|
||||||
"files.trimTrailingWhitespace": true,
|
|
||||||
"terminal.integrated.shell.linux": "/usr/bin/zsh",
|
|
||||||
"gitlens.showWelcomeOnInstall": false,
|
|
||||||
"gitlens.showWhatsNewAfterUpgrades": false,
|
|
||||||
"workbench.startupEditor": "none"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
bower_components
|
||||||
|
hass_frontend
|
||||||
|
build
|
||||||
|
build-temp
|
||||||
|
.git
|
65
.eslintrc
Normal file
65
.eslintrc
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"extends": "airbnb-base",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true,
|
||||||
|
"modules": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"pragma": "h"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"__DEV__": false,
|
||||||
|
"__DEMO__": false,
|
||||||
|
"Polymer": true,
|
||||||
|
"webkitSpeechRecognition": false,
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"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,
|
||||||
|
"no-var": 0,
|
||||||
|
"strict": 0,
|
||||||
|
"prefer-spread": 0,
|
||||||
|
"no-plusplus": 0,
|
||||||
|
"no-bitwise": 0,
|
||||||
|
"comma-dangle": 0,
|
||||||
|
"vars-on-top": 0,
|
||||||
|
"no-continue": 0,
|
||||||
|
"no-param-reassign": 0,
|
||||||
|
"no-multi-assign": 0,
|
||||||
|
"radix": 0,
|
||||||
|
"no-alert": 0,
|
||||||
|
"prefer-destructuring": 0,
|
||||||
|
"no-restricted-globals": 0,
|
||||||
|
"prefer-promise-reject-errors": 0,
|
||||||
|
"import/prefer-default-export": 0,
|
||||||
|
"react/jsx-no-bind": [2, { "ignoreRefs": true }],
|
||||||
|
"react/jsx-no-duplicate-props": 2,
|
||||||
|
"react/self-closing-comp": 2,
|
||||||
|
"react/prefer-es6-class": 2,
|
||||||
|
"react/no-string-refs": 2,
|
||||||
|
"react/require-render-return": 2,
|
||||||
|
"react/no-find-dom-node": 2,
|
||||||
|
"react/no-is-mounted": 2,
|
||||||
|
"react/jsx-no-comment-textnodes": 2,
|
||||||
|
"react/jsx-curly-spacing": 2,
|
||||||
|
"react/jsx-no-undef": 2,
|
||||||
|
"react/jsx-uses-react": 2,
|
||||||
|
"react/jsx-uses-vars": 2
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"html",
|
||||||
|
"react"
|
||||||
|
]
|
||||||
|
}
|
130
.eslintrc.json
130
.eslintrc.json
@@ -1,130 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": [
|
|
||||||
"airbnb-base",
|
|
||||||
"airbnb-typescript/base",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:wc/recommended",
|
|
||||||
"plugin:lit/all",
|
|
||||||
"plugin:lit-a11y/recommended",
|
|
||||||
"prettier"
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2020,
|
|
||||||
"ecmaFeatures": {
|
|
||||||
"modules": true
|
|
||||||
},
|
|
||||||
"sourceType": "module",
|
|
||||||
"project": "./tsconfig.json"
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"import/resolver": {
|
|
||||||
"webpack": {
|
|
||||||
"config": "./webpack.config.cjs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"__DEV__": false,
|
|
||||||
"__DEMO__": false,
|
|
||||||
"__BUILD__": false,
|
|
||||||
"__VERSION__": false,
|
|
||||||
"__STATIC_PATH__": false,
|
|
||||||
"__SUPERVISOR__": false,
|
|
||||||
"Polymer": true
|
|
||||||
},
|
|
||||||
"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",
|
|
||||||
"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",
|
|
||||||
"import/extensions": [
|
|
||||||
"error",
|
|
||||||
"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",
|
|
||||||
"lit/no-native-attributes": "warn",
|
|
||||||
"lit/no-this-assign-in-render": "warn",
|
|
||||||
"lit-a11y/click-events-have-key-events": ["off"],
|
|
||||||
"lit-a11y/no-autofocus": "off",
|
|
||||||
"lit-a11y/alt-text": "warn",
|
|
||||||
"lit-a11y/anchor-is-valid": "warn",
|
|
||||||
"lit-a11y/role-has-required-aria-attrs": "warn"
|
|
||||||
},
|
|
||||||
"plugins": ["disable", "unused-imports"],
|
|
||||||
"processor": "disable/disable"
|
|
||||||
}
|
|
14
.gitattributes
vendored
14
.gitattributes
vendored
@@ -1,14 +0,0 @@
|
|||||||
# Ensure Docker script files uses LF to support Docker for Windows.
|
|
||||||
# Ensure "git config --global core.autocrlf input" before you clone
|
|
||||||
* text eol=lf
|
|
||||||
*.ts whitespace=error
|
|
||||||
*.js whitespace=error
|
|
||||||
|
|
||||||
*.ico binary
|
|
||||||
*.jpg binary
|
|
||||||
*.png binary
|
|
||||||
*.zip binary
|
|
||||||
*.mp3 binary
|
|
||||||
|
|
||||||
demo/public/api/camera_proxy_stream/* binary
|
|
||||||
demo/public/api/media_player_proxy/* binary
|
|
100
.github/ISSUE_TEMPLATE.md
vendored
100
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,100 +0,0 @@
|
|||||||
---
|
|
||||||
name: Report a bug with the UI, Frontend or Lovelace
|
|
||||||
about: Report an issue related to the Home Assistant frontend.
|
|
||||||
labels: bug
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- READ THIS FIRST:
|
|
||||||
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
|
|
||||||
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
|
|
||||||
- Do not report issues for custom Lovelace cards.
|
|
||||||
- Provide as many details as possible. Paste logs, configuration samples and code into the backticks.
|
|
||||||
DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed without comment.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
- [ ] I have updated to the latest available Home Assistant version.
|
|
||||||
- [ ] I have cleared the cache of my browser.
|
|
||||||
- [ ] I have tried a different browser to see if it is related to my browser.
|
|
||||||
|
|
||||||
## The problem
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Describe the issue you are experiencing here to communicate to the
|
|
||||||
maintainers. Tell us about the current behavior.
|
|
||||||
If possible provide a screenshot with a description.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Expected behavior
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Describe what you expected to happen or it should look/behave.
|
|
||||||
If possible provide a screenshot with a description.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Steps to reproduce
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Provide steps for us, that helps reproducing your issue.
|
|
||||||
For example:
|
|
||||||
1. Add a climate integration
|
|
||||||
2. Navigate to Lovelace
|
|
||||||
3. Click more info of the climate entity
|
|
||||||
4. Set the HVAC action to heat
|
|
||||||
5. Set the temperature higher than the current temperature
|
|
||||||
6. Set the HVAC action to cool
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Environment
|
|
||||||
|
|
||||||
<!--
|
|
||||||
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.
|
|
||||||
|
|
||||||
Browser version and operating system is important! Please try to replicate
|
|
||||||
your issue in a different browser and be sure to include your findings.
|
|
||||||
-->
|
|
||||||
|
|
||||||
- Home Assistant release with the issue:
|
|
||||||
- Last working Home Assistant release (if known):
|
|
||||||
- Browser and browser version:
|
|
||||||
- Operating system:
|
|
||||||
|
|
||||||
## State of relevant entities
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If your issue is about how an entity is shown in the UI, please add the state
|
|
||||||
and attributes for all situations with a screenshot of the UI.
|
|
||||||
You can find this information at `/developer-tools/state`
|
|
||||||
-->
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Problem-relevant frontend 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Javascript errors shown in your browser console/inspector
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If you come across any Javascript or other error logs, e.g. in your browser
|
|
||||||
console/inspector please provide them.
|
|
||||||
-->
|
|
||||||
|
|
||||||
```txt
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Additional information
|
|
121
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
121
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -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.
|
|
17
.github/ISSUE_TEMPLATE/config.yml
vendored
17
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,17 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Request a feature for the UI / Dashboards
|
|
||||||
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
|
|
||||||
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.
|
|
||||||
- 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.
|
|
||||||
- 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!
|
|
83
.github/PULL_REQUEST_TEMPLATE.md
vendored
83
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,83 +0,0 @@
|
|||||||
<!--
|
|
||||||
You are amazing! Thanks for contributing to our project!
|
|
||||||
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Breaking change
|
|
||||||
|
|
||||||
<!--
|
|
||||||
If your PR contains a breaking change for existing users, it is important
|
|
||||||
to tell them what breaks, how to make it work again and why we did this.
|
|
||||||
This piece of text is published with the release notes, so it helps if you
|
|
||||||
write it towards our users, not us.
|
|
||||||
Note: Remove this section if this PR is NOT a breaking change.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Proposed change
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Describe the big picture of your changes here to communicate to the
|
|
||||||
maintainers why we should accept this pull request. If it fixes a bug
|
|
||||||
or resolves a feature request, be sure to link to that issue or discussion
|
|
||||||
in the additional information section.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Type of change
|
|
||||||
|
|
||||||
<!--
|
|
||||||
What type of change does your PR introduce to the Home Assistant frontend?
|
|
||||||
NOTE: Please, check only 1! box!
|
|
||||||
If your PR requires multiple boxes to be checked, you'll most likely need to
|
|
||||||
split it into multiple PRs. This makes things easier and faster to code review.
|
|
||||||
-->
|
|
||||||
|
|
||||||
- [ ] Dependency upgrade
|
|
||||||
- [ ] Bugfix (non-breaking change which fixes an issue)
|
|
||||||
- [ ] New feature (thank you!)
|
|
||||||
- [ ] Breaking change (fix/feature causing existing functionality to break)
|
|
||||||
- [ ] Code quality improvements to existing code or addition of tests
|
|
||||||
|
|
||||||
## Example configuration
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Supplying a configuration snippet, makes it easier for a maintainer to test
|
|
||||||
your PR.
|
|
||||||
-->
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Additional information
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Details are important, and help maintainers processing your PR.
|
|
||||||
Please be sure to fill out additional details, if applicable.
|
|
||||||
-->
|
|
||||||
|
|
||||||
- This PR fixes or closes issue: fixes #
|
|
||||||
- This PR is related to issue or discussion:
|
|
||||||
- Link to documentation pull request:
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Put an `x` in the boxes that apply. You can also fill these out after
|
|
||||||
creating the PR. If you're unsure about any of them, don't hesitate to ask.
|
|
||||||
We're here to help! This is simply a reminder of what we are going to look
|
|
||||||
for before merging your code.
|
|
||||||
-->
|
|
||||||
|
|
||||||
- [ ] The code change is tested and works locally.
|
|
||||||
- [ ] There is no commented out code in this PR.
|
|
||||||
- [ ] Tests have been added to verify that the new code works.
|
|
||||||
|
|
||||||
If user exposed functionality or configuration variables are added/changed:
|
|
||||||
|
|
||||||
- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thank you for contributing <3
|
|
||||||
-->
|
|
||||||
|
|
||||||
[docs-repository]: https://github.com/home-assistant/home-assistant.io
|
|
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
@@ -1,8 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
time: "06:00"
|
|
||||||
open-pull-requests-limit: 10
|
|
13
.github/move.yml
vendored
13
.github/move.yml
vendored
@@ -1,13 +0,0 @@
|
|||||||
# Configuration for move-issues - https://github.com/dessant/move-issues
|
|
||||||
|
|
||||||
# Delete the command comment. Ignored when the comment also contains other content
|
|
||||||
deleteCommand: true
|
|
||||||
# Close the source issue after moving
|
|
||||||
closeSourceIssue: true
|
|
||||||
# Lock the source issue after moving
|
|
||||||
lockSourceIssue: false
|
|
||||||
# Set custom aliases for targets
|
|
||||||
# aliases:
|
|
||||||
# r: repo
|
|
||||||
# or: owner/repo
|
|
||||||
|
|
4
.github/release-drafter.yml
vendored
4
.github/release-drafter.yml
vendored
@@ -1,4 +0,0 @@
|
|||||||
template: |
|
|
||||||
## What's Changed
|
|
||||||
|
|
||||||
$CHANGES
|
|
86
.github/workflows/cast_deployment.yaml
vendored
86
.github/workflows/cast_deployment.yaml
vendored
@@ -1,86 +0,0 @@
|
|||||||
name: Cast deployment
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *"
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 16
|
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy_dev:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Deploy Development
|
|
||||||
if: github.event_name != 'push'
|
|
||||||
environment:
|
|
||||||
name: Cast Development
|
|
||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
with:
|
|
||||||
ref: dev
|
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
|
|
||||||
- name: Build Cast
|
|
||||||
run: ./node_modules/.bin/gulp build-cast
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Deploy to Netlify
|
|
||||||
id: deploy
|
|
||||||
uses: netlify/actions/cli@master
|
|
||||||
with:
|
|
||||||
args: deploy --dir=cast/dist --alias dev
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
|
||||||
|
|
||||||
deploy_master:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Deploy Production
|
|
||||||
if: github.event_name == 'push'
|
|
||||||
environment:
|
|
||||||
name: Cast Production
|
|
||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
with:
|
|
||||||
ref: master
|
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
|
|
||||||
- name: Build Cast
|
|
||||||
run: ./node_modules/.bin/gulp build-cast
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Deploy to Netlify
|
|
||||||
id: deploy
|
|
||||||
uses: netlify/actions/cli@master
|
|
||||||
with:
|
|
||||||
args: deploy --dir=cast/dist --prod
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
|
98
.github/workflows/ci.yaml
vendored
98
.github/workflows/ci.yaml
vendored
@@ -1,98 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- master
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- master
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 16
|
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
name: Lint and check format
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
- name: Check for duplicate dependencies
|
|
||||||
run: yarn dedupe --check
|
|
||||||
- name: Build resources
|
|
||||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
|
||||||
- name: Run eslint
|
|
||||||
run: yarn run lint:eslint --quiet
|
|
||||||
- name: Run tsc
|
|
||||||
run: yarn run lint:types
|
|
||||||
- name: Run prettier
|
|
||||||
run: yarn run lint:prettier
|
|
||||||
test:
|
|
||||||
name: Run tests
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
- name: Build resources
|
|
||||||
run: ./node_modules/.bin/gulp build-translations build-locale-data
|
|
||||||
- name: Run Tests
|
|
||||||
run: yarn run test
|
|
||||||
build:
|
|
||||||
name: Build frontend
|
|
||||||
needs: [lint, test]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
- name: Build Application
|
|
||||||
run: ./node_modules/.bin/gulp build-app
|
|
||||||
env:
|
|
||||||
IS_TEST: "true"
|
|
||||||
supervisor:
|
|
||||||
name: Build supervisor
|
|
||||||
needs: [lint, test]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
- name: Build Application
|
|
||||||
run: ./node_modules/.bin/gulp build-hassio
|
|
||||||
env:
|
|
||||||
IS_TEST: "true"
|
|
60
.github/workflows/codeql-analysis.yml
vendored
60
.github/workflows/codeql-analysis.yml
vendored
@@ -1,60 +0,0 @@
|
|||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [dev, master]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [dev]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# Override automatic language detection by changing the below list
|
|
||||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
|
||||||
language: ['javascript']
|
|
||||||
# Learn more...
|
|
||||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
with:
|
|
||||||
# We must fetch at least the immediate parents so that if this is
|
|
||||||
# a pull request then we can checkout the head.
|
|
||||||
fetch-depth: 2
|
|
||||||
|
|
||||||
# If this run was triggered by a pull request event, then checkout
|
|
||||||
# the head of the pull request instead of the merge commit.
|
|
||||||
- run: git checkout HEAD^2
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v2
|
|
||||||
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
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 https://git.io/JvXDl
|
|
||||||
|
|
||||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
|
||||||
# and modify them (or add more) to build your code if your project
|
|
||||||
# uses a compiled language
|
|
||||||
|
|
||||||
#- run: |
|
|
||||||
# make bootstrap
|
|
||||||
# make release
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
87
.github/workflows/demo_deployment.yaml
vendored
87
.github/workflows/demo_deployment.yaml
vendored
@@ -1,87 +0,0 @@
|
|||||||
name: Demo deployment
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *"
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- master
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 16
|
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy_dev:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Demo Development
|
|
||||||
if: github.event_name != 'push' || github.ref_name != 'master'
|
|
||||||
environment:
|
|
||||||
name: Demo Development
|
|
||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
with:
|
|
||||||
ref: dev
|
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
|
|
||||||
- name: Build Demo
|
|
||||||
run: ./node_modules/.bin/gulp build-demo
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Deploy to Netlify
|
|
||||||
id: deploy
|
|
||||||
uses: netlify/actions/cli@master
|
|
||||||
with:
|
|
||||||
args: deploy --dir=demo/dist --prod
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
|
||||||
|
|
||||||
deploy_master:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Demo Production
|
|
||||||
if: github.event_name == 'push' && github.ref_name == 'master'
|
|
||||||
environment:
|
|
||||||
name: Demo Production
|
|
||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
with:
|
|
||||||
ref: master
|
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
|
|
||||||
- name: Build Demo
|
|
||||||
run: ./node_modules/.bin/gulp build-demo
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Deploy to Netlify
|
|
||||||
id: deploy
|
|
||||||
uses: netlify/actions/cli@master
|
|
||||||
with:
|
|
||||||
args: deploy --dir=demo/dist --prod
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }}
|
|
43
.github/workflows/design_deployment.yaml
vendored
43
.github/workflows/design_deployment.yaml
vendored
@@ -1,43 +0,0 @@
|
|||||||
name: Design deployment
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *"
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 16
|
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment:
|
|
||||||
name: Design
|
|
||||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
|
|
||||||
- name: Build Gallery
|
|
||||||
run: ./node_modules/.bin/gulp build-gallery
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Deploy to Netlify
|
|
||||||
id: deploy
|
|
||||||
uses: netlify/actions/cli@master
|
|
||||||
with:
|
|
||||||
args: deploy --dir=gallery/dist --prod
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
|
52
.github/workflows/design_preview.yaml
vendored
52
.github/workflows/design_preview.yaml
vendored
@@ -1,52 +0,0 @@
|
|||||||
name: Design preview
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- reopened
|
|
||||||
- labeled
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 16
|
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
preview:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
# Skip running on forks since it won't have access to secrets
|
|
||||||
# Skip running PRs without 'needs design preview' label
|
|
||||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
|
||||||
steps:
|
|
||||||
- name: Check out files from GitHub
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install --immutable
|
|
||||||
|
|
||||||
- name: Build Gallery
|
|
||||||
run: ./node_modules/.bin/gulp build-gallery
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Deploy preview to Netlify
|
|
||||||
id: deploy
|
|
||||||
uses: netlify/actions/cli@master
|
|
||||||
with:
|
|
||||||
args: deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}"
|
|
||||||
env:
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
|
||||||
|
|
||||||
- name: Generate summary
|
|
||||||
run: |
|
|
||||||
echo "${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}" >> "$GITHUB_STEP_SUMMARY"
|
|
20
.github/workflows/lock.yml
vendored
20
.github/workflows/lock.yml
vendored
@@ -1,20 +0,0 @@
|
|||||||
name: Lock
|
|
||||||
|
|
||||||
# yamllint disable-line rule:truthy
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 * * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lock:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: dessant/lock-threads@v4.0.0
|
|
||||||
with:
|
|
||||||
github-token: ${{ github.token }}
|
|
||||||
issue-lock-inactive-days: "30"
|
|
||||||
issue-exclude-created-before: "2020-10-01T00:00:00Z"
|
|
||||||
issue-lock-reason: ""
|
|
||||||
pr-lock-inactive-days: "1"
|
|
||||||
pr-exclude-created-before: "2020-11-01T00:00:00Z"
|
|
||||||
pr-lock-reason: ""
|
|
72
.github/workflows/nightly.yaml
vendored
72
.github/workflows/nightly.yaml
vendored
@@ -1,72 +0,0 @@
|
|||||||
name: Nightly
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 1 * * *"
|
|
||||||
|
|
||||||
env:
|
|
||||||
PYTHON_VERSION: "3.10"
|
|
||||||
NODE_VERSION: 16
|
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
actions: none
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
nightly:
|
|
||||||
name: Nightly
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout the repository
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
|
||||||
uses: actions/setup-node@v3.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install
|
|
||||||
|
|
||||||
- name: Download translations
|
|
||||||
run: ./script/translations_download
|
|
||||||
env:
|
|
||||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
|
||||||
|
|
||||||
- name: Bump version
|
|
||||||
run: script/version_bump.cjs nightly
|
|
||||||
|
|
||||||
- name: Build nightly Python wheels
|
|
||||||
run: |
|
|
||||||
pip install build
|
|
||||||
yarn install
|
|
||||||
export SKIP_FETCH_NIGHTLY_TRANSLATIONS=1
|
|
||||||
script/build_frontend
|
|
||||||
rm -rf dist home_assistant_frontend.egg-info
|
|
||||||
python3 -m build
|
|
||||||
|
|
||||||
- name: Archive translations
|
|
||||||
run: tar -czvf translations.tar.gz translations
|
|
||||||
|
|
||||||
- name: Upload build artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: wheels
|
|
||||||
path: dist/home_assistant_frontend*.whl
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Upload translations
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: translations
|
|
||||||
path: translations.tar.gz
|
|
||||||
if-no-files-found: error
|
|
14
.github/workflows/release-drafter.yaml
vendored
14
.github/workflows/release-drafter.yaml
vendored
@@ -1,14 +0,0 @@
|
|||||||
name: Release Drafter
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update_release_draft:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: release-drafter/release-drafter@v5
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
84
.github/workflows/release.yaml
vendored
84
.github/workflows/release.yaml
vendored
@@ -1,84 +0,0 @@
|
|||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types:
|
|
||||||
- published
|
|
||||||
|
|
||||||
env:
|
|
||||||
PYTHON_VERSION: "3.10"
|
|
||||||
NODE_VERSION: 16
|
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
|
||||||
|
|
||||||
# Set default workflow permissions
|
|
||||||
# All scopes not mentioned here are set to no access
|
|
||||||
# https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
|
|
||||||
permissions:
|
|
||||||
actions: none
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write # Required to upload release assets
|
|
||||||
steps:
|
|
||||||
- name: Checkout the repository
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
|
|
||||||
- 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.6.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install
|
|
||||||
|
|
||||||
- name: Download Translations
|
|
||||||
run: ./script/translations_download
|
|
||||||
env:
|
|
||||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
|
||||||
- name: Build and release package
|
|
||||||
run: |
|
|
||||||
python3 -m pip install twine build
|
|
||||||
export TWINE_USERNAME="__token__"
|
|
||||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
|
||||||
export SKIP_FETCH_NIGHTLY_TRANSLATIONS=1
|
|
||||||
script/release
|
|
||||||
|
|
||||||
- name: Upload release assets
|
|
||||||
uses: softprops/action-gh-release@v0.1.15
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
dist/*.whl
|
|
||||||
dist/*.tar.gz
|
|
||||||
|
|
||||||
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.10.1
|
|
||||||
with:
|
|
||||||
abi: cp310
|
|
||||||
tag: musllinux_1_2
|
|
||||||
arch: amd64
|
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
|
||||||
requirements: "requirements.txt"
|
|
42
.github/workflows/stale.yml
vendored
42
.github/workflows/stale.yml
vendored
@@ -1,42 +0,0 @@
|
|||||||
name: Stale
|
|
||||||
|
|
||||||
# yamllint disable-line rule:truthy
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 * * * *"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
stale:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: 90 days stale policy
|
|
||||||
uses: actions/stale@v8.0.0
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
days-before-stale: 90
|
|
||||||
days-before-close: 7
|
|
||||||
operations-per-run: 25
|
|
||||||
remove-stale-when-updated: true
|
|
||||||
stale-issue-label: "stale"
|
|
||||||
exempt-issue-labels: "no-stale,Help%20wanted,help-wanted,feature-request,feature%20request"
|
|
||||||
stale-issue-message: >
|
|
||||||
There hasn't been any activity on this issue recently. Due to the
|
|
||||||
high number of incoming GitHub notifications, we have to clean some
|
|
||||||
of the old issues, as many of them have already been resolved with
|
|
||||||
the latest updates.
|
|
||||||
|
|
||||||
Please make sure to update to the latest Home Assistant version and
|
|
||||||
check if that solves the issue. Let us know if that works for you by
|
|
||||||
adding a comment 👍
|
|
||||||
|
|
||||||
This issue has now been marked as stale and will be closed if no
|
|
||||||
further activity occurs. Thank you for your contributions.
|
|
||||||
|
|
||||||
stale-pr-label: "stale"
|
|
||||||
exempt-pr-labels: "no-stale"
|
|
||||||
stale-pr-message: >
|
|
||||||
There hasn't been any activity on this pull request recently. This
|
|
||||||
pull request has been automatically marked as stale because of that
|
|
||||||
and will be closed if no further activity occurs within 7 days.
|
|
||||||
|
|
||||||
Thank you for your contributions.
|
|
25
.github/workflows/translations.yaml
vendored
25
.github/workflows/translations.yaml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Translations
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
paths:
|
|
||||||
- src/translations/en.json
|
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 16
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
upload:
|
|
||||||
name: Upload
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout the repository
|
|
||||||
uses: actions/checkout@v3.5.2
|
|
||||||
|
|
||||||
- name: Upload Translations
|
|
||||||
run: |
|
|
||||||
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
|
|
||||||
|
|
||||||
./script/translations_upload_base
|
|
48
.gitignore
vendored
48
.gitignore
vendored
@@ -1,23 +1,10 @@
|
|||||||
.DS_Store
|
build/*
|
||||||
.reify-cache
|
build-temp/*
|
||||||
|
node_modules/*
|
||||||
# build
|
bower_components/*
|
||||||
build/
|
|
||||||
dist/
|
|
||||||
/hass_frontend/
|
|
||||||
/translations/
|
|
||||||
|
|
||||||
# yarn
|
|
||||||
.yarn/*
|
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/sdks
|
|
||||||
!.yarn/versions
|
|
||||||
.pnp.*
|
|
||||||
/node_modules/
|
|
||||||
yarn-error.log
|
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
.DS_Store
|
||||||
|
hass_frontend/*
|
||||||
|
|
||||||
# Python stuff
|
# Python stuff
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
@@ -27,23 +14,8 @@ npm-debug.log
|
|||||||
# venv stuff
|
# venv stuff
|
||||||
pyvenv.cfg
|
pyvenv.cfg
|
||||||
pip-selfcheck.json
|
pip-selfcheck.json
|
||||||
/venv/
|
venv
|
||||||
.venv
|
.venv
|
||||||
|
lib
|
||||||
# vscode
|
bin
|
||||||
.vscode/*
|
dist
|
||||||
!.vscode/extensions.json
|
|
||||||
!.vscode/launch.json
|
|
||||||
!.vscode/tasks.json
|
|
||||||
|
|
||||||
# Cast dev settings
|
|
||||||
src/cast/dev_const.ts
|
|
||||||
|
|
||||||
# Secrets
|
|
||||||
.lokalise_token
|
|
||||||
|
|
||||||
# asdf
|
|
||||||
.tool-versions
|
|
||||||
|
|
||||||
# Home Assistant config
|
|
||||||
/config/
|
|
||||||
|
6
.hound.yml
Normal file
6
.hound.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
jshint:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
eslint:
|
||||||
|
enabled: true
|
||||||
|
config_file: .eslintrc
|
@@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
yarn run lint-staged --relative --shell "/bin/bash"
|
|
@@ -1,9 +0,0 @@
|
|||||||
build
|
|
||||||
translations/*
|
|
||||||
node_modules/*
|
|
||||||
hass_frontend/*
|
|
||||||
pip-selfcheck.json
|
|
||||||
|
|
||||||
# vscode
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
24
.travis.yml
Normal file
24
.travis.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
sudo: false
|
||||||
|
language: node_js
|
||||||
|
cache:
|
||||||
|
yarn: true
|
||||||
|
directories:
|
||||||
|
- bower_components
|
||||||
|
install:
|
||||||
|
- yarn install
|
||||||
|
- ./node_modules/.bin/bower install
|
||||||
|
addons:
|
||||||
|
firefox: latest
|
||||||
|
apt:
|
||||||
|
sources:
|
||||||
|
- google-chrome
|
||||||
|
packages:
|
||||||
|
- google-chrome-stable
|
||||||
|
script:
|
||||||
|
- npm run build
|
||||||
|
- npm run test
|
||||||
|
- xvfb-run wct
|
||||||
|
- if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --plugin sauce; fi
|
||||||
|
dist: trusty
|
||||||
|
addons:
|
||||||
|
sauce_connect: true
|
9
.vscode/extensions.json
vendored
9
.vscode/extensions.json
vendored
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"esbenp.prettier-vscode",
|
|
||||||
"runem.lit-plugin",
|
|
||||||
"github.vscode-pull-request-github",
|
|
||||||
"eamodio.gitlens"
|
|
||||||
]
|
|
||||||
}
|
|
44
.vscode/launch.json
vendored
44
.vscode/launch.json
vendored
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
// https://github.com/microsoft/vscode-js-debug/blob/master/OPTIONS.md
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "Debug Frontend",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "pwa-chrome",
|
|
||||||
"url": "http://localhost:8123/",
|
|
||||||
"webRoot": "${workspaceFolder}/hass_frontend",
|
|
||||||
"disableNetworkCache": true,
|
|
||||||
"preLaunchTask": "Develop Frontend",
|
|
||||||
"outFiles": [
|
|
||||||
"${workspaceFolder}/hass_frontend/frontend_latest/*.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Debug Gallery",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "pwa-chrome",
|
|
||||||
"url": "http://localhost:8100/",
|
|
||||||
"webRoot": "${workspaceFolder}/gallery/dist",
|
|
||||||
"disableNetworkCache": true,
|
|
||||||
"preLaunchTask": "Develop Gallery"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Debug Demo",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "pwa-chrome",
|
|
||||||
"url": "http://localhost:8090/",
|
|
||||||
"webRoot": "${workspaceFolder}/demo/dist",
|
|
||||||
"disableNetworkCache": true,
|
|
||||||
"preLaunchTask": "Develop Demo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Debug Cast",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "pwa-chrome",
|
|
||||||
"url": "http://localhost:8080/",
|
|
||||||
"webRoot": "${workspaceFolder}/cast/dist",
|
|
||||||
"disableNetworkCache": true,
|
|
||||||
"preLaunchTask": "Develop Cast"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
214
.vscode/tasks.json
vendored
214
.vscode/tasks.json
vendored
@@ -1,214 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "Develop Frontend",
|
|
||||||
"type": "gulp",
|
|
||||||
"task": "develop-app",
|
|
||||||
// Sync changes here to other tasks until issue resolved
|
|
||||||
// https://github.com/Microsoft/vscode/issues/61497
|
|
||||||
"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": {
|
|
||||||
"kind": "build",
|
|
||||||
"isDefault": true
|
|
||||||
},
|
|
||||||
"runOptions": {
|
|
||||||
"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",
|
|
||||||
"task": "develop-gallery",
|
|
||||||
"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 Demo",
|
|
||||||
"type": "gulp",
|
|
||||||
"task": "develop-demo",
|
|
||||||
"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 Cast",
|
|
||||||
"type": "gulp",
|
|
||||||
"task": "develop-cast",
|
|
||||||
"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": "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
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Setup and fetch nightly translations",
|
|
||||||
"type": "gulp",
|
|
||||||
"task": "setup-and-fetch-nightly-translations",
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@@ -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++) {
|
|
541
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
541
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
File diff suppressed because one or more lines are too long
9
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
9
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
File diff suppressed because one or more lines are too long
873
.yarn/releases/yarn-3.5.0.cjs
vendored
873
.yarn/releases/yarn-3.5.0.cjs
vendored
File diff suppressed because one or more lines are too long
11
.yarnrc.yml
11
.yarnrc.yml
@@ -1,11 +0,0 @@
|
|||||||
defaultSemverRangePrefix: ""
|
|
||||||
|
|
||||||
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.5.0.cjs
|
|
@@ -2,139 +2,79 @@
|
|||||||
|
|
||||||
## Our Pledge
|
## Our Pledge
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
contributors and maintainers pledge to making participation in our project and
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
nationality, personal appearance, race, religion, or sexual identity and
|
||||||
and orientation.
|
orientation.
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
## Our Standards
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
Examples of behavior that contributes to creating a positive environment
|
||||||
community include:
|
include:
|
||||||
|
|
||||||
* Demonstrating empathy and kindness toward other people
|
* Using welcoming and inclusive language
|
||||||
* Being respectful of differing opinions, viewpoints, and experiences
|
* Being respectful of differing viewpoints and experiences
|
||||||
* Giving and gracefully accepting constructive feedback
|
* Gracefully accepting constructive criticism
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
* Focusing on what is best for the community
|
||||||
and learning from the experience
|
* Showing empathy towards other community members
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
|
||||||
overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
advances of any kind
|
advances
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
* Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or email
|
* Publishing others' private information, such as a physical or electronic
|
||||||
address, without their explicit permission
|
address, without explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
## Our Responsibilities
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
response to any instances of unacceptable behavior.
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
decisions when appropriate.
|
permanently any contributor for other behaviors that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
an individual is officially representing the community in public spaces.
|
when an individual is representing the project or its community. Examples of
|
||||||
Examples of representing our community include using an official e-mail address,
|
representing a project or community include using an official project e-mail
|
||||||
posting via an official social media account, or acting as an appointed
|
address, posting via an official social media account, or acting as an appointed
|
||||||
representative at an online or offline event.
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported by contacting the project team at [safety@home-assistant.io][email]. All
|
||||||
[safety@home-assistant.io][email] or by using the report/flag feature of
|
complaints will be reviewed and investigated and will result in a response that
|
||||||
the medium used. All complaints will be reviewed and investigated promptly and
|
is deemed necessary and appropriate to the circumstances. The project team is
|
||||||
fairly.
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
reporter of any incident.
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||||
version 2.0, available [here][version].
|
available [here][version].
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
|
||||||
enforcement ladder][mozilla].
|
|
||||||
|
|
||||||
## Adoption
|
## Adoption
|
||||||
|
|
||||||
This Code of Conduct was first adopted January 21st, 2017 and announced in
|
This Code of Conduct was first adopted January 21st, 2017 and announced in [this][coc-blog] blog post.
|
||||||
[this][coc-blog] blog post and has been updated on May 25th, 2020 to version
|
|
||||||
2.0 of the [Contributor Covenant][homepage] as announced in [this][coc2-blog]
|
|
||||||
blog post.
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
<https://www.contributor-covenant.org/faq>. Translations are available at
|
|
||||||
<https://www.contributor-covenant.org/translations>.
|
|
||||||
|
|
||||||
[coc-blog]: /blog/2017/01/21/home-assistant-governance/
|
|
||||||
[coc2-blog]: /blog/2020/05/25/code-of-conduct-updated/
|
|
||||||
[email]: mailto:safety@home-assistant.io
|
|
||||||
[homepage]: http://contributor-covenant.org
|
[homepage]: http://contributor-covenant.org
|
||||||
[mozilla]: https://github.com/mozilla/diversity
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
[version]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
[email]: mailto:safety@home-assistant.io
|
||||||
|
[coc-blog]: https://home-assistant.io/blog/2017/01/21/home-assistant-governance/
|
||||||
|
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
FROM node:8.2.1-alpine
|
||||||
|
|
||||||
|
# install yarn
|
||||||
|
ENV PATH /root/.yarn/bin:$PATH
|
||||||
|
|
||||||
|
RUN apk update \
|
||||||
|
&& apk add curl bash binutils tar git python3 \
|
||||||
|
&& rm -rf /var/cache/apk/* \
|
||||||
|
&& /bin/bash \
|
||||||
|
&& touch ~/.bashrc \
|
||||||
|
&& curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||||
|
|
||||||
|
RUN mkdir -p /frontend
|
||||||
|
WORKDIR /frontend
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
COPY package.json ./
|
||||||
|
RUN yarn
|
||||||
|
|
||||||
|
COPY bower.json ./
|
||||||
|
RUN ./node_modules/.bin/bower install --allow-root
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
CMD [ "/bin/bash", "./script/build_frontend" ]
|
@@ -1,4 +1,4 @@
|
|||||||
include README.md
|
include README.md
|
||||||
|
include LICENSE.md
|
||||||
graft hass_frontend
|
graft hass_frontend
|
||||||
graft hass_frontend_es5
|
|
||||||
recursive-exclude * *.py[co]
|
recursive-exclude * *.py[co]
|
||||||
|
32
README.md
32
README.md
@@ -1,29 +1,17 @@
|
|||||||
# Home Assistant Frontend
|
# Home Assistant Polymer [](https://travis-ci.org/home-assistant/home-assistant-polymer)
|
||||||
|
|
||||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend. The frontend is built on top of the following technologies:
|
||||||
|
|
||||||
[](https://demo.home-assistant.io/)
|
* [Websockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
|
||||||
|
* [Polymer](https://www.polymer-project.org/)
|
||||||
|
* [Rollup](http://rollupjs.org/) to package Home Assistant JS
|
||||||
|
* [Bower](https://bower.io) for Polymer package management
|
||||||
|
|
||||||
- [View demo of Home Assistant](https://demo.home-assistant.io/)
|
[](https://home-assistant.io/demo/)
|
||||||
- [More information about Home Assistant](https://home-assistant.io)
|
|
||||||
- [Frontend development instructions](https://developers.home-assistant.io/docs/frontend/development/)
|
|
||||||
|
|
||||||
## Development
|
[View demo of the Polymer frontend](https://home-assistant.io/demo/)
|
||||||
|
[More information about Home Assistant](https://home-assistant.io)
|
||||||
- Initial setup: `script/setup`
|
[Frontend development instructions](https://home-assistant.io/developers/frontend/)
|
||||||
- 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)
|
|
||||||
|
|
||||||
## Frontend development
|
|
||||||
|
|
||||||
### Classic environment
|
|
||||||
|
|
||||||
A complete guide can be found at the following [link](https://www.home-assistant.io/developers/frontend/). It describes a short guide for the build of project.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
||||||
|
|
||||||
We use [BrowserStack](https://www.browserstack.com) to test Home Assistant on a large variety of devices.
|
|
||||||
|
62
bower.json
Normal file
62
bower.json
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"name": "home-assistant",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"authors": [
|
||||||
|
"Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
|
||||||
|
],
|
||||||
|
"main": "src/home-assistant.html",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"app-layout": "^2.0.0",
|
||||||
|
"app-localize-behavior": "PolymerElements/app-localize-behavior#~2.0.0",
|
||||||
|
"app-route": "PolymerElements/app-route#^2.0.0",
|
||||||
|
"app-storage": "^2.0.2",
|
||||||
|
"fecha": "~2.3.0",
|
||||||
|
"font-roboto-local": "~1.0.1",
|
||||||
|
"font-roboto": "PolymerElements/font-roboto-local#~1.0.1",
|
||||||
|
"google-apis": "GoogleWebComponents/google-apis#~2.0.0",
|
||||||
|
"iron-autogrow-textarea": "PolymerElements/iron-autogrow-textarea#^2.0.0",
|
||||||
|
"iron-flex-layout": "PolymerElements/iron-flex-layout#^2.0.0",
|
||||||
|
"iron-icon": "PolymerElements/iron-icon#^2.0.0",
|
||||||
|
"iron-image": "PolymerElements/iron-image#^2.1.1",
|
||||||
|
"iron-input": "PolymerElements/iron-input#^2.0.0",
|
||||||
|
"iron-media-query": "PolymerElements/iron-media-query#^2.0.0",
|
||||||
|
"iron-pages": "PolymerElements/iron-pages#^2.0.0",
|
||||||
|
"leaflet": "^1.0.2",
|
||||||
|
"neon-animation": "PolymerElements/neon-animation#^2.0.1",
|
||||||
|
"paper-button": "PolymerElements/paper-button#^2.0.0",
|
||||||
|
"paper-card": "PolymerElements/paper-card#^2.0.0",
|
||||||
|
"paper-checkbox": "PolymerElements/paper-checkbox#^2.0.0",
|
||||||
|
"paper-dialog": "PolymerElements/paper-dialog#^2.0.0",
|
||||||
|
"paper-dialog-scrollable": "PolymerElements/paper-dialog-scrollable#^2.1.0",
|
||||||
|
"paper-drawer-panel": "PolymerElements/paper-drawer-panel#^2.0.0",
|
||||||
|
"paper-dropdown-menu": "PolymerElements/paper-dropdown-menu#^2.0.0",
|
||||||
|
"paper-fab": "PolymerElements/paper-fab#^2.0.0",
|
||||||
|
"paper-icon-button": "PolymerElements/paper-icon-button#^2.0.0",
|
||||||
|
"paper-input": "PolymerElements/paper-input#^2.0.1",
|
||||||
|
"paper-item": "PolymerElements/paper-item#^2.0.0",
|
||||||
|
"paper-listbox": "PolymerElements/paper-listbox#^2.0.0",
|
||||||
|
"paper-material": "PolymerElements/paper-material#^2.0.0",
|
||||||
|
"paper-menu-button": "PolymerElements/paper-menu-button#^2.0.0",
|
||||||
|
"paper-progress": "PolymerElements/paper-progress#^2.0.1",
|
||||||
|
"paper-radio-button": "PolymerElements/paper-radio-button#^2.0.0",
|
||||||
|
"paper-radio-group": "PolymerElements/paper-radio-group#^2.0.0",
|
||||||
|
"paper-scroll-header-panel": "~2.0.0",
|
||||||
|
"paper-slider": "PolymerElements/paper-slider#^2.0.1",
|
||||||
|
"paper-spinner": "PolymerElements/paper-spinner#^2.0.0",
|
||||||
|
"paper-styles": "PolymerElements/paper-styles#^2.0.0",
|
||||||
|
"paper-tabs": "PolymerElements/paper-tabs#^2.0.0",
|
||||||
|
"paper-time-input": "ryanburns23/paper-time-input#^2.0.4",
|
||||||
|
"paper-toast": "PolymerElements/paper-toast#^2.0.0",
|
||||||
|
"paper-toggle-button": "PolymerElements/paper-toggle-button#^2.0.0",
|
||||||
|
"polymer": "^2.1.1",
|
||||||
|
"vaadin-combo-box": "vaadin/vaadin-combo-box#^2.0.0",
|
||||||
|
"vaadin-date-picker": "vaadin/vaadin-date-picker#^2.0.0",
|
||||||
|
"web-animations-js": "^2.2.5",
|
||||||
|
"webcomponentsjs": "^1.0.10"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"web-component-tester": "^6.3.0"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "../.eslintrc.json",
|
|
||||||
"rules": {
|
|
||||||
"no-console": "off",
|
|
||||||
"import/no-extraneous-dependencies": "off",
|
|
||||||
"import/extensions": "off",
|
|
||||||
"import/no-dynamic-require": "off",
|
|
||||||
"global-require": "off",
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"prefer-arrow-callback": "off"
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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).
|
|
@@ -1,168 +0,0 @@
|
|||||||
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;
|
|
||||||
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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@@ -1,282 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
const env = require("./env.cjs");
|
|
||||||
const paths = require("./paths.cjs");
|
|
||||||
|
|
||||||
// GitHub base URL to use for production source maps
|
|
||||||
// Nightly builds use the commit SHA, otherwise assumes there is a tag that matches the version
|
|
||||||
module.exports.sourceMapURL = () => {
|
|
||||||
const ref = env.version().endsWith("dev")
|
|
||||||
? process.env.GITHUB_SHA || "dev"
|
|
||||||
: env.version();
|
|
||||||
return `https://raw.githubusercontent.com/home-assistant/frontend/${ref}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Files from NPM Packages that should not be imported
|
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
||||||
module.exports.ignorePackages = ({ latestBuild }) => [
|
|
||||||
// Part of yaml.js and only used for !!js functions that we don't use
|
|
||||||
require.resolve("esprima"),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Files from NPM packages that we should replace with empty file
|
|
||||||
module.exports.emptyPackages = ({ latestBuild, isHassioBuild }) =>
|
|
||||||
[
|
|
||||||
// Contains all color definitions for all material color sets.
|
|
||||||
// We don't use it
|
|
||||||
require.resolve("@polymer/paper-styles/color.js"),
|
|
||||||
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"),
|
|
||||||
// Compatibility not needed for latest builds
|
|
||||||
latestBuild &&
|
|
||||||
// wrapped in require.resolve so it blows up if file no longer exists
|
|
||||||
require.resolve(
|
|
||||||
path.resolve(paths.polymer_dir, "src/resources/compatibility.ts")
|
|
||||||
),
|
|
||||||
// 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 }) => ({
|
|
||||||
__DEV__: !isProdBuild,
|
|
||||||
__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(
|
|
||||||
isProdBuild ? "production" : "development"
|
|
||||||
),
|
|
||||||
...defineOverlay,
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.htmlMinifierOptions = {
|
|
||||||
caseSensitive: true,
|
|
||||||
collapseWhitespace: true,
|
|
||||||
conservativeCollapse: true,
|
|
||||||
decodeEntities: true,
|
|
||||||
removeComments: true,
|
|
||||||
removeRedundantAttributes: true,
|
|
||||||
minifyCSS: {
|
|
||||||
compatibility: "*,-properties.zeroUnits",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
|
|
||||||
safari10: !latestBuild,
|
|
||||||
ecma: latestBuild ? undefined : 5,
|
|
||||||
format: { comments: false },
|
|
||||||
sourceMap: !isTestBuild,
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
|
|
||||||
babelrc: false,
|
|
||||||
compact: false,
|
|
||||||
presets: [
|
|
||||||
!latestBuild && [
|
|
||||||
"@babel/preset-env",
|
|
||||||
{
|
|
||||||
useBuiltIns: "entry",
|
|
||||||
corejs: { version: "3.30", proposals: true },
|
|
||||||
bugfixes: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@babel/preset-typescript",
|
|
||||||
].filter(Boolean),
|
|
||||||
plugins: [
|
|
||||||
[
|
|
||||||
path.resolve(
|
|
||||||
paths.polymer_dir,
|
|
||||||
"build-scripts/babel-plugins/inline-constants-plugin.cjs"
|
|
||||||
),
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
{ loose: true, useBuiltIns: true },
|
|
||||||
],
|
|
||||||
// Only support the syntax, Webpack will handle it.
|
|
||||||
"@babel/plugin-syntax-import-meta",
|
|
||||||
"@babel/plugin-syntax-dynamic-import",
|
|
||||||
"@babel/plugin-syntax-top-level-await",
|
|
||||||
// Support various proposals
|
|
||||||
"@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 }],
|
|
||||||
// Minify template literals for production
|
|
||||||
isProdBuild && [
|
|
||||||
"template-html-minifier",
|
|
||||||
{
|
|
||||||
modules: {
|
|
||||||
lit: [
|
|
||||||
"html",
|
|
||||||
{ name: "svg", encapsulation: "svg" },
|
|
||||||
{ name: "css", encapsulation: "style" },
|
|
||||||
],
|
|
||||||
"@polymer/polymer/lib/utils/html-tag": ["html"],
|
|
||||||
},
|
|
||||||
strictCSS: true,
|
|
||||||
htmlMinifier: module.exports.htmlMinifierOptions,
|
|
||||||
failOnError: true, // we can turn this off in case of false positives
|
|
||||||
},
|
|
||||||
],
|
|
||||||
].filter(Boolean),
|
|
||||||
exclude: [
|
|
||||||
// \\ for Windows, / for Mac OS and Linux
|
|
||||||
/node_modules[\\/]core-js/,
|
|
||||||
/node_modules[\\/]webpack[\\/]buildin/,
|
|
||||||
],
|
|
||||||
sourceMaps: !isTestBuild,
|
|
||||||
});
|
|
||||||
|
|
||||||
const nameSuffix = (latestBuild) => (latestBuild ? "-latest" : "-es5");
|
|
||||||
|
|
||||||
const outputPath = (outputRoot, latestBuild) =>
|
|
||||||
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
|
||||||
|
|
||||||
const publicPath = (latestBuild, root = "") =>
|
|
||||||
latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`;
|
|
||||||
|
|
||||||
/*
|
|
||||||
BundleConfig {
|
|
||||||
// Object with entrypoints that need to be bundled
|
|
||||||
entry: { [name: string]: pathToFile },
|
|
||||||
// Folder where bundled files need to be written
|
|
||||||
outputPath: string,
|
|
||||||
// absolute url-path where bundled files can be found
|
|
||||||
publicPath: string,
|
|
||||||
// extra definitions that we need to replace in source
|
|
||||||
defineOverlay: {[name: string]: value },
|
|
||||||
// if this is a production build
|
|
||||||
isProdBuild: boolean,
|
|
||||||
// If we're targeting latest browsers
|
|
||||||
latestBuild: boolean,
|
|
||||||
// If we're doing a stats build (create nice chunk names)
|
|
||||||
isStatsBuild: boolean,
|
|
||||||
// If it's just a test build in CI, skip time on source map generation
|
|
||||||
isTestBuild: boolean,
|
|
||||||
// Names of entrypoints that should not be hashed
|
|
||||||
dontHash: Set<string>
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports.config = {
|
|
||||||
app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
|
|
||||||
return {
|
|
||||||
name: "app" + nameSuffix(latestBuild),
|
|
||||||
entry: {
|
|
||||||
service_worker: "./src/entrypoints/service_worker.ts",
|
|
||||||
app: "./src/entrypoints/app.ts",
|
|
||||||
authorize: "./src/entrypoints/authorize.ts",
|
|
||||||
onboarding: "./src/entrypoints/onboarding.ts",
|
|
||||||
core: "./src/entrypoints/core.ts",
|
|
||||||
"custom-panel": "./src/entrypoints/custom-panel.ts",
|
|
||||||
},
|
|
||||||
outputPath: outputPath(paths.app_output_root, latestBuild),
|
|
||||||
publicPath: publicPath(latestBuild),
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
isStatsBuild,
|
|
||||||
isTestBuild,
|
|
||||||
isWDS,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
demo({ isProdBuild, latestBuild, isStatsBuild }) {
|
|
||||||
return {
|
|
||||||
name: "demo" + nameSuffix(latestBuild),
|
|
||||||
entry: {
|
|
||||||
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
|
|
||||||
},
|
|
||||||
outputPath: outputPath(paths.demo_output_root, latestBuild),
|
|
||||||
publicPath: publicPath(latestBuild),
|
|
||||||
defineOverlay: {
|
|
||||||
__VERSION__: JSON.stringify(`DEMO-${env.version()}`),
|
|
||||||
__DEMO__: true,
|
|
||||||
},
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
isStatsBuild,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
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) {
|
|
||||||
entry.receiver = path.resolve(
|
|
||||||
paths.cast_dir,
|
|
||||||
"src/receiver/entrypoint.ts"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "cast" + nameSuffix(latestBuild),
|
|
||||||
entry,
|
|
||||||
outputPath: outputPath(paths.cast_output_root, latestBuild),
|
|
||||||
publicPath: publicPath(latestBuild),
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
defineOverlay: {
|
|
||||||
__BACKWARDS_COMPAT__: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
hassio({ isProdBuild, latestBuild, isStatsBuild, isTestBuild }) {
|
|
||||||
return {
|
|
||||||
name: "supervisor" + nameSuffix(latestBuild),
|
|
||||||
entry: {
|
|
||||||
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
|
|
||||||
},
|
|
||||||
outputPath: outputPath(paths.hassio_output_root, latestBuild),
|
|
||||||
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
isStatsBuild,
|
|
||||||
isTestBuild,
|
|
||||||
isHassioBuild: true,
|
|
||||||
defineOverlay: {
|
|
||||||
__SUPERVISOR__: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
gallery({ isProdBuild, latestBuild }) {
|
|
||||||
return {
|
|
||||||
name: "gallery" + nameSuffix(latestBuild),
|
|
||||||
entry: {
|
|
||||||
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),
|
|
||||||
},
|
|
||||||
outputPath: outputPath(paths.gallery_output_root, latestBuild),
|
|
||||||
publicPath: publicPath(latestBuild),
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
defineOverlay: {
|
|
||||||
__DEMO__: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,35 +0,0 @@
|
|||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
const paths = require("./paths.cjs");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
useRollup() {
|
|
||||||
return process.env.ROLLUP === "1";
|
|
||||||
},
|
|
||||||
useWDS() {
|
|
||||||
return process.env.WDS === "1";
|
|
||||||
},
|
|
||||||
isProdBuild() {
|
|
||||||
return (
|
|
||||||
process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isStatsBuild() {
|
|
||||||
return process.env.STATS === "1";
|
|
||||||
},
|
|
||||||
isTestBuild() {
|
|
||||||
return process.env.IS_TEST === "true";
|
|
||||||
},
|
|
||||||
isNetlify() {
|
|
||||||
return process.env.NETLIFY === "true";
|
|
||||||
},
|
|
||||||
version() {
|
|
||||||
const version = fs
|
|
||||||
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
|
|
||||||
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
|
|
||||||
if (!version) {
|
|
||||||
throw Error("Version not found");
|
|
||||||
}
|
|
||||||
return version[1];
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,59 +0,0 @@
|
|||||||
// Run HA develop mode
|
|
||||||
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const env = require("../env.cjs");
|
|
||||||
require("./clean.cjs");
|
|
||||||
require("./translations.cjs");
|
|
||||||
require("./locale-data.cjs");
|
|
||||||
require("./gen-icons-json.cjs");
|
|
||||||
require("./gather-static.cjs");
|
|
||||||
require("./compress.cjs");
|
|
||||||
require("./webpack.cjs");
|
|
||||||
require("./service-worker.cjs");
|
|
||||||
require("./entry-html.cjs");
|
|
||||||
require("./rollup.cjs");
|
|
||||||
require("./wds.cjs");
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"develop-app",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "development";
|
|
||||||
},
|
|
||||||
"clean",
|
|
||||||
gulp.parallel(
|
|
||||||
"gen-service-worker-app-dev",
|
|
||||||
"gen-icons-json",
|
|
||||||
"gen-pages-dev",
|
|
||||||
"gen-index-app-dev",
|
|
||||||
"build-translations",
|
|
||||||
"build-locale-data"
|
|
||||||
),
|
|
||||||
"copy-static-app",
|
|
||||||
env.useWDS()
|
|
||||||
? "wds-watch-app"
|
|
||||||
: env.useRollup()
|
|
||||||
? "rollup-watch-app"
|
|
||||||
: "webpack-watch-app"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"build-app",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "production";
|
|
||||||
},
|
|
||||||
"clean",
|
|
||||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
|
||||||
"copy-static-app",
|
|
||||||
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
|
||||||
// Don't compress running tests
|
|
||||||
...(env.isTestBuild() ? [] : ["compress-app"]),
|
|
||||||
gulp.parallel(
|
|
||||||
"gen-pages-prod",
|
|
||||||
"gen-index-app-prod",
|
|
||||||
"gen-service-worker-app-prod"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,40 +0,0 @@
|
|||||||
const gulp = require("gulp");
|
|
||||||
const env = require("../env.cjs");
|
|
||||||
|
|
||||||
require("./clean.cjs");
|
|
||||||
require("./translations.cjs");
|
|
||||||
require("./gather-static.cjs");
|
|
||||||
require("./webpack.cjs");
|
|
||||||
require("./service-worker.cjs");
|
|
||||||
require("./entry-html.cjs");
|
|
||||||
require("./rollup.cjs");
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"develop-cast",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "development";
|
|
||||||
},
|
|
||||||
"clean-cast",
|
|
||||||
"translations-enable-merge-backend",
|
|
||||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
|
||||||
"copy-static-cast",
|
|
||||||
"gen-index-cast-dev",
|
|
||||||
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"build-cast",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "production";
|
|
||||||
},
|
|
||||||
"clean-cast",
|
|
||||||
"translations-enable-merge-backend",
|
|
||||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
|
||||||
"copy-static-cast",
|
|
||||||
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
|
|
||||||
"gen-index-cast-prod"
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,40 +0,0 @@
|
|||||||
const del = import("del");
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
require("./translations.cjs");
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"clean",
|
|
||||||
gulp.parallel("clean-translations", async () =>
|
|
||||||
(await del).deleteSync([paths.app_output_root, paths.build_dir])
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"clean-demo",
|
|
||||||
gulp.parallel("clean-translations", async () =>
|
|
||||||
(await del).deleteSync([paths.demo_output_root, paths.build_dir])
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"clean-cast",
|
|
||||||
gulp.parallel("clean-translations", async () =>
|
|
||||||
(await del).deleteSync([paths.cast_output_root, paths.build_dir])
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task("clean-hassio", async () =>
|
|
||||||
(await del).deleteSync([paths.hassio_output_root, paths.build_dir])
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"clean-gallery",
|
|
||||||
gulp.parallel("clean-translations", async () =>
|
|
||||||
(await del).deleteSync([
|
|
||||||
paths.gallery_output_root,
|
|
||||||
paths.gallery_build,
|
|
||||||
paths.build_dir,
|
|
||||||
])
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,45 +0,0 @@
|
|||||||
// Tasks to compress
|
|
||||||
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const zopfli = require("gulp-zopfli-green");
|
|
||||||
const merge = require("merge-stream");
|
|
||||||
const path = require("path");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
|
|
||||||
const zopfliOptions = { threshold: 150 };
|
|
||||||
|
|
||||||
gulp.task("compress-app", function compressApp() {
|
|
||||||
const jsLatest = gulp
|
|
||||||
.src(path.resolve(paths.app_output_latest, "**/*.js"))
|
|
||||||
.pipe(zopfli(zopfliOptions))
|
|
||||||
.pipe(gulp.dest(paths.app_output_latest));
|
|
||||||
|
|
||||||
const jsEs5 = gulp
|
|
||||||
.src(path.resolve(paths.app_output_es5, "**/*.js"))
|
|
||||||
.pipe(zopfli(zopfliOptions))
|
|
||||||
.pipe(gulp.dest(paths.app_output_es5));
|
|
||||||
|
|
||||||
const polyfills = gulp
|
|
||||||
.src(path.resolve(paths.app_output_static, "polyfills/*.js"))
|
|
||||||
.pipe(zopfli(zopfliOptions))
|
|
||||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "polyfills")));
|
|
||||||
|
|
||||||
const translations = gulp
|
|
||||||
.src(path.resolve(paths.app_output_static, "translations/**/*.json"))
|
|
||||||
.pipe(zopfli(zopfliOptions))
|
|
||||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "translations")));
|
|
||||||
|
|
||||||
const icons = gulp
|
|
||||||
.src(path.resolve(paths.app_output_static, "mdi/*.json"))
|
|
||||||
.pipe(zopfli(zopfliOptions))
|
|
||||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "mdi")));
|
|
||||||
|
|
||||||
return merge(jsLatest, jsEs5, polyfills, translations, icons);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("compress-hassio", function compressApp() {
|
|
||||||
return gulp
|
|
||||||
.src(path.resolve(paths.hassio_output_root, "**/*.js"))
|
|
||||||
.pipe(zopfli(zopfliOptions))
|
|
||||||
.pipe(gulp.dest(paths.hassio_output_root));
|
|
||||||
});
|
|
@@ -1,47 +0,0 @@
|
|||||||
// Run demo develop mode
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const env = require("../env.cjs");
|
|
||||||
|
|
||||||
require("./clean.cjs");
|
|
||||||
require("./translations.cjs");
|
|
||||||
require("./gen-icons-json.cjs");
|
|
||||||
require("./gather-static.cjs");
|
|
||||||
require("./webpack.cjs");
|
|
||||||
require("./service-worker.cjs");
|
|
||||||
require("./entry-html.cjs");
|
|
||||||
require("./rollup.cjs");
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"develop-demo",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "development";
|
|
||||||
},
|
|
||||||
"clean-demo",
|
|
||||||
"translations-enable-merge-backend",
|
|
||||||
gulp.parallel(
|
|
||||||
"gen-icons-json",
|
|
||||||
"gen-index-demo-dev",
|
|
||||||
"build-translations",
|
|
||||||
"build-locale-data"
|
|
||||||
),
|
|
||||||
"copy-static-demo",
|
|
||||||
env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"build-demo",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "production";
|
|
||||||
},
|
|
||||||
"clean-demo",
|
|
||||||
// Cast needs to be backwards compatible and older HA has no translations
|
|
||||||
"translations-enable-merge-backend",
|
|
||||||
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
|
|
||||||
"copy-static-demo",
|
|
||||||
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
|
|
||||||
"gen-index-demo-prod"
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,69 +0,0 @@
|
|||||||
const gulp = require("gulp");
|
|
||||||
const fs = require("fs/promises");
|
|
||||||
const mapStream = require("map-stream");
|
|
||||||
|
|
||||||
const inDirFrontend = "translations/frontend";
|
|
||||||
const inDirBackend = "translations/backend";
|
|
||||||
const srcMeta = "src/translations/translationMetadata.json";
|
|
||||||
const encoding = "utf8";
|
|
||||||
|
|
||||||
function hasHtml(data) {
|
|
||||||
return /<[a-z][\s\S]*>/i.test(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function recursiveCheckHasHtml(file, data, errors, recKey) {
|
|
||||||
Object.keys(data).forEach(function (key) {
|
|
||||||
if (typeof data[key] === "object") {
|
|
||||||
const nextRecKey = recKey ? `${recKey}.${key}` : key;
|
|
||||||
recursiveCheckHasHtml(file, data[key], errors, nextRecKey);
|
|
||||||
} else if (hasHtml(data[key])) {
|
|
||||||
errors.push(`HTML found in ${file.path} at key ${recKey}.${key}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkHtml() {
|
|
||||||
const errors = [];
|
|
||||||
|
|
||||||
return mapStream(function (file, cb) {
|
|
||||||
const content = file.contents;
|
|
||||||
let error;
|
|
||||||
if (content) {
|
|
||||||
if (hasHtml(String(content))) {
|
|
||||||
const data = JSON.parse(String(content));
|
|
||||||
recursiveCheckHasHtml(file, data, errors);
|
|
||||||
if (errors.length > 0) {
|
|
||||||
error = errors.join("\r\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cb(error, file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backend translations do not currently pass HTML check so are excluded here for now
|
|
||||||
gulp.task("check-translations-html", function () {
|
|
||||||
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("check-all-files-exist", async function () {
|
|
||||||
const file = await fs.readFile(srcMeta, { encoding });
|
|
||||||
const meta = JSON.parse(file);
|
|
||||||
const writings = [];
|
|
||||||
Object.keys(meta).forEach((lang) => {
|
|
||||||
writings.push(
|
|
||||||
fs.writeFile(`${inDirFrontend}/${lang}.json`, JSON.stringify({}), {
|
|
||||||
flag: "wx",
|
|
||||||
}),
|
|
||||||
fs.writeFile(`${inDirBackend}/${lang}.json`, JSON.stringify({}), {
|
|
||||||
flag: "wx",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await Promise.allSettled(writings);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"check-downloaded-translations",
|
|
||||||
gulp.series("check-translations-html", "check-all-files-exist")
|
|
||||||
);
|
|
@@ -1,351 +0,0 @@
|
|||||||
// Tasks to generate entry HTML
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const fs = require("fs-extra");
|
|
||||||
const path = require("path");
|
|
||||||
const template = require("lodash.template");
|
|
||||||
const { minify } = require("html-minifier-terser");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
const env = require("../env.cjs");
|
|
||||||
const { htmlMinifierOptions, terserOptions } = require("../bundle.cjs");
|
|
||||||
|
|
||||||
const templatePath = (tpl) =>
|
|
||||||
path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`);
|
|
||||||
|
|
||||||
const readFile = (pth) => fs.readFileSync(pth).toString();
|
|
||||||
|
|
||||||
const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
|
|
||||||
const compiled = template(readFile(pathFunc(pth)));
|
|
||||||
return compiled({
|
|
||||||
...data,
|
|
||||||
useRollup: env.useRollup(),
|
|
||||||
useWDS: env.useWDS(),
|
|
||||||
renderTemplate,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderDemoTemplate = (pth, data = {}) =>
|
|
||||||
renderTemplate(pth, data, (tpl) =>
|
|
||||||
path.resolve(paths.demo_dir, "src/html/", `${tpl}.html.template`)
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderCastTemplate = (pth, data = {}) =>
|
|
||||||
renderTemplate(pth, data, (tpl) =>
|
|
||||||
path.resolve(paths.cast_dir, "src/html/", `${tpl}.html.template`)
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderGalleryTemplate = (pth, data = {}) =>
|
|
||||||
renderTemplate(pth, data, (tpl) =>
|
|
||||||
path.resolve(paths.gallery_dir, "src/html/", `${tpl}.html.template`)
|
|
||||||
);
|
|
||||||
|
|
||||||
const minifyHtml = (content) =>
|
|
||||||
minify(content, {
|
|
||||||
...htmlMinifierOptions,
|
|
||||||
conservativeCollapse: false,
|
|
||||||
minifyJS: terserOptions({
|
|
||||||
latestBuild: false, // Shared scripts should be ES5
|
|
||||||
isTestBuild: true, // Don't need source maps
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const PAGES = ["onboarding", "authorize"];
|
|
||||||
|
|
||||||
gulp.task("gen-pages-dev", (done) => {
|
|
||||||
for (const page of PAGES) {
|
|
||||||
const content = renderTemplate(page, {
|
|
||||||
latestPageJS: `/frontend_latest/${page}.js`,
|
|
||||||
|
|
||||||
es5PageJS: `/frontend_es5/${page}.js`,
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.app_output_root, `${page}.html`),
|
|
||||||
content
|
|
||||||
);
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-pages-prod", async () => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.app_output_latest,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const es5Manifest = require(path.resolve(
|
|
||||||
paths.app_output_es5,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
|
|
||||||
const minifiedHTML = [];
|
|
||||||
for (const page of PAGES) {
|
|
||||||
const content = renderTemplate(page, {
|
|
||||||
latestPageJS: latestManifest[`${page}.js`],
|
|
||||||
es5PageJS: es5Manifest[`${page}.js`],
|
|
||||||
});
|
|
||||||
|
|
||||||
minifiedHTML.push(
|
|
||||||
minifyHtml(content).then((minified) =>
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.app_output_root, `${page}.html`),
|
|
||||||
minified
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await Promise.all(minifiedHTML);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-app-dev", (done) => {
|
|
||||||
let latestAppJS;
|
|
||||||
let latestCoreJS;
|
|
||||||
let latestCustomPanelJS;
|
|
||||||
|
|
||||||
if (env.useWDS()) {
|
|
||||||
latestAppJS = "http://localhost:8000/src/entrypoints/app.ts";
|
|
||||||
latestCoreJS = "http://localhost:8000/src/entrypoints/core.ts";
|
|
||||||
latestCustomPanelJS =
|
|
||||||
"http://localhost:8000/src/entrypoints/custom-panel.ts";
|
|
||||||
} else {
|
|
||||||
latestAppJS = "/frontend_latest/app.js";
|
|
||||||
latestCoreJS = "/frontend_latest/core.js";
|
|
||||||
latestCustomPanelJS = "/frontend_latest/custom-panel.js";
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = renderTemplate("index", {
|
|
||||||
latestAppJS,
|
|
||||||
latestCoreJS,
|
|
||||||
latestCustomPanelJS,
|
|
||||||
|
|
||||||
es5AppJS: "/frontend_es5/app.js",
|
|
||||||
es5CoreJS: "/frontend_es5/core.js",
|
|
||||||
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
|
|
||||||
}).replace(/#THEMEC/g, "{{ theme_color }}");
|
|
||||||
|
|
||||||
fs.outputFileSync(path.resolve(paths.app_output_root, "index.html"), content);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-app-prod", async () => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.app_output_latest,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const es5Manifest = require(path.resolve(
|
|
||||||
paths.app_output_es5,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const content = renderTemplate("index", {
|
|
||||||
latestAppJS: latestManifest["app.js"],
|
|
||||||
latestCoreJS: latestManifest["core.js"],
|
|
||||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
|
||||||
|
|
||||||
es5AppJS: es5Manifest["app.js"],
|
|
||||||
es5CoreJS: es5Manifest["core.js"],
|
|
||||||
es5CustomPanelJS: es5Manifest["custom-panel.js"],
|
|
||||||
});
|
|
||||||
const minified = (await minifyHtml(content)).replace(
|
|
||||||
/#THEMEC/g,
|
|
||||||
"{{ theme_color }}"
|
|
||||||
);
|
|
||||||
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.app_output_root, "index.html"),
|
|
||||||
minified
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-cast-dev", (done) => {
|
|
||||||
const contentReceiver = renderCastTemplate("receiver", {
|
|
||||||
latestReceiverJS: "/frontend_latest/receiver.js",
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "receiver.html"),
|
|
||||||
contentReceiver
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentMedia = renderCastTemplate("media", {
|
|
||||||
latestMediaJS: "/frontend_latest/media.js",
|
|
||||||
es5MediaJS: "/frontend_es5/media.js",
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "media.html"),
|
|
||||||
contentMedia
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentFAQ = renderCastTemplate("launcher-faq", {
|
|
||||||
latestLauncherJS: "/frontend_latest/launcher.js",
|
|
||||||
es5LauncherJS: "/frontend_es5/launcher.js",
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "faq.html"),
|
|
||||||
contentFAQ
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentLauncher = renderCastTemplate("launcher", {
|
|
||||||
latestLauncherJS: "/frontend_latest/launcher.js",
|
|
||||||
es5LauncherJS: "/frontend_es5/launcher.js",
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "index.html"),
|
|
||||||
contentLauncher
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-cast-prod", (done) => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.cast_output_latest,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const es5Manifest = require(path.resolve(
|
|
||||||
paths.cast_output_es5,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
|
|
||||||
const contentReceiver = renderCastTemplate("receiver", {
|
|
||||||
latestReceiverJS: latestManifest["receiver.js"],
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "receiver.html"),
|
|
||||||
contentReceiver
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentMedia = renderCastTemplate("media", {
|
|
||||||
latestMediaJS: latestManifest["media.js"],
|
|
||||||
es5MediaJS: es5Manifest["media.js"],
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "media.html"),
|
|
||||||
contentMedia
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentFAQ = renderCastTemplate("launcher-faq", {
|
|
||||||
latestLauncherJS: latestManifest["launcher.js"],
|
|
||||||
es5LauncherJS: es5Manifest["launcher.js"],
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "faq.html"),
|
|
||||||
contentFAQ
|
|
||||||
);
|
|
||||||
|
|
||||||
const contentLauncher = renderCastTemplate("launcher", {
|
|
||||||
latestLauncherJS: latestManifest["launcher.js"],
|
|
||||||
es5LauncherJS: es5Manifest["launcher.js"],
|
|
||||||
});
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.cast_output_root, "index.html"),
|
|
||||||
contentLauncher
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-demo-dev", (done) => {
|
|
||||||
const content = renderDemoTemplate("index", {
|
|
||||||
latestDemoJS: "/frontend_latest/main.js",
|
|
||||||
|
|
||||||
es5DemoJS: "/frontend_es5/main.js",
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.demo_output_root, "index.html"),
|
|
||||||
content
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-demo-prod", async () => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.demo_output_latest,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const es5Manifest = require(path.resolve(
|
|
||||||
paths.demo_output_es5,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const content = renderDemoTemplate("index", {
|
|
||||||
latestDemoJS: latestManifest["main.js"],
|
|
||||||
|
|
||||||
es5DemoJS: es5Manifest["main.js"],
|
|
||||||
});
|
|
||||||
const minified = await minifyHtml(content);
|
|
||||||
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.demo_output_root, "index.html"),
|
|
||||||
minified
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-gallery-dev", (done) => {
|
|
||||||
const content = renderGalleryTemplate("index", {
|
|
||||||
latestGalleryJS: "./frontend_latest/entrypoint.js",
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.gallery_output_root, "index.html"),
|
|
||||||
content
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-gallery-prod", async () => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.gallery_output_latest,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const content = renderGalleryTemplate("index", {
|
|
||||||
latestGalleryJS: latestManifest["entrypoint.js"],
|
|
||||||
});
|
|
||||||
const minified = await minifyHtml(content);
|
|
||||||
|
|
||||||
fs.outputFileSync(
|
|
||||||
path.resolve(paths.gallery_output_root, "index.html"),
|
|
||||||
minified
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-hassio-dev", async () => {
|
|
||||||
writeHassioEntrypoint(
|
|
||||||
`${paths.hassio_publicPath}/frontend_latest/entrypoint.js`,
|
|
||||||
`${paths.hassio_publicPath}/frontend_es5/entrypoint.js`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-index-hassio-prod", async () => {
|
|
||||||
const latestManifest = require(path.resolve(
|
|
||||||
paths.hassio_output_latest,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
const es5Manifest = require(path.resolve(
|
|
||||||
paths.hassio_output_es5,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
writeHassioEntrypoint(
|
|
||||||
latestManifest["entrypoint.js"],
|
|
||||||
es5Manifest["entrypoint.js"]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
|
|
||||||
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
|
|
||||||
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve(paths.hassio_output_root, "entrypoint.js"),
|
|
||||||
`
|
|
||||||
function loadES5() {
|
|
||||||
var el = document.createElement('script');
|
|
||||||
el.src = '${es5Entrypoint}';
|
|
||||||
document.body.appendChild(el);
|
|
||||||
}
|
|
||||||
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
|
|
||||||
loadES5();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
new Function("import('${latestEntrypoint}')")();
|
|
||||||
} catch (err) {
|
|
||||||
loadES5();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
{ encoding: "utf-8" }
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,175 +0,0 @@
|
|||||||
// Task to download the latest Lokalise translations from the nightly workflow artifacts
|
|
||||||
|
|
||||||
const del = import("del");
|
|
||||||
const fs = require("fs/promises");
|
|
||||||
const path = require("path");
|
|
||||||
const process = require("process");
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const jszip = require("jszip");
|
|
||||||
const tar = require("tar");
|
|
||||||
const { Octokit } = require("@octokit/rest");
|
|
||||||
const { retry } = require("@octokit/plugin-retry");
|
|
||||||
const { createOAuthDeviceAuth } = require("@octokit/auth-oauth-device");
|
|
||||||
|
|
||||||
const MAX_AGE = 24; // hours
|
|
||||||
const OWNER = "home-assistant";
|
|
||||||
const REPO = "frontend";
|
|
||||||
const WORKFLOW_NAME = "nightly.yaml";
|
|
||||||
const ARTIFACT_NAME = "translations";
|
|
||||||
const CLIENT_ID = "Iv1.3914e28cb27834d1";
|
|
||||||
const EXTRACT_DIR = "translations";
|
|
||||||
const TOKEN_FILE = path.posix.join(EXTRACT_DIR, "token.json");
|
|
||||||
const ARTIFACT_FILE = path.posix.join(EXTRACT_DIR, "artifact.json");
|
|
||||||
|
|
||||||
let allowTokenSetup = false;
|
|
||||||
gulp.task("allow-setup-fetch-nightly-translations", (done) => {
|
|
||||||
allowTokenSetup = true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("fetch-nightly-translations", async function () {
|
|
||||||
// Skip all when environment flag is set (assumes translations are already in place)
|
|
||||||
if (process.env?.SKIP_FETCH_NIGHTLY_TRANSLATIONS) {
|
|
||||||
console.log("Skipping fetch due to environment signal");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read current translations artifact info if it exists,
|
|
||||||
// and stop if they are not old enough
|
|
||||||
let currentArtifact;
|
|
||||||
try {
|
|
||||||
currentArtifact = JSON.parse(await fs.readFile(ARTIFACT_FILE, "utf-8"));
|
|
||||||
const currentAge =
|
|
||||||
(Date.now() - Date.parse(currentArtifact.created_at)) / 3600000;
|
|
||||||
if (currentAge < MAX_AGE) {
|
|
||||||
console.log(
|
|
||||||
"Keeping current translations (only %s hours old)",
|
|
||||||
currentAge.toFixed(1)
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
currentArtifact = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// To store file writing promises
|
|
||||||
const createExtractDir = fs.mkdir(EXTRACT_DIR, { recursive: true });
|
|
||||||
const writings = [];
|
|
||||||
|
|
||||||
// Authenticate to GitHub using GitHub action token if it exists,
|
|
||||||
// otherwise look for a saved user token or generate a new one if none
|
|
||||||
let tokenAuth;
|
|
||||||
if (process.env.GITHUB_TOKEN) {
|
|
||||||
tokenAuth = { token: process.env.GITHUB_TOKEN };
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
tokenAuth = JSON.parse(await fs.readFile(TOKEN_FILE, "utf-8"));
|
|
||||||
} catch {
|
|
||||||
if (!allowTokenSetup) {
|
|
||||||
console.log("No token found so build wil continue with English only");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auth = createOAuthDeviceAuth({
|
|
||||||
clientType: "github-app",
|
|
||||||
clientId: CLIENT_ID,
|
|
||||||
onVerification: (verification) => {
|
|
||||||
console.log(
|
|
||||||
"Task needs to authenticate to GitHub to fetch the translations from nightly workflow\n" +
|
|
||||||
"Please go to %s to authorize this task\n" +
|
|
||||||
"\nEnter user code: %s\n\n" +
|
|
||||||
"This code will expire in %s minutes\n" +
|
|
||||||
"Task will automatically continue after authorization and token will be saved for future use",
|
|
||||||
verification.verification_uri,
|
|
||||||
verification.user_code,
|
|
||||||
(verification.expires_in / 60).toFixed(0)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
tokenAuth = await auth({ type: "oauth" });
|
|
||||||
writings.push(
|
|
||||||
createExtractDir.then(
|
|
||||||
fs.writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticate with token and request workflow runs from GitHub
|
|
||||||
console.log("Fetching new translations...");
|
|
||||||
const octokit = new (Octokit.plugin(retry))({
|
|
||||||
userAgent: "Fetch Nightly Translations",
|
|
||||||
auth: tokenAuth.token,
|
|
||||||
});
|
|
||||||
|
|
||||||
const workflowRunsResponse = await octokit.rest.actions.listWorkflowRuns({
|
|
||||||
owner: OWNER,
|
|
||||||
repo: REPO,
|
|
||||||
workflow_id: WORKFLOW_NAME,
|
|
||||||
status: "success",
|
|
||||||
event: "schedule",
|
|
||||||
per_page: 1,
|
|
||||||
exclude_pull_requests: true,
|
|
||||||
});
|
|
||||||
if (workflowRunsResponse.data.total_count === 0) {
|
|
||||||
throw Error("No successful nightly workflow runs found");
|
|
||||||
}
|
|
||||||
const latestNightlyRun = workflowRunsResponse.data.workflow_runs[0];
|
|
||||||
|
|
||||||
// Stop if current is already the latest, otherwise Find the translations artifact
|
|
||||||
if (currentArtifact?.workflow_run.id === latestNightlyRun.id) {
|
|
||||||
console.log("Stopping because current translations are still the latest");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const latestArtifact = (
|
|
||||||
await octokit.actions.listWorkflowRunArtifacts({
|
|
||||||
owner: OWNER,
|
|
||||||
repo: REPO,
|
|
||||||
run_id: latestNightlyRun.id,
|
|
||||||
})
|
|
||||||
).data.artifacts.find((artifact) => artifact.name === ARTIFACT_NAME);
|
|
||||||
if (!latestArtifact) {
|
|
||||||
throw Error("Latest nightly workflow run has no translations artifact");
|
|
||||||
}
|
|
||||||
writings.push(
|
|
||||||
createExtractDir.then(
|
|
||||||
fs.writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove the current translations
|
|
||||||
const deleteCurrent = Promise.all(writings).then(
|
|
||||||
(await del).deleteAsync([
|
|
||||||
`${EXTRACT_DIR}/*`,
|
|
||||||
`!${ARTIFACT_FILE}`,
|
|
||||||
`!${TOKEN_FILE}`,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get the download URL and follow the redirect to download (stored as ArrayBuffer)
|
|
||||||
const downloadResponse = await octokit.actions.downloadArtifact({
|
|
||||||
owner: OWNER,
|
|
||||||
repo: REPO,
|
|
||||||
artifact_id: latestArtifact.id,
|
|
||||||
archive_format: "zip",
|
|
||||||
});
|
|
||||||
if (downloadResponse.status !== 200) {
|
|
||||||
throw Error("Failure downloading translations artifact");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Artifact is a tarball, but GitHub adds it to a zip file
|
|
||||||
console.log("Unpacking downloaded translations...");
|
|
||||||
const zip = await jszip.loadAsync(downloadResponse.data);
|
|
||||||
await deleteCurrent;
|
|
||||||
const extractStream = zip.file(/.*/)[0].nodeStream().pipe(tar.extract());
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
extractStream.on("close", resolve).on("error", reject);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"setup-and-fetch-nightly-translations",
|
|
||||||
gulp.series(
|
|
||||||
"allow-setup-fetch-nightly-translations",
|
|
||||||
"fetch-nightly-translations"
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,198 +0,0 @@
|
|||||||
// 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.cjs");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
|
|
||||||
require("./clean.cjs");
|
|
||||||
require("./translations.cjs");
|
|
||||||
require("./gen-icons-json.cjs");
|
|
||||||
require("./gather-static.cjs");
|
|
||||||
require("./webpack.cjs");
|
|
||||||
require("./service-worker.cjs");
|
|
||||||
require("./entry-html.cjs");
|
|
||||||
require("./rollup.cjs");
|
|
||||||
|
|
||||||
gulp.task("gather-gallery-pages", async function gatherPages() {
|
|
||||||
const pageDir = path.resolve(paths.gallery_dir, "src/pages");
|
|
||||||
const files = await glob(path.resolve(pageDir, "**/*"));
|
|
||||||
|
|
||||||
const galleryBuild = path.resolve(paths.gallery_dir, "build");
|
|
||||||
fs.mkdirSync(galleryBuild, { recursive: true });
|
|
||||||
|
|
||||||
let content = "export const PAGES = {\n";
|
|
||||||
|
|
||||||
const processed = new Set();
|
|
||||||
|
|
||||||
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] = 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`;
|
|
||||||
}
|
|
||||||
|
|
||||||
content += "};\n";
|
|
||||||
|
|
||||||
// Generate sidebar
|
|
||||||
const sidebarPath = path.resolve(paths.gallery_dir, "sidebar.js");
|
|
||||||
const sidebar = (await import(sidebarPath)).default;
|
|
||||||
|
|
||||||
const pagesToProcess = {};
|
|
||||||
for (const key of processed) {
|
|
||||||
const [category, page] = key.split("/", 2);
|
|
||||||
if (!(category in pagesToProcess)) {
|
|
||||||
pagesToProcess[category] = new Set();
|
|
||||||
}
|
|
||||||
pagesToProcess[category].add(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const group of Object.values(sidebar)) {
|
|
||||||
const toProcess = pagesToProcess[group.category];
|
|
||||||
delete pagesToProcess[group.category];
|
|
||||||
|
|
||||||
if (!toProcess) {
|
|
||||||
console.error("Unknown category", group.category);
|
|
||||||
if (!group.pages) {
|
|
||||||
group.pages = [];
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any pre-defined groups will not be sorted.
|
|
||||||
if (group.pages) {
|
|
||||||
for (const page of group.pages) {
|
|
||||||
if (!toProcess.delete(page)) {
|
|
||||||
console.error("Found unreferenced demo", page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
group.pages = [];
|
|
||||||
}
|
|
||||||
for (const page of Array.from(toProcess).sort()) {
|
|
||||||
group.pages.push(page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [category, pages] of Object.entries(pagesToProcess)) {
|
|
||||||
sidebar.push({
|
|
||||||
category,
|
|
||||||
header: category,
|
|
||||||
pages: Array.from(pages).sort(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
content += `export const SIDEBAR = ${JSON.stringify(sidebar, null, 2)};\n`;
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve(galleryBuild, "import-pages.ts"),
|
|
||||||
content,
|
|
||||||
"utf-8"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"develop-gallery",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "development";
|
|
||||||
},
|
|
||||||
"clean-gallery",
|
|
||||||
"translations-enable-merge-backend",
|
|
||||||
gulp.parallel(
|
|
||||||
"gen-icons-json",
|
|
||||||
"build-translations",
|
|
||||||
"build-locale-data",
|
|
||||||
"gather-gallery-pages"
|
|
||||||
),
|
|
||||||
"copy-static-gallery",
|
|
||||||
"gen-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")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"build-gallery",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "production";
|
|
||||||
},
|
|
||||||
"clean-gallery",
|
|
||||||
"translations-enable-merge-backend",
|
|
||||||
gulp.parallel(
|
|
||||||
"gen-icons-json",
|
|
||||||
"build-translations",
|
|
||||||
"build-locale-data",
|
|
||||||
"gather-gallery-pages"
|
|
||||||
),
|
|
||||||
"copy-static-gallery",
|
|
||||||
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
|
|
||||||
"gen-index-gallery-prod"
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,185 +0,0 @@
|
|||||||
// Gulp task to gather all static files.
|
|
||||||
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs-extra");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
|
|
||||||
const npmPath = (...parts) =>
|
|
||||||
path.resolve(paths.polymer_dir, "node_modules", ...parts);
|
|
||||||
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);
|
|
||||||
|
|
||||||
function copyTranslations(staticDir) {
|
|
||||||
const staticPath = genStaticPath(staticDir);
|
|
||||||
|
|
||||||
// Translation output
|
|
||||||
fs.copySync(
|
|
||||||
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);
|
|
||||||
|
|
||||||
// MDI icons output
|
|
||||||
fs.copySync(polyPath("build/mdi"), staticPath("mdi"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyPolyfills(staticDir) {
|
|
||||||
const staticPath = genStaticPath(staticDir);
|
|
||||||
|
|
||||||
// For custom panels using ES5 builds that don't use Babel 7+
|
|
||||||
copyFileDir(
|
|
||||||
npmPath("@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"),
|
|
||||||
staticPath("polyfills/")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Web Component polyfills and adapters
|
|
||||||
copyFileDir(
|
|
||||||
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js"),
|
|
||||||
staticPath("polyfills/")
|
|
||||||
);
|
|
||||||
copyFileDir(
|
|
||||||
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js.map"),
|
|
||||||
staticPath("polyfills/")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyLoaderJS(staticDir) {
|
|
||||||
const staticPath = genStaticPath(staticDir);
|
|
||||||
copyFileDir(npmPath("systemjs/dist/s.min.js"), staticPath("js"));
|
|
||||||
copyFileDir(npmPath("systemjs/dist/s.min.js.map"), staticPath("js"));
|
|
||||||
}
|
|
||||||
|
|
||||||
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"),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
npmPath("leaflet/dist/leaflet.css"),
|
|
||||||
staticPath("images/leaflet/")
|
|
||||||
);
|
|
||||||
fs.copySync(
|
|
||||||
npmPath("leaflet/dist/images"),
|
|
||||||
staticPath("images/leaflet/images/")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
fs.copySync(polyPath("public"), paths.app_output_root);
|
|
||||||
|
|
||||||
copyLoaderJS(staticDir);
|
|
||||||
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 () => {
|
|
||||||
// Copy app static files
|
|
||||||
fs.copySync(
|
|
||||||
polyPath("public/static"),
|
|
||||||
path.resolve(paths.demo_output_root, "static")
|
|
||||||
);
|
|
||||||
// Copy demo static files
|
|
||||||
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_output_root);
|
|
||||||
|
|
||||||
copyLoaderJS(paths.demo_output_static);
|
|
||||||
copyPolyfills(paths.demo_output_static);
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("copy-static-cast", async () => {
|
|
||||||
// Copy app static files
|
|
||||||
fs.copySync(polyPath("public/static"), paths.cast_output_static);
|
|
||||||
// Copy cast static files
|
|
||||||
fs.copySync(path.resolve(paths.cast_dir, "public"), paths.cast_output_root);
|
|
||||||
|
|
||||||
copyLoaderJS(paths.cast_output_static);
|
|
||||||
copyPolyfills(paths.cast_output_static);
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("copy-static-gallery", async () => {
|
|
||||||
// Copy app static files
|
|
||||||
fs.copySync(polyPath("public/static"), paths.gallery_output_static);
|
|
||||||
// Copy gallery static files
|
|
||||||
fs.copySync(
|
|
||||||
path.resolve(paths.gallery_dir, "public"),
|
|
||||||
paths.gallery_output_root
|
|
||||||
);
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
@@ -1,167 +0,0 @@
|
|||||||
const gulp = require("gulp");
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
const hash = require("object-hash");
|
|
||||||
|
|
||||||
const ICON_PACKAGE_PATH = path.resolve(
|
|
||||||
__dirname,
|
|
||||||
"../../node_modules/@mdi/svg/"
|
|
||||||
);
|
|
||||||
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
|
|
||||||
const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json");
|
|
||||||
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
|
|
||||||
const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi");
|
|
||||||
const REMOVED_ICONS_PATH = path.resolve(__dirname, "../removedIcons.json");
|
|
||||||
|
|
||||||
const encoding = "utf8";
|
|
||||||
|
|
||||||
const getMeta = () => {
|
|
||||||
const file = fs.readFileSync(META_PATH, { encoding });
|
|
||||||
const meta = JSON.parse(file);
|
|
||||||
return meta.map((icon) => {
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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];
|
|
||||||
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;
|
|
||||||
|
|
||||||
let curSize = 0;
|
|
||||||
let startKey;
|
|
||||||
let icons = [];
|
|
||||||
|
|
||||||
Object.values(meta).forEach((icon) => {
|
|
||||||
if (startKey === undefined) {
|
|
||||||
startKey = icon.name;
|
|
||||||
}
|
|
||||||
curSize += icon.path.length;
|
|
||||||
icons.push(icon);
|
|
||||||
if (curSize > CHUNK_SIZE) {
|
|
||||||
chunks.push({
|
|
||||||
startKey,
|
|
||||||
endKey: icon.name,
|
|
||||||
icons,
|
|
||||||
});
|
|
||||||
curSize = 0;
|
|
||||||
startKey = undefined;
|
|
||||||
icons = [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
chunks.push({
|
|
||||||
startKey,
|
|
||||||
icons,
|
|
||||||
});
|
|
||||||
|
|
||||||
return chunks;
|
|
||||||
};
|
|
||||||
|
|
||||||
const findDifferentiator = (curString, prevString) => {
|
|
||||||
for (let i = 0; i < curString.length; i++) {
|
|
||||||
if (curString[i] !== prevString[i]) {
|
|
||||||
return curString.substring(0, i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error("Cannot find differentiator", curString, prevString);
|
|
||||||
};
|
|
||||||
|
|
||||||
gulp.task("gen-icons-json", (done) => {
|
|
||||||
const meta = getMeta();
|
|
||||||
|
|
||||||
const metaAndRemoved = addRemovedMeta(meta);
|
|
||||||
const split = splitBySize(metaAndRemoved);
|
|
||||||
|
|
||||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
||||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
||||||
}
|
|
||||||
const parts = [];
|
|
||||||
|
|
||||||
let lastEnd;
|
|
||||||
split.forEach((chunk) => {
|
|
||||||
let startKey;
|
|
||||||
if (lastEnd === undefined) {
|
|
||||||
chunk.startKey = undefined;
|
|
||||||
startKey = undefined;
|
|
||||||
} else {
|
|
||||||
startKey = findDifferentiator(chunk.startKey, lastEnd);
|
|
||||||
}
|
|
||||||
lastEnd = chunk.endKey;
|
|
||||||
|
|
||||||
const output = {};
|
|
||||||
chunk.icons.forEach((icon) => {
|
|
||||||
output[icon.name] = icon.path;
|
|
||||||
});
|
|
||||||
const filename = hash(output);
|
|
||||||
parts.push({ start: startKey, file: filename });
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve(OUTPUT_DIR, `${filename}.json`),
|
|
||||||
JSON.stringify(output)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const file = fs.readFileSync(PACKAGE_PATH, { encoding });
|
|
||||||
const packageMeta = JSON.parse(file);
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve(OUTPUT_DIR, "iconMetadata.json"),
|
|
||||||
JSON.stringify({ version: packageMeta.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();
|
|
||||||
});
|
|
@@ -1,46 +0,0 @@
|
|||||||
const gulp = require("gulp");
|
|
||||||
const env = require("../env.cjs");
|
|
||||||
require("./clean.cjs");
|
|
||||||
require("./compress.cjs");
|
|
||||||
require("./entry-html.cjs");
|
|
||||||
require("./gather-static.cjs");
|
|
||||||
require("./gen-icons-json.cjs");
|
|
||||||
require("./rollup.cjs");
|
|
||||||
require("./translations.cjs");
|
|
||||||
require("./webpack.cjs");
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"develop-hassio",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "development";
|
|
||||||
},
|
|
||||||
"clean-hassio",
|
|
||||||
"gen-dummy-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"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"build-hassio",
|
|
||||||
gulp.series(
|
|
||||||
async function setEnv() {
|
|
||||||
process.env.NODE_ENV = "production";
|
|
||||||
},
|
|
||||||
"clean-hassio",
|
|
||||||
"gen-dummy-icons-json",
|
|
||||||
"build-supervisor-translations",
|
|
||||||
"copy-translations-supervisor",
|
|
||||||
"build-locale-data",
|
|
||||||
"copy-locale-data-supervisor",
|
|
||||||
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
|
|
||||||
"gen-index-hassio-prod",
|
|
||||||
...// Don't compress running tests
|
|
||||||
(env.isTestBuild() ? [] : ["compress-hassio"])
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,72 +0,0 @@
|
|||||||
const del = import("del");
|
|
||||||
const path = require("path");
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const fs = require("fs");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
|
|
||||||
const outDir = "build/locale-data";
|
|
||||||
|
|
||||||
gulp.task("clean-locale-data", async () => (await del).deleteSync([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"
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,146 +0,0 @@
|
|||||||
// Tasks to run Rollup
|
|
||||||
const path = require("path");
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const rollup = require("rollup");
|
|
||||||
const handler = require("serve-handler");
|
|
||||||
const http = require("http");
|
|
||||||
const log = require("fancy-log");
|
|
||||||
const open = require("open");
|
|
||||||
const rollupConfig = require("../rollup.cjs");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
|
|
||||||
const bothBuilds = (createConfigFunc, params) =>
|
|
||||||
gulp.series(
|
|
||||||
async function buildLatest() {
|
|
||||||
await buildRollup(
|
|
||||||
createConfigFunc({
|
|
||||||
...params,
|
|
||||||
latestBuild: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
async function buildES5() {
|
|
||||||
await buildRollup(
|
|
||||||
createConfigFunc({
|
|
||||||
...params,
|
|
||||||
latestBuild: false,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function createServer(serveOptions) {
|
|
||||||
const server = http.createServer((request, response) =>
|
|
||||||
handler(request, response, {
|
|
||||||
public: serveOptions.root,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
server.listen(
|
|
||||||
serveOptions.port,
|
|
||||||
serveOptions.networkAccess ? "0.0.0.0" : undefined,
|
|
||||||
() => {
|
|
||||||
log.info(`Available at http://localhost:${serveOptions.port}`);
|
|
||||||
open(`http://localhost:${serveOptions.port}`);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function watchRollup(createConfig, extraWatchSrc = [], serveOptions = null) {
|
|
||||||
const { inputOptions, outputOptions } = createConfig({
|
|
||||||
isProdBuild: false,
|
|
||||||
latestBuild: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const watcher = rollup.watch({
|
|
||||||
...inputOptions,
|
|
||||||
output: [outputOptions],
|
|
||||||
watch: {
|
|
||||||
include: ["src/**"] + extraWatchSrc,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let startedHttp = false;
|
|
||||||
|
|
||||||
watcher.on("event", (event) => {
|
|
||||||
if (event.code === "BUNDLE_END") {
|
|
||||||
log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
|
||||||
} else if (event.code === "ERROR") {
|
|
||||||
log.error(event.error);
|
|
||||||
} else if (event.code === "END") {
|
|
||||||
if (startedHttp || !serveOptions) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
startedHttp = true;
|
|
||||||
createServer(serveOptions);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.watch(
|
|
||||||
path.join(paths.translations_src, "en.json"),
|
|
||||||
gulp.series("build-translations", "copy-translations-app")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildRollup(config) {
|
|
||||||
const bundle = await rollup.rollup(config.inputOptions);
|
|
||||||
await bundle.write(config.outputOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task("rollup-watch-app", () => {
|
|
||||||
watchRollup(rollupConfig.createAppConfig);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("rollup-watch-hassio", () => {
|
|
||||||
watchRollup(rollupConfig.createHassioConfig, ["hassio/src/**"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("rollup-dev-server-demo", () => {
|
|
||||||
watchRollup(rollupConfig.createDemoConfig, ["demo/src/**"], {
|
|
||||||
root: paths.demo_output_root,
|
|
||||||
port: 8090,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("rollup-dev-server-cast", () => {
|
|
||||||
watchRollup(rollupConfig.createCastConfig, ["cast/src/**"], {
|
|
||||||
root: paths.cast_output_root,
|
|
||||||
port: 8080,
|
|
||||||
networkAccess: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("rollup-dev-server-gallery", () => {
|
|
||||||
watchRollup(rollupConfig.createGalleryConfig, ["gallery/src/**"], {
|
|
||||||
root: paths.gallery_output_root,
|
|
||||||
port: 8100,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"rollup-prod-app",
|
|
||||||
bothBuilds(rollupConfig.createAppConfig, { isProdBuild: true })
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"rollup-prod-demo",
|
|
||||||
bothBuilds(rollupConfig.createDemoConfig, { isProdBuild: true })
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"rollup-prod-cast",
|
|
||||||
bothBuilds(rollupConfig.createCastConfig, { isProdBuild: true })
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task("rollup-prod-hassio", () =>
|
|
||||||
bothBuilds(rollupConfig.createHassioConfig, { isProdBuild: true })
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task("rollup-prod-gallery", () =>
|
|
||||||
buildRollup(
|
|
||||||
rollupConfig.createGalleryConfig({
|
|
||||||
isProdBuild: true,
|
|
||||||
latestBuild: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,94 +0,0 @@
|
|||||||
// Generate service worker.
|
|
||||||
// Based on manifest, create a file with the content as service_worker.js
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const path = require("path");
|
|
||||||
const fs = require("fs-extra");
|
|
||||||
const workboxBuild = require("workbox-build");
|
|
||||||
const sourceMapUrl = require("source-map-url");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
|
|
||||||
const swDest = path.resolve(paths.app_output_root, "service_worker.js");
|
|
||||||
|
|
||||||
const writeSW = (content) => fs.outputFileSync(swDest, content.trim() + "\n");
|
|
||||||
|
|
||||||
gulp.task("gen-service-worker-app-dev", (done) => {
|
|
||||||
writeSW(
|
|
||||||
`
|
|
||||||
console.debug('Service worker disabled in development');
|
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
|
||||||
// This will activate the dev service worker,
|
|
||||||
// removing any prod service worker the dev might have running
|
|
||||||
self.skipWaiting();
|
|
||||||
});
|
|
||||||
`
|
|
||||||
);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("gen-service-worker-app-prod", async () => {
|
|
||||||
// Read bundled source file
|
|
||||||
const bundleManifestLatest = require(path.resolve(
|
|
||||||
paths.app_output_latest,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
let serviceWorkerContent = fs.readFileSync(
|
|
||||||
paths.app_output_root + bundleManifestLatest["service_worker.js"],
|
|
||||||
"utf-8"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Delete old file from frontend_latest so manifest won't pick it up
|
|
||||||
fs.removeSync(
|
|
||||||
paths.app_output_root + bundleManifestLatest["service_worker.js"]
|
|
||||||
);
|
|
||||||
fs.removeSync(
|
|
||||||
paths.app_output_root + bundleManifestLatest["service_worker.js.map"]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove ES5
|
|
||||||
const bundleManifestES5 = require(path.resolve(
|
|
||||||
paths.app_output_es5,
|
|
||||||
"manifest.json"
|
|
||||||
));
|
|
||||||
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]);
|
|
||||||
fs.removeSync(
|
|
||||||
paths.app_output_root + bundleManifestES5["service_worker.js.map"]
|
|
||||||
);
|
|
||||||
|
|
||||||
const workboxManifest = await workboxBuild.getManifest({
|
|
||||||
// Files that mach this pattern will be considered unique and skip revision check
|
|
||||||
// ignore JS files + translation files
|
|
||||||
dontCacheBustURLsMatching: /(frontend_latest\/.+|static\/translations\/.+)/,
|
|
||||||
|
|
||||||
globDirectory: paths.app_output_root,
|
|
||||||
globPatterns: [
|
|
||||||
"frontend_latest/*.js",
|
|
||||||
// Cache all English translations because we catch them as fallback
|
|
||||||
// Using pattern to match hash instead of * to avoid caching en-GB
|
|
||||||
// 'v' added as valid hash letter because in dev we hash with 'dev'
|
|
||||||
"static/translations/**/en-+([a-fv0-9]).json",
|
|
||||||
// Icon shown on splash screen
|
|
||||||
"static/icons/favicon-192x192.png",
|
|
||||||
"static/icons/favicon.ico",
|
|
||||||
// Common fonts
|
|
||||||
"static/fonts/roboto/Roboto-Light.woff2",
|
|
||||||
"static/fonts/roboto/Roboto-Medium.woff2",
|
|
||||||
"static/fonts/roboto/Roboto-Regular.woff2",
|
|
||||||
"static/fonts/roboto/Roboto-Bold.woff2",
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const warning of workboxManifest.warnings) {
|
|
||||||
console.warn(warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove source map and add WB manifest
|
|
||||||
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
|
|
||||||
serviceWorkerContent = serviceWorkerContent.replace(
|
|
||||||
"WB_MANIFEST",
|
|
||||||
JSON.stringify(workboxManifest.manifestEntries)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Write new file to root
|
|
||||||
fs.writeFileSync(swDest, serviceWorkerContent);
|
|
||||||
});
|
|
@@ -1,447 +0,0 @@
|
|||||||
const del = import("del");
|
|
||||||
const crypto = require("crypto");
|
|
||||||
const path = require("path");
|
|
||||||
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 merge = require("gulp-merge-json");
|
|
||||||
const rename = require("gulp-rename");
|
|
||||||
const transform = require("gulp-json-transform");
|
|
||||||
const { mapFiles } = require("../util.cjs");
|
|
||||||
const env = require("../env.cjs");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
|
|
||||||
require("./fetch-nightly-translations.cjs");
|
|
||||||
|
|
||||||
const inFrontendDir = "translations/frontend";
|
|
||||||
const inBackendDir = "translations/backend";
|
|
||||||
const workDir = "build/translations";
|
|
||||||
const fullDir = workDir + "/full";
|
|
||||||
const coreDir = workDir + "/core";
|
|
||||||
const outDir = workDir + "/output";
|
|
||||||
let mergeBackend = false;
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"translations-enable-merge-backend",
|
|
||||||
gulp.parallel((done) => {
|
|
||||||
mergeBackend = true;
|
|
||||||
done();
|
|
||||||
}, "allow-setup-fetch-nightly-translations")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Panel translations which should be split from the core translations.
|
|
||||||
const TRANSLATION_FRAGMENTS = Object.keys(
|
|
||||||
require("../../src/translations/en.json").ui.panel
|
|
||||||
);
|
|
||||||
|
|
||||||
function recursiveFlatten(prefix, data) {
|
|
||||||
let output = {};
|
|
||||||
Object.keys(data).forEach((key) => {
|
|
||||||
if (typeof data[key] === "object") {
|
|
||||||
output = {
|
|
||||||
...output,
|
|
||||||
...recursiveFlatten(prefix + key + ".", data[key]),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
output[prefix + key] = data[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
function flatten(data) {
|
|
||||||
return recursiveFlatten("", data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function emptyFilter(data) {
|
|
||||||
const newData = {};
|
|
||||||
Object.keys(data).forEach((key) => {
|
|
||||||
if (data[key]) {
|
|
||||||
if (typeof data[key] === "object") {
|
|
||||||
newData[key] = emptyFilter(data[key]);
|
|
||||||
} else {
|
|
||||||
newData[key] = data[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function recursiveEmpty(data) {
|
|
||||||
const newData = {};
|
|
||||||
Object.keys(data).forEach((key) => {
|
|
||||||
if (data[key]) {
|
|
||||||
if (typeof data[key] === "object") {
|
|
||||||
newData[key] = recursiveEmpty(data[key]);
|
|
||||||
} else {
|
|
||||||
newData[key] = "TRANSLATED";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return newData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace Lokalise key placeholders with their actual values.
|
|
||||||
*
|
|
||||||
* We duplicate the behavior of Lokalise here so that placeholders can
|
|
||||||
* be included in src/translations/en.json, but still be usable while
|
|
||||||
* developing locally.
|
|
||||||
*
|
|
||||||
* @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
|
|
||||||
*/
|
|
||||||
const re_key_reference = /\[%key:([^%]+)%\]/;
|
|
||||||
function lokaliseTransform(data, original, file) {
|
|
||||||
const output = {};
|
|
||||||
Object.entries(data).forEach(([key, value]) => {
|
|
||||||
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) => {
|
|
||||||
if (!tr) {
|
|
||||||
throw Error(
|
|
||||||
`Invalid key placeholder ${lokalise_key} in ${file.path}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return tr[k];
|
|
||||||
}, original);
|
|
||||||
if (typeof replace !== "string") {
|
|
||||||
throw Error(
|
|
||||||
`Invalid key placeholder ${lokalise_key} in ${file.path}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return replace;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
gulp.task("clean-translations", async () => (await del).deleteSync([workDir]));
|
|
||||||
|
|
||||||
gulp.task("ensure-translations-build-dir", (done) => {
|
|
||||||
if (!fs.existsSync(workDir)) {
|
|
||||||
fs.mkdirSync(workDir, { recursive: true });
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("create-test-metadata", (cb) => {
|
|
||||||
fs.writeFile(
|
|
||||||
workDir + "/testMetadata.json",
|
|
||||||
JSON.stringify({
|
|
||||||
test: {
|
|
||||||
nativeName: "Test",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
cb
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"create-test-translation",
|
|
||||||
gulp.series("create-test-metadata", () =>
|
|
||||||
gulp
|
|
||||||
.src(path.join(paths.translations_src, "en.json"))
|
|
||||||
.pipe(transform((data, _file) => recursiveEmpty(data)))
|
|
||||||
.pipe(rename("test.json"))
|
|
||||||
.pipe(gulp.dest(workDir))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This task will build a master translation file, to be used as the base for
|
|
||||||
* all languages. This starts with src/translations/en.json, and replaces all
|
|
||||||
* Lokalise key placeholders with their target values. Under normal circumstances,
|
|
||||||
* this will be the same as translations/en.json However, we build it here to
|
|
||||||
* facilitate both making changes in development mode, and to ensure that the
|
|
||||||
* 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", () => {
|
|
||||||
const src = [path.join(paths.translations_src, "en.json")];
|
|
||||||
|
|
||||||
if (mergeBackend) {
|
|
||||||
src.push(path.join(inBackendDir, "en.json"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return gulp
|
|
||||||
.src(src)
|
|
||||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
|
||||||
.pipe(
|
|
||||||
merge({
|
|
||||||
fileName: "en.json",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(fullDir));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("build-merged-translations", () =>
|
|
||||||
gulp
|
|
||||||
.src(
|
|
||||||
[
|
|
||||||
inFrontendDir + "/*.json",
|
|
||||||
"!" + inFrontendDir + "/en.json",
|
|
||||||
workDir + "/test.json",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
allowEmpty: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
|
||||||
.pipe(
|
|
||||||
flatmap((stream, file) => {
|
|
||||||
// For each language generate a merged json file. It begins with the master
|
|
||||||
// translation as a failsafe for untranslated strings, and merges all parent
|
|
||||||
// tags into one file for each specific subtag
|
|
||||||
//
|
|
||||||
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
|
||||||
// Will be OK for now as long as we don't have anything more complicated
|
|
||||||
// than a base translation + region.
|
|
||||||
const tr = path.basename(file.history[0], ".json");
|
|
||||||
const subtags = tr.split("-");
|
|
||||||
const src = [fullDir + "/en.json"];
|
|
||||||
for (let i = 1; i <= subtags.length; i++) {
|
|
||||||
const lang = subtags.slice(0, i).join("-");
|
|
||||||
if (lang === "test") {
|
|
||||||
src.push(workDir + "/test.json");
|
|
||||||
} else if (lang !== "en") {
|
|
||||||
src.push(inFrontendDir + "/" + lang + ".json");
|
|
||||||
if (mergeBackend) {
|
|
||||||
src.push(inBackendDir + "/" + lang + ".json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gulp
|
|
||||||
.src(src, { allowEmpty: true })
|
|
||||||
.pipe(transform((data) => emptyFilter(data)))
|
|
||||||
.pipe(
|
|
||||||
merge({
|
|
||||||
fileName: tr + ".json",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(fullDir));
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
let taskName;
|
|
||||||
|
|
||||||
const splitTasks = [];
|
|
||||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
|
||||||
taskName = "build-translation-fragment-" + fragment;
|
|
||||||
gulp.task(taskName, () =>
|
|
||||||
// Return only the translations for this fragment.
|
|
||||||
gulp
|
|
||||||
.src(fullDir + "/*.json")
|
|
||||||
.pipe(
|
|
||||||
transform((data) => ({
|
|
||||||
ui: {
|
|
||||||
panel: {
|
|
||||||
[fragment]: data.ui.panel[fragment],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(workDir + "/" + fragment))
|
|
||||||
);
|
|
||||||
splitTasks.push(taskName);
|
|
||||||
});
|
|
||||||
|
|
||||||
taskName = "build-translation-core";
|
|
||||||
gulp.task(taskName, () =>
|
|
||||||
// Remove the fragment translations from the core translation.
|
|
||||||
gulp
|
|
||||||
.src(fullDir + "/*.json")
|
|
||||||
.pipe(
|
|
||||||
transform((data, _file) => {
|
|
||||||
TRANSLATION_FRAGMENTS.forEach((fragment) => {
|
|
||||||
delete data.ui.panel[fragment];
|
|
||||||
});
|
|
||||||
delete data.supervisor;
|
|
||||||
return data;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(coreDir))
|
|
||||||
);
|
|
||||||
|
|
||||||
splitTasks.push(taskName);
|
|
||||||
|
|
||||||
gulp.task("build-flattened-translations", () =>
|
|
||||||
// Flatten the split versions of our translations, and move them into outDir
|
|
||||||
gulp
|
|
||||||
.src(
|
|
||||||
TRANSLATION_FRAGMENTS.map(
|
|
||||||
(fragment) => workDir + "/" + fragment + "/*.json"
|
|
||||||
).concat(coreDir + "/*.json"),
|
|
||||||
{ base: workDir }
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
transform((data) =>
|
|
||||||
// Polymer.AppLocalizeBehavior requires flattened json
|
|
||||||
flatten(data)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.pipe(
|
|
||||||
rename((filePath) => {
|
|
||||||
if (filePath.dirname === "core") {
|
|
||||||
filePath.dirname = "";
|
|
||||||
}
|
|
||||||
// In dev we create the file with the fake hash in the filename
|
|
||||||
if (!env.isProdBuild()) {
|
|
||||||
filePath.basename += "-dev";
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(outDir))
|
|
||||||
);
|
|
||||||
|
|
||||||
const fingerprints = {};
|
|
||||||
|
|
||||||
gulp.task("build-translation-fingerprints", () => {
|
|
||||||
// Fingerprint full file of each language
|
|
||||||
const files = 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",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// In dev we create the file with the fake hash in the filename
|
|
||||||
if (env.isProdBuild()) {
|
|
||||||
mapFiles(outDir, ".json", (filename) => {
|
|
||||||
const parsed = path.parse(filename);
|
|
||||||
|
|
||||||
// nl.json -> nl-<hash>.json
|
|
||||||
if (!(parsed.name in fingerprints)) {
|
|
||||||
throw new Error(`Unable to find hash for ${filename}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.renameSync(
|
|
||||||
filename,
|
|
||||||
`${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${
|
|
||||||
parsed.ext
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const stream = source("translationFingerprints.json");
|
|
||||||
stream.write(JSON.stringify(fingerprints));
|
|
||||||
process.nextTick(() => stream.end());
|
|
||||||
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("build-translation-fragment-supervisor", () =>
|
|
||||||
gulp
|
|
||||||
.src(fullDir + "/*.json")
|
|
||||||
.pipe(transform((data) => data.supervisor))
|
|
||||||
.pipe(
|
|
||||||
rename((filePath) => {
|
|
||||||
// In dev we create the file with the fake hash in the filename
|
|
||||||
if (!env.isProdBuild()) {
|
|
||||||
filePath.basename += "-dev";
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(workDir + "/supervisor"))
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task("build-translation-flatten-supervisor", () =>
|
|
||||||
gulp
|
|
||||||
.src(workDir + "/supervisor/*.json")
|
|
||||||
.pipe(
|
|
||||||
transform((data) =>
|
|
||||||
// Polymer.AppLocalizeBehavior requires flattened json
|
|
||||||
flatten(data)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.pipe(gulp.dest(outDir))
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task("build-translation-write-metadata", () =>
|
|
||||||
gulp
|
|
||||||
.src(
|
|
||||||
[
|
|
||||||
path.join(paths.translations_src, "translationMetadata.json"),
|
|
||||||
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 {
|
|
||||||
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(
|
|
||||||
"build-translations",
|
|
||||||
gulp.series(
|
|
||||||
gulp.parallel(
|
|
||||||
"fetch-nightly-translations",
|
|
||||||
gulp.series("clean-translations", "ensure-translations-build-dir")
|
|
||||||
),
|
|
||||||
"create-translations",
|
|
||||||
"build-translation-fingerprints",
|
|
||||||
"build-translation-write-metadata"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task(
|
|
||||||
"build-supervisor-translations",
|
|
||||||
gulp.series(
|
|
||||||
gulp.parallel(
|
|
||||||
"fetch-nightly-translations",
|
|
||||||
gulp.series("clean-translations", "ensure-translations-build-dir")
|
|
||||||
),
|
|
||||||
"build-master-translation",
|
|
||||||
"build-merged-translations",
|
|
||||||
"build-translation-fragment-supervisor",
|
|
||||||
"build-translation-flatten-supervisor",
|
|
||||||
"build-translation-fingerprints",
|
|
||||||
"build-translation-write-metadata"
|
|
||||||
)
|
|
||||||
);
|
|
@@ -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,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,190 +0,0 @@
|
|||||||
// Tasks to run webpack.
|
|
||||||
const fs = require("fs");
|
|
||||||
const gulp = require("gulp");
|
|
||||||
const webpack = require("webpack");
|
|
||||||
const WebpackDevServer = require("webpack-dev-server");
|
|
||||||
const log = require("fancy-log");
|
|
||||||
const path = require("path");
|
|
||||||
const env = require("../env.cjs");
|
|
||||||
const paths = require("../paths.cjs");
|
|
||||||
const {
|
|
||||||
createAppConfig,
|
|
||||||
createDemoConfig,
|
|
||||||
createCastConfig,
|
|
||||||
createHassioConfig,
|
|
||||||
createGalleryConfig,
|
|
||||||
} = require("../webpack.cjs");
|
|
||||||
|
|
||||||
const bothBuilds = (createConfigFunc, params) => [
|
|
||||||
createConfigFunc({ ...params, latestBuild: true }),
|
|
||||||
createConfigFunc({ ...params, latestBuild: false }),
|
|
||||||
];
|
|
||||||
|
|
||||||
const isWsl =
|
|
||||||
fs.existsSync("/proc/version") &&
|
|
||||||
fs
|
|
||||||
.readFileSync("/proc/version", "utf-8")
|
|
||||||
.toLocaleLowerCase()
|
|
||||||
.includes("microsoft");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{
|
|
||||||
* compiler: import("webpack").Compiler,
|
|
||||||
* contentBase: string,
|
|
||||||
* port: number,
|
|
||||||
* listenHost?: string
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
const runDevServer = async ({
|
|
||||||
compiler,
|
|
||||||
contentBase,
|
|
||||||
port,
|
|
||||||
listenHost = "localhost",
|
|
||||||
}) => {
|
|
||||||
const server = new WebpackDevServer(
|
|
||||||
{
|
|
||||||
open: true,
|
|
||||||
host: listenHost,
|
|
||||||
port,
|
|
||||||
static: {
|
|
||||||
directory: contentBase,
|
|
||||||
watch: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
compiler
|
|
||||||
);
|
|
||||||
|
|
||||||
await server.start();
|
|
||||||
// Server listening
|
|
||||||
log("[webpack-dev-server]", `Project is running at http://localhost:${port}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const doneHandler = (done) => (err, stats) => {
|
|
||||||
if (err) {
|
|
||||||
log.error(err.stack || err);
|
|
||||||
if (err.details) {
|
|
||||||
log.error(err.details);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
|
||||||
console.log(stats.toString("minimal"));
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
|
||||||
|
|
||||||
if (done) {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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());
|
|
||||||
gulp.watch(
|
|
||||||
path.join(paths.translations_src, "en.json"),
|
|
||||||
gulp.series("create-translations", "copy-translations-app")
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("webpack-prod-app", () =>
|
|
||||||
prodBuild(
|
|
||||||
bothBuilds(createAppConfig, {
|
|
||||||
isProdBuild: true,
|
|
||||||
isStatsBuild: env.isStatsBuild(),
|
|
||||||
isTestBuild: env.isTestBuild(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
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", () =>
|
|
||||||
prodBuild(
|
|
||||||
bothBuilds(createDemoConfig, {
|
|
||||||
isProdBuild: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
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-watch-hassio", () => {
|
|
||||||
// This command will run forever because we don't close compiler
|
|
||||||
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")
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gulp.task("webpack-prod-hassio", () =>
|
|
||||||
prodBuild(
|
|
||||||
bothBuilds(createHassioConfig, {
|
|
||||||
isProdBuild: true,
|
|
||||||
isStatsBuild: env.isStatsBuild(),
|
|
||||||
isTestBuild: env.isTestBuild(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
@@ -1,47 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
polymer_dir: path.resolve(__dirname, ".."),
|
|
||||||
|
|
||||||
build_dir: path.resolve(__dirname, "../build"),
|
|
||||||
app_output_root: path.resolve(__dirname, "../hass_frontend"),
|
|
||||||
app_output_static: path.resolve(__dirname, "../hass_frontend/static"),
|
|
||||||
app_output_latest: path.resolve(
|
|
||||||
__dirname,
|
|
||||||
"../hass_frontend/frontend_latest"
|
|
||||||
),
|
|
||||||
app_output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
|
|
||||||
|
|
||||||
demo_dir: path.resolve(__dirname, "../demo"),
|
|
||||||
demo_output_root: path.resolve(__dirname, "../demo/dist"),
|
|
||||||
demo_output_static: path.resolve(__dirname, "../demo/dist/static"),
|
|
||||||
demo_output_latest: path.resolve(__dirname, "../demo/dist/frontend_latest"),
|
|
||||||
demo_output_es5: path.resolve(__dirname, "../demo/dist/frontend_es5"),
|
|
||||||
|
|
||||||
cast_dir: path.resolve(__dirname, "../cast"),
|
|
||||||
cast_output_root: path.resolve(__dirname, "../cast/dist"),
|
|
||||||
cast_output_static: path.resolve(__dirname, "../cast/dist/static"),
|
|
||||||
cast_output_latest: path.resolve(__dirname, "../cast/dist/frontend_latest"),
|
|
||||||
cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"),
|
|
||||||
|
|
||||||
gallery_dir: path.resolve(__dirname, "../gallery"),
|
|
||||||
gallery_build: path.resolve(__dirname, "../gallery/build"),
|
|
||||||
gallery_output_root: path.resolve(__dirname, "../gallery/dist"),
|
|
||||||
gallery_output_latest: path.resolve(
|
|
||||||
__dirname,
|
|
||||||
"../gallery/dist/frontend_latest"
|
|
||||||
),
|
|
||||||
gallery_output_static: path.resolve(__dirname, "../gallery/dist/static"),
|
|
||||||
|
|
||||||
hassio_dir: path.resolve(__dirname, "../hassio"),
|
|
||||||
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
|
|
||||||
hassio_output_static: path.resolve(__dirname, "../hassio/build/static"),
|
|
||||||
hassio_output_latest: path.resolve(
|
|
||||||
__dirname,
|
|
||||||
"../hassio/build/frontend_latest"
|
|
||||||
),
|
|
||||||
hassio_output_es5: path.resolve(__dirname, "../hassio/build/frontend_es5"),
|
|
||||||
hassio_publicPath: "/api/hassio/app",
|
|
||||||
|
|
||||||
translations_src: path.resolve(__dirname, "../src/translations"),
|
|
||||||
};
|
|
@@ -1 +0,0 @@
|
|||||||
[]
|
|
@@ -1,14 +0,0 @@
|
|||||||
module.exports = function (opts = {}) {
|
|
||||||
const dontHash = opts.dontHash || new Set();
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "dont-hash",
|
|
||||||
renderChunk(_code, chunk, _options) {
|
|
||||||
if (!chunk.isEntry || !dontHash.has(chunk.name)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
chunk.fileName = `${chunk.name}.js`;
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@@ -1,24 +0,0 @@
|
|||||||
module.exports = function (userOptions = {}) {
|
|
||||||
// Files need to be absolute paths.
|
|
||||||
// This only works if the file has no exports
|
|
||||||
// and only is imported for its side effects
|
|
||||||
const files = userOptions.files || [];
|
|
||||||
|
|
||||||
if (files.length === 0) {
|
|
||||||
return {
|
|
||||||
name: "ignore",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "ignore",
|
|
||||||
|
|
||||||
load(id) {
|
|
||||||
return files.some((toIgnorePath) => id.startsWith(toIgnorePath))
|
|
||||||
? {
|
|
||||||
code: "",
|
|
||||||
}
|
|
||||||
: null;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@@ -1,34 +0,0 @@
|
|||||||
const url = require("url");
|
|
||||||
|
|
||||||
const defaultOptions = {
|
|
||||||
publicPath: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = function (userOptions = {}) {
|
|
||||||
const options = { ...defaultOptions, ...userOptions };
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "manifest",
|
|
||||||
generateBundle(outputOptions, bundle) {
|
|
||||||
const manifest = {};
|
|
||||||
|
|
||||||
for (const chunk of Object.values(bundle)) {
|
|
||||||
if (!chunk.isEntry) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Add js extension to mimic Webpack manifest.
|
|
||||||
manifest[`${chunk.name}.js`] = url.resolve(
|
|
||||||
options.publicPath,
|
|
||||||
chunk.fileName
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emitFile({
|
|
||||||
type: "asset",
|
|
||||||
source: JSON.stringify(manifest, undefined, 2),
|
|
||||||
name: "manifest.json",
|
|
||||||
fileName: "manifest.json",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@@ -1,152 +0,0 @@
|
|||||||
// Worker plugin
|
|
||||||
// Each worker will include all of its dependencies
|
|
||||||
// instead of relying on an importer.
|
|
||||||
|
|
||||||
// Forked from v.1.4.1
|
|
||||||
// https://github.com/surma/rollup-plugin-off-main-thread
|
|
||||||
/**
|
|
||||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const rollup = require("rollup");
|
|
||||||
const path = require("path");
|
|
||||||
const MagicString = require("magic-string");
|
|
||||||
|
|
||||||
const defaultOpts = {
|
|
||||||
// A RegExp to find `new Workers()` calls. The second capture group _must_
|
|
||||||
// capture the provided file name without the quotes.
|
|
||||||
workerRegexp: /new Worker\((["'])(.+?)\1(,[^)]+)?\)/g,
|
|
||||||
plugins: ["node-resolve", "commonjs", "babel", "terser", "ignore"],
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getBundledWorker(workerPath, rollupOptions) {
|
|
||||||
const bundle = await rollup.rollup({
|
|
||||||
...rollupOptions,
|
|
||||||
input: {
|
|
||||||
worker: workerPath,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const { output } = await bundle.generate({
|
|
||||||
// Generates cleanest output, we shouldn't have any imports/exports
|
|
||||||
// that would be incompatible with ES5.
|
|
||||||
format: "es",
|
|
||||||
// We should not export anything. This will fail build if we are.
|
|
||||||
exports: "none",
|
|
||||||
});
|
|
||||||
|
|
||||||
let code;
|
|
||||||
|
|
||||||
for (const chunkOrAsset of output) {
|
|
||||||
if (chunkOrAsset.name === "worker") {
|
|
||||||
code = chunkOrAsset.code;
|
|
||||||
} else if (chunkOrAsset.type !== "asset") {
|
|
||||||
throw new Error("Unexpected extra output");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function (opts = {}) {
|
|
||||||
opts = { ...defaultOpts, ...opts };
|
|
||||||
|
|
||||||
let rollupOptions;
|
|
||||||
let refIds;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: "hass-worker",
|
|
||||||
|
|
||||||
async buildStart(options) {
|
|
||||||
refIds = {};
|
|
||||||
rollupOptions = {
|
|
||||||
plugins: options.plugins.filter((plugin) =>
|
|
||||||
opts.plugins.includes(plugin.name)
|
|
||||||
),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
async transform(code, id) {
|
|
||||||
// Copy the regexp as they are stateful and this hook is async.
|
|
||||||
const workerRegexp = new RegExp(
|
|
||||||
opts.workerRegexp.source,
|
|
||||||
opts.workerRegexp.flags
|
|
||||||
);
|
|
||||||
if (!workerRegexp.test(code)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ms = new MagicString(code);
|
|
||||||
// Reset the regexp
|
|
||||||
workerRegexp.lastIndex = 0;
|
|
||||||
for (;;) {
|
|
||||||
const match = workerRegexp.exec(code);
|
|
||||||
if (!match) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const workerFile = match[2];
|
|
||||||
let optionsObject = {};
|
|
||||||
// Parse the optional options object
|
|
||||||
if (match[3] && match[3].length > 0) {
|
|
||||||
// FIXME: ooooof!
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
||||||
optionsObject = new Function(`return ${match[3].slice(1)};`)();
|
|
||||||
}
|
|
||||||
delete optionsObject.type;
|
|
||||||
|
|
||||||
if (!/^.*\//.test(workerFile)) {
|
|
||||||
this.warn(
|
|
||||||
`Paths passed to the Worker constructor must be relative or absolute, i.e. start with /, ./ or ../ (just like dynamic import!). Ignoring "${workerFile}".`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find worker file and store it as a chunk with ID prefixed for our loader
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
const resolvedWorkerFile = (await this.resolve(workerFile, id)).id;
|
|
||||||
let chunkRefId;
|
|
||||||
if (resolvedWorkerFile in refIds) {
|
|
||||||
chunkRefId = refIds[resolvedWorkerFile];
|
|
||||||
} else {
|
|
||||||
this.addWatchFile(resolvedWorkerFile);
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
const source = await getBundledWorker(
|
|
||||||
resolvedWorkerFile,
|
|
||||||
rollupOptions
|
|
||||||
);
|
|
||||||
chunkRefId = refIds[resolvedWorkerFile] = this.emitFile({
|
|
||||||
name: path.basename(resolvedWorkerFile),
|
|
||||||
source,
|
|
||||||
type: "asset",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const workerParametersStartIndex = match.index + "new Worker(".length;
|
|
||||||
const workerParametersEndIndex =
|
|
||||||
match.index + match[0].length - ")".length;
|
|
||||||
|
|
||||||
ms.overwrite(
|
|
||||||
workerParametersStartIndex,
|
|
||||||
workerParametersEndIndex,
|
|
||||||
`import.meta.ROLLUP_FILE_URL_${chunkRefId}, ${JSON.stringify(
|
|
||||||
optionsObject
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: ms.toString(),
|
|
||||||
map: ms.generateMap({ hires: true }),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@@ -1,145 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
|
|
||||||
const commonjs = require("@rollup/plugin-commonjs");
|
|
||||||
const resolve = require("@rollup/plugin-node-resolve");
|
|
||||||
const json = require("@rollup/plugin-json");
|
|
||||||
const { babel } = require("@rollup/plugin-babel");
|
|
||||||
const replace = require("@rollup/plugin-replace");
|
|
||||||
const visualizer = require("rollup-plugin-visualizer");
|
|
||||||
const { string } = require("rollup-plugin-string");
|
|
||||||
const { terser } = require("rollup-plugin-terser");
|
|
||||||
const manifest = require("./rollup-plugins/manifest-plugin.cjs");
|
|
||||||
const worker = require("./rollup-plugins/worker-plugin.cjs");
|
|
||||||
const dontHashPlugin = require("./rollup-plugins/dont-hash-plugin.cjs");
|
|
||||||
const ignore = require("./rollup-plugins/ignore-plugin.cjs");
|
|
||||||
|
|
||||||
const bundle = require("./bundle.cjs");
|
|
||||||
const paths = require("./paths.cjs");
|
|
||||||
|
|
||||||
const extensions = [".js", ".ts"];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} arg
|
|
||||||
* @param { import("rollup").InputOption } arg.input
|
|
||||||
*/
|
|
||||||
const createRollupConfig = ({
|
|
||||||
entry,
|
|
||||||
outputPath,
|
|
||||||
defineOverlay,
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
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/configuration-options/#preserveentrysignatures
|
|
||||||
preserveEntrySignatures: false,
|
|
||||||
plugins: [
|
|
||||||
ignore({
|
|
||||||
files: bundle
|
|
||||||
.emptyPackages({ latestBuild })
|
|
||||||
// TEMP HACK: Makes Rollup build work again
|
|
||||||
.concat(
|
|
||||||
require.resolve(
|
|
||||||
"@webcomponents/scoped-custom-element-registry/scoped-custom-element-registry.min"
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
resolve({
|
|
||||||
extensions,
|
|
||||||
preferBuiltins: false,
|
|
||||||
browser: true,
|
|
||||||
rootDir: paths.polymer_dir,
|
|
||||||
}),
|
|
||||||
commonjs(),
|
|
||||||
json(),
|
|
||||||
babel({
|
|
||||||
...bundle.babelOptions({ latestBuild, isProdBuild }),
|
|
||||||
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 &&
|
|
||||||
manifest({
|
|
||||||
publicPath,
|
|
||||||
}),
|
|
||||||
!isWDS && worker(),
|
|
||||||
!isWDS && dontHashPlugin({ dontHash }),
|
|
||||||
!isWDS && isProdBuild && terser(bundle.terserOptions({ latestBuild })),
|
|
||||||
!isWDS &&
|
|
||||||
isStatsBuild &&
|
|
||||||
visualizer({
|
|
||||||
// https://github.com/btd/rollup-plugin-visualizer#options
|
|
||||||
open: true,
|
|
||||||
sourcemap: true,
|
|
||||||
}),
|
|
||||||
].filter(Boolean),
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* @type { import("rollup").OutputOptions }
|
|
||||||
*/
|
|
||||||
outputOptions: {
|
|
||||||
// https://rollupjs.org/configuration-options/#output-dir
|
|
||||||
dir: outputPath,
|
|
||||||
// https://rollupjs.org/configuration-options/#output-format
|
|
||||||
format: latestBuild ? "es" : "systemjs",
|
|
||||||
// https://rollupjs.org/configuration-options/#output-externallivebindings
|
|
||||||
externalLiveBindings: false,
|
|
||||||
// https://rollupjs.org/configuration-options/#output-entryfilenames
|
|
||||||
// https://rollupjs.org/configuration-options/#output-chunkfilenames
|
|
||||||
// https://rollupjs.org/configuration-options/#output-assetfilenames
|
|
||||||
entryFileNames:
|
|
||||||
isProdBuild && !isStatsBuild ? "[name]-[hash].js" : "[name].js",
|
|
||||||
chunkFileNames: isProdBuild && !isStatsBuild ? "c.[hash].js" : "[name].js",
|
|
||||||
assetFileNames: isProdBuild && !isStatsBuild ? "a.[hash].js" : "[name].js",
|
|
||||||
// https://rollupjs.org/configuration-options/#output-sourcemap
|
|
||||||
sourcemap: isProdBuild ? true : "inline",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild, isWDS }) =>
|
|
||||||
createRollupConfig(
|
|
||||||
bundle.config.app({
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
isStatsBuild,
|
|
||||||
isWDS,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
|
|
||||||
createRollupConfig(
|
|
||||||
bundle.config.demo({
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
isStatsBuild,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const createCastConfig = ({ isProdBuild, latestBuild }) =>
|
|
||||||
createRollupConfig(bundle.config.cast({ isProdBuild, latestBuild }));
|
|
||||||
|
|
||||||
const createHassioConfig = ({ isProdBuild, latestBuild }) =>
|
|
||||||
createRollupConfig(bundle.config.hassio({ isProdBuild, latestBuild }));
|
|
||||||
|
|
||||||
const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
|
|
||||||
createRollupConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createAppConfig,
|
|
||||||
createDemoConfig,
|
|
||||||
createCastConfig,
|
|
||||||
createHassioConfig,
|
|
||||||
createGalleryConfig,
|
|
||||||
};
|
|
@@ -1,16 +0,0 @@
|
|||||||
const path = require("path");
|
|
||||||
const fs = require("fs");
|
|
||||||
|
|
||||||
// Helper function to map recursively over files in a folder and it's subfolders
|
|
||||||
module.exports.mapFiles = function mapFiles(startPath, filter, mapFunc) {
|
|
||||||
const files = fs.readdirSync(startPath);
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
const filename = path.join(startPath, files[i]);
|
|
||||||
const stat = fs.lstatSync(filename);
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
mapFiles(filename, filter, mapFunc);
|
|
||||||
} else if (filename.indexOf(filter) >= 0) {
|
|
||||||
mapFunc(filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@@ -1,235 +0,0 @@
|
|||||||
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 paths = require("./paths.cjs");
|
|
||||||
const bundle = require("./bundle.cjs");
|
|
||||||
|
|
||||||
class LogStartCompilePlugin {
|
|
||||||
ignoredFirst = false;
|
|
||||||
|
|
||||||
apply(compiler) {
|
|
||||||
compiler.hooks.beforeCompile.tap("LogStartCompilePlugin", () => {
|
|
||||||
if (!this.ignoredFirst) {
|
|
||||||
this.ignoredFirst = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log("Changes detected. Starting compilation");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const createWebpackConfig = ({
|
|
||||||
name,
|
|
||||||
entry,
|
|
||||||
outputPath,
|
|
||||||
publicPath,
|
|
||||||
defineOverlay,
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
isStatsBuild,
|
|
||||||
isTestBuild,
|
|
||||||
isHassioBuild,
|
|
||||||
dontHash,
|
|
||||||
}) => {
|
|
||||||
if (!dontHash) {
|
|
||||||
dontHash = new Set();
|
|
||||||
}
|
|
||||||
const ignorePackages = bundle.ignorePackages({ latestBuild });
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
mode: isProdBuild ? "production" : "development",
|
|
||||||
target: ["web", latestBuild ? "es2017" : "es5"],
|
|
||||||
// For tests/CI, source maps are skipped to gain build speed
|
|
||||||
// For production, generate source maps for accurate stack traces without source code
|
|
||||||
// For development, generate "cheap" versions that can map to original line numbers
|
|
||||||
devtool: isTestBuild
|
|
||||||
? false
|
|
||||||
: isProdBuild
|
|
||||||
? "nosources-source-map"
|
|
||||||
: "eval-cheap-module-source-map",
|
|
||||||
entry,
|
|
||||||
node: false,
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.m?js$|\.ts$/,
|
|
||||||
use: {
|
|
||||||
loader: "babel-loader",
|
|
||||||
options: {
|
|
||||||
...bundle.babelOptions({ latestBuild, isProdBuild, isTestBuild }),
|
|
||||||
cacheDirectory: !isProdBuild,
|
|
||||||
cacheCompression: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
fullySpecified: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
type: "asset/source",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
minimizer: [
|
|
||||||
new TerserPlugin({
|
|
||||||
parallel: true,
|
|
||||||
extractComments: true,
|
|
||||||
terserOptions: bundle.terserOptions({ latestBuild, isTestBuild }),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
|
||||||
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
!isStatsBuild && new WebpackBar({ fancy: !isProdBuild }),
|
|
||||||
new WebpackManifestPlugin({
|
|
||||||
// Only include the JS of entrypoints
|
|
||||||
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
|
|
||||||
}),
|
|
||||||
new webpack.DefinePlugin(
|
|
||||||
bundle.definedVars({ isProdBuild, latestBuild, defineOverlay })
|
|
||||||
),
|
|
||||||
new webpack.IgnorePlugin({
|
|
||||||
checkResource(resource, context) {
|
|
||||||
// Only use ignore to intercept imports that we don't control
|
|
||||||
// inside node_module dependencies.
|
|
||||||
if (
|
|
||||||
!context.includes("/node_modules/") ||
|
|
||||||
// calling define.amd will call require("!!webpack amd options")
|
|
||||||
resource.startsWith("!!webpack") ||
|
|
||||||
// loaded by webpack dev server but doesn't exist.
|
|
||||||
resource === "webpack/hot"
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let fullPath;
|
|
||||||
try {
|
|
||||||
fullPath = resource.startsWith(".")
|
|
||||||
? path.resolve(context, resource)
|
|
||||||
: require.resolve(resource);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(
|
|
||||||
"Error in Home Assistant ignore plugin",
|
|
||||||
resource,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ignorePackages.some((toIgnorePath) =>
|
|
||||||
fullPath.startsWith(toIgnorePath)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
|
||||||
new RegExp(
|
|
||||||
bundle.emptyPackages({ latestBuild, isHassioBuild }).join("|")
|
|
||||||
),
|
|
||||||
path.resolve(paths.polymer_dir, "src/util/empty.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 }) =>
|
|
||||||
!isProdBuild || isStatsBuild || dontHash.has(chunk.name)
|
|
||||||
? "[name].js"
|
|
||||||
: "[name]-[contenthash].js",
|
|
||||||
chunkFilename:
|
|
||||||
isProdBuild && !isStatsBuild ? "[id]-[contenthash].js" : "[name].js",
|
|
||||||
assetModuleFilename:
|
|
||||||
isProdBuild && !isStatsBuild ? "[id]-[contenthash][ext]" : "[id][ext]",
|
|
||||||
hashFunction: "xxhash64",
|
|
||||||
hashDigest: "base64url",
|
|
||||||
hashDigestLength: 11, // full length of 64 bit base64url
|
|
||||||
path: outputPath,
|
|
||||||
publicPath,
|
|
||||||
// To silence warning in worker plugin
|
|
||||||
globalObject: "self",
|
|
||||||
// Since production source maps don't include sources, we need to point to them elsewhere
|
|
||||||
// For dependencies, just provide the path (no source in browser)
|
|
||||||
// Otherwise, point to the raw code on GitHub for browser to load
|
|
||||||
devtoolModuleFilenameTemplate:
|
|
||||||
!isTestBuild && isProdBuild
|
|
||||||
? (info) => {
|
|
||||||
const sourcePath = info.resourcePath.replace(/^\.\//, "");
|
|
||||||
if (
|
|
||||||
sourcePath.startsWith("node_modules") ||
|
|
||||||
sourcePath.startsWith("webpack")
|
|
||||||
) {
|
|
||||||
return `no-source/${sourcePath}`;
|
|
||||||
}
|
|
||||||
return `${bundle.sourceMapURL()}/${sourcePath}`;
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
experiments: {
|
|
||||||
topLevelAwait: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const createAppConfig = ({
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
isStatsBuild,
|
|
||||||
isTestBuild,
|
|
||||||
}) =>
|
|
||||||
createWebpackConfig(
|
|
||||||
bundle.config.app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild })
|
|
||||||
);
|
|
||||||
|
|
||||||
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) =>
|
|
||||||
createWebpackConfig(
|
|
||||||
bundle.config.demo({ isProdBuild, latestBuild, isStatsBuild })
|
|
||||||
);
|
|
||||||
|
|
||||||
const createCastConfig = ({ isProdBuild, latestBuild }) =>
|
|
||||||
createWebpackConfig(bundle.config.cast({ isProdBuild, latestBuild }));
|
|
||||||
|
|
||||||
const createHassioConfig = ({
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
isStatsBuild,
|
|
||||||
isTestBuild,
|
|
||||||
}) =>
|
|
||||||
createWebpackConfig(
|
|
||||||
bundle.config.hassio({
|
|
||||||
isProdBuild,
|
|
||||||
latestBuild,
|
|
||||||
isStatsBuild,
|
|
||||||
isTestBuild,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const createGalleryConfig = ({ isProdBuild, latestBuild }) =>
|
|
||||||
createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild }));
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createAppConfig,
|
|
||||||
createDemoConfig,
|
|
||||||
createCastConfig,
|
|
||||||
createHassioConfig,
|
|
||||||
createGalleryConfig,
|
|
||||||
};
|
|
@@ -1,56 +0,0 @@
|
|||||||
# Home Assistant Cast
|
|
||||||
|
|
||||||
Home Assistant Cast is made up of two separate applications:
|
|
||||||
|
|
||||||
- Chromecast receiver application that can connect to Home Assistant and display relevant information.
|
|
||||||
- Launcher website that allows users to authorize with their Home Assistant installation and launch the receiver app on their Chromecast.
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
- Run `script/develop_cast` to launch the Cast receiver dev server. Keep this running.
|
|
||||||
- Navigate to http://localhost:8080 to start the launcher
|
|
||||||
- Debug the receiver running on the Chromecast via [chrome://inspect/#devices](chrome://inspect/#devices)
|
|
||||||
|
|
||||||
## Setting up development environment
|
|
||||||
|
|
||||||
### Registering development cast app
|
|
||||||
|
|
||||||
- Go to https://cast.google.com/publish and enroll your account for the Google Cast SDK (costs \$5)
|
|
||||||
- Register your Chromecast as a testing device by entering the serial
|
|
||||||
- Add new application -> Custom Receiver
|
|
||||||
- Name: Home Assistant Dev
|
|
||||||
- Receiver Application URL: http://IP-OF-DEV-MACHINE:8080/receiver.html
|
|
||||||
- Guest Mode: off
|
|
||||||
- Google Case for Audio: off
|
|
||||||
|
|
||||||
### Setting dev variables
|
|
||||||
|
|
||||||
Open `src/cast/dev_const.ts` and change `CAST_DEV_APP_ID` to the ID of the app you just created. And set the `CAST_DEV_HASS_URL` to the url of you development machine.
|
|
||||||
|
|
||||||
### Changing configuration
|
|
||||||
|
|
||||||
In `configuration.yaml`, configure CORS for the HTTP integration:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
http:
|
|
||||||
cors_allowed_origins:
|
|
||||||
- https://cast.home-assistant.io
|
|
||||||
- http://IP-OF-DEV-MACHINE:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd cast
|
|
||||||
script/develop_cast
|
|
||||||
```
|
|
||||||
|
|
||||||
The launcher application will be accessible at [http://localhost:8080](http://localhost:8080) and the receiver application will be accessible at [http://localhost:8080/receiver.html](http://localhost:8080/receiver.html) (but only works if accessed by a Chromecast).
|
|
||||||
|
|
||||||
### Developing cast widgets in HA ui
|
|
||||||
|
|
||||||
If your work involves interaction with the Cast parts from the normal Home Assistant UI, you will need to have that development script running too (`script/develop`).
|
|
||||||
|
|
||||||
### Developing the cast demo
|
|
||||||
|
|
||||||
The cast demo is triggered from the Home Assistant demo. To work on that, you will also need to run the development script for the demo (`script/develop_demo`).
|
|
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
Cache-Control: public, max-age: 0, s-maxage=3600, must-revalidate
|
|
||||||
Content-Security-Policy: form-action https:
|
|
||||||
Feature-Policy: vibrate 'none'; geolocation 'none'; midi 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; vibrate 'none'; payment 'none'
|
|
||||||
Referrer-Policy: no-referrer-when-downgrade
|
|
||||||
X-Content-Type-Options: nosniff
|
|
||||||
X-Frame-Options: DENY
|
|
||||||
X-XSS-Protection: 1; mode=block
|
|
||||||
|
|
||||||
/images/*
|
|
||||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
|
||||||
|
|
||||||
/manifest.json
|
|
||||||
Cache-Control: public, max-age: 3600, s-maxage=3600
|
|
||||||
|
|
||||||
/frontend_es5/*
|
|
||||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
|
||||||
|
|
||||||
/frontend_latest/*
|
|
||||||
Cache-Control: public, max-age: 604800, s-maxage=604800
|
|
@@ -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
|
|
Binary file not shown.
Before Width: | Height: | Size: 59 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
Before Width: | Height: | Size: 186 KiB |
Binary file not shown.
Before Width: | Height: | Size: 68 KiB |
Binary file not shown.
Before Width: | Height: | Size: 36 KiB |
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"background_color": "#FFFFFF",
|
|
||||||
"description": "Show Home Assistant on your Chromecast or Google Assistant devices with a screen.",
|
|
||||||
"dir": "ltr",
|
|
||||||
"display": "standalone",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/images/ha-cast-icon.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"lang": "en-US",
|
|
||||||
"name": "Home Assistant Cast",
|
|
||||||
"short_name": "HA Cast",
|
|
||||||
"start_url": "/?homescreen=1",
|
|
||||||
"theme_color": "#03A9F4"
|
|
||||||
}
|
|
@@ -1,3 +0,0 @@
|
|||||||
self.addEventListener("fetch", function(event) {
|
|
||||||
event.respondWith(fetch(event.request));
|
|
||||||
});
|
|
@@ -1,10 +0,0 @@
|
|||||||
import rollup from "../build-scripts/rollup.cjs";
|
|
||||||
import env from "../build-scripts/env.cjs";
|
|
||||||
|
|
||||||
const config = rollup.createCastConfig({
|
|
||||||
isProdBuild: env.isProdBuild(),
|
|
||||||
latestBuild: true,
|
|
||||||
isStatsBuild: env.isStatsBuild(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default { ...config.inputOptions, output: config.outputOptions };
|
|
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Build the cast receiver
|
|
||||||
|
|
||||||
# Stop on errors
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cd "$(dirname "$0")/../.."
|
|
||||||
|
|
||||||
./node_modules/.bin/gulp build-cast
|
|
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Develop the cast receiver
|
|
||||||
|
|
||||||
# Stop on errors
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cd "$(dirname "$0")/../.."
|
|
||||||
|
|
||||||
./node_modules/.bin/gulp develop-cast
|
|
@@ -1,5 +0,0 @@
|
|||||||
# Run it twice, second time we just delete.
|
|
||||||
aws s3 sync dist s3://cast.home-assistant.io --acl public-read
|
|
||||||
# Don't delete as it might break open sites that need to load code splitted things.
|
|
||||||
# aws s3 sync dist s3://cast.home-assistant.io --acl public-read --delete
|
|
||||||
# Todo : update JS first, HTML last.
|
|
@@ -1,259 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Home Assistant Cast - FAQ</title>
|
|
||||||
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
|
|
||||||
<%= renderTemplate('_style_base') %>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #e5e5e5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta property="fb:app_id" content="338291289691179" />
|
|
||||||
<meta property="og:title" content="FAQ - Home Assistant Cast" />
|
|
||||||
<meta property="og:site_name" content="Home Assistant Cast" />
|
|
||||||
<meta property="og:url" content="https://cast.home-assistant.io/" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta
|
|
||||||
property="og:description"
|
|
||||||
content="Frequently asked questions about Home Assistant Cast."
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
property="og:image"
|
|
||||||
content="https://cast.home-assistant.io/images/google-nest-hub.png"
|
|
||||||
/>
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta name="twitter:site" content="@home_assistant" />
|
|
||||||
<meta name="twitter:title" content="FAQ - Home Assistant Cast" />
|
|
||||||
<meta
|
|
||||||
name="twitter:description"
|
|
||||||
content="Frequently asked questions about Home Assistant Cast."
|
|
||||||
/>
|
|
||||||
<meta
|
|
||||||
name="twitter:image"
|
|
||||||
content="https://cast.home-assistant.io/images/google-nest-hub.png"
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<%= renderTemplate('_js_base') %>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import("<%= latestLauncherJS %>");
|
|
||||||
window.latestJS = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
if (!window.latestJS) {
|
|
||||||
<% if (useRollup) { %>
|
|
||||||
_ls("/static/js/s.min.js").onload = function() {
|
|
||||||
System.import("<%= es5LauncherJS %>");
|
|
||||||
};
|
|
||||||
<% } else { %>
|
|
||||||
_ls("<%= es5LauncherJS %>");
|
|
||||||
<% } %>
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<hc-layout subtitle="FAQ">
|
|
||||||
<style>
|
|
||||||
a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="card-content">
|
|
||||||
<p><a href="/">« Back to Home Assistant Cast</a></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-header">What is Home Assistant Cast?</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
Home Assistant Cast allows you to show your Home Assistant data on a
|
|
||||||
Chromecast device and allows you to interact with Home Assistant on
|
|
||||||
Google Assistant devices with a screen.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-header">
|
|
||||||
What are the Home Assistant Cast requirements?
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
Home Assistant Cast requires a Home Assistant installation that is
|
|
||||||
accessible via HTTPS (the url starts with "https://").
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-header">What is Home Assistant?</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
Home Assistant is worlds biggest open source home automation platform
|
|
||||||
with a focus on privacy and local control. You can install Home
|
|
||||||
Assistant for free.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="https://www.home-assistant.io" target="_blank"
|
|
||||||
>Visit the Home Assistant website.</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-header" id="https">
|
|
||||||
Why does my Home Assistant needs to be served using HTTPS?
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
The Chromecast only works with websites served over HTTPS. This means
|
|
||||||
that the Home Assistant Cast app that runs on your Chromecast is
|
|
||||||
served over HTTPS. Websites served over HTTPS are restricted on what
|
|
||||||
content can be accessed on websites served over HTTP. This is called
|
|
||||||
mixed active content (<a
|
|
||||||
href="https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content#Mixed_active_content"
|
|
||||||
target="_blank"
|
|
||||||
>learn more @ MDN</a
|
|
||||||
>).
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The easiest way to get your Home Assistant installation served over
|
|
||||||
HTTPS is by signing up for
|
|
||||||
<a href="https://www.nabucasa.com" target="_blank"
|
|
||||||
>Home Assistant Cloud by Nabu Casa</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-header" id="https">
|
|
||||||
Why does Home Assistant Cast require me to authorize my Home Assistant
|
|
||||||
instance?
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
You're currently looking at the Home Assistant Cast launcher
|
|
||||||
application. This is a standalone application to launch Home Assistant
|
|
||||||
Cast on your Chromecast. Because Chromecasts do not allow us to log in
|
|
||||||
to Home Assistant, we need to supply authentication to it from the
|
|
||||||
launcher. This authentication is obtained when you authorize your
|
|
||||||
instance. Your authentication credentials will remain in your browser
|
|
||||||
and on your Cast device.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
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"
|
|
||||||
target="_blank"
|
|
||||||
>the source code</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The launcher application exists to make it possible to use Home
|
|
||||||
Assistant Cast with older versions of Home Assistant.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Starting with Home Assistant 0.97, Home Assistant Cast is also built
|
|
||||||
into the Lovelace UI as a special entities card row. Since the
|
|
||||||
Lovelace UI already has authentication, you will be able to start
|
|
||||||
casting right away.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-header">Wat does Home Assistant Cast do?</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
Home Assistant Cast is a receiver application for the Chromecast. When
|
|
||||||
loaded, it will make a direct connection to your Home Assistant
|
|
||||||
instance.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Home Assistant Cast is able to render any of your Lovelace views on
|
|
||||||
your Chromecast. Things that work in Lovelace in Home Assistant will
|
|
||||||
work in Home Assistant Cast:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>Render Lovelace views, including custom cards</li>
|
|
||||||
<li>
|
|
||||||
Real-time data stream will ensure the UI always shows the latest
|
|
||||||
state of your house
|
|
||||||
</li>
|
|
||||||
<li>Navigate between views using navigate actions or weblinks</li>
|
|
||||||
<li>
|
|
||||||
Instant updates of the casted Lovelace UI when you update your
|
|
||||||
Lovelace configuration.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<p>Things that currently do not work:</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Live videostreams using the streaming integration
|
|
||||||
</li>
|
|
||||||
<li>Specifying a view with a single card with "panel: true".</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-header" id="https">
|
|
||||||
How do I change what is shown on my Chromecast?
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
Home Assistant Cast allows you to show your Lovelace view on your
|
|
||||||
Chromecast. So to edit what is shown, you need to edit your Lovelace
|
|
||||||
UI.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To edit your Lovelace UI, open Home Assistant, click on the three-dot
|
|
||||||
menu in the top right and click on "Configure UI".
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-header" id="browser">
|
|
||||||
What browsers are supported?
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
Chromecast is a technology developed by Google, and is available on:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>Google Chrome (all platforms except iOS)</li>
|
|
||||||
<li>Microsoft Edge (all platforms except iOS)</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section-header">Why do some custom cards not work?</div>
|
|
||||||
<div class="card-content">
|
|
||||||
<p>
|
|
||||||
Home Assistant needs to be configured to allow Home Assistant Cast to
|
|
||||||
load custom cards. Starting with Home Assistant 0.97, this is done
|
|
||||||
automatically. If you are on an older version, or have manually
|
|
||||||
configured CORS for the HTTP integration, add the following to your
|
|
||||||
configuration.yaml file:
|
|
||||||
</p>
|
|
||||||
<pre>
|
|
||||||
http:
|
|
||||||
cors_allowed_origins:
|
|
||||||
- https://cast.home-assistant.io</pre
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
Some custom cards rely on things that are only available in the normal
|
|
||||||
Home Assistant interface. This requires an update by the custom card
|
|
||||||
developer.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If you're a custom card developer: the most common mistake is that
|
|
||||||
LitElement is extracted from an element that is not available on the
|
|
||||||
page.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</hc-layout>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var _gaq = [["_setAccount", "UA-57927901-9"], ["_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>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,57 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Home Assistant Cast</title>
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
|
|
||||||
<%= renderTemplate('_style_base') %>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #e5e5e5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta property="fb:app_id" content="338291289691179">
|
|
||||||
<meta property="og:title" content="Home Assistant Cast">
|
|
||||||
<meta property="og:site_name" content="Home Assistant Cast">
|
|
||||||
<meta property="og:url" content="https://cast.home-assistant.io/">
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen.">
|
|
||||||
<meta property="og:image" content="https://cast.home-assistant.io/images/google-nest-hub.png">
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
|
||||||
<meta name="twitter:site" content="@home_assistant">
|
|
||||||
<meta name="twitter:title" content="Home Assistant Cast">
|
|
||||||
<meta name="twitter:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen.">
|
|
||||||
<meta name="twitter:image" content="https://cast.home-assistant.io/images/google-nest-hub.png">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<%= renderTemplate('_js_base') %>
|
|
||||||
|
|
||||||
<hc-connect></hc-connect>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import("<%= latestLauncherJS %>");
|
|
||||||
window.latestJS = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
if (!window.latestJS) {
|
|
||||||
<% if (useRollup) { %>
|
|
||||||
_ls("/static/js/s.min.js").onload = function() {
|
|
||||||
System.import("<%= es5LauncherJS %>");
|
|
||||||
};
|
|
||||||
<% } else { %>
|
|
||||||
_ls("<%= es5LauncherJS %>");
|
|
||||||
<% } %>
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', 'UA-57927901-9', 'auto');
|
|
||||||
ga('send', 'pageview', location.pathname.includes("auth_callback") === -1 ? location.pathname : "/");
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -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>
|
|
@@ -1,18 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
|
|
||||||
<script type="module" src="<%= latestReceiverJS %>"></script>
|
|
||||||
<%= renderTemplate('_style_base') %>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: white;
|
|
||||||
font-size: initial;
|
|
||||||
}
|
|
||||||
</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>
|
|
||||||
</html>
|
|
@@ -1,4 +0,0 @@
|
|||||||
import "../../../src/resources/safari-14-attachshadow-patch";
|
|
||||||
import "../../../src/resources/ha-style";
|
|
||||||
import "../../../src/resources/roboto";
|
|
||||||
import "./layout/hc-connect";
|
|
@@ -1,279 +0,0 @@
|
|||||||
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 { CastManager } from "../../../../src/cast/cast_manager";
|
|
||||||
import {
|
|
||||||
castSendShowLovelaceView,
|
|
||||||
ensureConnectedCastSession,
|
|
||||||
} from "../../../../src/cast/receiver_messages";
|
|
||||||
import {
|
|
||||||
askWrite,
|
|
||||||
enableWrite,
|
|
||||||
saveTokens,
|
|
||||||
} from "../../../../src/common/auth/token_storage";
|
|
||||||
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,
|
|
||||||
LovelaceConfig,
|
|
||||||
} from "../../../../src/data/lovelace";
|
|
||||||
import "../../../../src/layouts/hass-loading-screen";
|
|
||||||
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
|
|
||||||
import "./hc-layout";
|
|
||||||
|
|
||||||
@customElement("hc-cast")
|
|
||||||
class HcCast extends LitElement {
|
|
||||||
@property() public auth!: Auth;
|
|
||||||
|
|
||||||
@property() public connection!: Connection;
|
|
||||||
|
|
||||||
@property() public castManager!: CastManager;
|
|
||||||
|
|
||||||
@state() private askWrite = false;
|
|
||||||
|
|
||||||
@state() private lovelaceConfig?: LovelaceConfig | null;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
if (this.lovelaceConfig === undefined) {
|
|
||||||
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>
|
|
||||||
`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<hc-layout .auth=${this.auth} .connection=${this.connection}>
|
|
||||||
${this.askWrite
|
|
||||||
? html`
|
|
||||||
<p class="question action-item">
|
|
||||||
Stay logged in?
|
|
||||||
<span>
|
|
||||||
<mwc-button @click=${this._handleSaveTokens}>
|
|
||||||
YES
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button @click=${this._handleSkipSaveTokens}>
|
|
||||||
NO
|
|
||||||
</mwc-button>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${error
|
|
||||||
? html` <div class="card-content">${error}</div> `
|
|
||||||
: !this.castManager.status
|
|
||||||
? html`
|
|
||||||
<p class="center-item">
|
|
||||||
<mwc-button raised @click=${this._handleLaunch}>
|
|
||||||
<ha-svg-icon .path=${mdiCast}></ha-svg-icon>
|
|
||||||
Start Casting
|
|
||||||
</mwc-button>
|
|
||||||
</p>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<div class="section-header">PICK A VIEW</div>
|
|
||||||
<paper-listbox
|
|
||||||
attr-for-selected="data-path"
|
|
||||||
.selected=${this.castManager.status.lovelacePath || ""}
|
|
||||||
>
|
|
||||||
${(this.lovelaceConfig
|
|
||||||
? this.lovelaceConfig.views
|
|
||||||
: [generateDefaultViewConfig({}, {}, {}, {}, () => "")]
|
|
||||||
).map(
|
|
||||||
(view, idx) => html`
|
|
||||||
<paper-icon-item
|
|
||||||
@click=${this._handlePickView}
|
|
||||||
data-path=${view.path || idx}
|
|
||||||
>
|
|
||||||
${view.icon
|
|
||||||
? html`
|
|
||||||
<ha-icon
|
|
||||||
.icon=${view.icon}
|
|
||||||
slot="item-icon"
|
|
||||||
></ha-icon>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${view.title || view.path}
|
|
||||||
</paper-icon-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</paper-listbox>
|
|
||||||
`}
|
|
||||||
<div class="card-actions">
|
|
||||||
${this.castManager.status
|
|
||||||
? html`
|
|
||||||
<mwc-button @click=${this._handleLaunch}>
|
|
||||||
<ha-svg-icon .path=${mdiCastConnected}></ha-svg-icon>
|
|
||||||
Manage
|
|
||||||
</mwc-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<div class="spacer"></div>
|
|
||||||
<mwc-button @click=${this._handleLogout}>Log out</mwc-button>
|
|
||||||
</div>
|
|
||||||
</hc-layout>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
|
|
||||||
const llColl = atLeastVersion(this.connection.haVersion, 0, 107)
|
|
||||||
? getLovelaceCollection(this.connection)
|
|
||||||
: getLegacyLovelaceCollection(this.connection);
|
|
||||||
// We first do a single refresh because we need to check if there is LL
|
|
||||||
// configuration.
|
|
||||||
llColl.refresh().then(
|
|
||||||
() => {
|
|
||||||
llColl.subscribe((config) => {
|
|
||||||
this.lovelaceConfig = config;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
this.lovelaceConfig = null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.askWrite = askWrite();
|
|
||||||
|
|
||||||
this.castManager.addEventListener("state-changed", () => {
|
|
||||||
this.requestUpdate();
|
|
||||||
});
|
|
||||||
this.castManager.addEventListener("connection-changed", () => {
|
|
||||||
this.requestUpdate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps) {
|
|
||||||
super.updated(changedProps);
|
|
||||||
toggleAttribute(
|
|
||||||
this,
|
|
||||||
"hide-icons",
|
|
||||||
this.lovelaceConfig
|
|
||||||
? !this.lovelaceConfig.views.some((view) => view.icon)
|
|
||||||
: true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleSkipSaveTokens() {
|
|
||||||
this.askWrite = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleSaveTokens() {
|
|
||||||
enableWrite();
|
|
||||||
this.askWrite = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleLaunch() {
|
|
||||||
this.castManager.requestSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handlePickView(ev: Event) {
|
|
||||||
const path = (ev.currentTarget as any).getAttribute("data-path");
|
|
||||||
await ensureConnectedCastSession(this.castManager!, this.auth!);
|
|
||||||
castSendShowLovelaceView(this.castManager, this.auth.data.hassUrl, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleLogout() {
|
|
||||||
try {
|
|
||||||
await this.auth.revoke();
|
|
||||||
saveTokens(null);
|
|
||||||
if (this.castManager.castSession) {
|
|
||||||
this.castManager.castContext.endCurrentSession(true);
|
|
||||||
}
|
|
||||||
this.connection.close();
|
|
||||||
location.reload();
|
|
||||||
} catch (err: any) {
|
|
||||||
alert("Unable to log out!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.center-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question {
|
|
||||||
position: relative;
|
|
||||||
padding: 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.question:before {
|
|
||||||
border-radius: 4px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
content: "";
|
|
||||||
background-color: var(--primary-color);
|
|
||||||
opacity: 0.12;
|
|
||||||
will-change: opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection,
|
|
||||||
.connection a {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-button ha-svg-icon {
|
|
||||||
margin-right: 8px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-listbox {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-listbox ha-icon {
|
|
||||||
padding: 12px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-icon-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-icon-item[disabled] {
|
|
||||||
cursor: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host([hide-icons]) paper-icon-item {
|
|
||||||
--paper-item-icon-width: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-content a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"hc-cast": HcCast;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,326 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import { mdiCastConnected, mdiCast } from "@mdi/js";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
|
||||||
Auth,
|
|
||||||
Connection,
|
|
||||||
createConnection,
|
|
||||||
ERR_CANNOT_CONNECT,
|
|
||||||
ERR_HASS_HOST_REQUIRED,
|
|
||||||
ERR_INVALID_AUTH,
|
|
||||||
ERR_INVALID_HTTPS_TO_HTTP,
|
|
||||||
getAuth,
|
|
||||||
getAuthOptions,
|
|
||||||
} from "home-assistant-js-websocket";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, state } from "lit/decorators";
|
|
||||||
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/layouts/hass-loading-screen";
|
|
||||||
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
|
|
||||||
import "./hc-layout";
|
|
||||||
|
|
||||||
const seeFAQ = (qid) => html`
|
|
||||||
See <a href="./faq.html${qid ? `#${qid}` : ""}">the FAQ</a> for more
|
|
||||||
information.
|
|
||||||
`;
|
|
||||||
const translateErr = (err) =>
|
|
||||||
err === ERR_CANNOT_CONNECT
|
|
||||||
? "Unable to connect"
|
|
||||||
: err === ERR_HASS_HOST_REQUIRED
|
|
||||||
? "Please enter a Home Assistant URL."
|
|
||||||
: err === ERR_INVALID_HTTPS_TO_HTTP
|
|
||||||
? html`
|
|
||||||
Cannot connect to Home Assistant instances over "http://".
|
|
||||||
${seeFAQ("https")}
|
|
||||||
`
|
|
||||||
: `Unknown error (${err}).`;
|
|
||||||
|
|
||||||
const INTRO = html`
|
|
||||||
<p>
|
|
||||||
Home Assistant Cast allows you to cast your Home Assistant installation to
|
|
||||||
Chromecast video devices and to Google Assistant devices with a screen.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
For more information, see the
|
|
||||||
<a href="./faq.html">frequently asked questions</a>.
|
|
||||||
</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
@customElement("hc-connect")
|
|
||||||
export class HcConnect extends LitElement {
|
|
||||||
@state() private loading = false;
|
|
||||||
|
|
||||||
// If we had stored credentials but we cannot connect,
|
|
||||||
// show a screen asking retry or logout.
|
|
||||||
@state() private cannotConnect = false;
|
|
||||||
|
|
||||||
@state() private error?: string | TemplateResult;
|
|
||||||
|
|
||||||
@state() private auth?: Auth;
|
|
||||||
|
|
||||||
@state() private connection?: Connection;
|
|
||||||
|
|
||||||
@state() private castManager?: CastManager | null;
|
|
||||||
|
|
||||||
private openDemo = false;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
if (this.cannotConnect) {
|
|
||||||
const tokens = loadTokens();
|
|
||||||
return html`
|
|
||||||
<hc-layout>
|
|
||||||
<div class="card-content">
|
|
||||||
Unable to connect to ${tokens!.hassUrl}.
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<a href="/">
|
|
||||||
<mwc-button> Retry </mwc-button>
|
|
||||||
</a>
|
|
||||||
<div class="spacer"></div>
|
|
||||||
<mwc-button @click=${this._handleLogout}>Log out</mwc-button>
|
|
||||||
</div>
|
|
||||||
</hc-layout>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.castManager === undefined || this.loading) {
|
|
||||||
return html` <hass-loading-screen no-toolbar></hass-loading-screen> `;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.castManager === null) {
|
|
||||||
return html`
|
|
||||||
<hc-layout>
|
|
||||||
<div class="card-content">
|
|
||||||
${INTRO}
|
|
||||||
<p class="error">
|
|
||||||
The Cast API is not available in your browser.
|
|
||||||
${seeFAQ("browser")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</hc-layout>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.auth) {
|
|
||||||
return html`
|
|
||||||
<hc-layout>
|
|
||||||
<div class="card-content">
|
|
||||||
${INTRO}
|
|
||||||
<p>
|
|
||||||
To get started, enter your Home Assistant URL and click authorize.
|
|
||||||
If you want a preview instead, click the show demo button.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<paper-input
|
|
||||||
label="Home Assistant URL"
|
|
||||||
placeholder="https://abcdefghijklmnop.ui.nabu.casa"
|
|
||||||
@keydown=${this._handleInputKeyDown}
|
|
||||||
></paper-input>
|
|
||||||
</p>
|
|
||||||
${this.error ? html` <p class="error">${this.error}</p> ` : ""}
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<mwc-button @click=${this._handleDemo}>
|
|
||||||
Show Demo
|
|
||||||
<ha-svg-icon
|
|
||||||
.path=${this.castManager.castState === "CONNECTED"
|
|
||||||
? mdiCastConnected
|
|
||||||
: mdiCast}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-button>
|
|
||||||
<div class="spacer"></div>
|
|
||||||
<mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
|
|
||||||
</div>
|
|
||||||
</hc-layout>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<hc-cast
|
|
||||||
.connection=${this.connection}
|
|
||||||
.auth=${this.auth}
|
|
||||||
.castManager=${this.castManager}
|
|
||||||
></hc-cast>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
import("./hc-cast");
|
|
||||||
|
|
||||||
getCastManager().then(
|
|
||||||
async (mgr) => {
|
|
||||||
this.castManager = mgr;
|
|
||||||
mgr.addEventListener("connection-changed", () => {
|
|
||||||
this.requestUpdate();
|
|
||||||
});
|
|
||||||
mgr.addEventListener("state-changed", () => {
|
|
||||||
if (this.openDemo && mgr.castState === "CONNECTED" && !this.auth) {
|
|
||||||
castSendShowDemo(mgr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (location.search.indexOf("auth_callback=1") !== -1) {
|
|
||||||
this._tryConnection("auth-callback");
|
|
||||||
} else if (loadTokens()) {
|
|
||||||
this._tryConnection("saved-tokens");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.castManager = null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
registerServiceWorker(this, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleDemo() {
|
|
||||||
this.openDemo = true;
|
|
||||||
if (this.castManager!.status && !this.castManager!.status.showDemo) {
|
|
||||||
castSendShowDemo(this.castManager!);
|
|
||||||
} else {
|
|
||||||
this.castManager!.requestSession();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleInputKeyDown(ev: KeyboardEvent) {
|
|
||||||
// Handle pressing enter.
|
|
||||||
if (ev.keyCode === 13) {
|
|
||||||
this._handleConnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleConnect() {
|
|
||||||
const inputEl = this.shadowRoot!.querySelector("paper-input")!;
|
|
||||||
const value = inputEl.value || "";
|
|
||||||
this.error = undefined;
|
|
||||||
|
|
||||||
if (value === "") {
|
|
||||||
this.error = "Please enter a Home Assistant URL.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (value.indexOf("://") === -1) {
|
|
||||||
this.error =
|
|
||||||
"Please enter your full URL, including the protocol part (https://).";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let url: URL;
|
|
||||||
try {
|
|
||||||
url = new URL(value);
|
|
||||||
} catch (err: any) {
|
|
||||||
this.error = "Invalid URL";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.protocol === "http:" && url.hostname !== "localhost") {
|
|
||||||
this.error = translateErr(ERR_INVALID_HTTPS_TO_HTTP);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this._tryConnection("user-request", `${url.protocol}//${url.host}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _tryConnection(
|
|
||||||
init: "auth-callback" | "user-request" | "saved-tokens",
|
|
||||||
hassUrl?: string
|
|
||||||
) {
|
|
||||||
const options: getAuthOptions = {
|
|
||||||
saveTokens,
|
|
||||||
loadTokens: () => Promise.resolve(loadTokens()),
|
|
||||||
};
|
|
||||||
if (hassUrl) {
|
|
||||||
options.hassUrl = hassUrl;
|
|
||||||
}
|
|
||||||
let auth: Auth;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.loading = true;
|
|
||||||
auth = await getAuth(options);
|
|
||||||
} catch (err: any) {
|
|
||||||
if (init === "saved-tokens" && err === ERR_CANNOT_CONNECT) {
|
|
||||||
this.cannotConnect = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.error = translateErr(err);
|
|
||||||
this.loading = false;
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
// Clear url if we have a auth callback in url.
|
|
||||||
if (location.search.includes("auth_callback=1")) {
|
|
||||||
history.replaceState(null, "", location.pathname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let conn: Connection;
|
|
||||||
|
|
||||||
try {
|
|
||||||
conn = await createConnection({ auth });
|
|
||||||
} catch (err: any) {
|
|
||||||
// In case of saved tokens, silently solve problems.
|
|
||||||
if (init === "saved-tokens") {
|
|
||||||
if (err === ERR_CANNOT_CONNECT) {
|
|
||||||
this.cannotConnect = true;
|
|
||||||
} else if (err === ERR_INVALID_AUTH) {
|
|
||||||
saveTokens(null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.error = translateErr(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.auth = auth;
|
|
||||||
this.connection = conn;
|
|
||||||
this.castManager!.auth = auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleLogout() {
|
|
||||||
try {
|
|
||||||
saveTokens(null);
|
|
||||||
location.reload();
|
|
||||||
} catch (err: any) {
|
|
||||||
alert("Unable to log out!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
.card-content a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
.card-actions a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error a {
|
|
||||||
color: darkred;
|
|
||||||
}
|
|
||||||
|
|
||||||
mwc-button ha-svg-icon {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spacer {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"hc-connect": HcConnect;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,165 +0,0 @@
|
|||||||
import {
|
|
||||||
Auth,
|
|
||||||
Connection,
|
|
||||||
getUser,
|
|
||||||
HassUser,
|
|
||||||
} from "home-assistant-js-websocket";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import "../../../../src/components/ha-card";
|
|
||||||
|
|
||||||
@customElement("hc-layout")
|
|
||||||
class HcLayout extends LitElement {
|
|
||||||
@property() public subtitle?: string | undefined;
|
|
||||||
|
|
||||||
@property() public auth?: Auth;
|
|
||||||
|
|
||||||
@property() public connection?: Connection;
|
|
||||||
|
|
||||||
@property() public user?: HassUser;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<ha-card>
|
|
||||||
<div class="layout">
|
|
||||||
<img
|
|
||||||
class="hero"
|
|
||||||
alt="A Google Nest Hub with a Home Assistant dashboard on its screen"
|
|
||||||
src="/images/google-nest-hub.png"
|
|
||||||
/>
|
|
||||||
<h1 class="card-header">
|
|
||||||
Home Assistant Cast${this.subtitle ? ` – ${this.subtitle}` : ""}
|
|
||||||
${this.auth
|
|
||||||
? html`
|
|
||||||
<div class="subtitle">
|
|
||||||
<a href=${this.auth.data.hassUrl} target="_blank"
|
|
||||||
>${this.auth.data.hassUrl.substr(
|
|
||||||
this.auth.data.hassUrl.indexOf("//") + 2
|
|
||||||
)}</a
|
|
||||||
>
|
|
||||||
${this.user ? html` – ${this.user.name} ` : ""}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</h1>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
<div class="footer">
|
|
||||||
<a href="./faq.html">Frequently Asked Questions</a> – Found a bug?
|
|
||||||
<a
|
|
||||||
href="https://github.com/home-assistant/frontend/issues"
|
|
||||||
target="_blank"
|
|
||||||
>Let us know!</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
|
|
||||||
if (this.connection) {
|
|
||||||
getUser(this.connection).then((user) => {
|
|
||||||
this.user = user;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
min-height: 100%;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-card {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
color: var(--ha-card-header-color, --primary-text-color);
|
|
||||||
font-family: var(--ha-card-header-font-family, inherit);
|
|
||||||
font-size: var(--ha-card-header-font-size, 24px);
|
|
||||||
letter-spacing: -0.012em;
|
|
||||||
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);
|
|
||||||
line-height: initial;
|
|
||||||
}
|
|
||||||
.subtitle a {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::slotted(.card-content:not(:first-child)),
|
|
||||||
slot:not(:first-child)::slotted(.card-content) {
|
|
||||||
padding-top: 0px;
|
|
||||||
margin-top: -8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::slotted(.section-header) {
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 4px 16px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::slotted(.card-content) {
|
|
||||||
padding: 16px;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host ::slotted(.card-actions) {
|
|
||||||
border-top: 1px solid #e8e8e8;
|
|
||||||
padding: 5px 16px;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 8px 0 24px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
.footer a {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (max-width: 500px) {
|
|
||||||
:host {
|
|
||||||
justify-content: flex-start;
|
|
||||||
min-height: 90%;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"hc-layout": HcLayout;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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();
|
|
@@ -1,2 +0,0 @@
|
|||||||
/* eslint-disable no-undef */
|
|
||||||
export const castContext = cast.framework.CastReceiverContext.getInstance();
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user