Compare commits
1 Commits
restructur
...
20220523.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ef92ed927e |
@@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Home Assistant Frontend",
|
|
||||||
"image": "mcr.microsoft.com/devcontainers/python:0-3.10",
|
|
||||||
"appPort": "8124:8123",
|
|
||||||
"postCreateCommand": "script/bootstrap",
|
|
||||||
"containerEnv": {
|
|
||||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}",
|
|
||||||
"DEVCONTAINER": "true"
|
|
||||||
},
|
|
||||||
"remoteUser": "vscode",
|
|
||||||
"remoteEnv": {
|
|
||||||
"PATH": "${containerEnv:PATH}:${containerWorkspaceFolder}/node_modules/.bin:/home/vscode/.local/bin"
|
|
||||||
},
|
|
||||||
"features": {
|
|
||||||
"ghcr.io/devcontainers/features/node:1": {
|
|
||||||
"version": "16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
13
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/python-3/.devcontainer/base.Dockerfile
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/python:0-3.9
|
||||||
|
|
||||||
|
ENV \
|
||||||
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
|
DEVCONTAINER=true \
|
||||||
|
PATH=$PATH:./node_modules/.bin
|
||||||
|
|
||||||
|
# Install nvm
|
||||||
|
COPY .nvmrc /tmp/.nvmrc
|
||||||
|
RUN \
|
||||||
|
su vscode -c \
|
||||||
|
"source /usr/local/share/nvm/nvm.sh && nvm install $(cat /tmp/.nvmrc) 2>&1"
|
37
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "Home Assistant Frontend",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"context": ".."
|
||||||
|
},
|
||||||
|
"appPort": "8124:8123",
|
||||||
|
"context": "..",
|
||||||
|
"postCreateCommand": "script/bootstrap",
|
||||||
|
"extensions": [
|
||||||
|
"github.vscode-pull-request-github",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"bierner.lit-html",
|
||||||
|
"runem.lit-plugin",
|
||||||
|
"ms-python.vscode-pylance"
|
||||||
|
],
|
||||||
|
"containerEnv": {
|
||||||
|
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"terminal.integrated.shell.linux": "/bin/bash",
|
||||||
|
"files.eol": "\n",
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.formatOnPaste": false,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.formatOnType": true,
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"files.trimTrailingWhitespace": true
|
||||||
|
}
|
||||||
|
}
|
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
|
|||||||
<!--
|
<!--
|
||||||
Provide details about the versions you are using, which helps us reproducing
|
Provide details about the versions you are using, which helps us reproducing
|
||||||
and finding the issue quicker. Version information is found in the
|
and finding the issue quicker. Version information is found in the
|
||||||
Home Assistant frontend: Settings -> About.
|
Home Assistant frontend: Configuration -> Info.
|
||||||
|
|
||||||
Browser version and operating system is important! Please try to replicate
|
Browser version and operating system is important! Please try to replicate
|
||||||
your issue in a different browser and be sure to include your findings.
|
your issue in a different browser and be sure to include your findings.
|
||||||
|
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -64,7 +64,7 @@ body:
|
|||||||
label: What version of Home Assistant Core has the issue?
|
label: What version of Home Assistant Core has the issue?
|
||||||
placeholder: core-
|
placeholder: core-
|
||||||
description: >
|
description: >
|
||||||
Can be found in: [Settings -> About](https://my.home-assistant.io/redirect/info/).
|
Can be found in the Configuration panel -> Info.
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: What was the last working version of Home Assistant Core?
|
label: What was the last working version of Home Assistant Core?
|
||||||
|
18
.github/dependabot.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: weekly
|
|
||||||
time: "06:00"
|
|
||||||
open-pull-requests-limit: 10
|
|
||||||
- package-ecosystem: "npm"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
||||||
time: "06:00"
|
|
||||||
open-pull-requests-limit: 5
|
|
||||||
ignore:
|
|
||||||
# Ignore rollup and plugins until everything else is updated
|
|
||||||
- dependency-name: "*rollup*"
|
|
||||||
- dependency-name: "@rollup/*"
|
|
90
.github/workflows/cast_deployment.yaml
vendored
@@ -1,90 +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.3.0
|
|
||||||
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
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
|
|
||||||
- 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.3.0
|
|
||||||
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
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
|
|
||||||
- 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 }}
|
|
19
.github/workflows/ci.yaml
vendored
@@ -11,18 +11,17 @@ on:
|
|||||||
- master
|
- master
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: 16
|
NODE_VERSION: 14
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
NODE_OPTIONS: --max_old_space_size=6144
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -44,9 +43,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -63,9 +62,9 @@ jobs:
|
|||||||
needs: [lint, test]
|
needs: [lint, test]
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -82,9 +81,9 @@ jobs:
|
|||||||
needs: [lint, test]
|
needs: [lint, test]
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
8
.github/workflows/codeql-analysis.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
# We must fetch at least the immediate parents so that if this is
|
# We must fetch at least the immediate parents so that if this is
|
||||||
# a pull request then we can checkout the head.
|
# a pull request then we can checkout the head.
|
||||||
@@ -36,14 +36,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@v1
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
@@ -57,4 +57,4 @@ jobs:
|
|||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@v1
|
||||||
|
35
.github/workflows/demo.yaml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Demo
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODE_VERSION: 14
|
||||||
|
NODE_OPTIONS: --max_old_space_size=6144
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out files from GitHub
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: yarn
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
|
- name: Build Demo
|
||||||
|
run: ./node_modules/.bin/gulp build-demo
|
||||||
|
- name: Deploy to Netlify
|
||||||
|
uses: netlify/actions/cli@master
|
||||||
|
env:
|
||||||
|
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||||
|
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
||||||
|
with:
|
||||||
|
args: deploy --dir=demo/dist --prod
|
91
.github/workflows/demo_deployment.yaml
vendored
@@ -1,91 +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 != '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.3.0
|
|
||||||
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
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
|
|
||||||
- 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 == '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.3.0
|
|
||||||
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
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
|
|
||||||
- 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 }}
|
|
45
.github/workflows/design_deployment.yaml
vendored
@@ -1,45 +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.3.0
|
|
||||||
|
|
||||||
- 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
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
|
|
||||||
- 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 }}
|
|
54
.github/workflows/design_preview.yaml
vendored
@@ -1,54 +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.3.0
|
|
||||||
|
|
||||||
- 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
|
|
||||||
env:
|
|
||||||
CI: true
|
|
||||||
|
|
||||||
- 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"
|
|
2
.github/workflows/lock.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v4.0.0
|
- uses: dessant/lock-threads@v2.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-lock-inactive-days: "30"
|
issue-lock-inactive-days: "30"
|
||||||
|
19
.github/workflows/netflify.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: Netlify
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trigger_builds:
|
||||||
|
name: Trigger netlify build preview
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- name: Trigger Cast build
|
||||||
|
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_CAST_DEV_BUILD_HOOK }}
|
||||||
|
|
||||||
|
- name: Trigger Demo build
|
||||||
|
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }}
|
||||||
|
|
||||||
|
- name: Trigger Design build
|
||||||
|
run: curl -X POST -d "NIGHTLY" https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }}
|
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.3.0
|
|
||||||
|
|
||||||
- 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.js 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
|
|
48
.github/workflows/release.yaml
vendored
@@ -6,8 +6,8 @@ on:
|
|||||||
- published
|
- published
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "3.10"
|
PYTHON_VERSION: 3.8
|
||||||
NODE_VERSION: 16
|
NODE_VERSION: 14
|
||||||
NODE_OPTIONS: --max_old_space_size=6144
|
NODE_OPTIONS: --max_old_space_size=6144
|
||||||
|
|
||||||
# Set default workflow permissions
|
# Set default workflow permissions
|
||||||
@@ -21,21 +21,21 @@ jobs:
|
|||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write # Required to upload release assets
|
contents: write # Required to upload release assets
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Verify version
|
- name: Verify version
|
||||||
uses: home-assistant/actions/helpers/verify-version@master
|
uses: home-assistant/actions/helpers/verify-version@master
|
||||||
|
|
||||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: ${{ env.PYTHON_VERSION }}
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
uses: actions/setup-node@v3.6.0
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: yarn
|
cache: yarn
|
||||||
@@ -52,11 +52,11 @@ jobs:
|
|||||||
python3 -m pip install twine build
|
python3 -m pip install twine build
|
||||||
export TWINE_USERNAME="__token__"
|
export TWINE_USERNAME="__token__"
|
||||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
||||||
export SKIP_FETCH_NIGHTLY_TRANSLATIONS=1
|
|
||||||
script/release
|
script/release
|
||||||
|
|
||||||
- name: Upload release assets
|
- name: Upload release assets
|
||||||
uses: softprops/action-gh-release@v0.1.15
|
uses: softprops/action-gh-release@v0.1.14
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
dist/*.whl
|
dist/*.whl
|
||||||
@@ -74,11 +74,33 @@ jobs:
|
|||||||
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
|
version=$(echo "${{ github.ref }}" | awk -F"/" '{print $NF}' )
|
||||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Upload requirements.txt
|
||||||
uses: home-assistant/wheels@2022.10.1
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
abi: cp310
|
name: requirements
|
||||||
tag: musllinux_1_2
|
path: ./requirements.txt
|
||||||
arch: amd64
|
|
||||||
|
build-wheels:
|
||||||
|
name: Build wheels for ${{ matrix.arch }}
|
||||||
|
needs: wheels-init
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
|
||||||
|
tag:
|
||||||
|
- "3.9-alpine3.14"
|
||||||
|
steps:
|
||||||
|
- name: Download requirements.txt
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: requirements
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
uses: home-assistant/wheels@master
|
||||||
|
with:
|
||||||
|
tag: ${{ matrix.tag }}
|
||||||
|
arch: ${{ matrix.arch }}
|
||||||
|
wheels-host: ${{ secrets.WHEELS_HOST }}
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
|
wheels-user: wheels
|
||||||
requirements: "requirements.txt"
|
requirements: "requirements.txt"
|
||||||
|
2
.github/workflows/stale.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 90 days stale policy
|
- name: 90 days stale policy
|
||||||
uses: actions/stale@v7.0.0
|
uses: actions/stale@v3.0.13
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
days-before-stale: 90
|
days-before-stale: 90
|
||||||
|
4
.github/workflows/translations.yaml
vendored
@@ -8,7 +8,7 @@ on:
|
|||||||
- src/translations/en.json
|
- src/translations/en.json
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: 16
|
NODE_VERSION: 14
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
upload:
|
upload:
|
||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v3.3.0
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Upload Translations
|
- name: Upload Translations
|
||||||
run: |
|
run: |
|
||||||
|
13
.gitignore
vendored
@@ -2,10 +2,9 @@
|
|||||||
.reify-cache
|
.reify-cache
|
||||||
|
|
||||||
# build
|
# build
|
||||||
build/
|
build
|
||||||
dist/
|
hass_frontend/*
|
||||||
/hass_frontend/
|
dist
|
||||||
/translations/
|
|
||||||
|
|
||||||
# yarn
|
# yarn
|
||||||
.yarn/*
|
.yarn/*
|
||||||
@@ -15,7 +14,7 @@ dist/
|
|||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
.pnp.*
|
.pnp.*
|
||||||
/node_modules/
|
node_modules/*
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ npm-debug.log
|
|||||||
# venv stuff
|
# venv stuff
|
||||||
pyvenv.cfg
|
pyvenv.cfg
|
||||||
pip-selfcheck.json
|
pip-selfcheck.json
|
||||||
/venv/
|
venv/*
|
||||||
.venv
|
.venv
|
||||||
|
|
||||||
# vscode
|
# vscode
|
||||||
@@ -46,4 +45,4 @@ src/cast/dev_const.ts
|
|||||||
.tool-versions
|
.tool-versions
|
||||||
|
|
||||||
# Home Assistant config
|
# Home Assistant config
|
||||||
/config/
|
/config
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
#!/usr/bin/env sh
|
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
|
||||||
|
|
||||||
yarn run lint-staged --relative --shell "/bin/bash"
|
|
5
.vscode/extensions.json
vendored
@@ -2,8 +2,7 @@
|
|||||||
"recommendations": [
|
"recommendations": [
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"runem.lit-plugin",
|
"bierner.lit-html",
|
||||||
"github.vscode-pull-request-github",
|
"runem.lit-plugin"
|
||||||
"eamodio.gitlens"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
10
.vscode/tasks.json
vendored
@@ -181,7 +181,7 @@
|
|||||||
{
|
{
|
||||||
"label": "Run HA Core for Supervisor in devcontainer",
|
"label": "Run HA Core for Supervisor in devcontainer",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "SUPERVISOR=${input:supervisorHost} SUPERVISOR_TOKEN=${input:supervisorToken} script/core",
|
"command": "HASSIO=${input:supervisorHost} HASSIO_TOKEN=${input:supervisorToken} script/core",
|
||||||
"isBackground": true,
|
"isBackground": true,
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@@ -191,13 +191,7 @@
|
|||||||
"runOptions": {
|
"runOptions": {
|
||||||
"instanceLimit": 1
|
"instanceLimit": 1
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{
|
|
||||||
"label": "Setup and fetch nightly translations",
|
|
||||||
"type": "gulp",
|
|
||||||
"task": "setup-and-fetch-nightly-translations",
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"inputs": [
|
"inputs": [
|
||||||
{
|
{
|
||||||
|
29
.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
diff --git a/polyfillLoaders/EventTarget.js b/polyfillLoaders/EventTarget.js
|
||||||
|
index 4e18ade7ba485849f17f28c94c42f0e0e01ac387..8f34f4f646c7f7becc208fb5a546c96034fc74dc 100644
|
||||||
|
--- a/polyfillLoaders/EventTarget.js
|
||||||
|
+++ b/polyfillLoaders/EventTarget.js
|
||||||
|
@@ -6,16 +6,15 @@
|
||||||
|
let _ET;
|
||||||
|
let ET;
|
||||||
|
export default async function EventTarget() {
|
||||||
|
- return ET || init();
|
||||||
|
+ return ET || init();
|
||||||
|
}
|
||||||
|
async function init() {
|
||||||
|
- _ET = window.EventTarget;
|
||||||
|
- try {
|
||||||
|
- new _ET();
|
||||||
|
- }
|
||||||
|
- catch (_a) {
|
||||||
|
- _ET = (await import('event-target-shim')).EventTarget;
|
||||||
|
- }
|
||||||
|
- return (ET = _ET);
|
||||||
|
+ _ET = window.EventTarget;
|
||||||
|
+ try {
|
||||||
|
+ new _ET();
|
||||||
|
+ } catch (_a) {
|
||||||
|
+ _ET = (await import("event-target-shim")).default.EventTarget;
|
||||||
|
+ }
|
||||||
|
+ return (ET = _ET);
|
||||||
|
}
|
||||||
|
//# sourceMappingURL=EventTarget.js.map
|
12
.yarn/patches/@material/mwc-icon-button/remove-icon.patch
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
diff --git a/mwc-icon-button-base.js b/mwc-icon-button-base.js
|
||||||
|
index 45cdaab93ccc0a6daaaaabc01266dcdc32e46bfd..b3ea5b541597308d85f86ce6c23fd00785fda835 100644
|
||||||
|
--- a/mwc-icon-button-base.js
|
||||||
|
+++ b/mwc-icon-button-base.js
|
||||||
|
@@ -63,7 +63,6 @@ export class IconButtonBase extends LitElement {
|
||||||
|
@touchend="${this.handleRippleDeactivate}"
|
||||||
|
@touchcancel="${this.handleRippleDeactivate}"
|
||||||
|
>${this.renderRipple()}
|
||||||
|
- <i class="material-icons">${this.icon}</i>
|
||||||
|
<span
|
||||||
|
><slot></slot
|
||||||
|
></span>
|
566
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
5
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
785
.yarn/releases/yarn-3.2.0.cjs
vendored
Executable file
823
.yarn/releases/yarn-3.3.1.cjs
vendored
@@ -6,4 +6,4 @@ plugins:
|
|||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
spec: "@yarnpkg/plugin-interactive-tools"
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.3.1.cjs
|
yarnPath: .yarn/releases/yarn-3.2.0.cjs
|
||||||
|
7
build-scripts/.eslintrc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"import/no-extraneous-dependencies": 0,
|
||||||
|
"no-restricted-syntax": 0,
|
||||||
|
"no-console": 0
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "../.eslintrc.json",
|
"extends": "../.eslintrc.json",
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "off",
|
"import/no-extraneous-dependencies": 0,
|
||||||
"import/no-extraneous-dependencies": "off",
|
"global-require": 0
|
||||||
"import/extensions": "off",
|
|
||||||
"import/no-dynamic-require": "off",
|
|
||||||
"global-require": "off",
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"prefer-arrow-callback": "off"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
|
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
|
||||||
@@ -28,6 +29,7 @@ module.exports = function inlineConstants(babel, options, cwd) {
|
|||||||
const absolute = module.startsWith(".")
|
const absolute = module.startsWith(".")
|
||||||
? require.resolve(module, { paths: [cwd] })
|
? require.resolve(module, { paths: [cwd] })
|
||||||
: module;
|
: module;
|
||||||
|
// eslint-disable-next-line import/no-dynamic-require
|
||||||
return [absolute, require(absolute)];
|
return [absolute, require(absolute)];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const env = require("./env.js");
|
const env = require("./env.js");
|
||||||
const paths = require("./paths.js");
|
const paths = require("./paths.js");
|
||||||
|
|
||||||
// Files from NPM Packages that should not be imported
|
// Files from NPM Packages that should not be imported
|
||||||
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
||||||
module.exports.ignorePackages = ({ latestBuild }) => [
|
module.exports.ignorePackages = ({ latestBuild }) => [
|
||||||
// Part of yaml.js and only used for !!js functions that we don't use
|
// Part of yaml.js and only used for !!js functions that we don't use
|
||||||
require.resolve("esprima"),
|
require.resolve("esprima"),
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const paths = require("./paths.js");
|
const paths = require("./paths.js");
|
||||||
@@ -25,8 +26,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
version() {
|
version() {
|
||||||
const version = fs
|
const version = fs
|
||||||
.readFileSync(path.resolve(paths.polymer_dir, "pyproject.toml"), "utf8")
|
.readFileSync(path.resolve(paths.polymer_dir, "setup.cfg"), "utf8")
|
||||||
.match(/version\W+=\W"(\d{8}\.\d(?:\.dev)?)"/);
|
.match(/version\W+=\W(\d{8}\.\d)/);
|
||||||
if (!version) {
|
if (!version) {
|
||||||
throw Error("Version not found");
|
throw Error("Version not found");
|
||||||
}
|
}
|
||||||
|
@@ -1,40 +1,36 @@
|
|||||||
const del = import("del");
|
const del = require("del");
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const paths = require("../paths");
|
const paths = require("../paths");
|
||||||
require("./translations");
|
require("./translations");
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"clean",
|
"clean",
|
||||||
gulp.parallel("clean-translations", async () =>
|
gulp.parallel("clean-translations", () =>
|
||||||
(await del).deleteSync([paths.app_output_root, paths.build_dir])
|
del([paths.app_output_root, paths.build_dir])
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"clean-demo",
|
"clean-demo",
|
||||||
gulp.parallel("clean-translations", async () =>
|
gulp.parallel("clean-translations", () =>
|
||||||
(await del).deleteSync([paths.demo_output_root, paths.build_dir])
|
del([paths.demo_output_root, paths.build_dir])
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"clean-cast",
|
"clean-cast",
|
||||||
gulp.parallel("clean-translations", async () =>
|
gulp.parallel("clean-translations", () =>
|
||||||
(await del).deleteSync([paths.cast_output_root, paths.build_dir])
|
del([paths.cast_output_root, paths.build_dir])
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task("clean-hassio", async () =>
|
gulp.task("clean-hassio", () =>
|
||||||
(await del).deleteSync([paths.hassio_output_root, paths.build_dir])
|
del([paths.hassio_output_root, paths.build_dir])
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"clean-gallery",
|
"clean-gallery",
|
||||||
gulp.parallel("clean-translations", async () =>
|
gulp.parallel("clean-translations", () =>
|
||||||
(await del).deleteSync([
|
del([paths.gallery_output_root, paths.gallery_build, paths.build_dir])
|
||||||
paths.gallery_output_root,
|
|
||||||
paths.gallery_build,
|
|
||||||
paths.build_dir,
|
|
||||||
])
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@@ -1,12 +1,17 @@
|
|||||||
|
const del = require("del");
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const fs = require("fs/promises");
|
const fs = require("fs");
|
||||||
const mapStream = require("map-stream");
|
const mapStream = require("map-stream");
|
||||||
|
|
||||||
const inDirFrontend = "translations/frontend";
|
const inDirFrontend = "translations/frontend";
|
||||||
const inDirBackend = "translations/backend";
|
const inDirBackend = "translations/backend";
|
||||||
|
const downloadDir = "translations/downloads";
|
||||||
const srcMeta = "src/translations/translationMetadata.json";
|
const srcMeta = "src/translations/translationMetadata.json";
|
||||||
|
|
||||||
const encoding = "utf8";
|
const encoding = "utf8";
|
||||||
|
|
||||||
|
const tasks = [];
|
||||||
|
|
||||||
function hasHtml(data) {
|
function hasHtml(data) {
|
||||||
return /<[a-z][\s\S]*>/i.test(data);
|
return /<[a-z][\s\S]*>/i.test(data);
|
||||||
}
|
}
|
||||||
@@ -41,29 +46,50 @@ function checkHtml() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backend translations do not currently pass HTML check so are excluded here for now
|
let taskName = "clean-downloaded-translations";
|
||||||
gulp.task("check-translations-html", function () {
|
gulp.task(taskName, function () {
|
||||||
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
|
return del([`${downloadDir}/**`]);
|
||||||
});
|
});
|
||||||
|
tasks.push(taskName);
|
||||||
|
|
||||||
gulp.task("check-all-files-exist", async function () {
|
taskName = "check-translations-html";
|
||||||
const file = await fs.readFile(srcMeta, { encoding });
|
gulp.task(taskName, function () {
|
||||||
|
return gulp.src(`${downloadDir}/*.json`).pipe(checkHtml());
|
||||||
|
});
|
||||||
|
tasks.push(taskName);
|
||||||
|
|
||||||
|
taskName = "check-all-files-exist";
|
||||||
|
gulp.task(taskName, function () {
|
||||||
|
const file = fs.readFileSync(srcMeta, { encoding });
|
||||||
const meta = JSON.parse(file);
|
const meta = JSON.parse(file);
|
||||||
const writings = [];
|
|
||||||
Object.keys(meta).forEach((lang) => {
|
Object.keys(meta).forEach((lang) => {
|
||||||
writings.push(
|
if (!fs.existsSync(`${inDirFrontend}/${lang}.json`)) {
|
||||||
fs.writeFile(`${inDirFrontend}/${lang}.json`, JSON.stringify({}), {
|
fs.writeFileSync(`${inDirFrontend}/${lang}.json`, JSON.stringify({}));
|
||||||
flag: "wx",
|
}
|
||||||
}),
|
if (!fs.existsSync(`${inDirBackend}/${lang}.json`)) {
|
||||||
fs.writeFile(`${inDirBackend}/${lang}.json`, JSON.stringify({}), {
|
fs.writeFileSync(`${inDirBackend}/${lang}.json`, JSON.stringify({}));
|
||||||
flag: "wx",
|
}
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
await Promise.allSettled(writings);
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
|
tasks.push(taskName);
|
||||||
|
|
||||||
|
taskName = "move-downloaded-translations";
|
||||||
|
gulp.task(taskName, function () {
|
||||||
|
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDirFrontend));
|
||||||
|
});
|
||||||
|
tasks.push(taskName);
|
||||||
|
|
||||||
|
taskName = "check-downloaded-translations";
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"check-downloaded-translations",
|
taskName,
|
||||||
gulp.series("check-translations-html", "check-all-files-exist")
|
gulp.series(
|
||||||
|
"check-translations-html",
|
||||||
|
"move-downloaded-translations",
|
||||||
|
"check-all-files-exist",
|
||||||
|
"clean-downloaded-translations"
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
tasks.push(taskName);
|
||||||
|
|
||||||
|
module.exports = tasks;
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
// Tasks to generate entry HTML
|
// Tasks to generate entry HTML
|
||||||
|
/* eslint-disable import/no-dynamic-require */
|
||||||
|
/* eslint-disable global-require */
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const fs = require("fs-extra");
|
const fs = require("fs-extra");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
@@ -89,9 +91,7 @@ gulp.task("gen-pages-prod", (done) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("gen-index-app-dev", (done) => {
|
gulp.task("gen-index-app-dev", (done) => {
|
||||||
let latestAppJS;
|
let latestAppJS, latestCoreJS, latestCustomPanelJS;
|
||||||
let latestCoreJS;
|
|
||||||
let latestCustomPanelJS;
|
|
||||||
|
|
||||||
if (env.useWDS()) {
|
if (env.useWDS()) {
|
||||||
latestAppJS = "http://localhost:8000/src/entrypoints/app.ts";
|
latestAppJS = "http://localhost:8000/src/entrypoints/app.ts";
|
||||||
|
@@ -1,174 +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 { 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({
|
|
||||||
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,3 +1,4 @@
|
|||||||
|
/* eslint-disable */
|
||||||
// Run demo develop mode
|
// Run demo develop mode
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@@ -40,7 +41,7 @@ gulp.task("gather-gallery-pages", async function gatherPages() {
|
|||||||
}
|
}
|
||||||
processed.add(pageId);
|
processed.add(pageId);
|
||||||
|
|
||||||
const [category] = pageId.split("/", 2);
|
const [category, name] = pageId.split("/", 2);
|
||||||
|
|
||||||
const demoFile = path.resolve(pageDir, `${pageId}.ts`);
|
const demoFile = path.resolve(pageDir, `${pageId}.ts`);
|
||||||
const descriptionFile = path.resolve(pageDir, `${pageId}.markdown`);
|
const descriptionFile = path.resolve(pageDir, `${pageId}.markdown`);
|
||||||
|
@@ -156,12 +156,3 @@ gulp.task("gen-icons-json", (done) => {
|
|||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("gen-dummy-icons-json", (done) => {
|
|
||||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
||||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(path.resolve(OUTPUT_DIR, "iconList.json"), "[]");
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
@@ -9,7 +9,6 @@ require("./compress.js");
|
|||||||
require("./rollup.js");
|
require("./rollup.js");
|
||||||
require("./gather-static.js");
|
require("./gather-static.js");
|
||||||
require("./translations.js");
|
require("./translations.js");
|
||||||
require("./gen-icons-json.js");
|
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"develop-hassio",
|
"develop-hassio",
|
||||||
@@ -18,7 +17,6 @@ gulp.task(
|
|||||||
process.env.NODE_ENV = "development";
|
process.env.NODE_ENV = "development";
|
||||||
},
|
},
|
||||||
"clean-hassio",
|
"clean-hassio",
|
||||||
"gen-dummy-icons-json",
|
|
||||||
"gen-index-hassio-dev",
|
"gen-index-hassio-dev",
|
||||||
"build-supervisor-translations",
|
"build-supervisor-translations",
|
||||||
"copy-translations-supervisor",
|
"copy-translations-supervisor",
|
||||||
@@ -35,7 +33,6 @@ gulp.task(
|
|||||||
process.env.NODE_ENV = "production";
|
process.env.NODE_ENV = "production";
|
||||||
},
|
},
|
||||||
"clean-hassio",
|
"clean-hassio",
|
||||||
"gen-dummy-icons-json",
|
|
||||||
"build-supervisor-translations",
|
"build-supervisor-translations",
|
||||||
"copy-translations-supervisor",
|
"copy-translations-supervisor",
|
||||||
"build-locale-data",
|
"build-locale-data",
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
const del = import("del");
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
|
const del = require("del");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@@ -6,7 +8,7 @@ const paths = require("../paths");
|
|||||||
|
|
||||||
const outDir = "build/locale-data";
|
const outDir = "build/locale-data";
|
||||||
|
|
||||||
gulp.task("clean-locale-data", async () => (await del).deleteSync([outDir]));
|
gulp.task("clean-locale-data", () => del([outDir]));
|
||||||
|
|
||||||
gulp.task("ensure-locale-data-build-dir", (done) => {
|
gulp.task("ensure-locale-data-build-dir", (done) => {
|
||||||
if (!fs.existsSync(outDir)) {
|
if (!fs.existsSync(outDir)) {
|
||||||
|
@@ -5,9 +5,9 @@ const rollup = require("rollup");
|
|||||||
const handler = require("serve-handler");
|
const handler = require("serve-handler");
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
const log = require("fancy-log");
|
const log = require("fancy-log");
|
||||||
const open = require("open");
|
|
||||||
const rollupConfig = require("../rollup");
|
const rollupConfig = require("../rollup");
|
||||||
const paths = require("../paths");
|
const paths = require("../paths");
|
||||||
|
const open = require("open");
|
||||||
|
|
||||||
const bothBuilds = (createConfigFunc, params) =>
|
const bothBuilds = (createConfigFunc, params) =>
|
||||||
gulp.series(
|
gulp.series(
|
||||||
@@ -30,11 +30,11 @@ const bothBuilds = (createConfigFunc, params) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
function createServer(serveOptions) {
|
function createServer(serveOptions) {
|
||||||
const server = http.createServer((request, response) =>
|
const server = http.createServer((request, response) => {
|
||||||
handler(request, response, {
|
return handler(request, response, {
|
||||||
public: serveOptions.root,
|
public: serveOptions.root,
|
||||||
})
|
});
|
||||||
);
|
});
|
||||||
|
|
||||||
server.listen(
|
server.listen(
|
||||||
serveOptions.port,
|
serveOptions.port,
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
// Generate service worker.
|
// Generate service worker.
|
||||||
// Based on manifest, create a file with the content as service_worker.js
|
// Based on manifest, create a file with the content as service_worker.js
|
||||||
|
/* eslint-disable import/no-dynamic-require */
|
||||||
|
/* eslint-disable global-require */
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs-extra");
|
const fs = require("fs-extra");
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
const del = import("del");
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
const crypto = require("crypto");
|
const crypto = require("crypto");
|
||||||
|
const del = require("del");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const source = require("vinyl-source-stream");
|
const source = require("vinyl-source-stream");
|
||||||
const vinylBuffer = require("vinyl-buffer");
|
const vinylBuffer = require("vinyl-buffer");
|
||||||
@@ -13,8 +15,6 @@ const { mapFiles } = require("../util");
|
|||||||
const env = require("../env");
|
const env = require("../env");
|
||||||
const paths = require("../paths");
|
const paths = require("../paths");
|
||||||
|
|
||||||
require("./fetch-nightly-translations");
|
|
||||||
|
|
||||||
const inFrontendDir = "translations/frontend";
|
const inFrontendDir = "translations/frontend";
|
||||||
const inBackendDir = "translations/backend";
|
const inBackendDir = "translations/backend";
|
||||||
const workDir = "build/translations";
|
const workDir = "build/translations";
|
||||||
@@ -23,13 +23,10 @@ const coreDir = workDir + "/core";
|
|||||||
const outDir = workDir + "/output";
|
const outDir = workDir + "/output";
|
||||||
let mergeBackend = false;
|
let mergeBackend = false;
|
||||||
|
|
||||||
gulp.task(
|
gulp.task("translations-enable-merge-backend", (done) => {
|
||||||
"translations-enable-merge-backend",
|
mergeBackend = true;
|
||||||
gulp.parallel((done) => {
|
done();
|
||||||
mergeBackend = true;
|
});
|
||||||
done();
|
|
||||||
}, "allow-setup-fetch-nightly-translations")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Panel translations which should be split from the core translations.
|
// Panel translations which should be split from the core translations.
|
||||||
const TRANSLATION_FRAGMENTS = Object.keys(
|
const TRANSLATION_FRAGMENTS = Object.keys(
|
||||||
@@ -120,7 +117,7 @@ function lokaliseTransform(data, original, file) {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
gulp.task("clean-translations", async () => (await del).deleteSync([workDir]));
|
gulp.task("clean-translations", () => del([workDir]));
|
||||||
|
|
||||||
gulp.task("ensure-translations-build-dir", (done) => {
|
gulp.task("ensure-translations-build-dir", (done) => {
|
||||||
if (!fs.existsSync(workDir)) {
|
if (!fs.existsSync(workDir)) {
|
||||||
@@ -173,24 +170,17 @@ gulp.task("build-master-translation", () => {
|
|||||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||||
.pipe(
|
.pipe(
|
||||||
merge({
|
merge({
|
||||||
fileName: "en.json",
|
fileName: "translationMaster.json",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.pipe(gulp.dest(fullDir));
|
.pipe(gulp.dest(workDir));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("build-merged-translations", () =>
|
gulp.task("build-merged-translations", () =>
|
||||||
gulp
|
gulp
|
||||||
.src(
|
.src([inFrontendDir + "/*.json", workDir + "/test.json"], {
|
||||||
[
|
allowEmpty: true,
|
||||||
inFrontendDir + "/*.json",
|
})
|
||||||
"!" + inFrontendDir + "/en.json",
|
|
||||||
workDir + "/test.json",
|
|
||||||
],
|
|
||||||
{
|
|
||||||
allowEmpty: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||||
.pipe(
|
.pipe(
|
||||||
flatmap((stream, file) => {
|
flatmap((stream, file) => {
|
||||||
@@ -203,7 +193,7 @@ gulp.task("build-merged-translations", () =>
|
|||||||
// than a base translation + region.
|
// than a base translation + region.
|
||||||
const tr = path.basename(file.history[0], ".json");
|
const tr = path.basename(file.history[0], ".json");
|
||||||
const subtags = tr.split("-");
|
const subtags = tr.split("-");
|
||||||
const src = [fullDir + "/en.json"];
|
const src = [workDir + "/translationMaster.json"];
|
||||||
for (let i = 1; i <= subtags.length; i++) {
|
for (let i = 1; i <= subtags.length; i++) {
|
||||||
const lang = subtags.slice(0, i).join("-");
|
const lang = subtags.slice(0, i).join("-");
|
||||||
if (lang === "test") {
|
if (lang === "test") {
|
||||||
@@ -388,6 +378,7 @@ gulp.task("build-translation-write-metadata", () =>
|
|||||||
if (value.nativeName) {
|
if (value.nativeName) {
|
||||||
newData[key] = value;
|
newData[key] = value;
|
||||||
} else {
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.warn(
|
console.warn(
|
||||||
`Skipping language ${key}. Native name was not translated.`
|
`Skipping language ${key}. Native name was not translated.`
|
||||||
);
|
);
|
||||||
@@ -420,10 +411,8 @@ gulp.task(
|
|||||||
gulp.task(
|
gulp.task(
|
||||||
"build-translations",
|
"build-translations",
|
||||||
gulp.series(
|
gulp.series(
|
||||||
gulp.parallel(
|
"clean-translations",
|
||||||
"fetch-nightly-translations",
|
"ensure-translations-build-dir",
|
||||||
gulp.series("clean-translations", "ensure-translations-build-dir")
|
|
||||||
),
|
|
||||||
"create-translations",
|
"create-translations",
|
||||||
"build-translation-fingerprints",
|
"build-translation-fingerprints",
|
||||||
"build-translation-write-metadata"
|
"build-translation-write-metadata"
|
||||||
@@ -433,10 +422,8 @@ gulp.task(
|
|||||||
gulp.task(
|
gulp.task(
|
||||||
"build-supervisor-translations",
|
"build-supervisor-translations",
|
||||||
gulp.series(
|
gulp.series(
|
||||||
gulp.parallel(
|
"clean-translations",
|
||||||
"fetch-nightly-translations",
|
"ensure-translations-build-dir",
|
||||||
gulp.series("clean-translations", "ensure-translations-build-dir")
|
|
||||||
),
|
|
||||||
"build-master-translation",
|
"build-master-translation",
|
||||||
"build-merged-translations",
|
"build-merged-translations",
|
||||||
"build-translation-fragment-supervisor",
|
"build-translation-fragment-supervisor",
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
// Tasks to run webpack.
|
// Tasks to run webpack.
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
@@ -68,6 +69,7 @@ const doneHandler = (done) => (err, stats) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(stats.toString("minimal"));
|
console.log(stats.toString("minimal"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@@ -1,30 +1 @@
|
|||||||
[
|
[]
|
||||||
{
|
|
||||||
"path": "M20,20H7A2,2 0 0,1 5,18V8.94L2.23,5.64C2.09,5.47 2,5.24 2,5A1,1 0 0,1 3,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20M8.5,7A0.5,0.5 0 0,0 8,7.5V8.5A0.5,0.5 0 0,0 8.5,9H18.5A0.5,0.5 0 0,0 19,8.5V7.5A0.5,0.5 0 0,0 18.5,7H8.5M8.5,11A0.5,0.5 0 0,0 8,11.5V12.5A0.5,0.5 0 0,0 8.5,13H18.5A0.5,0.5 0 0,0 19,12.5V11.5A0.5,0.5 0 0,0 18.5,11H8.5M8.5,15A0.5,0.5 0 0,0 8,15.5V16.5A0.5,0.5 0 0,0 8.5,17H13.5A0.5,0.5 0 0,0 14,16.5V15.5A0.5,0.5 0 0,0 13.5,15H8.5Z",
|
|
||||||
"name": "android-messages"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M20,12L17.5,10.5L15,12V4H20V12Z",
|
|
||||||
"name": "book-variant-multiple"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "M21,14H3V4H21M21,2H3C1.89,2 1,2.89 1,4V16A2,2 0 0,0 3,18H10L8,21V22H16V21L14,18H21A2,2 0 0,0 23,16V4C23,2.89 22.1,2 21,2Z",
|
|
||||||
"name": "desktop-mac"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "M21,14V4H3V14H21M21,2A2,2 0 0,1 23,4V16A2,2 0 0,1 21,18H14L16,21V22H8V21L10,18H3C1.89,18 1,17.1 1,16V4C1,2.89 1.89,2 3,2H21M4,5H15V10H4V5M16,5H20V7H16V5M20,8V13H16V8H20M4,11H9V13H4V11M10,11H15V13H10V11Z",
|
|
||||||
"name": "desktop-mac-dashboard"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "M22,24L16.75,19L17.38,21H4.5A2.5,2.5 0 0,1 2,18.5V3.5A2.5,2.5 0 0,1 4.5,1H19.5A2.5,2.5 0 0,1 22,3.5V24M12,6.8C9.32,6.8 7.44,7.95 7.44,7.95C8.47,7.03 10.27,6.5 10.27,6.5L10.1,6.33C8.41,6.36 6.88,7.53 6.88,7.53C5.16,11.12 5.27,14.22 5.27,14.22C6.67,16.03 8.75,15.9 8.75,15.9L9.46,15C8.21,14.73 7.42,13.62 7.42,13.62C7.42,13.62 9.3,14.9 12,14.9C14.7,14.9 16.58,13.62 16.58,13.62C16.58,13.62 15.79,14.73 14.54,15L15.25,15.9C15.25,15.9 17.33,16.03 18.73,14.22C18.73,14.22 18.84,11.12 17.12,7.53C17.12,7.53 15.59,6.36 13.9,6.33L13.73,6.5C13.73,6.5 15.53,7.03 16.56,7.95C16.56,7.95 14.68,6.8 12,6.8M9.93,10.59C10.58,10.59 11.11,11.16 11.1,11.86C11.1,12.55 10.58,13.13 9.93,13.13C9.29,13.13 8.77,12.55 8.77,11.86C8.77,11.16 9.28,10.59 9.93,10.59M14.1,10.59C14.75,10.59 15.27,11.16 15.27,11.86C15.27,12.55 14.75,13.13 14.1,13.13C13.46,13.13 12.94,12.55 12.94,11.86C12.94,11.16 13.45,10.59 14.1,10.59Z",
|
|
||||||
"name": "discord"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "M8.06,7.78C7.5,7.78 7.17,7.73 7.08,7.64L6.66,13.73C7.19,14.05 7.88,14.3 8.72,14.5C9.56,14.71 10.78,14.77 12.38,14.67C13.97,14.58 15.63,14.23 17.34,13.64L16.55,4.22C15.67,5.09 14.38,5.91 12.66,6.66C11.13,7.31 9.81,7.69 8.72,7.78H8.06M7.97,5.34C7.28,5.94 7,6.34 7.13,6.56C7.22,6.78 7.7,6.84 8.58,6.75C9.67,6.66 10.91,6.31 12.28,5.72C13.22,5.31 14.03,4.88 14.72,4.41C15.41,3.94 15.88,3.55 16.13,3.23C16.38,2.92 16.47,2.7 16.41,2.58C16.34,2.42 16.03,2.34 15.47,2.34C14.34,2.34 12.94,2.7 11.25,3.42C9.81,4.05 8.72,4.69 7.97,5.34M17.34,2.2C17.41,2.33 17.44,2.47 17.44,2.63L18.61,17C18.61,18.73 18,20.09 16.83,21.07C15.64,22.05 14.03,22.55 12,22.55C10,22.55 8.4,22.04 7.2,21C6,20 5.39,18.64 5.39,16.92L6.09,6.47C6.09,6.22 6.2,5.94 6.42,5.63C6.64,5.31 6.84,5.06 7.03,4.88L7.36,4.59C8.33,3.78 9.5,3.08 10.88,2.5C11.81,2.08 12.73,1.77 13.62,1.57C14.5,1.37 15.3,1.3 16,1.38C16.71,1.46 17.16,1.73 17.34,2.2Z",
|
|
||||||
"name": "google-home"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "M19.25,19H4.75V3H19.25M14,22H10V21H14M18,0H6A3,3 0 0,0 3,3V21A3,3 0 0,0 6,24H18A3,3 0 0,0 21,21V3A3,3 0 0,0 18,0Z",
|
|
||||||
"name": "tablet-android"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
@@ -81,13 +81,13 @@ module.exports = function (opts = {}) {
|
|||||||
opts.workerRegexp.flags
|
opts.workerRegexp.flags
|
||||||
);
|
);
|
||||||
if (!workerRegexp.test(code)) {
|
if (!workerRegexp.test(code)) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ms = new MagicString(code);
|
const ms = new MagicString(code);
|
||||||
// Reset the regexp
|
// Reset the regexp
|
||||||
workerRegexp.lastIndex = 0;
|
workerRegexp.lastIndex = 0;
|
||||||
for (;;) {
|
while (true) {
|
||||||
const match = workerRegexp.exec(code);
|
const match = workerRegexp.exec(code);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
break;
|
break;
|
||||||
@@ -98,7 +98,6 @@ module.exports = function (opts = {}) {
|
|||||||
// Parse the optional options object
|
// Parse the optional options object
|
||||||
if (match[3] && match[3].length > 0) {
|
if (match[3] && match[3].length > 0) {
|
||||||
// FIXME: ooooof!
|
// FIXME: ooooof!
|
||||||
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
||||||
optionsObject = new Function(`return ${match[3].slice(1)};`)();
|
optionsObject = new Function(`return ${match[3].slice(1)};`)();
|
||||||
}
|
}
|
||||||
delete optionsObject.type;
|
delete optionsObject.type;
|
||||||
@@ -111,14 +110,12 @@ module.exports = function (opts = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find worker file and store it as a chunk with ID prefixed for our loader
|
// 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;
|
const resolvedWorkerFile = (await this.resolve(workerFile, id)).id;
|
||||||
let chunkRefId;
|
let chunkRefId;
|
||||||
if (resolvedWorkerFile in refIds) {
|
if (resolvedWorkerFile in refIds) {
|
||||||
chunkRefId = refIds[resolvedWorkerFile];
|
chunkRefId = refIds[resolvedWorkerFile];
|
||||||
} else {
|
} else {
|
||||||
this.addWatchFile(resolvedWorkerFile);
|
this.addWatchFile(resolvedWorkerFile);
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
const source = await getBundledWorker(
|
const source = await getBundledWorker(
|
||||||
resolvedWorkerFile,
|
resolvedWorkerFile,
|
||||||
rollupOptions
|
rollupOptions
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const commonjs = require("@rollup/plugin-commonjs");
|
const commonjs = require("@rollup/plugin-commonjs");
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
@@ -75,7 +76,7 @@ const createWebpackConfig = ({
|
|||||||
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
!isStatsBuild && new WebpackBar({ fancy: !isProdBuild }),
|
new WebpackBar({ fancy: !isProdBuild }),
|
||||||
new WebpackManifestPlugin({
|
new WebpackManifestPlugin({
|
||||||
// Only include the JS of entrypoints
|
// Only include the JS of entrypoints
|
||||||
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
|
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
|
||||||
@@ -102,6 +103,7 @@ const createWebpackConfig = ({
|
|||||||
? path.resolve(context, resource)
|
? path.resolve(context, resource)
|
||||||
: require.resolve(resource);
|
: require.resolve(resource);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.error(
|
console.error(
|
||||||
"Error in Home Assistant ignore plugin",
|
"Error in Home Assistant ignore plugin",
|
||||||
resource,
|
resource,
|
||||||
|
@@ -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
|
|
@@ -213,7 +213,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Google Chrome (all platforms except iOS)</li>
|
<li>Google Chrome (all platforms except iOS)</li>
|
||||||
<li>Microsoft Edge (all platforms except iOS)</li>
|
<li>Microsoft Edge (all platforms)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -88,7 +88,7 @@ class HcCast extends LitElement {
|
|||||||
>
|
>
|
||||||
${(this.lovelaceConfig
|
${(this.lovelaceConfig
|
||||||
? this.lovelaceConfig.views
|
? this.lovelaceConfig.views
|
||||||
: [generateDefaultViewConfig({}, {}, {}, {}, () => "")]
|
: [generateDefaultViewConfig([], [], [], {}, () => "")]
|
||||||
).map(
|
).map(
|
||||||
(view, idx) => html`
|
(view, idx) => html`
|
||||||
<paper-icon-item
|
<paper-icon-item
|
||||||
@@ -181,7 +181,7 @@ class HcCast extends LitElement {
|
|||||||
private async _handlePickView(ev: Event) {
|
private async _handlePickView(ev: Event) {
|
||||||
const path = (ev.currentTarget as any).getAttribute("data-path");
|
const path = (ev.currentTarget as any).getAttribute("data-path");
|
||||||
await ensureConnectedCastSession(this.castManager!, this.auth!);
|
await ensureConnectedCastSession(this.castManager!, this.auth!);
|
||||||
castSendShowLovelaceView(this.castManager, path, this.auth.data.hassUrl);
|
castSendShowLovelaceView(this.castManager, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleLogout() {
|
private async _handleLogout() {
|
||||||
|
@@ -22,11 +22,7 @@ class HcLayout extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<img
|
<img class="hero" src="/images/google-nest-hub.png" />
|
||||||
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">
|
<h1 class="card-header">
|
||||||
Home Assistant Cast${this.subtitle ? ` – ${this.subtitle}` : ""}
|
Home Assistant Cast${this.subtitle ? ` – ${this.subtitle}` : ""}
|
||||||
${this.auth
|
${this.auth
|
||||||
@@ -48,7 +44,7 @@ class HcLayout extends LitElement {
|
|||||||
<div class="footer">
|
<div class="footer">
|
||||||
<a href="./faq.html">Frequently Asked Questions</a> – Found a bug?
|
<a href="./faq.html">Frequently Asked Questions</a> – Found a bug?
|
||||||
<a
|
<a
|
||||||
href="https://github.com/home-assistant/frontend/issues"
|
href="https://github.com/home-assistant/home-assistant-polymer/issues"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>Let us know!</a
|
>Let us know!</a
|
||||||
>
|
>
|
||||||
|
@@ -12,7 +12,6 @@ class HcLaunchScreen extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<img
|
<img
|
||||||
alt="Home Assistant logo on left, Nabu Casa logo on right, and red heart in center"
|
|
||||||
src="https://www.home-assistant.io/images/blog/2018-09-thinking-big/social.png"
|
src="https://www.home-assistant.io/images/blog/2018-09-thinking-big/social.png"
|
||||||
/>
|
/>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
|
@@ -33,6 +33,7 @@ import { castContext } from "../cast_context";
|
|||||||
import "./hc-launch-screen";
|
import "./hc-launch-screen";
|
||||||
|
|
||||||
let resourcesLoaded = false;
|
let resourcesLoaded = false;
|
||||||
|
|
||||||
@customElement("hc-main")
|
@customElement("hc-main")
|
||||||
export class HcMain extends HassElement {
|
export class HcMain extends HassElement {
|
||||||
@state() private _showDemo = false;
|
@state() private _showDemo = false;
|
||||||
@@ -45,8 +46,6 @@ export class HcMain extends HassElement {
|
|||||||
|
|
||||||
@state() private _urlPath?: string | null;
|
@state() private _urlPath?: string | null;
|
||||||
|
|
||||||
private _hassUUID?: string;
|
|
||||||
|
|
||||||
private _unsubLovelace?: UnsubscribeFunc;
|
private _unsubLovelace?: UnsubscribeFunc;
|
||||||
|
|
||||||
public processIncomingMessage(msg: HassMessage) {
|
public processIncomingMessage(msg: HassMessage) {
|
||||||
@@ -126,7 +125,6 @@ export class HcMain extends HassElement {
|
|||||||
|
|
||||||
if (this.hass) {
|
if (this.hass) {
|
||||||
status.hassUrl = this.hass.auth.data.hassUrl;
|
status.hassUrl = this.hass.auth.data.hassUrl;
|
||||||
status.hassUUID = this._hassUUID;
|
|
||||||
status.lovelacePath = this._lovelacePath;
|
status.lovelacePath = this._lovelacePath;
|
||||||
status.urlPath = this._urlPath;
|
status.urlPath = this._urlPath;
|
||||||
}
|
}
|
||||||
@@ -165,18 +163,6 @@ export class HcMain extends HassElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private async _handleGetStatusMessage(msg: GetStatusMessage) {
|
private async _handleGetStatusMessage(msg: GetStatusMessage) {
|
||||||
if (
|
|
||||||
(this.hass && msg.hassUUID && msg.hassUUID !== this._hassUUID) ||
|
|
||||||
(this.hass && msg.hassUrl && msg.hassUrl !== this.hass.auth.data.hassUrl)
|
|
||||||
) {
|
|
||||||
this._error = "Not connected to the same Home Assistant instance.";
|
|
||||||
this._sendError(
|
|
||||||
ReceiverErrorCode.WRONG_INSTANCE,
|
|
||||||
this._error,
|
|
||||||
msg.senderId!
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._sendStatus(msg.senderId!);
|
this._sendStatus(msg.senderId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +179,6 @@ export class HcMain extends HassElement {
|
|||||||
expires_in: 0,
|
expires_in: 0,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
this._hassUUID = msg.hassUUID;
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage = this._getErrorMessage(err);
|
const errorMessage = this._getErrorMessage(err);
|
||||||
this._error = errorMessage;
|
this._error = errorMessage;
|
||||||
@@ -224,29 +209,9 @@ export class HcMain extends HassElement {
|
|||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
this._sendStatus(msg.senderId!);
|
this._sendStatus(msg.senderId!);
|
||||||
this._error = "Cannot show Lovelace because we're not connected.";
|
this._error = "Cannot show Lovelace because we're not connected.";
|
||||||
this._sendError(
|
this._sendError(ReceiverErrorCode.NOT_CONNECTED, this._error);
|
||||||
ReceiverErrorCode.NOT_CONNECTED,
|
|
||||||
this._error,
|
|
||||||
msg.senderId!
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
(msg.hassUUID && msg.hassUUID !== this._hassUUID) ||
|
|
||||||
(msg.hassUrl && msg.hassUrl !== this.hass.auth.data.hassUrl)
|
|
||||||
) {
|
|
||||||
this._sendStatus(msg.senderId!);
|
|
||||||
this._error =
|
|
||||||
"Cannot show Lovelace because we're not connected to the same Home Assistant instance.";
|
|
||||||
this._sendError(
|
|
||||||
ReceiverErrorCode.WRONG_INSTANCE,
|
|
||||||
this._error,
|
|
||||||
msg.senderId!
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
if (msg.urlPath === "lovelace") {
|
if (msg.urlPath === "lovelace") {
|
||||||
msg.urlPath = null;
|
msg.urlPath = null;
|
||||||
|
@@ -508,7 +508,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
|||||||
origin_addresses: ["XYZ"],
|
origin_addresses: ["XYZ"],
|
||||||
status: "OK",
|
status: "OK",
|
||||||
mode: "driving",
|
mode: "driving",
|
||||||
units: "us_customary",
|
units: "imperial",
|
||||||
duration_in_traffic: "41 mins",
|
duration_in_traffic: "41 mins",
|
||||||
duration: "44 mins",
|
duration: "44 mins",
|
||||||
distance: "34.3 mi",
|
distance: "34.3 mi",
|
||||||
@@ -527,7 +527,7 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
|
|||||||
origin_addresses: ["XYZ"],
|
origin_addresses: ["XYZ"],
|
||||||
status: "OK",
|
status: "OK",
|
||||||
mode: "driving",
|
mode: "driving",
|
||||||
units: "us_customary",
|
units: "imperial",
|
||||||
duration_in_traffic: "37 mins",
|
duration_in_traffic: "37 mins",
|
||||||
duration: "37 mins",
|
duration: "37 mins",
|
||||||
distance: "30.2 mi",
|
distance: "30.2 mi",
|
||||||
|
@@ -194,7 +194,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
|||||||
type: "state-icon",
|
type: "state-icon",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "group.downstairs_lights",
|
entity_id: "group.downstairs_lights",
|
||||||
},
|
},
|
||||||
service: "homeassistant.toggle",
|
service: "homeassistant.toggle",
|
||||||
@@ -1196,7 +1196,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
|||||||
left: "15%",
|
left: "15%",
|
||||||
},
|
},
|
||||||
type: "state-icon",
|
type: "state-icon",
|
||||||
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
|
entity: "binary_sensor.water_leak_sensor_158d0002338651",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prefix: "Kitchen: ",
|
prefix: "Kitchen: ",
|
||||||
@@ -1206,7 +1206,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
|||||||
top: "89%",
|
top: "89%",
|
||||||
left: "32%",
|
left: "32%",
|
||||||
},
|
},
|
||||||
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
|
entity: "binary_sensor.water_leak_sensor_158d0002338651",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
style: {
|
style: {
|
||||||
@@ -1215,7 +1215,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
|||||||
left: "60%",
|
left: "60%",
|
||||||
},
|
},
|
||||||
type: "state-icon",
|
type: "state-icon",
|
||||||
entity: "binary_sensor.water_leak_sensor_158d0002338651",
|
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prefix: "Bathroom: ",
|
prefix: "Bathroom: ",
|
||||||
@@ -1225,7 +1225,7 @@ export const demoLovelaceJimpower: DemoConfig["lovelace"] = () => ({
|
|||||||
top: "89%",
|
top: "89%",
|
||||||
left: "77%",
|
left: "77%",
|
||||||
},
|
},
|
||||||
entity: "binary_sensor.water_leak_sensor_158d0002338651",
|
entity: "binary_sensor.water_leak_sensor_158d00026e26dc",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
type: "picture-elements",
|
type: "picture-elements",
|
||||||
|
@@ -59,7 +59,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
attributes: {
|
attributes: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
radius: 50,
|
radius: 50,
|
||||||
friendly_name: "School",
|
friendly_name: "Skolan",
|
||||||
icon: "mdi:school",
|
icon: "mdi:school",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -137,7 +137,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
state: "73",
|
state: "73",
|
||||||
attributes: {
|
attributes: {
|
||||||
unit_of_measurement: "%",
|
unit_of_measurement: "%",
|
||||||
friendly_name: "Oskar battery",
|
friendly_name: "oskar batteri",
|
||||||
device_class: "battery",
|
device_class: "battery",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -146,7 +146,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
state: "88",
|
state: "88",
|
||||||
attributes: {
|
attributes: {
|
||||||
unit_of_measurement: "%",
|
unit_of_measurement: "%",
|
||||||
friendly_name: "Bella battery",
|
friendly_name: "bella batteri",
|
||||||
device_class: "battery",
|
device_class: "battery",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -154,7 +154,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
entity_id: "binary_sensor.unifi_camera",
|
entity_id: "binary_sensor.unifi_camera",
|
||||||
state: "off",
|
state: "off",
|
||||||
attributes: {
|
attributes: {
|
||||||
friendly_name: "Motion sensor camera",
|
friendly_name: "R\u00f6relsesensor kamera",
|
||||||
icon: "mdi:walk",
|
icon: "mdi:walk",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -707,7 +707,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
cloudiness: 25,
|
cloudiness: 25,
|
||||||
friendly_name: "Weather",
|
friendly_name: "V\u00e4der",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"binary_sensor.ubiquiti_switch": {
|
"binary_sensor.ubiquiti_switch": {
|
||||||
@@ -731,7 +731,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
round_trip_time_max: "0.626",
|
round_trip_time_max: "0.626",
|
||||||
round_trip_time_mdev: "",
|
round_trip_time_mdev: "",
|
||||||
round_trip_time_min: "0.358",
|
round_trip_time_min: "0.358",
|
||||||
friendly_name: "Entrance camera",
|
friendly_name: "Entr\u00e9 kamera",
|
||||||
device_class: "connectivity",
|
device_class: "connectivity",
|
||||||
icon: "mdi:cctv",
|
icon: "mdi:cctv",
|
||||||
},
|
},
|
||||||
@@ -797,7 +797,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
attributes: {
|
attributes: {
|
||||||
battery_level: 34,
|
battery_level: 34,
|
||||||
on: true,
|
on: true,
|
||||||
friendly_name: "Porch motion sensor",
|
friendly_name: "altan_motion_sensor",
|
||||||
device_class: "motion",
|
device_class: "motion",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -807,7 +807,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
attributes: {
|
attributes: {
|
||||||
battery_level: 88,
|
battery_level: 88,
|
||||||
on: true,
|
on: true,
|
||||||
friendly_name: "Back door sensor",
|
friendly_name: "Altand\u00f6rren sensor",
|
||||||
device_class: "opening",
|
device_class: "opening",
|
||||||
icon: "mdi:door",
|
icon: "mdi:door",
|
||||||
},
|
},
|
||||||
@@ -818,7 +818,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
attributes: {
|
attributes: {
|
||||||
battery_level: 74,
|
battery_level: 74,
|
||||||
on: true,
|
on: true,
|
||||||
friendly_name: "Bathroom motion sensor",
|
friendly_name: "badrumssensor",
|
||||||
device_class: "motion",
|
device_class: "motion",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -829,7 +829,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
battery_level: 47,
|
battery_level: 47,
|
||||||
on: true,
|
on: true,
|
||||||
dark: true,
|
dark: true,
|
||||||
friendly_name: "Basement motion sensor",
|
friendly_name: "R\u00f6relsesensor k\u00e4llaren 1",
|
||||||
device_class: "motion",
|
device_class: "motion",
|
||||||
icon: "mdi:walk",
|
icon: "mdi:walk",
|
||||||
},
|
},
|
||||||
@@ -841,7 +841,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
battery_level: 60,
|
battery_level: 60,
|
||||||
on: true,
|
on: true,
|
||||||
dark: true,
|
dark: true,
|
||||||
friendly_name: "Laundy room motion sensor",
|
friendly_name: "R\u00f6relsesensor tv\u00e4ttstugan",
|
||||||
device_class: "motion",
|
device_class: "motion",
|
||||||
icon: "mdi:walk",
|
icon: "mdi:walk",
|
||||||
},
|
},
|
||||||
@@ -863,7 +863,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
attributes: {
|
attributes: {
|
||||||
battery_level: 60,
|
battery_level: 60,
|
||||||
on: true,
|
on: true,
|
||||||
friendly_name: "Pantry motion sensor",
|
friendly_name: "R\u00f6relsesensor skafferiet",
|
||||||
device_class: "motion",
|
device_class: "motion",
|
||||||
icon: "mdi:walk",
|
icon: "mdi:walk",
|
||||||
},
|
},
|
||||||
@@ -875,7 +875,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
battery_level: 60,
|
battery_level: 60,
|
||||||
on: true,
|
on: true,
|
||||||
dark: true,
|
dark: true,
|
||||||
friendly_name: "Stair motion sensor",
|
friendly_name: "R\u00f6relsesensor k\u00e4llaren 2",
|
||||||
device_class: "motion",
|
device_class: "motion",
|
||||||
icon: "mdi:walk",
|
icon: "mdi:walk",
|
||||||
},
|
},
|
||||||
@@ -887,7 +887,7 @@ export const demoEntitiesKernehed: DemoConfig["entities"] = () =>
|
|||||||
battery_level: 47,
|
battery_level: 47,
|
||||||
on: true,
|
on: true,
|
||||||
dark: true,
|
dark: true,
|
||||||
friendly_name: "Bench sensor",
|
friendly_name: "B\u00e4nksensor",
|
||||||
device_class: "motion",
|
device_class: "motion",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -277,7 +277,7 @@ export const demoLovelaceKernehed: DemoConfig["lovelace"] = () => ({
|
|||||||
],
|
],
|
||||||
show_header_toggle: false,
|
show_header_toggle: false,
|
||||||
type: "entities",
|
type: "entities",
|
||||||
title: "Bandwidth",
|
title: "Bandbredd",
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// title: "Updater",
|
// title: "Updater",
|
||||||
|
@@ -377,7 +377,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
name: "AC bed",
|
name: "AC bed",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "script.air_cleaner_quiet",
|
entity_id: "script.air_cleaner_quiet",
|
||||||
},
|
},
|
||||||
service: "script.turn_on",
|
service: "script.turn_on",
|
||||||
@@ -390,7 +390,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
name: "AC bed",
|
name: "AC bed",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "script.air_cleaner_auto",
|
entity_id: "script.air_cleaner_auto",
|
||||||
},
|
},
|
||||||
service: "script.turn_on",
|
service: "script.turn_on",
|
||||||
@@ -403,7 +403,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
name: "AC bed",
|
name: "AC bed",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "script.air_cleaner_turbo",
|
entity_id: "script.air_cleaner_turbo",
|
||||||
},
|
},
|
||||||
service: "script.turn_on",
|
service: "script.turn_on",
|
||||||
@@ -416,7 +416,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
name: "AC",
|
name: "AC",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "script.ac_off",
|
entity_id: "script.ac_off",
|
||||||
},
|
},
|
||||||
service: "script.turn_on",
|
service: "script.turn_on",
|
||||||
@@ -429,7 +429,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
name: "AC",
|
name: "AC",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "script.ac_on",
|
entity_id: "script.ac_on",
|
||||||
},
|
},
|
||||||
service: "script.turn_on",
|
service: "script.turn_on",
|
||||||
@@ -629,7 +629,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
entity: "scene.morning_lights",
|
entity: "scene.morning_lights",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "scene.morning_lights",
|
entity_id: "scene.morning_lights",
|
||||||
},
|
},
|
||||||
service: "scene.turn_on",
|
service: "scene.turn_on",
|
||||||
@@ -641,7 +641,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
entity: "scene.movie_time",
|
entity: "scene.movie_time",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "scene.movie_time",
|
entity_id: "scene.movie_time",
|
||||||
},
|
},
|
||||||
service: "scene.turn_on",
|
service: "scene.turn_on",
|
||||||
@@ -702,7 +702,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
entity: "light.downstairs_lights",
|
entity: "light.downstairs_lights",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "light.downstairs_lights",
|
entity_id: "light.downstairs_lights",
|
||||||
},
|
},
|
||||||
service: "light.toggle",
|
service: "light.toggle",
|
||||||
@@ -714,7 +714,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
|||||||
entity: "light.upstairs_lights",
|
entity: "light.upstairs_lights",
|
||||||
tap_action: {
|
tap_action: {
|
||||||
action: "call-service",
|
action: "call-service",
|
||||||
data: {
|
service_data: {
|
||||||
entity_id: "light.upstairs_lights",
|
entity_id: "light.upstairs_lights",
|
||||||
},
|
},
|
||||||
service: "light.toggle",
|
service: "light.toggle",
|
||||||
|
@@ -138,7 +138,7 @@ if (!window.cardTools) {
|
|||||||
return cardTools.createThing("row", config);
|
return cardTools.createThing("row", config);
|
||||||
|
|
||||||
const domain = config.entity.split(".", 1)[0];
|
const domain = config.entity.split(".", 1)[0];
|
||||||
Object.assign(config, { type: DEFAULT_ROWS[domain] || "simple" });
|
Object.assign(config, { type: DEFAULT_ROWS[domain] || "text" });
|
||||||
return cardTools.createThing("entity-row", config);
|
return cardTools.createThing("entity-row", config);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
|
import "../../src/resources/compatibility";
|
||||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||||
import { navigate } from "../../src/common/navigate";
|
import { navigate } from "../../src/common/navigate";
|
||||||
import {
|
import {
|
||||||
@@ -6,25 +7,22 @@ import {
|
|||||||
provideHass,
|
provideHass,
|
||||||
} from "../../src/fake_data/provide_hass";
|
} from "../../src/fake_data/provide_hass";
|
||||||
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
|
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
|
||||||
import "../../src/resources/compatibility";
|
|
||||||
import { HomeAssistant } from "../../src/types";
|
import { HomeAssistant } from "../../src/types";
|
||||||
import { selectedDemoConfig } from "./configs/demo-configs";
|
import { selectedDemoConfig } from "./configs/demo-configs";
|
||||||
import { mockAuth } from "./stubs/auth";
|
import { mockAuth } from "./stubs/auth";
|
||||||
import { mockConfigEntries } from "./stubs/config_entries";
|
|
||||||
import { mockEnergy } from "./stubs/energy";
|
|
||||||
import { energyEntities } from "./stubs/entities";
|
|
||||||
import { mockEntityRegistry } from "./stubs/entity_registry";
|
|
||||||
import { mockEvents } from "./stubs/events";
|
import { mockEvents } from "./stubs/events";
|
||||||
import { mockFrontend } from "./stubs/frontend";
|
import { mockFrontend } from "./stubs/frontend";
|
||||||
import { mockHistory } from "./stubs/history";
|
import { mockHistory } from "./stubs/history";
|
||||||
import { mockLovelace } from "./stubs/lovelace";
|
import { mockLovelace } from "./stubs/lovelace";
|
||||||
import { mockMediaPlayer } from "./stubs/media_player";
|
import { mockMediaPlayer } from "./stubs/media_player";
|
||||||
import { mockPersistentNotification } from "./stubs/persistent_notification";
|
import { mockPersistentNotification } from "./stubs/persistent_notification";
|
||||||
import { mockRecorder } from "./stubs/recorder";
|
|
||||||
import { mockShoppingList } from "./stubs/shopping_list";
|
import { mockShoppingList } from "./stubs/shopping_list";
|
||||||
import { mockSystemLog } from "./stubs/system_log";
|
import { mockSystemLog } from "./stubs/system_log";
|
||||||
import { mockTemplate } from "./stubs/template";
|
import { mockTemplate } from "./stubs/template";
|
||||||
import { mockTranslations } from "./stubs/translations";
|
import { mockTranslations } from "./stubs/translations";
|
||||||
|
import { mockEnergy } from "./stubs/energy";
|
||||||
|
import { mockConfig } from "./stubs/config";
|
||||||
|
import { energyEntities } from "./stubs/entities";
|
||||||
|
|
||||||
class HaDemo extends HomeAssistantAppEl {
|
class HaDemo extends HomeAssistantAppEl {
|
||||||
protected async _initializeHass() {
|
protected async _initializeHass() {
|
||||||
@@ -46,7 +44,6 @@ class HaDemo extends HomeAssistantAppEl {
|
|||||||
mockAuth(hass);
|
mockAuth(hass);
|
||||||
mockTranslations(hass);
|
mockTranslations(hass);
|
||||||
mockHistory(hass);
|
mockHistory(hass);
|
||||||
mockRecorder(hass);
|
|
||||||
mockShoppingList(hass);
|
mockShoppingList(hass);
|
||||||
mockSystemLog(hass);
|
mockSystemLog(hass);
|
||||||
mockTemplate(hass);
|
mockTemplate(hass);
|
||||||
@@ -54,40 +51,8 @@ class HaDemo extends HomeAssistantAppEl {
|
|||||||
mockMediaPlayer(hass);
|
mockMediaPlayer(hass);
|
||||||
mockFrontend(hass);
|
mockFrontend(hass);
|
||||||
mockEnergy(hass);
|
mockEnergy(hass);
|
||||||
|
mockConfig(hass);
|
||||||
mockPersistentNotification(hass);
|
mockPersistentNotification(hass);
|
||||||
mockConfigEntries(hass);
|
|
||||||
mockEntityRegistry(hass, [
|
|
||||||
{
|
|
||||||
config_entry_id: "co2signal",
|
|
||||||
device_id: "co2signal",
|
|
||||||
area_id: null,
|
|
||||||
disabled_by: null,
|
|
||||||
entity_id: "sensor.co2_intensity",
|
|
||||||
id: "sensor.co2_intensity",
|
|
||||||
name: null,
|
|
||||||
icon: null,
|
|
||||||
platform: "co2signal",
|
|
||||||
hidden_by: null,
|
|
||||||
entity_category: null,
|
|
||||||
has_entity_name: false,
|
|
||||||
unique_id: "co2_intensity",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config_entry_id: "co2signal",
|
|
||||||
device_id: "co2signal",
|
|
||||||
area_id: null,
|
|
||||||
disabled_by: null,
|
|
||||||
entity_id: "sensor.grid_fossil_fuel_percentage",
|
|
||||||
id: "sensor.co2_intensity",
|
|
||||||
name: null,
|
|
||||||
icon: null,
|
|
||||||
platform: "co2signal",
|
|
||||||
hidden_by: null,
|
|
||||||
entity_category: null,
|
|
||||||
has_entity_name: false,
|
|
||||||
unique_id: "grid_fossil_fuel_percentage",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
hass.addEntities(energyEntities());
|
hass.addEntities(energyEntities());
|
||||||
|
|
||||||
@@ -122,9 +87,3 @@ class HaDemo extends HomeAssistantAppEl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("ha-demo", HaDemo);
|
customElements.define("ha-demo", HaDemo);
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-demo": HaDemo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
41
demo/src/stubs/config.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockConfig = (hass: MockHomeAssistant) => {
|
||||||
|
hass.mockAPI("config/config_entries/entry", () => [
|
||||||
|
{
|
||||||
|
entry_id: "co2signal",
|
||||||
|
domain: "co2signal",
|
||||||
|
title: "CO2 Signal",
|
||||||
|
source: "user",
|
||||||
|
state: "loaded",
|
||||||
|
supports_options: false,
|
||||||
|
supports_unload: true,
|
||||||
|
pref_disable_new_entities: false,
|
||||||
|
pref_disable_polling: false,
|
||||||
|
disabled_by: null,
|
||||||
|
reason: null,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
hass.mockWS("config/entity_registry/list", () => [
|
||||||
|
{
|
||||||
|
config_entry_id: "co2signal",
|
||||||
|
device_id: "co2signal",
|
||||||
|
area_id: null,
|
||||||
|
disabled_by: null,
|
||||||
|
entity_id: "sensor.co2_intensity",
|
||||||
|
name: null,
|
||||||
|
icon: null,
|
||||||
|
platform: "co2signal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
config_entry_id: "co2signal",
|
||||||
|
device_id: "co2signal",
|
||||||
|
area_id: null,
|
||||||
|
disabled_by: null,
|
||||||
|
entity_id: "sensor.grid_fossil_fuel_percentage",
|
||||||
|
name: null,
|
||||||
|
icon: null,
|
||||||
|
platform: "co2signal",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
@@ -1,20 +0,0 @@
|
|||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
|
||||||
|
|
||||||
export const mockConfigEntries = (hass: MockHomeAssistant) => {
|
|
||||||
hass.mockWS("config_entries/get", () => [
|
|
||||||
{
|
|
||||||
entry_id: "co2signal",
|
|
||||||
domain: "co2signal",
|
|
||||||
title: "CO2 Signal",
|
|
||||||
source: "user",
|
|
||||||
state: "loaded",
|
|
||||||
supports_options: false,
|
|
||||||
supports_remove_device: false,
|
|
||||||
supports_unload: true,
|
|
||||||
pref_disable_new_entities: false,
|
|
||||||
pref_disable_polling: false,
|
|
||||||
disabled_by: null,
|
|
||||||
reason: null,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
};
|
|
@@ -1,101 +1,90 @@
|
|||||||
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
|
import { format, startOfToday, startOfTomorrow } from "date-fns/esm";
|
||||||
import {
|
import { EnergySolarForecasts } from "../../../src/data/energy";
|
||||||
EnergyInfo,
|
|
||||||
EnergyPreferences,
|
|
||||||
EnergySolarForecasts,
|
|
||||||
FossilEnergyConsumption,
|
|
||||||
} from "../../../src/data/energy";
|
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
export const mockEnergy = (hass: MockHomeAssistant) => {
|
export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||||
hass.mockWS(
|
hass.mockWS("energy/get_prefs", () => ({
|
||||||
"energy/get_prefs",
|
energy_sources: [
|
||||||
(): EnergyPreferences => ({
|
{
|
||||||
energy_sources: [
|
type: "grid",
|
||||||
{
|
flow_from: [
|
||||||
type: "grid",
|
{
|
||||||
flow_from: [
|
stat_energy_from: "sensor.energy_consumption_tarif_1",
|
||||||
{
|
stat_cost: "sensor.energy_consumption_tarif_1_cost",
|
||||||
stat_energy_from: "sensor.energy_consumption_tarif_1",
|
entity_energy_from: "sensor.energy_consumption_tarif_1",
|
||||||
stat_cost: "sensor.energy_consumption_tarif_1_cost",
|
entity_energy_price: null,
|
||||||
entity_energy_price: null,
|
number_energy_price: null,
|
||||||
number_energy_price: null,
|
},
|
||||||
},
|
{
|
||||||
{
|
stat_energy_from: "sensor.energy_consumption_tarif_2",
|
||||||
stat_energy_from: "sensor.energy_consumption_tarif_2",
|
stat_cost: "sensor.energy_consumption_tarif_2_cost",
|
||||||
stat_cost: "sensor.energy_consumption_tarif_2_cost",
|
entity_energy_from: "sensor.energy_consumption_tarif_2",
|
||||||
entity_energy_price: null,
|
entity_energy_price: null,
|
||||||
number_energy_price: null,
|
number_energy_price: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
flow_to: [
|
flow_to: [
|
||||||
{
|
{
|
||||||
stat_energy_to: "sensor.energy_production_tarif_1",
|
stat_energy_to: "sensor.energy_production_tarif_1",
|
||||||
stat_compensation:
|
stat_compensation: "sensor.energy_production_tarif_1_compensation",
|
||||||
"sensor.energy_production_tarif_1_compensation",
|
entity_energy_to: "sensor.energy_production_tarif_1",
|
||||||
entity_energy_price: null,
|
entity_energy_price: null,
|
||||||
number_energy_price: null,
|
number_energy_price: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
stat_energy_to: "sensor.energy_production_tarif_2",
|
stat_energy_to: "sensor.energy_production_tarif_2",
|
||||||
stat_compensation:
|
stat_compensation: "sensor.energy_production_tarif_2_compensation",
|
||||||
"sensor.energy_production_tarif_2_compensation",
|
entity_energy_to: "sensor.energy_production_tarif_2",
|
||||||
entity_energy_price: null,
|
entity_energy_price: null,
|
||||||
number_energy_price: null,
|
number_energy_price: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
cost_adjustment_day: 0,
|
cost_adjustment_day: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "solar",
|
type: "solar",
|
||||||
stat_energy_from: "sensor.solar_production",
|
stat_energy_from: "sensor.solar_production",
|
||||||
config_entry_solar_forecast: ["solar_forecast"],
|
config_entry_solar_forecast: ["solar_forecast"],
|
||||||
},
|
},
|
||||||
/* {
|
/* {
|
||||||
type: "battery",
|
type: "battery",
|
||||||
stat_energy_from: "sensor.battery_output",
|
stat_energy_from: "sensor.battery_output",
|
||||||
stat_energy_to: "sensor.battery_input",
|
stat_energy_to: "sensor.battery_input",
|
||||||
}, */
|
}, */
|
||||||
{
|
{
|
||||||
type: "gas",
|
type: "gas",
|
||||||
stat_energy_from: "sensor.energy_gas",
|
stat_energy_from: "sensor.energy_gas",
|
||||||
stat_cost: "sensor.energy_gas_cost",
|
stat_cost: "sensor.energy_gas_cost",
|
||||||
entity_energy_price: null,
|
entity_energy_from: "sensor.energy_gas",
|
||||||
number_energy_price: null,
|
entity_energy_price: null,
|
||||||
},
|
number_energy_price: null,
|
||||||
],
|
},
|
||||||
device_consumption: [
|
],
|
||||||
{
|
device_consumption: [
|
||||||
stat_consumption: "sensor.energy_car",
|
{
|
||||||
},
|
stat_consumption: "sensor.energy_car",
|
||||||
{
|
},
|
||||||
stat_consumption: "sensor.energy_ac",
|
{
|
||||||
},
|
stat_consumption: "sensor.energy_ac",
|
||||||
{
|
},
|
||||||
stat_consumption: "sensor.energy_washing_machine",
|
{
|
||||||
},
|
stat_consumption: "sensor.energy_washing_machine",
|
||||||
{
|
},
|
||||||
stat_consumption: "sensor.energy_dryer",
|
{
|
||||||
},
|
stat_consumption: "sensor.energy_dryer",
|
||||||
{
|
},
|
||||||
stat_consumption: "sensor.energy_heat_pump",
|
{
|
||||||
},
|
stat_consumption: "sensor.energy_heat_pump",
|
||||||
{
|
},
|
||||||
stat_consumption: "sensor.energy_boiler",
|
{
|
||||||
},
|
stat_consumption: "sensor.energy_boiler",
|
||||||
],
|
},
|
||||||
})
|
],
|
||||||
);
|
}));
|
||||||
hass.mockWS(
|
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
|
||||||
"energy/info",
|
hass.mockWS("energy/fossil_energy_consumption", ({ period }) => ({
|
||||||
(): EnergyInfo => ({ cost_sensors: {}, solar_forecast_domains: [] })
|
start: period === "month" ? 250 : period === "day" ? 10 : 2,
|
||||||
);
|
}));
|
||||||
hass.mockWS(
|
|
||||||
"energy/fossil_energy_consumption",
|
|
||||||
({ period }): FossilEnergyConsumption => ({
|
|
||||||
start: period === "month" ? 250 : period === "day" ? 10 : 2,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const todayString = format(startOfToday(), "yyyy-MM-dd");
|
const todayString = format(startOfToday(), "yyyy-MM-dd");
|
||||||
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
|
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
|
||||||
hass.mockWS(
|
hass.mockWS(
|
||||||
|
@@ -4,6 +4,4 @@ import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
|||||||
export const mockEntityRegistry = (
|
export const mockEntityRegistry = (
|
||||||
hass: MockHomeAssistant,
|
hass: MockHomeAssistant,
|
||||||
data: EntityRegistryEntry[] = []
|
data: EntityRegistryEntry[] = []
|
||||||
) => {
|
) => hass.mockWS("config/entity_registry/list", () => data);
|
||||||
hass.mockWS("config/entity_registry/list", () => data);
|
|
||||||
};
|
|
||||||
|
@@ -1,4 +1,12 @@
|
|||||||
|
import {
|
||||||
|
addDays,
|
||||||
|
addHours,
|
||||||
|
addMonths,
|
||||||
|
differenceInHours,
|
||||||
|
endOfDay,
|
||||||
|
} from "date-fns/esm";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { StatisticValue } from "../../../src/data/history";
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
interface HistoryQueryParams {
|
interface HistoryQueryParams {
|
||||||
@@ -64,6 +72,331 @@ const generateHistory = (state, deltas) => {
|
|||||||
|
|
||||||
const incrementalUnits = ["clients", "queries", "ads"];
|
const incrementalUnits = ["clients", "queries", "ads"];
|
||||||
|
|
||||||
|
const generateMeanStatistics = (
|
||||||
|
id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||||
|
initValue: number,
|
||||||
|
maxDiff: number
|
||||||
|
) => {
|
||||||
|
const statistics: StatisticValue[] = [];
|
||||||
|
let currentDate = new Date(start);
|
||||||
|
currentDate.setMinutes(0, 0, 0);
|
||||||
|
let lastVal = initValue;
|
||||||
|
const now = new Date();
|
||||||
|
while (end > currentDate && currentDate < now) {
|
||||||
|
const delta = Math.random() * maxDiff;
|
||||||
|
const mean = lastVal + delta;
|
||||||
|
statistics.push({
|
||||||
|
statistic_id: id,
|
||||||
|
start: currentDate.toISOString(),
|
||||||
|
end: currentDate.toISOString(),
|
||||||
|
mean,
|
||||||
|
min: mean - Math.random() * maxDiff,
|
||||||
|
max: mean + Math.random() * maxDiff,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: mean,
|
||||||
|
sum: null,
|
||||||
|
});
|
||||||
|
lastVal = mean;
|
||||||
|
currentDate =
|
||||||
|
period === "day"
|
||||||
|
? addDays(currentDate, 1)
|
||||||
|
: period === "month"
|
||||||
|
? addMonths(currentDate, 1)
|
||||||
|
: addHours(currentDate, 1);
|
||||||
|
}
|
||||||
|
return statistics;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateSumStatistics = (
|
||||||
|
id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||||
|
initValue: number,
|
||||||
|
maxDiff: number
|
||||||
|
) => {
|
||||||
|
const statistics: StatisticValue[] = [];
|
||||||
|
let currentDate = new Date(start);
|
||||||
|
currentDate.setMinutes(0, 0, 0);
|
||||||
|
let sum = initValue;
|
||||||
|
const now = new Date();
|
||||||
|
while (end > currentDate && currentDate < now) {
|
||||||
|
const add = Math.random() * maxDiff;
|
||||||
|
sum += add;
|
||||||
|
statistics.push({
|
||||||
|
statistic_id: id,
|
||||||
|
start: currentDate.toISOString(),
|
||||||
|
end: currentDate.toISOString(),
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: initValue + sum,
|
||||||
|
sum,
|
||||||
|
});
|
||||||
|
currentDate =
|
||||||
|
period === "day"
|
||||||
|
? addDays(currentDate, 1)
|
||||||
|
: period === "month"
|
||||||
|
? addMonths(currentDate, 1)
|
||||||
|
: addHours(currentDate, 1);
|
||||||
|
}
|
||||||
|
return statistics;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateCurvedStatistics = (
|
||||||
|
id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
_period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||||
|
initValue: number,
|
||||||
|
maxDiff: number,
|
||||||
|
metered: boolean
|
||||||
|
) => {
|
||||||
|
const statistics: StatisticValue[] = [];
|
||||||
|
let currentDate = new Date(start);
|
||||||
|
currentDate.setMinutes(0, 0, 0);
|
||||||
|
let sum = initValue;
|
||||||
|
const hours = differenceInHours(end, start) - 1;
|
||||||
|
let i = 0;
|
||||||
|
let half = false;
|
||||||
|
const now = new Date();
|
||||||
|
while (end > currentDate && currentDate < now) {
|
||||||
|
const add = Math.random() * maxDiff;
|
||||||
|
sum += i * add;
|
||||||
|
statistics.push({
|
||||||
|
statistic_id: id,
|
||||||
|
start: currentDate.toISOString(),
|
||||||
|
end: currentDate.toISOString(),
|
||||||
|
mean: null,
|
||||||
|
min: null,
|
||||||
|
max: null,
|
||||||
|
last_reset: "1970-01-01T00:00:00+00:00",
|
||||||
|
state: initValue + sum,
|
||||||
|
sum: metered ? sum : null,
|
||||||
|
});
|
||||||
|
currentDate = addHours(currentDate, 1);
|
||||||
|
if (!half && i > hours / 2) {
|
||||||
|
half = true;
|
||||||
|
}
|
||||||
|
i += half ? -1 : 1;
|
||||||
|
}
|
||||||
|
return statistics;
|
||||||
|
};
|
||||||
|
|
||||||
|
const statisticsFunctions: Record<
|
||||||
|
string,
|
||||||
|
(
|
||||||
|
id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
period: "5minute" | "hour" | "day" | "month"
|
||||||
|
) => StatisticValue[]
|
||||||
|
> = {
|
||||||
|
"sensor.energy_consumption_tarif_1": (
|
||||||
|
id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
period = "hour"
|
||||||
|
) => {
|
||||||
|
if (period !== "hour") {
|
||||||
|
return generateSumStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
period === "day" ? 17 : 504
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
|
||||||
|
const morningLow = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
morningEnd,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
0.7
|
||||||
|
);
|
||||||
|
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||||
|
const morningFinalVal = morningLow.length
|
||||||
|
? morningLow[morningLow.length - 1].sum!
|
||||||
|
: 0;
|
||||||
|
const empty = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
morningEnd,
|
||||||
|
eveningStart,
|
||||||
|
period,
|
||||||
|
morningFinalVal,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const eveningLow = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
eveningStart,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
morningFinalVal,
|
||||||
|
0.7
|
||||||
|
);
|
||||||
|
return [...morningLow, ...empty, ...eveningLow];
|
||||||
|
},
|
||||||
|
"sensor.energy_consumption_tarif_2": (
|
||||||
|
id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
period = "hour"
|
||||||
|
) => {
|
||||||
|
if (period !== "hour") {
|
||||||
|
return generateSumStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
period === "day" ? 17 : 504
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||||
|
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
||||||
|
const highTarif = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
morningEnd,
|
||||||
|
eveningStart,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
0.3
|
||||||
|
);
|
||||||
|
const highTarifFinalVal = highTarif.length
|
||||||
|
? highTarif[highTarif.length - 1].sum!
|
||||||
|
: 0;
|
||||||
|
const morning = generateSumStatistics(id, start, morningEnd, period, 0, 0);
|
||||||
|
const evening = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
eveningStart,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
highTarifFinalVal,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return [...morning, ...highTarif, ...evening];
|
||||||
|
},
|
||||||
|
"sensor.energy_production_tarif_1": (id, start, end, period = "hour") =>
|
||||||
|
generateSumStatistics(id, start, end, period, 0, 0),
|
||||||
|
"sensor.energy_production_tarif_1_compensation": (
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
period = "hour"
|
||||||
|
) => generateSumStatistics(id, start, end, period, 0, 0),
|
||||||
|
"sensor.energy_production_tarif_2": (id, start, end, period = "hour") => {
|
||||||
|
if (period !== "hour") {
|
||||||
|
return generateSumStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
period === "day" ? 17 : 504
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
||||||
|
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
||||||
|
const dayEnd = new Date(endOfDay(productionEnd));
|
||||||
|
const production = generateCurvedStatistics(
|
||||||
|
id,
|
||||||
|
productionStart,
|
||||||
|
productionEnd,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
0.15,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const productionFinalVal = production.length
|
||||||
|
? production[production.length - 1].sum!
|
||||||
|
: 0;
|
||||||
|
const morning = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
productionStart,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const evening = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
productionEnd,
|
||||||
|
dayEnd,
|
||||||
|
period,
|
||||||
|
productionFinalVal,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const rest = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
dayEnd,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
productionFinalVal,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
return [...morning, ...production, ...evening, ...rest];
|
||||||
|
},
|
||||||
|
"sensor.solar_production": (id, start, end, period = "hour") => {
|
||||||
|
if (period !== "hour") {
|
||||||
|
return generateSumStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
period === "day" ? 17 : 504
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
||||||
|
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
||||||
|
const dayEnd = new Date(endOfDay(productionEnd));
|
||||||
|
const production = generateCurvedStatistics(
|
||||||
|
id,
|
||||||
|
productionStart,
|
||||||
|
productionEnd,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
0.3,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
const productionFinalVal = production.length
|
||||||
|
? production[production.length - 1].sum!
|
||||||
|
: 0;
|
||||||
|
const morning = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
productionStart,
|
||||||
|
period,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const evening = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
productionEnd,
|
||||||
|
dayEnd,
|
||||||
|
period,
|
||||||
|
productionFinalVal,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const rest = generateSumStatistics(
|
||||||
|
id,
|
||||||
|
dayEnd,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
productionFinalVal,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
return [...morning, ...production, ...evening, ...rest];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||||
mockHass.mockAPI(
|
mockHass.mockAPI(
|
||||||
new RegExp("history/period/.+"),
|
new RegExp("history/period/.+"),
|
||||||
@@ -133,4 +466,42 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
mockHass.mockWS("history/list_statistic_ids", () => []);
|
||||||
|
mockHass.mockWS(
|
||||||
|
"history/statistics_during_period",
|
||||||
|
({ statistic_ids, start_time, end_time, period }, hass) => {
|
||||||
|
const start = new Date(start_time);
|
||||||
|
const end = end_time ? new Date(end_time) : new Date();
|
||||||
|
|
||||||
|
const statistics: Record<string, StatisticValue[]> = {};
|
||||||
|
|
||||||
|
statistic_ids.forEach((id: string) => {
|
||||||
|
if (id in statisticsFunctions) {
|
||||||
|
statistics[id] = statisticsFunctions[id](id, start, end, period);
|
||||||
|
} else {
|
||||||
|
const entityState = hass.states[id];
|
||||||
|
const state = entityState ? Number(entityState.state) : 1;
|
||||||
|
statistics[id] =
|
||||||
|
entityState && "last_reset" in entityState.attributes
|
||||||
|
? generateSumStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
state,
|
||||||
|
state * (state > 80 ? 0.01 : 0.05)
|
||||||
|
)
|
||||||
|
: generateMeanStatistics(
|
||||||
|
id,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
period,
|
||||||
|
state,
|
||||||
|
state * (state > 80 ? 0.05 : 0.1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,342 +0,0 @@
|
|||||||
import {
|
|
||||||
addDays,
|
|
||||||
addHours,
|
|
||||||
addMonths,
|
|
||||||
differenceInHours,
|
|
||||||
endOfDay,
|
|
||||||
} from "date-fns";
|
|
||||||
import {
|
|
||||||
Statistics,
|
|
||||||
StatisticsMetaData,
|
|
||||||
StatisticValue,
|
|
||||||
} from "../../../src/data/recorder";
|
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
|
||||||
|
|
||||||
const generateMeanStatistics = (
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
|
||||||
initValue: number,
|
|
||||||
maxDiff: number
|
|
||||||
): StatisticValue[] => {
|
|
||||||
const statistics: StatisticValue[] = [];
|
|
||||||
let currentDate = new Date(start);
|
|
||||||
currentDate.setMinutes(0, 0, 0);
|
|
||||||
let lastVal = initValue;
|
|
||||||
const now = new Date();
|
|
||||||
while (end > currentDate && currentDate < now) {
|
|
||||||
const delta = Math.random() * maxDiff;
|
|
||||||
const mean = lastVal + delta;
|
|
||||||
statistics.push({
|
|
||||||
start: currentDate.getTime(),
|
|
||||||
end: currentDate.getTime(),
|
|
||||||
mean,
|
|
||||||
min: mean - Math.random() * maxDiff,
|
|
||||||
max: mean + Math.random() * maxDiff,
|
|
||||||
last_reset: 0,
|
|
||||||
state: mean,
|
|
||||||
sum: null,
|
|
||||||
});
|
|
||||||
lastVal = mean;
|
|
||||||
currentDate =
|
|
||||||
period === "day"
|
|
||||||
? addDays(currentDate, 1)
|
|
||||||
: period === "month"
|
|
||||||
? addMonths(currentDate, 1)
|
|
||||||
: addHours(currentDate, 1);
|
|
||||||
}
|
|
||||||
return statistics;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateSumStatistics = (
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
|
||||||
initValue: number,
|
|
||||||
maxDiff: number
|
|
||||||
): StatisticValue[] => {
|
|
||||||
const statistics: StatisticValue[] = [];
|
|
||||||
let currentDate = new Date(start);
|
|
||||||
currentDate.setMinutes(0, 0, 0);
|
|
||||||
let sum = initValue;
|
|
||||||
const now = new Date();
|
|
||||||
while (end > currentDate && currentDate < now) {
|
|
||||||
const add = Math.random() * maxDiff;
|
|
||||||
sum += add;
|
|
||||||
statistics.push({
|
|
||||||
start: currentDate.getTime(),
|
|
||||||
end: currentDate.getTime(),
|
|
||||||
mean: null,
|
|
||||||
min: null,
|
|
||||||
max: null,
|
|
||||||
last_reset: 0,
|
|
||||||
state: initValue + sum,
|
|
||||||
sum,
|
|
||||||
});
|
|
||||||
currentDate =
|
|
||||||
period === "day"
|
|
||||||
? addDays(currentDate, 1)
|
|
||||||
: period === "month"
|
|
||||||
? addMonths(currentDate, 1)
|
|
||||||
: addHours(currentDate, 1);
|
|
||||||
}
|
|
||||||
return statistics;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateCurvedStatistics = (
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
_period: "5minute" | "hour" | "day" | "month" = "hour",
|
|
||||||
initValue: number,
|
|
||||||
maxDiff: number,
|
|
||||||
metered: boolean
|
|
||||||
): StatisticValue[] => {
|
|
||||||
const statistics: StatisticValue[] = [];
|
|
||||||
let currentDate = new Date(start);
|
|
||||||
currentDate.setMinutes(0, 0, 0);
|
|
||||||
let sum = initValue;
|
|
||||||
const hours = differenceInHours(end, start) - 1;
|
|
||||||
let i = 0;
|
|
||||||
let half = false;
|
|
||||||
const now = new Date();
|
|
||||||
while (end > currentDate && currentDate < now) {
|
|
||||||
const add = Math.random() * maxDiff;
|
|
||||||
sum += i * add;
|
|
||||||
statistics.push({
|
|
||||||
start: currentDate.getTime(),
|
|
||||||
end: currentDate.getTime(),
|
|
||||||
mean: null,
|
|
||||||
min: null,
|
|
||||||
max: null,
|
|
||||||
last_reset: 0,
|
|
||||||
state: initValue + sum,
|
|
||||||
sum: metered ? sum : null,
|
|
||||||
});
|
|
||||||
currentDate = addHours(currentDate, 1);
|
|
||||||
if (!half && i > hours / 2) {
|
|
||||||
half = true;
|
|
||||||
}
|
|
||||||
i += half ? -1 : 1;
|
|
||||||
}
|
|
||||||
return statistics;
|
|
||||||
};
|
|
||||||
|
|
||||||
const statisticsFunctions: Record<
|
|
||||||
string,
|
|
||||||
(
|
|
||||||
id: string,
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
period: "5minute" | "hour" | "day" | "month"
|
|
||||||
) => StatisticValue[]
|
|
||||||
> = {
|
|
||||||
"sensor.energy_consumption_tarif_1": (
|
|
||||||
_id: string,
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
period = "hour"
|
|
||||||
) => {
|
|
||||||
if (period !== "hour") {
|
|
||||||
return generateSumStatistics(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
period === "day" ? 17 : 504
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
|
|
||||||
const morningLow = generateSumStatistics(start, morningEnd, period, 0, 0.7);
|
|
||||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
|
||||||
const morningFinalVal = morningLow.length
|
|
||||||
? morningLow[morningLow.length - 1].sum!
|
|
||||||
: 0;
|
|
||||||
const empty = generateSumStatistics(
|
|
||||||
morningEnd,
|
|
||||||
eveningStart,
|
|
||||||
period,
|
|
||||||
morningFinalVal,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const eveningLow = generateSumStatistics(
|
|
||||||
eveningStart,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
morningFinalVal,
|
|
||||||
0.7
|
|
||||||
);
|
|
||||||
return [...morningLow, ...empty, ...eveningLow];
|
|
||||||
},
|
|
||||||
"sensor.energy_consumption_tarif_2": (
|
|
||||||
_id: string,
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
period = "hour"
|
|
||||||
) => {
|
|
||||||
if (period !== "hour") {
|
|
||||||
return generateSumStatistics(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
period === "day" ? 17 : 504
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
|
||||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
|
||||||
const highTarif = generateSumStatistics(
|
|
||||||
morningEnd,
|
|
||||||
eveningStart,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
0.3
|
|
||||||
);
|
|
||||||
const highTarifFinalVal = highTarif.length
|
|
||||||
? highTarif[highTarif.length - 1].sum!
|
|
||||||
: 0;
|
|
||||||
const morning = generateSumStatistics(start, morningEnd, period, 0, 0);
|
|
||||||
const evening = generateSumStatistics(
|
|
||||||
eveningStart,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
highTarifFinalVal,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
return [...morning, ...highTarif, ...evening];
|
|
||||||
},
|
|
||||||
"sensor.energy_production_tarif_1": (_id, start, end, period = "hour") =>
|
|
||||||
generateSumStatistics(start, end, period, 0, 0),
|
|
||||||
"sensor.energy_production_tarif_1_compensation": (
|
|
||||||
_id,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period = "hour"
|
|
||||||
) => generateSumStatistics(start, end, period, 0, 0),
|
|
||||||
"sensor.energy_production_tarif_2": (_id, start, end, period = "hour") => {
|
|
||||||
if (period !== "hour") {
|
|
||||||
return generateSumStatistics(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
period === "day" ? 17 : 504
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
|
||||||
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
|
||||||
const dayEnd = new Date(endOfDay(productionEnd));
|
|
||||||
const production = generateCurvedStatistics(
|
|
||||||
productionStart,
|
|
||||||
productionEnd,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
0.15,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
const productionFinalVal = production.length
|
|
||||||
? production[production.length - 1].sum!
|
|
||||||
: 0;
|
|
||||||
const morning = generateSumStatistics(start, productionStart, period, 0, 0);
|
|
||||||
const evening = generateSumStatistics(
|
|
||||||
productionEnd,
|
|
||||||
dayEnd,
|
|
||||||
period,
|
|
||||||
productionFinalVal,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const rest = generateSumStatistics(
|
|
||||||
dayEnd,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
productionFinalVal,
|
|
||||||
1
|
|
||||||
);
|
|
||||||
return [...morning, ...production, ...evening, ...rest];
|
|
||||||
},
|
|
||||||
"sensor.solar_production": (_id, start, end, period = "hour") => {
|
|
||||||
if (period !== "hour") {
|
|
||||||
return generateSumStatistics(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
period === "day" ? 17 : 504
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
|
||||||
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
|
||||||
const dayEnd = new Date(endOfDay(productionEnd));
|
|
||||||
const production = generateCurvedStatistics(
|
|
||||||
productionStart,
|
|
||||||
productionEnd,
|
|
||||||
period,
|
|
||||||
0,
|
|
||||||
0.3,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
const productionFinalVal = production.length
|
|
||||||
? production[production.length - 1].sum!
|
|
||||||
: 0;
|
|
||||||
const morning = generateSumStatistics(start, productionStart, period, 0, 0);
|
|
||||||
const evening = generateSumStatistics(
|
|
||||||
productionEnd,
|
|
||||||
dayEnd,
|
|
||||||
period,
|
|
||||||
productionFinalVal,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const rest = generateSumStatistics(
|
|
||||||
dayEnd,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
productionFinalVal,
|
|
||||||
2
|
|
||||||
);
|
|
||||||
return [...morning, ...production, ...evening, ...rest];
|
|
||||||
},
|
|
||||||
};
|
|
||||||
export const mockRecorder = (mockHass: MockHomeAssistant) => {
|
|
||||||
mockHass.mockWS(
|
|
||||||
"recorder/get_statistics_metadata",
|
|
||||||
(): StatisticsMetaData[] => []
|
|
||||||
);
|
|
||||||
mockHass.mockWS(
|
|
||||||
"recorder/list_statistic_ids",
|
|
||||||
(): StatisticsMetaData[] => []
|
|
||||||
);
|
|
||||||
mockHass.mockWS(
|
|
||||||
"recorder/statistics_during_period",
|
|
||||||
({ statistic_ids, start_time, end_time, period }, hass): Statistics => {
|
|
||||||
const start = new Date(start_time);
|
|
||||||
const end = end_time ? new Date(end_time) : new Date();
|
|
||||||
|
|
||||||
const statistics: Record<string, StatisticValue[]> = {};
|
|
||||||
|
|
||||||
statistic_ids.forEach((id: string) => {
|
|
||||||
if (id in statisticsFunctions) {
|
|
||||||
statistics[id] = statisticsFunctions[id](id, start, end, period);
|
|
||||||
} else {
|
|
||||||
const entityState = hass.states[id];
|
|
||||||
const state = entityState ? Number(entityState.state) : 1;
|
|
||||||
statistics[id] =
|
|
||||||
entityState && "last_reset" in entityState.attributes
|
|
||||||
? generateSumStatistics(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
state,
|
|
||||||
state * (state > 80 ? 0.01 : 0.05)
|
|
||||||
)
|
|
||||||
: generateMeanStatistics(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
period,
|
|
||||||
state,
|
|
||||||
state * (state > 80 ? 0.05 : 0.1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return statistics;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 94 KiB |
BIN
gallery/public/images/using-our-logo.png
Normal file
After Width: | Height: | Size: 32 KiB |
35
gallery/script/netlify_build_gallery
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
TARGET_LABEL="needs design preview"
|
||||||
|
|
||||||
|
if [[ "$NETLIFY" != "true" ]]; then
|
||||||
|
echo "This script can only be run on Netlify"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function createStatus() {
|
||||||
|
state="$1"
|
||||||
|
description="$2"
|
||||||
|
target_url="$3"
|
||||||
|
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
"https://api.github.com/repos/home-assistant/frontend/statuses/$COMMIT_REF" \
|
||||||
|
-d '{"state": "'"${state}"'", "context": "Netlify/Design Preview Build", "description": "'"$description"'", "target_url": "'"$target_url"'"}'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if [[ "${PULL_REQUEST}" == "true" ]]; then
|
||||||
|
if [[ "$(curl -sSLf -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
"https://api.github.com/repos/home-assistant/frontend/pulls/${REVIEW_ID}" | jq '.labels[].name' -r)" =~ "$TARGET_LABEL" ]]; then
|
||||||
|
createStatus "pending" "Building design preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||||
|
gulp build-gallery
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
createStatus "success" "Build complete" "$DEPLOY_PRIME_URL"
|
||||||
|
else
|
||||||
|
createStatus "error" "Build failed" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
createStatus "success" "Build was not requested by PR label"
|
||||||
|
fi
|
||||||
|
elif [[ "$INCOMING_HOOK_BODY" == "NIGHTLY" ]]; then
|
||||||
|
gulp build-gallery
|
||||||
|
fi
|
@@ -8,7 +8,7 @@ module.exports = [
|
|||||||
{
|
{
|
||||||
category: "lovelace",
|
category: "lovelace",
|
||||||
// Label for in the sidebar
|
// Label for in the sidebar
|
||||||
header: "Dashboards",
|
header: "Lovelace",
|
||||||
// Specify order of pages. Any pages in the category folder but not listed here will
|
// Specify order of pages. Any pages in the category folder but not listed here will
|
||||||
// automatically be added after the pages listed here.
|
// automatically be added after the pages listed here.
|
||||||
pages: ["introduction"],
|
pages: ["introduction"],
|
||||||
@@ -34,7 +34,7 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
category: "misc",
|
category: "misc",
|
||||||
header: "Miscellaneous",
|
header: "Miscelaneous",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
category: "brand",
|
category: "brand",
|
||||||
|
@@ -119,7 +119,7 @@ export const basicTrace: DemoTrace = {
|
|||||||
params: {
|
params: {
|
||||||
domain: "input_boolean",
|
domain: "input_boolean",
|
||||||
service: "toggle",
|
service: "toggle",
|
||||||
data: {},
|
service_data: {},
|
||||||
target: {
|
target: {
|
||||||
entity_id: ["input_boolean.toggle_4"],
|
entity_id: ["input_boolean.toggle_4"],
|
||||||
},
|
},
|
||||||
@@ -164,7 +164,7 @@ export const basicTrace: DemoTrace = {
|
|||||||
params: {
|
params: {
|
||||||
domain: "input_boolean",
|
domain: "input_boolean",
|
||||||
service: "toggle",
|
service: "toggle",
|
||||||
data: {},
|
service_data: {},
|
||||||
target: {
|
target: {
|
||||||
entity_id: ["input_boolean.toggle_2"],
|
entity_id: ["input_boolean.toggle_2"],
|
||||||
},
|
},
|
||||||
@@ -182,7 +182,7 @@ export const basicTrace: DemoTrace = {
|
|||||||
params: {
|
params: {
|
||||||
domain: "input_boolean",
|
domain: "input_boolean",
|
||||||
service: "toggle",
|
service: "toggle",
|
||||||
data: {},
|
service_data: {},
|
||||||
target: {
|
target: {
|
||||||
entity_id: ["input_boolean.toggle_3"],
|
entity_id: ["input_boolean.toggle_3"],
|
||||||
},
|
},
|
||||||
@@ -200,7 +200,7 @@ export const basicTrace: DemoTrace = {
|
|||||||
params: {
|
params: {
|
||||||
domain: "input_boolean",
|
domain: "input_boolean",
|
||||||
service: "toggle",
|
service: "toggle",
|
||||||
data: {},
|
service_data: {},
|
||||||
target: {
|
target: {
|
||||||
entity_id: ["input_boolean.toggle_4"],
|
entity_id: ["input_boolean.toggle_4"],
|
||||||
},
|
},
|
||||||
|
@@ -5,7 +5,7 @@ import { html, css, LitElement, PropertyValues } from "lit";
|
|||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import "../../src/components/ha-icon-button";
|
import "../../src/components/ha-icon-button";
|
||||||
import "../../src/managers/notification-manager";
|
import "../../src/managers/notification-manager";
|
||||||
import { HaExpansionPanel } from "../../src/components/ha-expansion-panel";
|
import "../../src/components/ha-expansion-panel";
|
||||||
import { haStyle } from "../../src/resources/styles";
|
import { haStyle } from "../../src/resources/styles";
|
||||||
import { PAGES, SIDEBAR } from "../build/import-pages";
|
import { PAGES, SIDEBAR } from "../build/import-pages";
|
||||||
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../src/common/dom/dynamic-element-directive";
|
||||||
@@ -174,10 +174,9 @@ class HaGallery extends LitElement {
|
|||||||
const menuItem = this.shadowRoot!.querySelector(
|
const menuItem = this.shadowRoot!.querySelector(
|
||||||
`a[href="#${this._page}"]`
|
`a[href="#${this._page}"]`
|
||||||
)!;
|
)!;
|
||||||
|
|
||||||
// Make sure section is expanded
|
// Make sure section is expanded
|
||||||
if (menuItem.parentElement instanceof HaExpansionPanel) {
|
if (menuItem.parentElement instanceof HTMLDetailsElement) {
|
||||||
menuItem.parentElement.expanded = true;
|
menuItem.parentElement.open = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,56 +0,0 @@
|
|||||||
---
|
|
||||||
title: When to use remove, delete, add and create
|
|
||||||
subtitle: The difference between remove/delete and add/create.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Remove vs Delete
|
|
||||||
Remove and Delete are quite similar, but can be frustrating if used inconsistently.
|
|
||||||
|
|
||||||
## Remove
|
|
||||||
Take away and set aside, but kept in existence.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
* Removing a user's permission
|
|
||||||
* Removing a user from a group
|
|
||||||
* Removing links between items
|
|
||||||
* Removing a widget
|
|
||||||
* Removing a link
|
|
||||||
* Removing an item from a cart
|
|
||||||
|
|
||||||
## Delete
|
|
||||||
Erase, rendered nonexistent or nonrecoverable.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
* Deleting a field
|
|
||||||
* Deleting a value in a field
|
|
||||||
* Deleting a task
|
|
||||||
* Deleting a group
|
|
||||||
* Deleting a permission
|
|
||||||
* Deleting a calendar event
|
|
||||||
|
|
||||||
# Add vs Create
|
|
||||||
In most cases, Create can be paired with Delete, and Add can be paired with Remove.
|
|
||||||
|
|
||||||
## Add
|
|
||||||
An already-exisiting item.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
* Adding a permission to a user
|
|
||||||
* Adding a user to a group
|
|
||||||
* Adding links between items
|
|
||||||
* Adding a widget
|
|
||||||
* Adding a link
|
|
||||||
* Adding an item to a cart
|
|
||||||
|
|
||||||
## Create
|
|
||||||
Something made from scratch.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
* Creating a new field
|
|
||||||
* Creating a new value in a field
|
|
||||||
* Creating a new task
|
|
||||||
* Creating a new group
|
|
||||||
* Creating a new permission
|
|
||||||
* Creating a new calendar event
|
|
||||||
|
|
||||||
Based on this is [UX magazine article](https://uxmag.com/articles/ui-copy-remove-vs-delete2-banner).
|
|
@@ -1,9 +1,7 @@
|
|||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import { html, css, LitElement, TemplateResult } from "lit";
|
import { html, css, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-yaml-editor";
|
|
||||||
import { Action } from "../../../../src/data/script";
|
|
||||||
import { describeAction } from "../../../../src/data/script_i18n";
|
import { describeAction } from "../../../../src/data/script_i18n";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
@@ -90,15 +88,6 @@ const ACTIONS = [
|
|||||||
then: [{ delay: "00:00:01" }],
|
then: [{ delay: "00:00:01" }],
|
||||||
else: [{ delay: "00:00:05" }],
|
else: [{ delay: "00:00:05" }],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
if: [{ condition: "state" }],
|
|
||||||
then: [{ delay: "00:00:01" }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
if: [{ condition: "state" }, { condition: "state" }],
|
|
||||||
then: [{ delay: "00:00:01" }],
|
|
||||||
else: [{ delay: "00:00:05" }],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
choose: [
|
choose: [
|
||||||
{
|
{
|
||||||
@@ -114,38 +103,16 @@ const ACTIONS = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialAction: Action = {
|
|
||||||
service: "light.turn_on",
|
|
||||||
target: {
|
|
||||||
entity_id: "light.kitchen",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("demo-automation-describe-action")
|
@customElement("demo-automation-describe-action")
|
||||||
export class DemoAutomationDescribeAction extends LitElement {
|
export class DemoAutomationDescribeAction extends LitElement {
|
||||||
@property({ attribute: false }) hass!: HomeAssistant;
|
@property({ attribute: false }) hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() _action = initialAction;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-card header="Actions">
|
<ha-card header="Actions">
|
||||||
<div class="action">
|
|
||||||
<span>
|
|
||||||
${this._action
|
|
||||||
? describeAction(this.hass, this._action)
|
|
||||||
: "<invalid YAML>"}
|
|
||||||
</span>
|
|
||||||
<ha-yaml-editor
|
|
||||||
label="Action Config"
|
|
||||||
.defaultValue=${initialAction}
|
|
||||||
@value-changed=${this._dataChanged}
|
|
||||||
></ha-yaml-editor>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${ACTIONS.map(
|
${ACTIONS.map(
|
||||||
(conf) => html`
|
(conf) => html`
|
||||||
<div class="action">
|
<div class="action">
|
||||||
@@ -165,11 +132,6 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
hass.addEntities(ENTITIES);
|
hass.addEntities(ENTITIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dataChanged(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._action = ev.detail.isValid ? ev.detail.value : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
@@ -185,9 +147,6 @@ export class DemoAutomationDescribeAction extends LitElement {
|
|||||||
span {
|
span {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
ha-yaml-editor {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,82 +1,31 @@
|
|||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { html, css, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-yaml-editor";
|
|
||||||
import { Condition } from "../../../../src/data/automation";
|
|
||||||
import { describeCondition } from "../../../../src/data/automation_i18n";
|
import { describeCondition } from "../../../../src/data/automation_i18n";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
|
||||||
|
|
||||||
const ENTITIES = [
|
|
||||||
getEntity("light", "kitchen", "on", {
|
|
||||||
friendly_name: "Kitchen Light",
|
|
||||||
}),
|
|
||||||
getEntity("device_tracker", "person", "home", {
|
|
||||||
friendly_name: "Person",
|
|
||||||
}),
|
|
||||||
getEntity("zone", "home", "", {
|
|
||||||
friendly_name: "Home",
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
const conditions = [
|
const conditions = [
|
||||||
{ condition: "and" },
|
{ condition: "and" },
|
||||||
{ condition: "not" },
|
{ condition: "not" },
|
||||||
{ condition: "or" },
|
{ condition: "or" },
|
||||||
{ condition: "state", entity_id: "light.kitchen", state: "on" },
|
{ condition: "state" },
|
||||||
{
|
{ condition: "numeric_state" },
|
||||||
condition: "numeric_state",
|
|
||||||
entity_id: "light.kitchen",
|
|
||||||
attribute: "brightness",
|
|
||||||
below: 80,
|
|
||||||
above: 20,
|
|
||||||
},
|
|
||||||
{ condition: "sun", after: "sunset" },
|
{ condition: "sun", after: "sunset" },
|
||||||
{ condition: "sun", after: "sunrise", offset: "-01:00" },
|
{ condition: "sun", after: "sunrise" },
|
||||||
{ condition: "zone", entity_id: "device_tracker.person", zone: "zone.home" },
|
{ condition: "zone" },
|
||||||
{ condition: "trigger", id: "motion" },
|
|
||||||
{ condition: "time" },
|
{ condition: "time" },
|
||||||
{ condition: "template" },
|
{ condition: "template" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialCondition: Condition = {
|
|
||||||
condition: "state",
|
|
||||||
entity_id: "light.kitchen",
|
|
||||||
state: "on",
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("demo-automation-describe-condition")
|
@customElement("demo-automation-describe-condition")
|
||||||
export class DemoAutomationDescribeCondition extends LitElement {
|
export class DemoAutomationDescribeCondition extends LitElement {
|
||||||
@property({ attribute: false }) hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() _condition = initialCondition;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card header="Conditions">
|
<ha-card header="Conditions">
|
||||||
<div class="condition">
|
|
||||||
<span>
|
|
||||||
${this._condition
|
|
||||||
? describeCondition(this._condition, this.hass)
|
|
||||||
: "<invalid YAML>"}
|
|
||||||
</span>
|
|
||||||
<ha-yaml-editor
|
|
||||||
label="Condition Config"
|
|
||||||
.defaultValue=${initialCondition}
|
|
||||||
@value-changed=${this._dataChanged}
|
|
||||||
></ha-yaml-editor>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${conditions.map(
|
${conditions.map(
|
||||||
(conf) => html`
|
(conf) => html`
|
||||||
<div class="condition">
|
<div class="condition">
|
||||||
<span>${describeCondition(conf as any, this.hass)}</span>
|
<span>${describeCondition(conf as any)}</span>
|
||||||
<pre>${dump(conf)}</pre>
|
<pre>${dump(conf)}</pre>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@@ -85,18 +34,6 @@ export class DemoAutomationDescribeCondition extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
const hass = provideHass(this);
|
|
||||||
hass.updateTranslations(null, "en");
|
|
||||||
hass.addEntities(ENTITIES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _dataChanged(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._condition = ev.detail.isValid ? ev.detail.value : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
@@ -112,9 +49,6 @@ export class DemoAutomationDescribeCondition extends LitElement {
|
|||||||
span {
|
span {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
ha-yaml-editor {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,92 +1,34 @@
|
|||||||
import { dump } from "js-yaml";
|
import { dump } from "js-yaml";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { html, css, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-yaml-editor";
|
|
||||||
import { Trigger } from "../../../../src/data/automation";
|
|
||||||
import { describeTrigger } from "../../../../src/data/automation_i18n";
|
import { describeTrigger } from "../../../../src/data/automation_i18n";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
|
||||||
|
|
||||||
const ENTITIES = [
|
|
||||||
getEntity("light", "kitchen", "on", {
|
|
||||||
friendly_name: "Kitchen Light",
|
|
||||||
}),
|
|
||||||
getEntity("person", "person", "", {
|
|
||||||
friendly_name: "Person",
|
|
||||||
}),
|
|
||||||
getEntity("zone", "home", "", {
|
|
||||||
friendly_name: "Home",
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
const triggers = [
|
const triggers = [
|
||||||
{ platform: "state", entity_id: "light.kitchen", from: "off", to: "on" },
|
{ platform: "state" },
|
||||||
{ platform: "mqtt" },
|
{ platform: "mqtt" },
|
||||||
{
|
{ platform: "geo_location" },
|
||||||
platform: "geo_location",
|
{ platform: "homeassistant" },
|
||||||
source: "test_source",
|
{ platform: "numeric_state" },
|
||||||
zone: "zone.home",
|
{ platform: "sun" },
|
||||||
event: "enter",
|
|
||||||
},
|
|
||||||
{ platform: "homeassistant", event: "start" },
|
|
||||||
{
|
|
||||||
platform: "numeric_state",
|
|
||||||
entity_id: "light.kitchen",
|
|
||||||
attribute: "brightness",
|
|
||||||
below: 80,
|
|
||||||
above: 20,
|
|
||||||
},
|
|
||||||
{ platform: "sun", event: "sunset" },
|
|
||||||
{ platform: "time_pattern" },
|
{ platform: "time_pattern" },
|
||||||
{ platform: "webhook" },
|
{ platform: "webhook" },
|
||||||
{
|
{ platform: "zone" },
|
||||||
platform: "zone",
|
|
||||||
entity_id: "person.person",
|
|
||||||
zone: "zone.home",
|
|
||||||
event: "enter",
|
|
||||||
},
|
|
||||||
{ platform: "tag" },
|
{ platform: "tag" },
|
||||||
{ platform: "time", at: "15:32" },
|
{ platform: "time" },
|
||||||
{ platform: "template" },
|
{ platform: "template" },
|
||||||
{ platform: "event", event_type: "homeassistant_started" },
|
{ platform: "event" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const initialTrigger: Trigger = {
|
|
||||||
platform: "state",
|
|
||||||
entity_id: "light.kitchen",
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("demo-automation-describe-trigger")
|
@customElement("demo-automation-describe-trigger")
|
||||||
export class DemoAutomationDescribeTrigger extends LitElement {
|
export class DemoAutomationDescribeTrigger extends LitElement {
|
||||||
@property({ attribute: false }) hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() _trigger = initialTrigger;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card header="Triggers">
|
<ha-card header="Triggers">
|
||||||
<div class="trigger">
|
|
||||||
<span>
|
|
||||||
${this._trigger
|
|
||||||
? describeTrigger(this._trigger, this.hass)
|
|
||||||
: "<invalid YAML>"}
|
|
||||||
</span>
|
|
||||||
<ha-yaml-editor
|
|
||||||
label="Trigger Config"
|
|
||||||
.defaultValue=${initialTrigger}
|
|
||||||
@value-changed=${this._dataChanged}
|
|
||||||
></ha-yaml-editor>
|
|
||||||
</div>
|
|
||||||
${triggers.map(
|
${triggers.map(
|
||||||
(conf) => html`
|
(conf) => html`
|
||||||
<div class="trigger">
|
<div class="trigger">
|
||||||
<span>${describeTrigger(conf as any, this.hass)}</span>
|
<span>${describeTrigger(conf as any)}</span>
|
||||||
<pre>${dump(conf)}</pre>
|
<pre>${dump(conf)}</pre>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@@ -95,18 +37,6 @@ export class DemoAutomationDescribeTrigger extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
const hass = provideHass(this);
|
|
||||||
hass.updateTranslations(null, "en");
|
|
||||||
hass.addEntities(ENTITIES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _dataChanged(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this._trigger = ev.detail.isValid ? ev.detail.value : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
@@ -122,9 +52,6 @@ export class DemoAutomationDescribeTrigger extends LitElement {
|
|||||||
span {
|
span {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
ha-yaml-editor {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
/* eslint-disable lit/no-template-arrow */
|
||||||
import { LitElement, TemplateResult, html, css } from "lit";
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
@@ -47,8 +47,6 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
|
|||||||
class DemoHaAutomationEditorAction extends LitElement {
|
class DemoHaAutomationEditorAction extends LitElement {
|
||||||
@state() private hass!: HomeAssistant;
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _disabled = false;
|
|
||||||
|
|
||||||
private data: any = SCHEMAS.map((info) => info.actions);
|
private data: any = SCHEMAS.map((info) => info.actions);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -69,15 +67,6 @@ class DemoHaAutomationEditorAction extends LitElement {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
};
|
};
|
||||||
return html`
|
return html`
|
||||||
<div class="options">
|
|
||||||
<ha-formfield label="Disabled">
|
|
||||||
<ha-switch
|
|
||||||
.name=${"disabled"}
|
|
||||||
.checked=${this._disabled}
|
|
||||||
@change=${this._handleOptionChange}
|
|
||||||
></ha-switch>
|
|
||||||
</ha-formfield>
|
|
||||||
</div>
|
|
||||||
${SCHEMAS.map(
|
${SCHEMAS.map(
|
||||||
(info, sampleIdx) => html`
|
(info, sampleIdx) => html`
|
||||||
<demo-black-white-row
|
<demo-black-white-row
|
||||||
@@ -92,7 +81,6 @@ class DemoHaAutomationEditorAction extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.actions=${this.data[sampleIdx]}
|
.actions=${this.data[sampleIdx]}
|
||||||
.sampleIdx=${sampleIdx}
|
.sampleIdx=${sampleIdx}
|
||||||
.disabled=${this._disabled}
|
|
||||||
@value-changed=${valueChanged}
|
@value-changed=${valueChanged}
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`
|
`
|
||||||
@@ -102,20 +90,6 @@ class DemoHaAutomationEditorAction extends LitElement {
|
|||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleOptionChange(ev) {
|
|
||||||
this[`_${ev.target.name}`] = ev.target.checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
.options {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 16px auto;
|
|
||||||
}
|
|
||||||
.options ha-formfield {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
/* eslint-disable lit/no-template-arrow */
|
||||||
import { LitElement, TemplateResult, html, css } from "lit";
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
@@ -83,8 +83,6 @@ const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
|
|||||||
class DemoHaAutomationEditorCondition extends LitElement {
|
class DemoHaAutomationEditorCondition extends LitElement {
|
||||||
@state() private hass!: HomeAssistant;
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _disabled = false;
|
|
||||||
|
|
||||||
private data: any = SCHEMAS.map((info) => info.conditions);
|
private data: any = SCHEMAS.map((info) => info.conditions);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -105,15 +103,6 @@ class DemoHaAutomationEditorCondition extends LitElement {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
};
|
};
|
||||||
return html`
|
return html`
|
||||||
<div class="options">
|
|
||||||
<ha-formfield label="Disabled">
|
|
||||||
<ha-switch
|
|
||||||
.name=${"disabled"}
|
|
||||||
.checked=${this._disabled}
|
|
||||||
@change=${this._handleOptionChange}
|
|
||||||
></ha-switch>
|
|
||||||
</ha-formfield>
|
|
||||||
</div>
|
|
||||||
${SCHEMAS.map(
|
${SCHEMAS.map(
|
||||||
(info, sampleIdx) => html`
|
(info, sampleIdx) => html`
|
||||||
<demo-black-white-row
|
<demo-black-white-row
|
||||||
@@ -128,7 +117,6 @@ class DemoHaAutomationEditorCondition extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.conditions=${this.data[sampleIdx]}
|
.conditions=${this.data[sampleIdx]}
|
||||||
.sampleIdx=${sampleIdx}
|
.sampleIdx=${sampleIdx}
|
||||||
.disabled=${this._disabled}
|
|
||||||
@value-changed=${valueChanged}
|
@value-changed=${valueChanged}
|
||||||
></ha-automation-condition>
|
></ha-automation-condition>
|
||||||
`
|
`
|
||||||
@@ -138,20 +126,6 @@ class DemoHaAutomationEditorCondition extends LitElement {
|
|||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleOptionChange(ev) {
|
|
||||||
this[`_${ev.target.name}`] = ev.target.checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
.options {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 16px auto;
|
|
||||||
}
|
|
||||||
.options ha-formfield {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
/* eslint-disable lit/no-template-arrow */
|
||||||
import { LitElement, TemplateResult, html, css } from "lit";
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
@@ -107,8 +107,6 @@ const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
|||||||
class DemoHaAutomationEditorTrigger extends LitElement {
|
class DemoHaAutomationEditorTrigger extends LitElement {
|
||||||
@state() private hass!: HomeAssistant;
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _disabled = false;
|
|
||||||
|
|
||||||
private data: any = SCHEMAS.map((info) => info.triggers);
|
private data: any = SCHEMAS.map((info) => info.triggers);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -129,15 +127,6 @@ class DemoHaAutomationEditorTrigger extends LitElement {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
};
|
};
|
||||||
return html`
|
return html`
|
||||||
<div class="options">
|
|
||||||
<ha-formfield label="Disabled">
|
|
||||||
<ha-switch
|
|
||||||
.name=${"disabled"}
|
|
||||||
.checked=${this._disabled}
|
|
||||||
@change=${this._handleOptionChange}
|
|
||||||
></ha-switch>
|
|
||||||
</ha-formfield>
|
|
||||||
</div>
|
|
||||||
${SCHEMAS.map(
|
${SCHEMAS.map(
|
||||||
(info, sampleIdx) => html`
|
(info, sampleIdx) => html`
|
||||||
<demo-black-white-row
|
<demo-black-white-row
|
||||||
@@ -152,7 +141,6 @@ class DemoHaAutomationEditorTrigger extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.triggers=${this.data[sampleIdx]}
|
.triggers=${this.data[sampleIdx]}
|
||||||
.sampleIdx=${sampleIdx}
|
.sampleIdx=${sampleIdx}
|
||||||
.disabled=${this._disabled}
|
|
||||||
@value-changed=${valueChanged}
|
@value-changed=${valueChanged}
|
||||||
></ha-automation-trigger>
|
></ha-automation-trigger>
|
||||||
`
|
`
|
||||||
@@ -162,20 +150,6 @@ class DemoHaAutomationEditorTrigger extends LitElement {
|
|||||||
)}
|
)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleOptionChange(ev) {
|
|
||||||
this[`_${ev.target.name}`] = ev.target.checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
.options {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 16px auto;
|
|
||||||
}
|
|
||||||
.options ha-formfield {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
title: "Logo"
|
title: "Logo"
|
||||||
---
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
# Using our logo
|
# Using our logo
|
||||||
|
|
||||||
As a community, we are proud of our logo. Follow these guidelines to ensure it always looks its best. Our logo follows Google's material design spec and uses the blue interface color.
|
As a community, we are proud of our logo. Follow these guidelines to ensure it always looks its best. Our logo follows Google's material design spec and uses the blue interface color.
|
||||||
|
@@ -1,15 +1,8 @@
|
|||||||
---
|
---
|
||||||
title: Alert
|
title: Alerts
|
||||||
subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
|
subtitle: An alert displays a short, important message in a way that attracts the user's attention without interrupting the user's task.
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
|
||||||
ha-alert {
|
|
||||||
display: block;
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
# Alert `<ha-alert>`
|
# Alert `<ha-alert>`
|
||||||
The alert offers four severity levels that set a distinctive icon and color.
|
The alert offers four severity levels that set a distinctive icon and color.
|
||||||
|
|
||||||
|
@@ -98,9 +98,7 @@ const alerts: {
|
|||||||
description: "Alert with slotted image",
|
description: "Alert with slotted image",
|
||||||
type: "warning",
|
type: "warning",
|
||||||
iconSlot: html`<span slot="icon" class="image"
|
iconSlot: html`<span slot="icon" class="image"
|
||||||
><img
|
><img src="https://www.home-assistant.io/images/home-assistant-logo.svg"
|
||||||
alt="Home Assistant logo"
|
|
||||||
src="https://www.home-assistant.io/images/home-assistant-logo.svg"
|
|
||||||
/></span>`,
|
/></span>`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
title: Bar Slider
|
|
||||||
---
|
|
@@ -1,170 +0,0 @@
|
|||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, state } from "lit/decorators";
|
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
|
||||||
import { repeat } from "lit/directives/repeat";
|
|
||||||
import "../../../../src/components/ha-bar-slider";
|
|
||||||
import "../../../../src/components/ha-card";
|
|
||||||
|
|
||||||
const sliders: {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
mode?: "start" | "end" | "cursor";
|
|
||||||
class?: string;
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
id: "slider-start",
|
|
||||||
label: "Slider (start mode)",
|
|
||||||
mode: "start",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "slider-end",
|
|
||||||
label: "Slider (end mode)",
|
|
||||||
mode: "end",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "slider-cursor",
|
|
||||||
label: "Slider (cursor mode)",
|
|
||||||
mode: "cursor",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "slider-start-custom",
|
|
||||||
label: "Slider (start mode) and custom style",
|
|
||||||
mode: "start",
|
|
||||||
class: "custom",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "slider-end-custom",
|
|
||||||
label: "Slider (end mode) and custom style",
|
|
||||||
mode: "end",
|
|
||||||
class: "custom",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "slider-cursor-custom",
|
|
||||||
label: "Slider (cursor mode) and custom style",
|
|
||||||
mode: "cursor",
|
|
||||||
class: "custom",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@customElement("demo-components-ha-bar-slider")
|
|
||||||
export class DemoHaBarSlider extends LitElement {
|
|
||||||
@state() private value = 50;
|
|
||||||
|
|
||||||
@state() private sliderPosition?: number;
|
|
||||||
|
|
||||||
handleValueChanged(e: CustomEvent) {
|
|
||||||
this.value = e.detail.value as number;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSliderMoved(e: CustomEvent) {
|
|
||||||
this.sliderPosition = e.detail.value as number;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<p><b>Slider values</b></p>
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>position</td>
|
|
||||||
<td>${this.sliderPosition ?? "-"}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>value</td>
|
|
||||||
<td>${this.value ?? "-"}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
${repeat(sliders, (slider) => {
|
|
||||||
const { id, label, ...config } = slider;
|
|
||||||
return html`
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<label id=${id}>${label}</label>
|
|
||||||
<pre>Config: ${JSON.stringify(config)}</pre>
|
|
||||||
<ha-bar-slider
|
|
||||||
.value=${this.value}
|
|
||||||
.mode=${config.mode}
|
|
||||||
class=${ifDefined(config.class)}
|
|
||||||
@value-changed=${this.handleValueChanged}
|
|
||||||
@slider-moved=${this.handleSliderMoved}
|
|
||||||
aria-labelledby=${id}
|
|
||||||
>
|
|
||||||
</ha-bar-slider>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<p class="title"><b>Vertical</b></p>
|
|
||||||
<div class="vertical-sliders">
|
|
||||||
${repeat(sliders, (slider) => {
|
|
||||||
const { id, label, ...config } = slider;
|
|
||||||
return html`
|
|
||||||
<ha-bar-slider
|
|
||||||
.value=${this.value}
|
|
||||||
.mode=${config.mode}
|
|
||||||
vertical
|
|
||||||
class=${ifDefined(config.class)}
|
|
||||||
@value-changed=${this.handleValueChanged}
|
|
||||||
@slider-moved=${this.handleSliderMoved}
|
|
||||||
aria-label=${label}
|
|
||||||
>
|
|
||||||
</ha-bar-slider>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
|
||||||
return css`
|
|
||||||
ha-card {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 24px auto;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.custom {
|
|
||||||
--slider-bar-color: #ffcf4c;
|
|
||||||
--slider-bar-background: #ffcf4c;
|
|
||||||
--slider-bar-background-opacity: 0.2;
|
|
||||||
--slider-bar-thickness: 100px;
|
|
||||||
--slider-bar-border-radius: 24px;
|
|
||||||
}
|
|
||||||
.vertical-sliders {
|
|
||||||
height: 300px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
p.title {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.vertical-sliders > *:not(:last-child) {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"demo-components-ha-bar-slider": DemoHaBarSlider;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
title: Bar Switch
|
|
||||||
---
|
|
@@ -1,145 +0,0 @@
|
|||||||
import {
|
|
||||||
mdiGarage,
|
|
||||||
mdiGarageOpen,
|
|
||||||
mdiLightbulb,
|
|
||||||
mdiLightbulbOff,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, state } from "lit/decorators";
|
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
|
||||||
import { repeat } from "lit/directives/repeat";
|
|
||||||
import "../../../../src/components/ha-bar-switch";
|
|
||||||
import "../../../../src/components/ha-card";
|
|
||||||
|
|
||||||
const switches: {
|
|
||||||
id: string;
|
|
||||||
label: string;
|
|
||||||
class?: string;
|
|
||||||
reversed?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
id: "switch",
|
|
||||||
label: "Switch",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "switch-reversed",
|
|
||||||
label: "Switch Reversed",
|
|
||||||
reversed: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "switch-custom",
|
|
||||||
label: "Switch and custom style",
|
|
||||||
class: "custom",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "switch-disabled",
|
|
||||||
label: "Disabled Switch",
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@customElement("demo-components-ha-bar-switch")
|
|
||||||
export class DemoHaBarSwitch extends LitElement {
|
|
||||||
@state() private checked = false;
|
|
||||||
|
|
||||||
handleValueChanged(e: any) {
|
|
||||||
this.checked = e.target.checked as boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
${repeat(switches, (sw) => {
|
|
||||||
const { id, label, ...config } = sw;
|
|
||||||
return html`
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<label id=${id}>${label}</label>
|
|
||||||
<pre>Config: ${JSON.stringify(config)}</pre>
|
|
||||||
<ha-bar-switch
|
|
||||||
.checked=${this.checked}
|
|
||||||
class=${ifDefined(config.class)}
|
|
||||||
@change=${this.handleValueChanged}
|
|
||||||
.pathOn=${mdiLightbulb}
|
|
||||||
.pathOff=${mdiLightbulbOff}
|
|
||||||
aria-labelledby=${id}
|
|
||||||
disabled=${ifDefined(config.disabled)}
|
|
||||||
reversed=${ifDefined(config.reversed)}
|
|
||||||
>
|
|
||||||
</ha-bar-switch>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
<ha-card>
|
|
||||||
<div class="card-content">
|
|
||||||
<p class="title"><b>Vertical</b></p>
|
|
||||||
<div class="vertical-switches">
|
|
||||||
${repeat(switches, (sw) => {
|
|
||||||
const { id, label, ...config } = sw;
|
|
||||||
return html`
|
|
||||||
<ha-bar-switch
|
|
||||||
.checked=${this.checked}
|
|
||||||
vertical
|
|
||||||
class=${ifDefined(config.class)}
|
|
||||||
@change=${this.handleValueChanged}
|
|
||||||
aria-label=${label}
|
|
||||||
.pathOn=${mdiGarageOpen}
|
|
||||||
.pathOff=${mdiGarage}
|
|
||||||
disabled=${ifDefined(config.disabled)}
|
|
||||||
reversed=${ifDefined(config.reversed)}
|
|
||||||
>
|
|
||||||
</ha-bar-switch>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
|
||||||
return css`
|
|
||||||
ha-card {
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 24px auto;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.custom {
|
|
||||||
--switch-bar-on-color: var(--green-color);
|
|
||||||
--switch-bar-off-color: var(--red-color);
|
|
||||||
--switch-bar-thickness: 100px;
|
|
||||||
--switch-bar-border-radius: 24px;
|
|
||||||
--switch-bar-padding: 6px;
|
|
||||||
--mdc-icon-size: 24px;
|
|
||||||
}
|
|
||||||
.vertical-switches {
|
|
||||||
height: 300px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
p.title {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.vertical-switches > *:not(:last-child) {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"demo-components-ha-bar-switch": DemoHaBarSwitch;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,4 +1,3 @@
|
|||||||
---
|
---
|
||||||
title: Bar
|
title: Progress Bars
|
||||||
subtitle: Can be used to communicate progress of a task.
|
|
||||||
---
|
---
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
---
|
---
|
||||||
title: Chip
|
title: Chips
|
||||||
---
|
---
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
title: Dialog
|
|
||||||
subtitle: Dialogs provide important prompts in a user flow.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Material Design 3
|
|
||||||
|
|
||||||
Our dialogs are based on the latest version of Material Design. Specs and guidelines can be found on its [website](https://m3.material.io/components/dialogs/overview).
|
|
||||||
|
|
||||||
# Highlighted guidelines
|
|
||||||
|
|
||||||
## Content
|
|
||||||
* A best practice is to always use a title, even if it is optional by Material guidelines.
|
|
||||||
* People mainly read the title and a button. Put the most important information in those two.
|
|
||||||
* Try to avoid user generated content in the title, this could make the title unreadable long.
|
|
||||||
* If users become unsure, they read the description. Make sure this explains what will happen.
|
|
||||||
* Strive for minimalism.
|
|
||||||
|
|
||||||
## Buttons and X-icon
|
|
||||||
* Keep the labels short, for example `Save`, `Delete`, `Enable`.
|
|
||||||
* Dialog with actions must always have a discard button. On desktop a `Cancel` button and X-icon, on mobile only the X-icon.
|
|
||||||
* Destructive actions should be a red warning button.
|
|
||||||
* Alert or confirmation dialogs only have buttons and no X-icon.
|
|
||||||
* Try to avoid three buttons in one dialog. Especially when you leave the dialog task unfinished.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
### Confirmation dialog
|
|
||||||
> **Delete dashboard?**
|
|
||||||
>
|
|
||||||
> Dashboard [dashboard name] will be permanently deleted from Home Assistant.
|
|
||||||
>
|
|
||||||
> Cancel / Delete
|
|