Compare commits

..

1 Commits

Author SHA1 Message Date
Philip Allgaier
67598ea877 Gallery: Added switch and gauge component pagess + CSS vars 2022-11-29 23:37:20 +01:00
447 changed files with 7048 additions and 13459 deletions

View File

@@ -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
View 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"

View 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
}
}

View File

@@ -6,13 +6,3 @@ updates:
interval: weekly interval: weekly
time: "06:00" time: "06:00"
open-pull-requests-limit: 10 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/*"

View File

@@ -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 }}

View File

@@ -20,9 +20,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@v3
- 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@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@@ -44,9 +44,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@v3
- 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@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@@ -63,9 +63,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@v3
- 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@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
@@ -82,9 +82,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@v3
- 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@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn

View File

@@ -23,7 +23,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.3.0 uses: actions/checkout@v3
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.

View File

@@ -1,9 +1,9 @@
name: Design deployment name: Demo
on: on:
workflow_dispatch: push:
schedule: branches:
- cron: "0 0 * * *" - dev
env: env:
NODE_VERSION: 16 NODE_VERSION: 16
@@ -12,34 +12,24 @@ env:
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment:
name: Design
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.3.0 uses: actions/checkout@v3
- 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@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
run: yarn install run: yarn install
env: env:
CI: true CI: true
- name: Build Demo
- name: Build Gallery run: ./node_modules/.bin/gulp build-demo
run: ./node_modules/.bin/gulp build-gallery
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to Netlify - name: Deploy to Netlify
id: deploy run: npx netlify-cli deploy --dir=demo/dist --prod
uses: netlify/actions/cli@master
with:
args: deploy --dir=gallery/dist --prod
env: env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}

View File

@@ -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 }}

View File

@@ -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"

View File

@@ -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@v3.0.0
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
View File

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

View File

@@ -21,7 +21,7 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.3.0 uses: actions/checkout@v3
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4 uses: actions/setup-python@v4
@@ -29,7 +29,7 @@ jobs:
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@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn

View File

@@ -24,7 +24,7 @@ jobs:
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@v3
- name: Verify version - name: Verify version
uses: home-assistant/actions/helpers/verify-version@master uses: home-assistant/actions/helpers/verify-version@master
@@ -35,7 +35,7 @@ jobs:
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@v3
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn

View File

@@ -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@v6.0.1
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 90 days-before-stale: 90

View File

@@ -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@v3
- name: Upload Translations - name: Upload Translations
run: | run: |

14
.gitignore vendored
View File

@@ -2,10 +2,10 @@
.reify-cache .reify-cache
# build # build
build/ build
dist/ hass_frontend/*
/hass_frontend/ dist
/translations/ translations
# yarn # yarn
.yarn/* .yarn/*
@@ -15,7 +15,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 +27,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 +46,4 @@ src/cast/dev_const.ts
.tool-versions .tool-versions
# Home Assistant config # Home Assistant config
/config/ /config

View File

@@ -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"
] ]
} }

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

783
.yarn/releases/yarn-3.2.3.cjs vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -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.3.cjs

View File

@@ -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,
])
) )
); );

View File

@@ -1,5 +1,5 @@
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";
@@ -46,21 +46,18 @@ gulp.task("check-translations-html", function () {
return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml()); return gulp.src([`${inDirFrontend}/*.json`]).pipe(checkHtml());
}); });
gulp.task("check-all-files-exist", async function () { gulp.task("check-all-files-exist", function () {
const file = await fs.readFile(srcMeta, { encoding }); 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();
}); });
gulp.task( gulp.task(

View File

@@ -1,9 +1,9 @@
// Task to download the latest Lokalise translations from the nightly workflow artifacts // Task to download the latest Lokalise translations from the nightly workflow artifacts
const del = import("del");
const fs = require("fs/promises"); const fs = require("fs/promises");
const path = require("path"); const path = require("path");
const process = require("process"); const process = require("process");
const del = require("del");
const gulp = require("gulp"); const gulp = require("gulp");
const jszip = require("jszip"); const jszip = require("jszip");
const tar = require("tar"); const tar = require("tar");
@@ -17,8 +17,8 @@ const WORKFLOW_NAME = "nightly.yaml";
const ARTIFACT_NAME = "translations"; const ARTIFACT_NAME = "translations";
const CLIENT_ID = "Iv1.3914e28cb27834d1"; const CLIENT_ID = "Iv1.3914e28cb27834d1";
const EXTRACT_DIR = "translations"; const EXTRACT_DIR = "translations";
const TOKEN_FILE = path.posix.join(EXTRACT_DIR, "token.json"); const TOKEN_FILE = path.join(EXTRACT_DIR, "token.json");
const ARTIFACT_FILE = path.posix.join(EXTRACT_DIR, "artifact.json"); const ARTIFACT_FILE = path.join(EXTRACT_DIR, "artifact.json");
let allowTokenSetup = false; let allowTokenSetup = false;
gulp.task("allow-setup-fetch-nightly-translations", (done) => { gulp.task("allow-setup-fetch-nightly-translations", (done) => {
@@ -137,11 +137,7 @@ gulp.task("fetch-nightly-translations", async function () {
// Remove the current translations // Remove the current translations
const deleteCurrent = Promise.all(writings).then( const deleteCurrent = Promise.all(writings).then(
(await del).deleteAsync([ del([`${EXTRACT_DIR}/*`, `!${ARTIFACT_FILE}`, `!${TOKEN_FILE}`])
`${EXTRACT_DIR}/*`,
`!${ARTIFACT_FILE}`,
`!${TOKEN_FILE}`,
])
); );
// Get the download URL and follow the redirect to download (stored as ArrayBuffer) // Get the download URL and follow the redirect to download (stored as ArrayBuffer)

View File

@@ -1,4 +1,4 @@
const del = import("del"); 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 +6,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)) {

View File

@@ -1,5 +1,5 @@
const del = import("del");
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,7 +13,7 @@ const { mapFiles } = require("../util");
const env = require("../env"); const env = require("../env");
const paths = require("../paths"); const paths = require("../paths");
require("./fetch-nightly-translations"); require("./fetch-nightly_translations");
const inFrontendDir = "translations/frontend"; const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend"; const inBackendDir = "translations/backend";
@@ -120,7 +120,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)) {

View File

@@ -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() {

View File

@@ -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

View File

@@ -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">

View File

@@ -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;

View 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

View File

@@ -1,5 +1,5 @@
--- ---
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.
--- ---

View File

@@ -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>`,
}, },
{ {

View File

@@ -1,3 +1,3 @@
--- ---
title: Bar Slider title: Bar Sliders
--- ---

View File

@@ -8,7 +8,7 @@ import "../../../../src/components/ha-card";
const sliders: { const sliders: {
id: string; id: string;
label: string; label: string;
mode?: "start" | "end" | "cursor"; mode?: "start" | "end" | "indicator";
class?: string; class?: string;
}[] = [ }[] = [
{ {
@@ -22,9 +22,9 @@ const sliders: {
mode: "end", mode: "end",
}, },
{ {
id: "slider-cursor", id: "slider-indicator",
label: "Slider (cursor mode)", label: "Slider (indicator mode)",
mode: "cursor", mode: "indicator",
}, },
{ {
id: "slider-start-custom", id: "slider-start-custom",
@@ -39,9 +39,9 @@ const sliders: {
class: "custom", class: "custom",
}, },
{ {
id: "slider-cursor-custom", id: "slider-indicator-custom",
label: "Slider (cursor mode) and custom style", label: "Slider (indicator mode) and custom style",
mode: "cursor", mode: "indicator",
class: "custom", class: "custom",
}, },
]; ];
@@ -142,8 +142,7 @@ export class DemoHaBarSlider extends LitElement {
} }
.custom { .custom {
--slider-bar-color: #ffcf4c; --slider-bar-color: #ffcf4c;
--slider-bar-background: #ffcf4c; --slider-bar-background: #ffcf4c64;
--slider-bar-background-opacity: 0.2;
--slider-bar-thickness: 100px; --slider-bar-thickness: 100px;
--slider-bar-border-radius: 24px; --slider-bar-border-radius: 24px;
} }

View File

@@ -1,3 +1,3 @@
--- ---
title: Bar Switch title: Bar Switches
--- ---

View File

@@ -115,8 +115,8 @@ export class DemoHaBarSwitch extends LitElement {
font-weight: 600; font-weight: 600;
} }
.custom { .custom {
--switch-bar-on-color: var(--green-color); --switch-bar-color-on: var(--rgb-green-color);
--switch-bar-off-color: var(--red-color); --switch-bar-color-off: var(--rgb-red-color);
--switch-bar-thickness: 100px; --switch-bar-thickness: 100px;
--switch-bar-border-radius: 24px; --switch-bar-border-radius: 24px;
--switch-bar-padding: 6px; --switch-bar-padding: 6px;

View File

@@ -1,4 +1,3 @@
--- ---
title: Bar title: Progress Bars
subtitle: Can be used to communicate progress of a task.
--- ---

View File

@@ -1,3 +1,3 @@
--- ---
title: Chip title: Chips
--- ---

View File

@@ -1,5 +1,5 @@
--- ---
title: Dialog title: Dialogs
subtitle: Dialogs provide important prompts in a user flow. subtitle: Dialogs provide important prompts in a user flow.
--- ---

View File

@@ -99,19 +99,16 @@ const AREAS = [
area_id: "backyard", area_id: "backyard",
name: "Backyard", name: "Backyard",
picture: null, picture: null,
aliases: [],
}, },
{ {
area_id: "bedroom", area_id: "bedroom",
name: "Bedroom", name: "Bedroom",
picture: null, picture: null,
aliases: [],
}, },
{ {
area_id: "livingroom", area_id: "livingroom",
name: "Livingroom", name: "Livingroom",
picture: null, picture: null,
aliases: [],
}, },
]; ];

View File

@@ -1,5 +1,5 @@
--- ---
title: Selector title: Selectors
--- ---
See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/). See the website for [list of available selectors](https://www.home-assistant.io/docs/blueprint/selectors/).

View File

@@ -95,19 +95,16 @@ const AREAS = [
area_id: "backyard", area_id: "backyard",
name: "Backyard", name: "Backyard",
picture: null, picture: null,
aliases: [],
}, },
{ {
area_id: "bedroom", area_id: "bedroom",
name: "Bedroom", name: "Bedroom",
picture: null, picture: null,
aliases: [],
}, },
{ {
area_id: "livingroom", area_id: "livingroom",
name: "Livingroom", name: "Livingroom",
picture: null, picture: null,
aliases: [],
}, },
]; ];

View File

@@ -1,3 +1,3 @@
--- ---
title: Tip title: Tips
--- ---

View File

@@ -142,25 +142,6 @@ const CONFIGS = [
heading: "Basic", heading: "Basic",
config: ` config: `
- type: entities - type: entities
entities:
- scene.romantic_lights
- device_tracker.demo_paulus
- cover.kitchen_window
- group.kitchen
- lock.kitchen_door
- light.bed_light
- light.non_existing
- climate.ecobee
- input_number.number
- sensor.humidity
- text.message
`,
},
{
heading: "With enabled state color",
config: `
- type: entities
state_color: true
entities: entities:
- scene.romantic_lights - scene.romantic_lights
- device_tracker.demo_paulus - device_tracker.demo_paulus

View File

@@ -35,11 +35,11 @@ const CONFIGS = [
`, `,
}, },
{ {
heading: "With State", heading: "Without State",
config: ` config: `
- type: button - type: button
entity: light.bed_light entity: light.bed_light
show_state: true show_state: false
`, `,
}, },
{ {

View File

@@ -62,21 +62,6 @@ const CONFIGS = [
heading: "Basic example", heading: "Basic example",
config: ` config: `
- type: glance - type: glance
entities:
- device_tracker.demo_paulus
- media_player.living_room
- sun.sun
- cover.kitchen_window
- light.kitchen_lights
- lock.kitchen_door
- light.ceiling_lights
`,
},
{
heading: "No state colors",
config: `
- type: glance
state_color: false
entities: entities:
- device_tracker.demo_paulus - device_tracker.demo_paulus
- media_player.living_room - media_player.living_room

View File

@@ -1,3 +1,3 @@
--- ---
title: Grid and Stack Card title: Grid And Stack Card
--- ---

View File

@@ -4,12 +4,14 @@ import {
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { css, html, LitElement, TemplateResult } from "lit"; import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { computeDomain } from "../../../../src/common/entity/compute_domain"; import { computeDomain } from "../../../../src/common/entity/compute_domain";
import { computeStateDisplay } from "../../../../src/common/entity/compute_state_display"; import { computeStateDisplay } from "../../../../src/common/entity/compute_state_display";
import { stateColorCss } from "../../../../src/common/entity/state_color";
import { stateIconPath } from "../../../../src/common/entity/state_icon_path";
import "../../../../src/components/data-table/ha-data-table"; import "../../../../src/components/data-table/ha-data-table";
import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table"; import type { DataTableColumnContainer } from "../../../../src/components/data-table/ha-data-table";
import "../../../../src/components/entity/state-badge";
import "../../../../src/components/ha-chip"; import "../../../../src/components/ha-chip";
import { provideHass } from "../../../../src/fake_data/provide_hass"; import { provideHass } from "../../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
@@ -103,27 +105,16 @@ const ENTITIES: HassEntity[] = [
createEntity("alarm_control_panel.arming", "arming"), createEntity("alarm_control_panel.arming", "arming"),
createEntity("alarm_control_panel.disarming", "disarming"), createEntity("alarm_control_panel.disarming", "disarming"),
createEntity("alarm_control_panel.triggered", "triggered"), createEntity("alarm_control_panel.triggered", "triggered"),
// Alert
createEntity("alert.idle", "idle"),
createEntity("alert.off", "off"),
createEntity("alert.on", "on"),
// Automation
createEntity("automation.off", "off"),
createEntity("automation.on", "on"),
// Binary Sensor // Binary Sensor
...BINARY_SENSOR_DEVICE_CLASSES.map((dc) => [ ...BINARY_SENSOR_DEVICE_CLASSES.map((dc) =>
createEntity(`binary_sensor.${dc}`, "off", dc), createEntity(`binary_sensor.${dc}`, "on", dc)
createEntity(`binary_sensor.${dc}`, "on", dc), ),
]).reduce((arr, item) => [...arr, ...item], []),
// Button // Button
createEntity("button.restart", "unknown", "restart"), createEntity("button.restart", "unknown", "restart"),
createEntity("button.update", "unknown", "update"), createEntity("button.update", "unknown", "update"),
// Calendar // Calendar
createEntity("calendar.off", "off"),
createEntity("calendar.on", "on"), createEntity("calendar.on", "on"),
// Camera createEntity("calendar.off", "off"),
createEntity("camera.idle", "idle"),
createEntity("camera.streaming", "streaming"),
// Climate // Climate
createEntity("climate.off", "off"), createEntity("climate.off", "off"),
createEntity("climate.heat", "heat"), createEntity("climate.heat", "heat"),
@@ -132,25 +123,11 @@ const ENTITIES: HassEntity[] = [
createEntity("climate.auto", "auto"), createEntity("climate.auto", "auto"),
createEntity("climate.dry", "dry"), createEntity("climate.dry", "dry"),
createEntity("climate.fan_only", "fan_only"), createEntity("climate.fan_only", "fan_only"),
createEntity("climate.auto_idle", "auto", undefined, { hvac_action: "idle" }),
createEntity("climate.auto_off", "auto", undefined, { hvac_action: "off" }),
createEntity("climate.auto_heating", "auto", undefined, {
hvac_action: "heating",
}),
createEntity("climate.auto_cooling", "auto", undefined, {
hvac_action: "cooling",
}),
createEntity("climate.auto_dry", "auto", undefined, {
hvac_action: "drying",
}),
createEntity("climate.auto_fan", "auto", undefined, {
hvac_action: "fan",
}),
// Cover // Cover
createEntity("cover.closing", "closing"),
createEntity("cover.closed", "closed"),
createEntity("cover.opening", "opening"), createEntity("cover.opening", "opening"),
createEntity("cover.open", "open"), createEntity("cover.open", "open"),
createEntity("cover.closing", "closing"),
createEntity("cover.closed", "closed"),
createEntity("cover.awning", "open", "awning"), createEntity("cover.awning", "open", "awning"),
createEntity("cover.blind", "open", "blind"), createEntity("cover.blind", "open", "blind"),
createEntity("cover.curtain", "open", "curtain"), createEntity("cover.curtain", "open", "curtain"),
@@ -162,27 +139,21 @@ const ENTITIES: HassEntity[] = [
createEntity("cover.shutter", "open", "shutter"), createEntity("cover.shutter", "open", "shutter"),
createEntity("cover.window", "open", "window"), createEntity("cover.window", "open", "window"),
// Device tracker/person // Device tracker/person
createEntity("device_tracker.not_home", "not_home"),
createEntity("device_tracker.home", "home"), createEntity("device_tracker.home", "home"),
createEntity("device_tracker.not_home", "not_home"),
createEntity("device_tracker.work", "work"), createEntity("device_tracker.work", "work"),
createEntity("person.home", "home"), createEntity("person.home", "home"),
createEntity("person.not_home", "not_home"), createEntity("person.not_home", "not_home"),
createEntity("person.work", "work"), createEntity("person.work", "work"),
// Fan // Fan
createEntity("fan.off", "off"),
createEntity("fan.on", "on"), createEntity("fan.on", "on"),
// Camera createEntity("fan.off", "off"),
createEntity("group.off", "off"),
createEntity("group.on", "on"),
// Humidifier // Humidifier
createEntity("humidifier.off", "off"),
createEntity("humidifier.on", "on"), createEntity("humidifier.on", "on"),
// Helpers createEntity("humidifier.off", "off"),
createEntity("input_boolean.off", "off"),
createEntity("input_boolean.on", "on"),
// Light // Light
createEntity("light.off", "off"),
createEntity("light.on", "on"), createEntity("light.on", "on"),
createEntity("light.off", "off"),
// Locks // Locks
createEntity("lock.locked", "locked"), createEntity("lock.locked", "locked"),
createEntity("lock.unlocked", "unlocked"), createEntity("lock.unlocked", "unlocked"),
@@ -209,33 +180,15 @@ const ENTITIES: HassEntity[] = [
createEntity("media_player.speaker_playing", "playing", "speaker"), createEntity("media_player.speaker_playing", "playing", "speaker"),
createEntity("media_player.speaker_paused", "paused", "speaker"), createEntity("media_player.speaker_paused", "paused", "speaker"),
createEntity("media_player.speaker_standby", "standby", "speaker"), createEntity("media_player.speaker_standby", "standby", "speaker"),
// Plant
createEntity("plant.ok", "ok"),
createEntity("plant.problem", "problem"),
// Remote
createEntity("remote.off", "off"),
createEntity("remote.on", "on"),
// Schedule
createEntity("schedule.off", "off"),
createEntity("schedule.on", "on"),
// Script
createEntity("script.off", "off"),
createEntity("script.on", "on"),
// Sensor // Sensor
...SENSOR_DEVICE_CLASSES.map((dc) => createEntity(`sensor.${dc}`, "10", dc)), ...SENSOR_DEVICE_CLASSES.map((dc) => createEntity(`sensor.${dc}`, "10", dc)),
// Battery sensor // Battery sensor
...[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, "unknown", "not_valid"].map( ...[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100].map((value) =>
(value) => createEntity(`sensor.battery_${value}`, value.toString(), "battery")
createEntity(`sensor.battery_${value}`, value.toString(), "battery")
), ),
// Siren // Siren
createEntity("siren.off", "off"), createEntity("siren.off", "off"),
createEntity("siren.on", "on"), createEntity("siren.on", "on"),
// Sun
createEntity("sun.below", "below_horizon"),
createEntity("sun.above", "above_horizon"),
createEntity("sun.unknown", "unknown"),
createEntity("sun.unavailable", "unavailable"),
// Switch // Switch
createEntity("switch.off", "off"), createEntity("switch.off", "off"),
createEntity("switch.on", "on"), createEntity("switch.on", "on"),
@@ -243,13 +196,9 @@ const ENTITIES: HassEntity[] = [
createEntity("switch.outlet_on", "on", "outlet"), createEntity("switch.outlet_on", "on", "outlet"),
createEntity("switch.switch_off", "off", "switch"), createEntity("switch.switch_off", "off", "switch"),
createEntity("switch.switch_on", "on", "switch"), createEntity("switch.switch_on", "on", "switch"),
// Timer
createEntity("timer.idle", "idle"),
createEntity("timer.active", "active"),
createEntity("timer.paused", "paused"),
// Vacuum // Vacuum
createEntity("vacuum.docked", "docked"),
createEntity("vacuum.cleaning", "cleaning"), createEntity("vacuum.cleaning", "cleaning"),
createEntity("vacuum.docked", "docked"),
createEntity("vacuum.paused", "paused"), createEntity("vacuum.paused", "paused"),
createEntity("vacuum.idle", "idle"), createEntity("vacuum.idle", "idle"),
createEntity("vacuum.returning", "returning"), createEntity("vacuum.returning", "returning"),
@@ -331,15 +280,21 @@ export class DemoEntityState extends LitElement {
const columns: DataTableColumnContainer<EntityRowData> = { const columns: DataTableColumnContainer<EntityRowData> = {
icon: { icon: {
title: "Icon", title: "Icon",
template: (_, entry) => html` template: (_, entry) => {
<state-badge const cssColor = stateColorCss(entry.stateObj);
.stateObj=${entry.stateObj} return html`
.stateColor=${true} <ha-svg-icon
></state-badge> style=${styleMap({
`, color: `rgb(${cssColor})`,
})}
.path=${stateIconPath(entry.stateObj)}
>
</ha-svg-icon>
`;
},
}, },
entity_id: { entity_id: {
title: "Entity ID", title: "Entity id",
width: "30%", width: "30%",
filterable: true, filterable: true,
sortable: true, sortable: true,
@@ -352,8 +307,7 @@ export class DemoEntityState extends LitElement {
html`${computeStateDisplay( html`${computeStateDisplay(
hass.localize, hass.localize,
entry.stateObj, entry.stateObj,
hass.locale, hass.locale
hass.entities
)}`, )}`,
}, },
device_class: { device_class: {

View File

@@ -29,9 +29,7 @@ class HassioAddonRepositoryEl extends LitElement {
if (filter) { if (filter) {
return filterAndSort(addons, filter); return filterAndSort(addons, filter);
} }
return addons.sort((a, b) => return addons.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name));
caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language)
);
}); });
protected render(): TemplateResult { protected render(): TemplateResult {

View File

@@ -404,7 +404,6 @@ class HassioAddonInfo extends LitElement {
? html` ? html`
<img <img
class="logo" class="logo"
alt=""
src="/api/hassio/addons/${this.addon.slug}/logo" src="/api/hassio/addons/${this.addon.slug}/logo"
/> />
` `

View File

@@ -15,7 +15,7 @@ class SupervisorFormfieldLabel extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
${this.imageUrl ${this.imageUrl
? html`<img loading="lazy" alt="" src=${this.imageUrl} class="icon" />` ? html`<img loading="lazy" .src=${this.imageUrl} class="icon" />`
: this.iconPath : this.iconPath
? html`<ha-svg-icon .path=${this.iconPath} class="icon"></ha-svg-icon>` ? html`<ha-svg-icon .path=${this.iconPath} class="icon"></ha-svg-icon>`
: ""} : ""}

View File

@@ -35,13 +35,7 @@ class HassioAddons extends LitElement {
</ha-card> </ha-card>
` `
: this.supervisor.addon.addons : this.supervisor.addon.addons
.sort((a, b) => .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
caseInsensitiveStringCompare(
a.name,
b.name,
this.hass.locale.language
)
)
.map( .map(
(addon) => html` (addon) => html`
<ha-card <ha-card

View File

@@ -28,7 +28,6 @@ class HassioDashboard extends LitElement {
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.route=${this.route} .route=${this.route}
back-path="/config"
.header=${this.supervisor.localize("panel.addons")} .header=${this.supervisor.localize("panel.addons")}
> >
<hassio-addons <hassio-addons

View File

@@ -15,12 +15,7 @@ import { HomeAssistant } from "../../../../src/types";
import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware"; import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
const _filterDevices = memoizeOne( const _filterDevices = memoizeOne(
( (showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) =>
showAdvanced: boolean,
hardware: HassioHardwareInfo,
filter: string,
language: string
) =>
hardware.devices hardware.devices
.filter( .filter(
(device) => (device) =>
@@ -33,7 +28,7 @@ const _filterDevices = memoizeOne(
.toLocaleLowerCase() .toLocaleLowerCase()
.includes(filter)) .includes(filter))
) )
.sort((a, b) => stringCompare(a.name, b.name, language)) .sort((a, b) => stringCompare(a.name, b.name))
); );
@customElement("dialog-hassio-hardware") @customElement("dialog-hassio-hardware")
@@ -61,8 +56,7 @@ class HassioHardwareDialog extends LitElement {
const devices = _filterDevices( const devices = _filterDevices(
this.hass.userData?.showAdvanced || false, this.hass.userData?.showAdvanced || false,
this._dialogParams.hardware, this._dialogParams.hardware,
(this._filter || "").toLowerCase(), (this._filter || "").toLowerCase()
this.hass.locale.language
); );
return html` return html`

View File

@@ -68,9 +68,7 @@ class HassioRepositoriesDialog extends LitElement {
repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons repo.slug !== "a0d7b954" && // Home Assistant Community Add-ons
repo.slug !== "5c53de3b" // The ESPHome repository repo.slug !== "5c53de3b" // The ESPHome repository
) )
.sort((a, b) => .sort((a, b) => caseInsensitiveStringCompare(a.name, b.name))
caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language)
)
); );
private _filteredUsedRepositories = memoizeOne( private _filteredUsedRepositories = memoizeOne(

View File

@@ -59,11 +59,7 @@ class HassioIngressView extends LitElement {
return html` <hass-loading-screen></hass-loading-screen> `; return html` <hass-loading-screen></hass-loading-screen> `;
} }
const iframe = html`<iframe const iframe = html`<iframe src=${this._addon.ingress_url!}></iframe>`;
title=${this._addon.name}
src=${this._addon.ingress_url!}
>
</iframe>`;
if (!this.ingressPanel) { if (!this.ingressPanel) {
return html`<hass-subpage return html`<hass-subpage

View File

@@ -5,5 +5,4 @@ module.exports = {
'printf "%s\n" "Translation files should not be added or modified here. Instead, make the necessary modifications in src/translations/en.json. Other languages are managed externally. Please see https://developers.home-assistant.io/docs/translations/ for details." ' + 'printf "%s\n" "Translation files should not be added or modified here. Instead, make the necessary modifications in src/translations/en.json. Other languages are managed externally. Please see https://developers.home-assistant.io/docs/translations/ for details." ' +
files.join(" ") + files.join(" ") +
" >&2 && exit 1", " >&2 && exit 1",
"/yarn.lock": () => "yarn dedupe",
}; };

View File

@@ -24,17 +24,22 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^6.0.0", "@braintree/sanitize-url": "^5.0.2",
"@codemirror/autocomplete": "^6.4.0", "@codemirror/autocomplete": "^0.19.12",
"@codemirror/commands": "^6.1.3", "@codemirror/commands": "^0.19.8",
"@codemirror/language": "^6.4.0", "@codemirror/gutter": "^0.19.9",
"@codemirror/legacy-modes": "^6.3.1", "@codemirror/highlight": "^0.19.7",
"@codemirror/search": "^6.2.3", "@codemirror/history": "^0.19.2",
"@codemirror/state": "^6.2.0", "@codemirror/legacy-modes": "^0.19.0",
"@codemirror/view": "^6.7.1", "@codemirror/rectangular-selection": "^0.19.1",
"@codemirror/search": "^0.19.6",
"@codemirror/state": "^0.19.6",
"@codemirror/stream-parser": "^0.19.5",
"@codemirror/text": "^0.19.6",
"@codemirror/view": "^0.19.40",
"@formatjs/intl-datetimeformat": "^4.2.5", "@formatjs/intl-datetimeformat": "^4.2.5",
"@formatjs/intl-getcanonicallocales": "^2.0.5", "@formatjs/intl-getcanonicallocales": "^1.8.0",
"@formatjs/intl-locale": "^3.0.11", "@formatjs/intl-locale": "^2.4.40",
"@formatjs/intl-numberformat": "^7.2.5", "@formatjs/intl-numberformat": "^7.2.5",
"@formatjs/intl-pluralrules": "^4.1.5", "@formatjs/intl-pluralrules": "^4.1.5",
"@formatjs/intl-relativetimeformat": "^9.3.2", "@formatjs/intl-relativetimeformat": "^9.3.2",
@@ -44,35 +49,34 @@
"@fullcalendar/interaction": "5.9.0", "@fullcalendar/interaction": "5.9.0",
"@fullcalendar/list": "5.9.0", "@fullcalendar/list": "5.9.0",
"@fullcalendar/timegrid": "5.9.0", "@fullcalendar/timegrid": "5.9.0",
"@lezer/highlight": "^1.1.3", "@lit-labs/motion": "^1.0.2",
"@lit-labs/motion": "^1.0.3", "@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
"@lit-labs/virtualizer": "^1.0.1", "@material/chips": "14.0.0-canary.261f2db59.0",
"@material/chips": "=14.0.0-canary.53b3cad2f.0", "@material/data-table": "14.0.0-canary.261f2db59.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0", "@material/mwc-button": "0.25.3",
"@material/mwc-button": "^0.27.0", "@material/mwc-checkbox": "0.25.3",
"@material/mwc-checkbox": "^0.27.0", "@material/mwc-circular-progress": "0.25.3",
"@material/mwc-circular-progress": "^0.27.0", "@material/mwc-dialog": "0.25.3",
"@material/mwc-dialog": "^0.27.0", "@material/mwc-drawer": "^0.25.3",
"@material/mwc-drawer": "^0.27.0", "@material/mwc-fab": "0.25.3",
"@material/mwc-fab": "^0.27.0", "@material/mwc-formfield": "0.25.3",
"@material/mwc-formfield": "^0.27.0", "@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
"@material/mwc-icon-button": "^0.27.0", "@material/mwc-linear-progress": "0.25.3",
"@material/mwc-linear-progress": "^0.27.0", "@material/mwc-list": "^0.25.3",
"@material/mwc-list": "^0.27.0", "@material/mwc-menu": "0.25.3",
"@material/mwc-menu": "^0.27.0", "@material/mwc-radio": "0.25.3",
"@material/mwc-radio": "^0.27.0", "@material/mwc-ripple": "0.25.3",
"@material/mwc-ripple": "^0.27.0", "@material/mwc-select": "0.25.3",
"@material/mwc-select": "^0.27.0", "@material/mwc-slider": "0.25.3",
"@material/mwc-slider": "^0.27.0", "@material/mwc-switch": "0.25.3",
"@material/mwc-switch": "^0.27.0", "@material/mwc-tab": "0.25.3",
"@material/mwc-tab": "^0.27.0", "@material/mwc-tab-bar": "0.25.3",
"@material/mwc-tab-bar": "^0.27.0", "@material/mwc-textarea": "^0.25.3",
"@material/mwc-textarea": "^0.27.0", "@material/mwc-textfield": "0.25.3",
"@material/mwc-textfield": "^0.27.0", "@material/mwc-top-app-bar-fixed": "^0.25.3",
"@material/mwc-top-app-bar-fixed": "^0.27.0", "@material/top-app-bar": "14.0.0-canary.261f2db59.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0", "@mdi/js": "7.0.96",
"@mdi/js": "7.1.96", "@mdi/svg": "7.0.96",
"@mdi/svg": "7.1.96",
"@polymer/app-layout": "^3.1.0", "@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1", "@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1", "@polymer/iron-icon": "^3.0.1",
@@ -87,48 +91,46 @@
"@polymer/paper-toast": "^3.0.1", "@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.4.1", "@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.6.0", "@thomasloven/round-slider": "0.5.4",
"@vaadin/combo-box": "^23.3.5", "@vaadin/combo-box": "^23.2.9",
"@vaadin/vaadin-themable-mixin": "^23.3.5", "@vaadin/vaadin-themable-mixin": "^23.2.9",
"@vibrant/color": "^3.2.1-alpha.1", "@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1", "@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
"@vue/web-component-wrapper": "^1.3.0", "@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/scoped-custom-element-registry": "^0.0.5", "@webcomponents/scoped-custom-element-registry": "^0.0.5",
"@webcomponents/webcomponentsjs": "^2.2.10", "@webcomponents/webcomponentsjs": "^2.2.10",
"app-datepicker": "^5.1.0", "app-datepicker": "^5.0.1",
"chart.js": "^3.3.2", "chart.js": "^3.3.2",
"comlink": "^4.3.1", "comlink": "^4.3.1",
"core-js": "^3.15.2", "core-js": "^3.15.2",
"cropperjs": "^1.5.13", "cropperjs": "^1.5.12",
"date-fns": "^2.29.3", "date-fns": "^2.23.0",
"date-fns-tz": "^1.3.7",
"deep-clone-simple": "^1.1.1", "deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1", "deep-freeze": "^0.0.1",
"fuse.js": "^6.6.2", "fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2", "google-timezones-json": "^1.0.2",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"hls.js": "^1.3.1", "hls.js": "^1.2.5",
"home-assistant-js-websocket": "^8.0.1", "home-assistant-js-websocket": "^8.0.1",
"idb-keyval": "^5.1.3", "idb-keyval": "^5.1.3",
"intl-messageformat": "^10.2.5", "intl-messageformat": "^9.9.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"leaflet": "^1.7.1", "leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4", "leaflet-draw": "^1.0.4",
"lit": "^2.6.1", "lit": "^2.1.2",
"marked": "^4.0.12", "marked": "^4.0.12",
"memoize-one": "^6.0.0", "memoize-one": "^5.2.1",
"node-vibrant": "3.2.1-alpha.1", "node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "^0.3.2", "proxy-polyfill": "^0.3.2",
"punycode": "^2.3.0", "punycode": "^2.1.1",
"qr-scanner": "^1.3.0", "qr-scanner": "^1.3.0",
"qrcode": "^1.5.1", "qrcode": "^1.4.4",
"regenerator-runtime": "^0.13.11", "regenerator-runtime": "^0.13.8",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"rrule": "^2.7.1",
"sortablejs": "^1.14.0", "sortablejs": "^1.14.0",
"superstruct": "^1.0.3", "superstruct": "^0.15.2",
"tinykeys": "^1.1.3", "tinykeys": "^1.1.3",
"tsparticles": "^1.34.0", "tsparticles": "^1.34.0",
"unfetch": "^4.1.0", "unfetch": "^4.1.0",
@@ -137,19 +139,19 @@
"vue": "^2.6.12", "vue": "^2.6.12",
"vue2-daterange-picker": "^0.5.1", "vue2-daterange-picker": "^0.5.1",
"weekstart": "^1.1.0", "weekstart": "^1.1.0",
"workbox-cacheable-response": "^6.5.4", "workbox-cacheable-response": "^6.4.2",
"workbox-core": "^6.5.4", "workbox-core": "^6.4.2",
"workbox-expiration": "^6.5.4", "workbox-expiration": "^6.4.2",
"workbox-precaching": "^6.5.4", "workbox-precaching": "^6.4.2",
"workbox-routing": "^6.5.4", "workbox-routing": "^6.4.2",
"workbox-strategies": "^6.5.4", "workbox-strategies": "^6.4.2",
"xss": "^1.0.14" "xss": "^1.0.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.2", "@babel/core": "^7.20.2",
"@babel/plugin-external-helpers": "^7.18.6", "@babel/plugin-external-helpers": "^7.18.6",
"@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.20.7", "@babel/plugin-proposal-decorators": "^7.20.2",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@babel/plugin-proposal-object-rest-spread": "^7.20.2", "@babel/plugin-proposal-object-rest-spread": "^7.20.2",
"@babel/plugin-proposal-optional-chaining": "^7.18.9", "@babel/plugin-proposal-optional-chaining": "^7.18.9",
@@ -160,7 +162,7 @@
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.18.6",
"@koa/cors": "^3.1.0", "@koa/cors": "^3.1.0",
"@octokit/auth-oauth-device": "^4.0.2", "@octokit/auth-oauth-device": "^4.0.2",
"@octokit/rest": "^19.0.7", "@octokit/rest": "^19.0.4",
"@open-wc/dev-server-hmr": "^0.0.2", "@open-wc/dev-server-hmr": "^0.0.2",
"@rollup/plugin-babel": "^5.2.1", "@rollup/plugin-babel": "^5.2.1",
"@rollup/plugin-commonjs": "^11.1.0", "@rollup/plugin-commonjs": "^11.1.0",
@@ -169,89 +171,94 @@
"@rollup/plugin-replace": "^2.3.2", "@rollup/plugin-replace": "^2.3.2",
"@types/chromecast-caf-receiver": "5.0.12", "@types/chromecast-caf-receiver": "5.0.12",
"@types/chromecast-caf-sender": "^1.0.3", "@types/chromecast-caf-sender": "^1.0.3",
"@types/glob": "^8", "@types/glob": "^7",
"@types/hammerjs": "^2.0.41", "@types/hammerjs": "^2.0.41",
"@types/js-yaml": "^4", "@types/js-yaml": "^4",
"@types/leaflet": "^1", "@types/leaflet": "^1",
"@types/leaflet-draw": "^1", "@types/leaflet-draw": "^1",
"@types/marked": "^4", "@types/marked": "^4",
"@types/mocha": "^8", "@types/mocha": "^8",
"@types/qrcode": "^1.5.0", "@types/qrcode": "^1.4.2",
"@types/sortablejs": "^1", "@types/sortablejs": "^1",
"@types/tar": "^6", "@types/tar": "^6",
"@types/webspeechapi": "^0.0.29", "@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^5.46.1", "@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.49.0", "@typescript-eslint/parser": "^5.44.0",
"@web/dev-server": "^0.0.24", "@web/dev-server": "^0.0.24",
"@web/dev-server-rollup": "^0.2.11", "@web/dev-server-rollup": "^0.2.11",
"babel-loader": "^9.1.0", "babel-loader": "^9.1.0",
"chai": "^4.3.4", "chai": "^4.3.4",
"del": "^7.0.0", "del": "^4.0.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1", "eslint-config-airbnb-base": "^14.2.1",
"eslint-config-airbnb-typescript": "^14.0.0", "eslint-config-airbnb-typescript": "^14.0.0",
"eslint-config-prettier": "^8.6.0", "eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-webpack": "^0.13.1", "eslint-import-resolver-webpack": "^0.13.1",
"eslint-plugin-disable": "^2.0.1", "eslint-plugin-disable": "^2.0.1",
"eslint-plugin-import": "^2.24.2", "eslint-plugin-import": "^2.24.2",
"eslint-plugin-lit": "^1.6.1", "eslint-plugin-lit": "^1.6.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unused-imports": "^1.1.5", "eslint-plugin-unused-imports": "^1.1.5",
"eslint-plugin-wc": "^1.4.0", "eslint-plugin-wc": "^1.3.2",
"fancy-log": "^2.0.0", "fancy-log": "^1.3.3",
"fs-extra": "^11.1.0", "fs-extra": "^7.0.1",
"glob": "^8.1.0", "glob": "^7.2.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-flatmap": "^1.0.2", "gulp-flatmap": "^1.0.2",
"gulp-json-transform": "^0.4.6", "gulp-json-transform": "^0.4.6",
"gulp-merge-json": "^2.1.2", "gulp-merge-json": "^1.3.1",
"gulp-rename": "^2.0.0", "gulp-rename": "^2.0.0",
"gulp-zopfli-green": "^3.0.1", "gulp-zopfli-green": "^3.0.1",
"html-minifier": "^4.0.0", "html-minifier": "^4.0.0",
"husky": "^8.0.3", "husky": "^8.0.1",
"instant-mocha": "^1.3.1", "instant-mocha": "^1.3.1",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"lint-staged": "^13.1.0", "lint-staged": "^13.0.3",
"lit-analyzer": "^1.2.1", "lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0", "lodash.template": "^4.5.0",
"magic-string": "^0.25.7", "magic-string": "^0.25.7",
"map-stream": "^0.0.7", "map-stream": "^0.0.7",
"merge-stream": "^1.0.1", "merge-stream": "^1.0.1",
"mocha": "^8.4.0", "mocha": "^8.4.0",
"object-hash": "^3.0.0", "object-hash": "^2.0.3",
"open": "^8.4.0", "open": "^7.0.4",
"pinst": "^3.0.0", "pinst": "^3.0.0",
"prettier": "^2.8.3", "prettier": "^2.4.1",
"require-dir": "^1.2.0", "require-dir": "^1.2.0",
"rollup": "^2.8.2", "rollup": "^2.8.2",
"rollup-plugin-string": "^3.0.0", "rollup-plugin-string": "^3.0.0",
"rollup-plugin-terser": "^5.3.0", "rollup-plugin-terser": "^5.3.0",
"rollup-plugin-visualizer": "^5.9.0", "rollup-plugin-visualizer": "^4.0.4",
"serve": "^11.3.2", "serve": "^11.3.2",
"sinon": "^15.0.1", "sinon": "^11.0.0",
"source-map-url": "^0.4.0", "source-map-url": "^0.4.0",
"systemjs": "^6.3.2", "systemjs": "^6.3.2",
"tar": "^6.1.11", "tar": "^6.1.11",
"terser-webpack-plugin": "^5.2.4", "terser-webpack-plugin": "^5.2.4",
"ts-lit-plugin": "^1.2.1", "ts-lit-plugin": "^1.2.1",
"typescript": "^4.9.4", "typescript": "^4.9.3",
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0", "vinyl-source-stream": "^2.0.0",
"webpack": "^5.55.1", "webpack": "^5.55.1",
"webpack-cli": "^5.0.1", "webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.11.1", "webpack-dev-server": "^4.3.0",
"webpack-manifest-plugin": "^4.0.2", "webpack-manifest-plugin": "^4.0.2",
"webpackbar": "^5.0.2", "webpackbar": "^5.0.0-3",
"workbox-build": "^6.5.4" "workbox-build": "^6.4.2"
}, },
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch", "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": { "resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch", "@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@webcomponents/webcomponentsjs": "^2.2.10" "@webcomponents/webcomponentsjs": "^2.2.10",
"lit": "^2.1.2",
"lit-html": "2.1.2",
"lit-element": "3.1.2",
"@lit/reactive-element": "1.2.1"
}, },
"main": "src/home-assistant.js", "main": "src/home-assistant.js",
"prettier": { "prettier": {
"trailingComma": "es5", "trailingComma": "es5",
"arrowParens": "always" "arrowParens": "always"
}, },
"packageManager": "yarn@3.3.1" "packageManager": "yarn@3.2.3"
} }

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20230128.0" version = "20221108.0"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "The Home Assistant frontend" description = "The Home Assistant frontend"
readme = "README.md" readme = "README.md"

View File

@@ -7,4 +7,4 @@ set -e
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
# Install node modules # Install node modules
yarn install yarn install

View File

@@ -1,9 +0,0 @@
#!/bin/sh
# Setup translation fetching during development
# Stop on errors
set -e
cd "$(dirname "$0")/.."
./node_modules/.bin/gulp setup-and-fetch-nightly-translations

View File

@@ -8,8 +8,6 @@ import { BaseCastMessage } from "./types";
export interface GetStatusMessage extends BaseCastMessage { export interface GetStatusMessage extends BaseCastMessage {
type: "get_status"; type: "get_status";
hassUrl?: string;
hassUUID?: string;
} }
export interface ConnectMessage extends BaseCastMessage { export interface ConnectMessage extends BaseCastMessage {
@@ -17,15 +15,12 @@ export interface ConnectMessage extends BaseCastMessage {
refreshToken: string; refreshToken: string;
clientId: string | null; clientId: string | null;
hassUrl: string; hassUrl: string;
hassUUID?: string;
} }
export interface ShowLovelaceViewMessage extends BaseCastMessage { export interface ShowLovelaceViewMessage extends BaseCastMessage {
type: "show_lovelace_view"; type: "show_lovelace_view";
viewPath: string | number | null; viewPath: string | number | null;
urlPath: string | null; urlPath: string | null;
hassUrl: string;
hassUUID?: string;
} }
export interface ShowDemoMessage extends BaseCastMessage { export interface ShowDemoMessage extends BaseCastMessage {
@@ -48,7 +43,6 @@ export const castSendAuth = (cast: CastManager, auth: Auth) =>
export const castSendShowLovelaceView = ( export const castSendShowLovelaceView = (
cast: CastManager, cast: CastManager,
hassUrl: string,
viewPath: ShowLovelaceViewMessage["viewPath"], viewPath: ShowLovelaceViewMessage["viewPath"],
urlPath?: string | null urlPath?: string | null
) => ) =>
@@ -56,7 +50,6 @@ export const castSendShowLovelaceView = (
type: "show_lovelace_view", type: "show_lovelace_view",
viewPath, viewPath,
urlPath: urlPath || null, urlPath: urlPath || null,
hassUrl: CAST_DEV ? CAST_DEV_HASS_URL : hassUrl,
}); });
export const castSendShowDemo = (cast: CastManager) => export const castSendShowDemo = (cast: CastManager) =>

View File

@@ -7,7 +7,6 @@ export interface ReceiverStatusMessage extends BaseCastMessage {
connected: boolean; connected: boolean;
showDemo: boolean; showDemo: boolean;
hassUrl?: string; hassUrl?: string;
hassUUID?: string;
lovelacePath?: string | number | null; lovelacePath?: string | number | null;
urlPath?: string | null; urlPath?: string | null;
} }
@@ -24,7 +23,6 @@ export const enum ReceiverErrorCode {
CONNECTION_LOST = 3, CONNECTION_LOST = 3,
HASS_URL_MISSING = 4, HASS_URL_MISSING = 4,
NO_HTTPS = 5, NO_HTTPS = 5,
WRONG_INSTANCE = 20,
NOT_CONNECTED = 21, NOT_CONNECTED = 21,
FETCH_CONFIG_FAILED = 22, FETCH_CONFIG_FAILED = 22,
} }

View File

@@ -1,5 +0,0 @@
// Creates a type predicate function for determining if an array literal includes a given value
export const arrayLiteralIncludes =
<T extends readonly unknown[]>(array: T) =>
(searchElement: unknown, fromIndex?: number): searchElement is T[number] =>
array.includes(searchElement as T[number], fromIndex);

View File

@@ -1,3 +1,5 @@
import { hex2rgb } from "./convert-color";
export const THEME_COLORS = new Set([ export const THEME_COLORS = new Set([
"primary", "primary",
"accent", "accent",
@@ -19,17 +21,22 @@ export const THEME_COLORS = new Set([
"orange", "orange",
"deep-orange", "deep-orange",
"brown", "brown",
"light-grey",
"grey", "grey",
"dark-grey",
"blue-grey", "blue-grey",
"black", "black",
"white", "white",
]); ]);
export function computeCssColor(color: string): string { export function computeRgbColor(color: string): string {
if (THEME_COLORS.has(color)) { if (THEME_COLORS.has(color)) {
return `var(--${color}-color)`; return `var(--rgb-${color}-color)`;
}
if (color.startsWith("#")) {
try {
return hex2rgb(color).join(", ");
} catch (err) {
return "";
}
} }
return color; return color;
} }

View File

@@ -21,8 +21,6 @@ import {
mdiCommentAlert, mdiCommentAlert,
mdiCounter, mdiCounter,
mdiCurrentAc, mdiCurrentAc,
mdiDatabase,
mdiEarHearing,
mdiEye, mdiEye,
mdiFan, mdiFan,
mdiFlash, mdiFlash,
@@ -54,12 +52,9 @@ import {
mdiScriptText, mdiScriptText,
mdiSineWave, mdiSineWave,
mdiSpeedometer, mdiSpeedometer,
mdiSunWireless,
mdiThermometer, mdiThermometer,
mdiThermometerLines,
mdiThermostat, mdiThermostat,
mdiTimerOutline, mdiTimerOutline,
mdiTransmissionTower,
mdiVideo, mdiVideo,
mdiWater, mdiWater,
mdiWaterPercent, mdiWaterPercent,
@@ -131,13 +126,10 @@ export const FIXED_DOMAIN_ICONS = {
export const FIXED_DEVICE_CLASS_ICONS = { export const FIXED_DEVICE_CLASS_ICONS = {
apparent_power: mdiFlash, apparent_power: mdiFlash,
aqi: mdiAirFilter, aqi: mdiAirFilter,
atmospheric_pressure: mdiThermometerLines,
// battery: mdiBattery, => not included by design since `sensorIcon()` will dynamically determine the icon // battery: mdiBattery, => not included by design since `sensorIcon()` will dynamically determine the icon
carbon_dioxide: mdiMoleculeCo2, carbon_dioxide: mdiMoleculeCo2,
carbon_monoxide: mdiMoleculeCo, carbon_monoxide: mdiMoleculeCo,
current: mdiCurrentAc, current: mdiCurrentAc,
data_rate: mdiTransmissionTower,
data_size: mdiDatabase,
date: mdiCalendar, date: mdiCalendar,
distance: mdiArrowLeftRight, distance: mdiArrowLeftRight,
duration: mdiProgressClock, duration: mdiProgressClock,
@@ -146,7 +138,6 @@ export const FIXED_DEVICE_CLASS_ICONS = {
gas: mdiMeterGas, gas: mdiMeterGas,
humidity: mdiWaterPercent, humidity: mdiWaterPercent,
illuminance: mdiBrightness5, illuminance: mdiBrightness5,
irradiance: mdiSunWireless,
moisture: mdiWaterPercent, moisture: mdiWaterPercent,
monetary: mdiCash, monetary: mdiCash,
nitrogen_dioxide: mdiMolecule, nitrogen_dioxide: mdiMolecule,
@@ -163,7 +154,6 @@ export const FIXED_DEVICE_CLASS_ICONS = {
pressure: mdiGauge, pressure: mdiGauge,
reactive_power: mdiFlash, reactive_power: mdiFlash,
signal_strength: mdiWifi, signal_strength: mdiWifi,
sound_pressure: mdiEarHearing,
speed: mdiSpeedometer, speed: mdiSpeedometer,
sulphur_dioxide: mdiMolecule, sulphur_dioxide: mdiMolecule,
temperature: mdiThermometer, temperature: mdiThermometer,
@@ -198,15 +188,6 @@ export const DOMAINS_WITH_CARD = [
"water_heater", "water_heater",
]; ];
export const SENSOR_ENTITIES = [
"sensor",
"binary_sensor",
"calendar",
"camera",
"device_tracker",
"weather",
];
/** Domains that render an input element instead of a text value when displayed in a row. /** Domains that render an input element instead of a text value when displayed in a row.
* Those rows should then not show a cursor pointer when hovered (which would normally * Those rows should then not show a cursor pointer when hovered (which would normally
* be the default) unless the element itself enforces it (e.g. a button). Also those elements * be the default) unless the element itself enforces it (e.g. a button). Also those elements

View File

@@ -7,12 +7,10 @@ if (__BUILD__ === "latest" && polyfillsLoaded) {
} }
// Tuesday, August 10 // Tuesday, August 10
export const formatDateWeekdayDay = ( export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) =>
dateObj: Date, formatDateWeekdayMem(locale).format(dateObj);
locale: FrontendLocaleData
) => formatDateWeekdayDayMem(locale).format(dateObj);
const formatDateWeekdayDayMem = memoizeOne( const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) => (locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, { new Intl.DateTimeFormat(locale.language, {
weekday: "long", weekday: "long",
@@ -94,14 +92,3 @@ const formatDateYearMem = memoizeOne(
year: "numeric", year: "numeric",
}) })
); );
// Monday
export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) =>
formatDateWeekdayMem(locale).format(dateObj);
const formatDateWeekdayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new Intl.DateTimeFormat(locale.language, {
weekday: "long",
})
);

View File

@@ -18,7 +18,7 @@ export const relativeTime = (
to?: Date, to?: Date,
includeTense = true includeTense = true
): string => { ): string => {
const diff = selectUnit(from, to, locale); const diff = selectUnit(from, to);
if (includeTense) { if (includeTense) {
return formatRelTimeMem(locale).format(diff.value, diff.unit); return formatRelTimeMem(locale).format(diff.value, diff.unit);
} }

View File

@@ -39,5 +39,5 @@ export default function scrollToTarget(element, target) {
); );
requestAnimationFrame(updateFrame.bind(element)); requestAnimationFrame(updateFrame.bind(element));
} }
}).call(element); }.call(element));
} }

View File

@@ -0,0 +1,19 @@
export const alarmControlPanelColor = (state?: string): string | undefined => {
switch (state) {
case "armed_away":
case "armed_vacation":
case "armed_home":
case "armed_night":
case "armed_custom_bypass":
return "alarm-armed";
case "pending":
return "alarm-pending";
case "arming":
case "disarming":
return "alarm-arming";
case "triggered":
return "alarm-triggered";
default:
return undefined;
}
};

View File

@@ -1,15 +1,15 @@
export const batteryStateColorProperty = ( import { HassEntity } from "home-assistant-js-websocket";
state: string
): string | undefined => { export const batteryStateColor = (stateObj: HassEntity) => {
const value = Number(state); const value = Number(stateObj.state);
if (isNaN(value)) { if (isNaN(value)) {
return undefined; return "sensor-battery-unknown";
} }
if (value >= 70) { if (value >= 70) {
return "--state-sensor-battery-high-color"; return "sensor-battery-high";
} }
if (value >= 30) { if (value >= 30) {
return "--state-sensor-battery-medium-color"; return "sensor-battery-medium";
} }
return "--state-sensor-battery-low-color"; return "sensor-battery-low";
}; };

View File

@@ -0,0 +1,20 @@
import { HassEntity } from "home-assistant-js-websocket";
const ALERTING_DEVICE_CLASSES = new Set([
"battery",
"carbon_monoxide",
"gas",
"heat",
"problem",
"safety",
"smoke",
"tamper",
]);
export const binarySensorColor = (stateObj: HassEntity): string | undefined => {
const deviceClass = stateObj?.attributes.device_class;
return deviceClass && ALERTING_DEVICE_CLASSES.has(deviceClass)
? "binary-sensor-alerting"
: "binary-sensor";
};

View File

@@ -0,0 +1,18 @@
export const climateColor = (state: string): string | undefined => {
switch (state) {
case "auto":
return "climate-auto";
case "cool":
return "climate-cool";
case "dry":
return "climate-dry";
case "fan_only":
return "climate-fan-only";
case "heat":
return "climate-heat";
case "heat_cool":
return "climate-heat-cool";
default:
return undefined;
}
};

View File

@@ -0,0 +1,13 @@
export const lockColor = (state?: string): string | undefined => {
switch (state) {
case "locked":
return "lock-locked";
case "jammed":
return "lock-jammed";
case "locking":
case "unlocking":
return "lock-pending";
default:
return undefined;
}
};

View File

@@ -0,0 +1,12 @@
import { HassEntity } from "home-assistant-js-websocket";
import { batteryStateColor } from "./battery_color";
export const sensorColor = (stateObj: HassEntity): string | undefined => {
const deviceClass = stateObj?.attributes.device_class;
if (deviceClass === "battery") {
return batteryStateColor(stateObj);
}
return undefined;
};

View File

@@ -1,52 +0,0 @@
import { HassEntity } from "home-assistant-js-websocket";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { HomeAssistant } from "../../types";
import { LocalizeFunc } from "../translations/localize";
import { computeDomain } from "./compute_domain";
export const computeAttributeValueDisplay = (
localize: LocalizeFunc,
stateObj: HassEntity,
entities: HomeAssistant["entities"],
attribute: string,
value?: any
): string => {
const entityId = stateObj.entity_id;
const attributeValue =
value !== undefined ? value : stateObj.attributes[attribute];
const domain = computeDomain(entityId);
const entity = entities[entityId] as EntityRegistryEntry | undefined;
const translationKey = entity?.translation_key;
return (
(translationKey &&
localize(
`component.${entity.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.state.${attributeValue}`
)) ||
localize(
`component.${domain}.state_attributes._.${attribute}.state.${attributeValue}`
) ||
attributeValue
);
};
export const computeAttributeNameDisplay = (
localize: LocalizeFunc,
stateObj: HassEntity,
entities: HomeAssistant["entities"],
attribute: string
): string => {
const entityId = stateObj.entity_id;
const domain = computeDomain(entityId);
const entity = entities[entityId] as EntityRegistryEntry | undefined;
const translationKey = entity?.translation_key;
return (
(translationKey &&
localize(
`component.${entity.platform}.entity.${domain}.${translationKey}.state_attributes.${attribute}.name`
)) ||
localize(`component.${domain}.state_attributes._.${attribute}.name`) ||
attribute
);
};

View File

@@ -1,12 +1,10 @@
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE, UNKNOWN } from "../../data/entity"; import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
import { EntityRegistryEntry } from "../../data/entity_registry";
import { FrontendLocaleData } from "../../data/translation"; import { FrontendLocaleData } from "../../data/translation";
import { import {
updateIsInstallingFromAttributes, updateIsInstallingFromAttributes,
UPDATE_SUPPORT_PROGRESS, UPDATE_SUPPORT_PROGRESS,
} from "../../data/update"; } from "../../data/update";
import { HomeAssistant } from "../../types";
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration"; import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
import { formatDate } from "../datetime/format_date"; import { formatDate } from "../datetime/format_date";
import { formatDateTime } from "../datetime/format_date_time"; import { formatDateTime } from "../datetime/format_date_time";
@@ -25,13 +23,11 @@ export const computeStateDisplay = (
localize: LocalizeFunc, localize: LocalizeFunc,
stateObj: HassEntity, stateObj: HassEntity,
locale: FrontendLocaleData, locale: FrontendLocaleData,
entities: HomeAssistant["entities"],
state?: string state?: string
): string => ): string =>
computeStateDisplayFromEntityAttributes( computeStateDisplayFromEntityAttributes(
localize, localize,
locale, locale,
entities,
stateObj.entity_id, stateObj.entity_id,
stateObj.attributes, stateObj.attributes,
state !== undefined ? state : stateObj.state state !== undefined ? state : stateObj.state
@@ -40,7 +36,6 @@ export const computeStateDisplay = (
export const computeStateDisplayFromEntityAttributes = ( export const computeStateDisplayFromEntityAttributes = (
localize: LocalizeFunc, localize: LocalizeFunc,
locale: FrontendLocaleData, locale: FrontendLocaleData,
entities: HomeAssistant["entities"],
entityId: string, entityId: string,
attributes: any, attributes: any,
state: string state: string
@@ -199,13 +194,7 @@ export const computeStateDisplayFromEntityAttributes = (
: localize("ui.card.update.up_to_date"); : localize("ui.card.update.up_to_date");
} }
const entity = entities[entityId] as EntityRegistryEntry | undefined;
return ( return (
(entity?.translation_key &&
localize(
`component.${entity.platform}.entity.${domain}.${entity.translation_key}.state.${state}`
)) ||
// Return device class translation // Return device class translation
(attributes.device_class && (attributes.device_class &&
localize( localize(

View File

@@ -3,8 +3,6 @@ import {
mdiAccountArrowRight, mdiAccountArrowRight,
mdiAirHumidifier, mdiAirHumidifier,
mdiAirHumidifierOff, mdiAirHumidifierOff,
mdiAudioVideo,
mdiAudioVideoOff,
mdiBluetooth, mdiBluetooth,
mdiBluetoothConnect, mdiBluetoothConnect,
mdiCalendar, mdiCalendar,
@@ -27,6 +25,8 @@ import {
mdiPackageUp, mdiPackageUp,
mdiPowerPlug, mdiPowerPlug,
mdiPowerPlugOff, mdiPowerPlugOff,
mdiAudioVideo,
mdiAudioVideoOff,
mdiRestart, mdiRestart,
mdiSpeaker, mdiSpeaker,
mdiSpeakerOff, mdiSpeakerOff,
@@ -53,7 +53,6 @@ import { DEFAULT_DOMAIN_ICON, FIXED_DOMAIN_ICONS } from "../const";
import { alarmPanelIcon } from "./alarm_panel_icon"; import { alarmPanelIcon } from "./alarm_panel_icon";
import { binarySensorIcon } from "./binary_sensor_icon"; import { binarySensorIcon } from "./binary_sensor_icon";
import { coverIcon } from "./cover_icon"; import { coverIcon } from "./cover_icon";
import { numberIcon } from "./number_icon";
import { sensorIcon } from "./sensor_icon"; import { sensorIcon } from "./sensor_icon";
export const domainIcon = ( export const domainIcon = (
@@ -109,7 +108,7 @@ export const domainIconWithoutDefault = (
return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount; return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
case "humidifier": case "humidifier":
return compareState === "off" ? mdiAirHumidifierOff : mdiAirHumidifier; return state && state === "off" ? mdiAirHumidifierOff : mdiAirHumidifier;
case "input_boolean": case "input_boolean":
return compareState === "on" return compareState === "on"
@@ -181,18 +180,6 @@ export const domainIconWithoutDefault = (
} }
} }
case "number": {
const icon = numberIcon(stateObj);
if (icon) {
return icon;
}
break;
}
case "person":
return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
case "switch": case "switch":
switch (stateObj?.attributes.device_class) { switch (stateObj?.attributes.device_class) {
case "outlet": case "outlet":

View File

@@ -2,7 +2,7 @@ import { HassEntity } from "home-assistant-js-websocket";
import { computeStateDomain } from "./compute_state_domain"; import { computeStateDomain } from "./compute_state_domain";
import { UNAVAILABLE_STATES } from "../../data/entity"; import { UNAVAILABLE_STATES } from "../../data/entity";
export const FIXED_DOMAIN_STATES = { const FIXED_DOMAIN_STATES = {
alarm_control_panel: [ alarm_control_panel: [
"armed_away", "armed_away",
"armed_custom_bypass", "armed_custom_bypass",
@@ -57,7 +57,7 @@ export const FIXED_DOMAIN_STATES = {
"windy-variant", "windy-variant",
"windy", "windy",
], ],
} as const; };
const FIXED_DOMAIN_ATTRIBUTE_STATES = { const FIXED_DOMAIN_ATTRIBUTE_STATES = {
alarm_control_panel: { alarm_control_panel: {
@@ -261,11 +261,6 @@ export const getStates = (
result.push(...state.attributes.activity_list); result.push(...state.attributes.activity_list);
} }
break; break;
case "sensor":
if (!attribute && state.attributes.device_class === "enum") {
result.push(...state.attributes.options);
}
break;
case "vacuum": case "vacuum":
if (attribute === "fan_speed") { if (attribute === "fan_speed") {
result.push(...state.attributes.fan_speed_list); result.push(...state.attributes.fan_speed_list);

View File

@@ -1,13 +0,0 @@
/** Return an icon representing a number state. */
import { HassEntity } from "home-assistant-js-websocket";
import { FIXED_DEVICE_CLASS_ICONS } from "../const";
export const numberIcon = (stateObj?: HassEntity): string | undefined => {
const dclass = stateObj?.attributes.device_class;
if (dclass && dclass in FIXED_DEVICE_CLASS_ICONS) {
return FIXED_DEVICE_CLASS_ICONS[dclass];
}
return undefined;
};

View File

@@ -1,5 +1,5 @@
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { isUnavailableState, OFF, UNAVAILABLE } from "../../data/entity"; import { OFF_STATES, UNAVAILABLE } from "../../data/entity";
import { computeDomain } from "./compute_domain"; import { computeDomain } from "./compute_domain";
export function stateActive(stateObj: HassEntity, state?: string): boolean { export function stateActive(stateObj: HassEntity, state?: string): boolean {
@@ -10,32 +10,21 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
return compareState !== UNAVAILABLE; return compareState !== UNAVAILABLE;
} }
if (isUnavailableState(compareState)) { if (OFF_STATES.includes(compareState)) {
return false;
}
// The "off" check is relevant for most domains, but there are exceptions
// such as "alert" where "off" is still a somewhat active state and
// therefore gets a custom color and "idle" is instead the state that
// matches what most other domains consider inactive.
if (compareState === OFF && domain !== "alert") {
return false; return false;
} }
// Custom cases // Custom cases
switch (domain) { switch (domain) {
case "alarm_control_panel":
return compareState !== "disarmed";
case "alert":
// "on" and "off" are active, as "off" just means alert was acknowledged but is still active
return compareState !== "idle";
case "cover": case "cover":
return compareState !== "closed"; return !["closed", "closing"].includes(compareState);
case "device_tracker": case "device_tracker":
case "person": case "person":
return compareState !== "not_home"; return compareState !== "not_home";
case "alarm_control_panel":
return compareState !== "disarmed";
case "lock": case "lock":
return compareState !== "locked"; return compareState !== "unlocked";
case "media_player": case "media_player":
return compareState !== "standby"; return compareState !== "standby";
case "vacuum": case "vacuum":
@@ -44,11 +33,7 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
return compareState === "problem"; return compareState === "problem";
case "group": case "group":
return ["on", "home", "open", "locked", "problem"].includes(compareState); return ["on", "home", "open", "locked", "problem"].includes(compareState);
case "timer": default:
return compareState === "active"; return true;
case "camera":
return compareState === "streaming";
} }
return true;
} }

View File

@@ -1,102 +1,79 @@
/** Return an color representing a state. */ /** Return an color representing a state. */
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { UNAVAILABLE } from "../../data/entity"; import { UpdateEntity, updateIsInstalling } from "../../data/update";
import { computeCssVariable } from "../../resources/css-variables"; import { alarmControlPanelColor } from "./color/alarm_control_panel_color";
import { slugify } from "../string/slugify"; import { binarySensorColor } from "./color/binary_sensor_color";
import { batteryStateColorProperty } from "./color/battery_color"; import { climateColor } from "./color/climate_color";
import { lockColor } from "./color/lock_color";
import { sensorColor } from "./color/sensor_color";
import { computeDomain } from "./compute_domain"; import { computeDomain } from "./compute_domain";
import { stateActive } from "./state_active"; import { stateActive } from "./state_active";
const STATE_COLORED_DOMAIN = new Set([ export const stateColorCss = (stateObj?: HassEntity, state?: string) => {
"alarm_control_panel", if (!stateObj || !stateActive(stateObj, state)) {
"alert", return `var(--rgb-disabled-color)`;
"automation",
"binary_sensor",
"calendar",
"camera",
"climate",
"cover",
"device_tracker",
"fan",
"group",
"humidifier",
"input_boolean",
"light",
"lock",
"media_player",
"person",
"plant",
"remote",
"schedule",
"script",
"siren",
"sun",
"switch",
"timer",
"update",
"vacuum",
]);
export const stateColorCss = (stateObj: HassEntity, state?: string) => {
const compareState = state !== undefined ? state : stateObj?.state;
if (compareState === UNAVAILABLE) {
return `var(--state-unavailable-color)`;
} }
const properties = stateColorProperties(stateObj, state); const color = stateColor(stateObj, state);
if (properties) {
return computeCssVariable(properties); if (color) {
} return `var(--rgb-state-${color}-color)`;
}
return undefined;
}; return `var(--rgb-state-default-color)`;
};
export const domainStateColorProperties = (
stateObj: HassEntity, export const stateColor = (stateObj: HassEntity, state?: string) => {
state?: string const compareState = state !== undefined ? state : stateObj?.state;
): string[] => { const domain = computeDomain(stateObj.entity_id);
const compareState = state !== undefined ? state : stateObj.state;
const domain = computeDomain(stateObj.entity_id); switch (domain) {
const active = stateActive(stateObj, state); case "alarm_control_panel":
return alarmControlPanelColor(compareState);
const properties: string[] = [];
case "binary_sensor":
const stateKey = slugify(compareState, "_"); return binarySensorColor(stateObj);
const activeKey = active ? "active" : "inactive";
case "cover":
const dc = stateObj.attributes.device_class; return "cover";
if (dc) { case "climate":
properties.push(`--state-${domain}-${dc}-${stateKey}-color`); return climateColor(compareState);
}
case "fan":
properties.push( return "fan";
`--state-${domain}-${stateKey}-color`,
`--state-${domain}-${activeKey}-color`, case "lock":
`--state-${activeKey}-color` return lockColor(compareState);
);
case "light":
return properties; return "light";
};
case "humidifier":
export const stateColorProperties = ( return "humidifier";
stateObj: HassEntity,
state?: string case "media_player":
): string[] | undefined => { return "media-player";
const compareState = state !== undefined ? state : stateObj?.state;
const domain = computeDomain(stateObj.entity_id); case "sensor":
const dc = stateObj.attributes.device_class; return sensorColor(stateObj);
// Special rules for battery coloring case "vacuum":
if (domain === "sensor" && dc === "battery") { return "vacuum";
const property = batteryStateColorProperty(compareState);
if (property) { case "siren":
return [property]; return "siren";
}
} case "sun":
return compareState === "above_horizon" ? "sun-day" : "sun-night";
if (STATE_COLORED_DOMAIN.has(domain)) {
return domainStateColorProperties(stateObj, state); case "switch":
return "switch";
case "update":
return updateIsInstalling(stateObj as UpdateEntity)
? "update-installing"
: "update";
} }
return undefined; return undefined;

View File

@@ -86,7 +86,7 @@ export const protocolIntegrationPicked = async (
"ui.panel.config.integrations.config_flow.missing_zwave_zigbee", "ui.panel.config.integrations.config_flow.missing_zwave_zigbee",
{ {
integration: "Zigbee", integration: "Zigbee",
brand: options?.brand || options?.domain || "Zigbee", brand: options?.brand || options?.domain || "Z-Wave",
supported_hardware_link: html`<a supported_hardware_link: html`<a
href=${documentationUrl( href=${documentationUrl(
hass, hass,

View File

@@ -1,15 +1,4 @@
import memoizeOne from "memoize-one"; export const stringCompare = (a: string, b: string) => {
const collator = memoizeOne(
(language: string | undefined) => new Intl.Collator(language)
);
const caseInsensitiveCollator = memoizeOne(
(language: string | undefined) =>
new Intl.Collator(language, { sensitivity: "accent" })
);
const fallbackStringCompare = (a: string, b: string) => {
if (a < b) { if (a < b) {
return -1; return -1;
} }
@@ -20,28 +9,5 @@ const fallbackStringCompare = (a: string, b: string) => {
return 0; return 0;
}; };
export const stringCompare = ( export const caseInsensitiveStringCompare = (a: string, b: string) =>
a: string, stringCompare(a.toLowerCase(), b.toLowerCase());
b: string,
language: string | undefined = undefined
) => {
// @ts-ignore
if (Intl?.Collator) {
return collator(language).compare(a, b);
}
return fallbackStringCompare(a, b);
};
export const caseInsensitiveStringCompare = (
a: string,
b: string,
language: string | undefined = undefined
) => {
// @ts-ignore
if (Intl?.Collator) {
return caseInsensitiveCollator(language).compare(a, b);
}
return fallbackStringCompare(a.toLowerCase(), b.toLowerCase());
};

View File

@@ -1,10 +1,32 @@
import { css } from "lit"; import { css } from "lit";
export const iconColorCSS = css` export const iconColorCSS = css`
ha-state-icon[data-domain="alarm_control_panel"][data-state="pending"], ha-state-icon[data-active][data-domain="alert"],
ha-state-icon[data-domain="alarm_control_panel"][data-state="arming"], ha-state-icon[data-active][data-domain="automation"],
ha-state-icon[data-domain="alarm_control_panel"][data-state="triggered"], ha-state-icon[data-active][data-domain="binary_sensor"],
ha-state-icon[data-domain="lock"][data-state="jammed"] { ha-state-icon[data-active][data-domain="calendar"],
ha-state-icon[data-active][data-domain="camera"],
ha-state-icon[data-active][data-domain="cover"],
ha-state-icon[data-active][data-domain="device_tracker"],
ha-state-icon[data-active][data-domain="fan"],
ha-state-icon[data-active][data-domain="humidifier"],
ha-state-icon[data-active][data-domain="light"],
ha-state-icon[data-active][data-domain="input_boolean"],
ha-state-icon[data-active][data-domain="lock"],
ha-state-icon[data-active][data-domain="media_player"],
ha-state-icon[data-active][data-domain="remote"],
ha-state-icon[data-active][data-domain="script"],
ha-state-icon[data-active][data-domain="sun"],
ha-state-icon[data-active][data-domain="switch"],
ha-state-icon[data-active][data-domain="timer"],
ha-state-icon[data-active][data-domain="vacuum"],
ha-state-icon[data-active][data-domain="group"] {
color: var(--paper-item-icon-active-color, #fdd835);
}
ha-state-icon[data-active][data-domain="alarm_control_panel"][data-state="pending"],
ha-state-icon[data-active][data-domain="alarm_control_panel"][data-state="arming"],
ha-state-icon[data-active][data-domain="alarm_control_panel"][data-state="triggered"] {
animation: pulse 1s infinite; animation: pulse 1s infinite;
} }

View File

@@ -1,10 +0,0 @@
import { addDays, startOfWeek } from "date-fns";
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { formatDateWeekday } from "../datetime/format_date";
export const dayNames = memoizeOne((locale: FrontendLocaleData): string[] =>
Array.from({ length: 7 }, (_, d) =>
formatDateWeekday(addDays(startOfWeek(new Date()), d), locale)
)
);

View File

@@ -12,10 +12,12 @@ import { getLocalLanguage } from "../../util/common-translation";
export type LocalizeKeys = export type LocalizeKeys =
| FlattenObjectKeys<Omit<TranslationDict, "supervisor">> | FlattenObjectKeys<Omit<TranslationDict, "supervisor">>
| `panel.${string}` | `panel.${string}`
| `state.${string}`
| `state_attributes.${string}`
| `state_badge.${string}`
| `ui.card.alarm_control_panel.${string}` | `ui.card.alarm_control_panel.${string}`
| `ui.card.weather.attributes.${string}` | `ui.card.weather.attributes.${string}`
| `ui.card.weather.cardinal_direction.${string}` | `ui.card.weather.cardinal_direction.${string}`
| `ui.components.calendar.event.rrule.${string}`
| `ui.components.logbook.${string}` | `ui.components.logbook.${string}`
| `ui.components.selectors.file.${string}` | `ui.components.selectors.file.${string}`
| `ui.dialogs.entity_registry.editor.${string}` | `ui.dialogs.entity_registry.editor.${string}`
@@ -28,10 +30,13 @@ export type LocalizeKeys =
| `ui.panel.config.dashboard.${string}` | `ui.panel.config.dashboard.${string}`
| `ui.panel.config.devices.${string}` | `ui.panel.config.devices.${string}`
| `ui.panel.config.energy.${string}` | `ui.panel.config.energy.${string}`
| `ui.panel.config.helpers.${string}`
| `ui.panel.config.info.${string}` | `ui.panel.config.info.${string}`
| `ui.panel.config.logs.${string}`
| `ui.panel.config.lovelace.${string}` | `ui.panel.config.lovelace.${string}`
| `ui.panel.config.network.${string}` | `ui.panel.config.network.${string}`
| `ui.panel.config.scene.${string}` | `ui.panel.config.scene.${string}`
| `ui.panel.config.url.${string}`
| `ui.panel.config.zha.${string}` | `ui.panel.config.zha.${string}`
| `ui.panel.config.zwave_js.${string}` | `ui.panel.config.zwave_js.${string}`
| `ui.panel.lovelace.card.${string}` | `ui.panel.lovelace.card.${string}`

View File

@@ -1,10 +0,0 @@
import { addMonths, startOfYear } from "date-fns";
import memoizeOne from "memoize-one";
import { FrontendLocaleData } from "../../data/translation";
import { formatDateMonth } from "../datetime/format_date";
export const monthNames = memoizeOne((locale: FrontendLocaleData): string[] =>
Array.from({ length: 12 }, (_, m) =>
formatDateMonth(addMonths(startOfYear(new Date()), m), locale)
)
);

View File

@@ -1,7 +1,3 @@
import { differenceInDays, differenceInWeeks, startOfWeek } from "date-fns/esm";
import { FrontendLocaleData } from "../../data/translation";
import { firstWeekdayIndex } from "../datetime/first_weekday";
export type Unit = export type Unit =
| "second" | "second"
| "minute" | "minute"
@@ -15,12 +11,13 @@ export type Unit =
const MS_PER_SECOND = 1e3; const MS_PER_SECOND = 1e3;
const SECS_PER_MIN = 60; const SECS_PER_MIN = 60;
const SECS_PER_HOUR = SECS_PER_MIN * 60; const SECS_PER_HOUR = SECS_PER_MIN * 60;
const SECS_PER_DAY = SECS_PER_HOUR * 24;
const SECS_PER_WEEK = SECS_PER_DAY * 7;
// Adapted from https://github.com/formatjs/formatjs/blob/186cef62f980ec66252ee232f438a42d0b51b9f9/packages/intl-utils/src/diff.ts // Adapted from https://github.com/formatjs/formatjs/blob/186cef62f980ec66252ee232f438a42d0b51b9f9/packages/intl-utils/src/diff.ts
export function selectUnit( export function selectUnit(
from: Date | number, from: Date | number,
to: Date | number = Date.now(), to: Date | number = Date.now(),
locale: FrontendLocaleData,
thresholds: Partial<Thresholds> = {} thresholds: Partial<Thresholds> = {}
): { value: number; unit: Unit } { ): { value: number; unit: Unit } {
const resolvedThresholds: Thresholds = { const resolvedThresholds: Thresholds = {
@@ -52,56 +49,29 @@ export function selectUnit(
}; };
} }
const fromDate = new Date(from); const days = secs / SECS_PER_DAY;
const toDate = new Date(to);
// Set time component to zero, which allows us to compare only the days
fromDate.setHours(0, 0, 0, 0);
toDate.setHours(0, 0, 0, 0);
const days = differenceInDays(fromDate, toDate);
if (days === 0) {
return {
value: Math.round(hours),
unit: "hour",
};
}
if (Math.abs(days) < resolvedThresholds.day) { if (Math.abs(days) < resolvedThresholds.day) {
return { return {
value: days, value: Math.round(days),
unit: "day", unit: "day",
}; };
} }
const firstWeekday = firstWeekdayIndex(locale); const weeks = secs / SECS_PER_WEEK;
const fromWeek = startOfWeek(fromDate, { weekStartsOn: firstWeekday });
const toWeek = startOfWeek(toDate, { weekStartsOn: firstWeekday });
const weeks = differenceInWeeks(fromWeek, toWeek);
if (weeks === 0) {
return {
value: days,
unit: "day",
};
}
if (Math.abs(weeks) < resolvedThresholds.week) { if (Math.abs(weeks) < resolvedThresholds.week) {
return { return {
value: weeks, value: Math.round(weeks),
unit: "week", unit: "week",
}; };
} }
const fromDate = new Date(from);
const toDate = new Date(to);
const years = fromDate.getFullYear() - toDate.getFullYear(); const years = fromDate.getFullYear() - toDate.getFullYear();
const months = years * 12 + fromDate.getMonth() - toDate.getMonth(); const months = years * 12 + fromDate.getMonth() - toDate.getMonth();
if (months === 0) { if (Math.round(Math.abs(months)) < resolvedThresholds.month) {
return { return {
value: weeks, value: Math.round(months),
unit: "week",
};
}
if (Math.abs(months) < resolvedThresholds.month || years === 0) {
return {
value: months,
unit: "month", unit: "month",
}; };
} }

View File

@@ -40,7 +40,7 @@ import {
formatDateMonth, formatDateMonth,
formatDateMonthYear, formatDateMonthYear,
formatDateShort, formatDateShort,
formatDateWeekdayDay, formatDateWeekday,
formatDateYear, formatDateYear,
} from "../../common/datetime/format_date"; } from "../../common/datetime/format_date";
import { import {
@@ -92,7 +92,7 @@ _adapters._date.override({
case "hour": case "hour":
return formatTime(new Date(time), this.options.locale); return formatTime(new Date(time), this.options.locale);
case "weekday": case "weekday":
return formatDateWeekdayDay(new Date(time), this.options.locale); return formatDateWeekday(new Date(time), this.options.locale);
case "date": case "date":
return formatDate(new Date(time), this.options.locale); return formatDate(new Date(time), this.options.locale);
case "day": case "day":

View File

@@ -10,8 +10,6 @@ import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map"; import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import { clamp } from "../../common/number/clamp"; import { clamp } from "../../common/number/clamp";
import { computeRTL } from "../../common/util/compute_rtl";
import { HomeAssistant } from "../../types";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000; export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
@@ -24,8 +22,6 @@ interface Tooltip extends TooltipModel<any> {
export default class HaChartBase extends LitElement { export default class HaChartBase extends LitElement {
public chart?: Chart; public chart?: Chart;
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: "chart-type", reflect: true }) @property({ attribute: "chart-type", reflect: true })
public chartType: ChartType = "line"; public chartType: ChartType = "line";
@@ -37,8 +33,6 @@ export default class HaChartBase extends LitElement {
@property({ type: Number }) public height?: number; @property({ type: Number }) public height?: number;
@property({ type: Number }) public paddingYAxis = 0;
@state() private _chartHeight?: number; @state() private _chartHeight?: number;
@state() private _tooltip?: Tooltip; @state() private _tooltip?: Tooltip;
@@ -134,8 +128,6 @@ export default class HaChartBase extends LitElement {
style=${styleMap({ style=${styleMap({
height: `${this.height ?? this._chartHeight}px`, height: `${this.height ?? this._chartHeight}px`,
overflow: this._chartHeight ? "initial" : "hidden", overflow: this._chartHeight ? "initial" : "hidden",
"padding-left": `${computeRTL(this.hass) ? 0 : this.paddingYAxis}px`,
"padding-right": `${computeRTL(this.hass) ? this.paddingYAxis : 0}px`,
})} })}
> >
<canvas></canvas> <canvas></canvas>

View File

@@ -2,8 +2,6 @@ import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
import { html, LitElement, PropertyValues } from "lit"; import { html, LitElement, PropertyValues } from "lit";
import { property, state } from "lit/decorators"; import { property, state } from "lit/decorators";
import { getGraphColorByIndex } from "../../common/color/colors"; import { getGraphColorByIndex } from "../../common/color/colors";
import { fireEvent } from "../../common/dom/fire_event";
import { computeRTL } from "../../common/util/compute_rtl";
import { import {
formatNumber, formatNumber,
numberFormatToLocale, numberFormatToLocale,
@@ -28,36 +26,28 @@ class StateHistoryChartLine extends LitElement {
@property() public identifier?: string; @property() public identifier?: string;
@property({ type: Boolean }) public showNames = true; @property({ type: Boolean }) public isSingleDevice = false;
@property({ attribute: false }) public endTime!: Date; @property({ attribute: false }) public endTime!: Date;
@property({ type: Number }) public paddingYAxis = 0;
@property({ type: Number }) public chartIndex?;
@state() private _chartData?: ChartData<"line">; @state() private _chartData?: ChartData<"line">;
@state() private _chartOptions?: ChartOptions; @state() private _chartOptions?: ChartOptions;
@state() private _yWidth = 0;
private _chartTime: Date = new Date(); private _chartTime: Date = new Date();
protected render() { protected render() {
return html` return html`
<ha-chart-base <ha-chart-base
.hass=${this.hass}
.data=${this._chartData} .data=${this._chartData}
.options=${this._chartOptions} .options=${this._chartOptions}
.paddingYAxis=${this.paddingYAxis - this._yWidth}
chart-type="line" chart-type="line"
></ha-chart-base> ></ha-chart-base>
`; `;
} }
public willUpdate(changedProps: PropertyValues) { public willUpdate(changedProps: PropertyValues) {
if (!this.hasUpdated || changedProps.has("showNames")) { if (!this.hasUpdated) {
this._chartOptions = { this._chartOptions = {
parsing: false, parsing: false,
animation: false, animation: false,
@@ -94,16 +84,6 @@ class StateHistoryChartLine extends LitElement {
display: true, display: true,
text: this.unit, text: this.unit,
}, },
afterUpdate: (y) => {
if (this._yWidth !== Math.floor(y.width)) {
this._yWidth = Math.floor(y.width);
fireEvent(this, "y-width-changed", {
value: this._yWidth,
chartIndex: this.chartIndex,
});
}
},
position: computeRTL(this.hass) ? "right" : "left",
}, },
}, },
plugins: { plugins: {
@@ -121,7 +101,7 @@ class StateHistoryChartLine extends LitElement {
propagate: true, propagate: true,
}, },
legend: { legend: {
display: this.showNames, display: !this.isSingleDevice,
labels: { labels: {
usePointStyle: true, usePointStyle: true,
}, },

View File

@@ -1,15 +1,73 @@
import type { ChartData, ChartDataset, ChartOptions } from "chart.js"; import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit"; import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { getGraphColorByIndex } from "../../common/color/colors";
import { rgb2hex } from "../../common/color/convert-color";
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time"; import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
import { fireEvent } from "../../common/dom/fire_event"; import { stateActive } from "../../common/entity/state_active";
import { stateColor } from "../../common/entity/state_color";
import { numberFormatToLocale } from "../../common/number/format_number"; import { numberFormatToLocale } from "../../common/number/format_number";
import { computeRTL } from "../../common/util/compute_rtl"; import { computeRTL } from "../../common/util/compute_rtl";
import { TimelineEntity } from "../../data/history"; import { TimelineEntity } from "../../data/history";
import { HomeAssistant } from "../../types"; import { HomeAssistant } from "../../types";
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base"; import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
import type { TimeLineData } from "./timeline-chart/const"; import type { TimeLineData } from "./timeline-chart/const";
import { computeTimelineColor } from "./timeline-chart/timeline-color";
const stateColorTokenMap: Map<string, string> = new Map();
const stateColorMap: Map<string, string> = new Map();
let colorIndex = 0;
export const getStateColorToken = (
stateString: string,
entityState?: HassEntity
) => {
if (!entityState || !stateActive(entityState, stateString)) {
return `disabled`;
}
const color = stateColor(entityState, stateString);
if (color) {
return `state-${color}`;
}
return undefined;
};
const getColor = (
stateString: string,
computedStyles: CSSStyleDeclaration,
entityState?: HassEntity
) => {
const stateColorToken = getStateColorToken(stateString, entityState);
if (stateColorToken) {
if (stateColorTokenMap.has(stateColorToken)) {
return stateColorTokenMap.get(stateColorToken);
}
const value = computedStyles.getPropertyValue(
`--rgb-${stateColorToken}-color`
);
if (value) {
const parsedValue = value.split(",").map((v) => Number(v)) as [
number,
number,
number
];
const hexValue = rgb2hex(parsedValue);
stateColorTokenMap.set(stateColorToken, hexValue);
return hexValue;
}
}
if (stateColorMap.has(stateString)) {
return stateColorMap.get(stateString);
}
const color = getGraphColorByIndex(colorIndex, computedStyles);
colorIndex++;
stateColorMap.set(stateString, color);
return color;
};
@customElement("state-history-chart-timeline") @customElement("state-history-chart-timeline")
export class StateHistoryChartTimeline extends LitElement { export class StateHistoryChartTimeline extends LitElement {
@@ -25,7 +83,7 @@ export class StateHistoryChartTimeline extends LitElement {
@property() public identifier?: string; @property() public identifier?: string;
@property({ type: Boolean }) public showNames = true; @property({ type: Boolean }) public isSingleDevice = false;
@property({ type: Boolean }) public chunked = false; @property({ type: Boolean }) public chunked = false;
@@ -33,26 +91,18 @@ export class StateHistoryChartTimeline extends LitElement {
@property({ attribute: false }) public endTime!: Date; @property({ attribute: false }) public endTime!: Date;
@property({ type: Number }) public paddingYAxis = 0;
@property({ type: Number }) public chartIndex?;
@state() private _chartData?: ChartData<"timeline">; @state() private _chartData?: ChartData<"timeline">;
@state() private _chartOptions?: ChartOptions<"timeline">; @state() private _chartOptions?: ChartOptions<"timeline">;
@state() private _yWidth = 0;
private _chartTime: Date = new Date(); private _chartTime: Date = new Date();
protected render() { protected render() {
return html` return html`
<ha-chart-base <ha-chart-base
.hass=${this.hass}
.data=${this._chartData} .data=${this._chartData}
.options=${this._chartOptions} .options=${this._chartOptions}
.height=${this.data.length * 30 + 30} .height=${this.data.length * 30 + 30}
.paddingYAxis=${this.paddingYAxis - this._yWidth}
chart-type="timeline" chart-type="timeline"
></ha-chart-base> ></ha-chart-base>
`; `;
@@ -73,11 +123,7 @@ export class StateHistoryChartTimeline extends LitElement {
this._generateData(); this._generateData();
} }
if ( if (changedProps.has("startTime") || changedProps.has("endTime")) {
changedProps.has("startTime") ||
changedProps.has("endTime") ||
changedProps.has("showNames")
) {
this._createOptions(); this._createOptions();
} }
} }
@@ -129,7 +175,8 @@ export class StateHistoryChartTimeline extends LitElement {
drawTicks: false, drawTicks: false,
}, },
ticks: { ticks: {
display: this.chunked || this.showNames, display:
this.chunked || !this.isSingleDevice || this.data.length !== 1,
}, },
afterSetDimensions: (y) => { afterSetDimensions: (y) => {
y.maxWidth = y.chart.width * 0.18; y.maxWidth = y.chart.width * 0.18;
@@ -140,15 +187,6 @@ export class StateHistoryChartTimeline extends LitElement {
scaleInstance.width = narrow ? 105 : 185; scaleInstance.width = narrow ? 105 : 185;
} }
}, },
afterUpdate: (y) => {
if (this._yWidth !== Math.floor(y.width)) {
this._yWidth = Math.floor(y.width);
fireEvent(this, "y-width-changed", {
value: this._yWidth,
chartIndex: this.chartIndex,
});
}
},
position: computeRTL(this.hass) ? "right" : "left", position: computeRTL(this.hass) ? "right" : "left",
}, },
}, },
@@ -233,7 +271,7 @@ export class StateHistoryChartTimeline extends LitElement {
start: prevLastChanged, start: prevLastChanged,
end: newLastChanged, end: newLastChanged,
label: locState, label: locState,
color: computeTimelineColor( color: getColor(
prevState, prevState,
computedStyles, computedStyles,
this.hass.states[stateInfo.entity_id] this.hass.states[stateInfo.entity_id]
@@ -251,7 +289,7 @@ export class StateHistoryChartTimeline extends LitElement {
start: prevLastChanged, start: prevLastChanged,
end: endTime, end: endTime,
label: locState, label: locState,
color: computeTimelineColor( color: getColor(
prevState, prevState,
computedStyles, computedStyles,
this.hass.states[stateInfo.entity_id] this.hass.states[stateInfo.entity_id]

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