mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-09 13:09:38 +00:00
Compare commits
8 Commits
20210803.1
...
dash
Author | SHA1 | Date | |
---|---|---|---|
![]() |
50047e2f10 | ||
![]() |
2811541fba | ||
![]() |
57788cec44 | ||
![]() |
0aa314d9ae | ||
![]() |
f8d97735b8 | ||
![]() |
830136b874 | ||
![]() |
dd01710784 | ||
![]() |
f7d0736731 |
@@ -35,51 +35,55 @@
|
|||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"class-methods-use-this": "off",
|
"class-methods-use-this": 0,
|
||||||
"new-cap": "off",
|
"new-cap": 0,
|
||||||
"prefer-template": "off",
|
"prefer-template": 0,
|
||||||
"object-shorthand": "off",
|
"object-shorthand": 0,
|
||||||
"func-names": "off",
|
"func-names": 0,
|
||||||
"no-underscore-dangle": "off",
|
"prefer-arrow-callback": 0,
|
||||||
"strict": "off",
|
"no-underscore-dangle": 0,
|
||||||
"no-plusplus": "off",
|
"strict": 0,
|
||||||
"no-bitwise": "error",
|
"prefer-spread": 0,
|
||||||
"comma-dangle": "off",
|
"no-plusplus": 0,
|
||||||
"vars-on-top": "off",
|
"no-bitwise": 2,
|
||||||
"no-continue": "off",
|
"comma-dangle": 0,
|
||||||
"no-param-reassign": "off",
|
"vars-on-top": 0,
|
||||||
"no-multi-assign": "off",
|
"no-continue": 0,
|
||||||
"no-console": "error",
|
"no-param-reassign": 0,
|
||||||
"radix": "off",
|
"no-multi-assign": 0,
|
||||||
"no-alert": "off",
|
"no-console": 2,
|
||||||
"no-nested-ternary": "off",
|
"radix": 0,
|
||||||
"prefer-destructuring": "off",
|
"no-alert": 0,
|
||||||
|
"no-return-await": 0,
|
||||||
|
"no-nested-ternary": 0,
|
||||||
|
"prefer-destructuring": 0,
|
||||||
"no-restricted-globals": [2, "event"],
|
"no-restricted-globals": [2, "event"],
|
||||||
"prefer-promise-reject-errors": "off",
|
"prefer-promise-reject-errors": 0,
|
||||||
"import/prefer-default-export": "off",
|
"import/order": 0,
|
||||||
"import/no-default-export": "off",
|
"import/prefer-default-export": 0,
|
||||||
"import/no-unresolved": "off",
|
"import/no-unresolved": 0,
|
||||||
"import/no-cycle": "off",
|
"import/no-cycle": 0,
|
||||||
"import/extensions": [
|
"import/extensions": [
|
||||||
"error",
|
2,
|
||||||
"ignorePackages",
|
"ignorePackages",
|
||||||
{ "ts": "never", "js": "never" }
|
{ "ts": "never", "js": "never" }
|
||||||
],
|
],
|
||||||
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
|
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
|
||||||
"object-curly-newline": "off",
|
"object-curly-newline": 0,
|
||||||
"default-case": "off",
|
"default-case": 0,
|
||||||
"wc/no-self-class": "off",
|
"wc/no-self-class": 0,
|
||||||
"no-shadow": "off",
|
"no-shadow": 0,
|
||||||
"@typescript-eslint/camelcase": "off",
|
"@typescript-eslint/camelcase": 0,
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": 0,
|
||||||
"@typescript-eslint/no-use-before-define": "off",
|
"@typescript-eslint/no-use-before-define": 0,
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
"@typescript-eslint/no-non-null-assertion": 0,
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": 0,
|
||||||
"@typescript-eslint/explicit-function-return-type": "off",
|
"@typescript-eslint/no-unused-vars": 0,
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
"@typescript-eslint/explicit-function-return-type": 0,
|
||||||
|
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||||
"@typescript-eslint/no-shadow": ["error"],
|
"@typescript-eslint/no-shadow": ["error"],
|
||||||
"@typescript-eslint/naming-convention": [
|
"@typescript-eslint/naming-convention": [
|
||||||
"off",
|
0,
|
||||||
{
|
{
|
||||||
"selector": "default",
|
"selector": "default",
|
||||||
"format": ["camelCase", "snake_case"],
|
"format": ["camelCase", "snake_case"],
|
||||||
@@ -97,20 +101,9 @@
|
|||||||
"format": ["PascalCase"]
|
"format": ["PascalCase"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"lit/attribute-value-entities": 0
|
||||||
"unused-imports/no-unused-vars": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"vars": "all",
|
|
||||||
"varsIgnorePattern": "^_",
|
|
||||||
"args": "after-used",
|
|
||||||
"argsIgnorePattern": "^_",
|
|
||||||
"ignoreRestSiblings": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"unused-imports/no-unused-imports": "error",
|
|
||||||
"lit/attribute-value-entities": "off"
|
|
||||||
},
|
},
|
||||||
"plugins": ["disable", "unused-imports"],
|
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
|
||||||
"processor": "disable/disable"
|
"processor": "disable/disable",
|
||||||
|
"ignorePatterns": ["src/resources/lit-virtualizer/*"]
|
||||||
}
|
}
|
||||||
|
76
.github/workflows/ci.yaml
vendored
76
.github/workflows/ci.yaml
vendored
@@ -10,21 +10,26 @@ on:
|
|||||||
- dev
|
- dev
|
||||||
- master
|
- master
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 14
|
|
||||||
NODE_OPTIONS: --max_old_space_size=4096
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Setting up Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: 12.x
|
||||||
cache: yarn
|
- name: Get yarn cache path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
- name: Fetching Yarn cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
env:
|
env:
|
||||||
@@ -37,35 +42,51 @@ jobs:
|
|||||||
run: yarn run lint:types
|
run: yarn run lint:types
|
||||||
- name: Run prettier
|
- name: Run prettier
|
||||||
run: yarn run lint:prettier
|
run: yarn run lint:prettier
|
||||||
- name: Check for duplicate dependencies
|
|
||||||
run: yarn dedupe --check
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Setting up Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: 12.x
|
||||||
cache: yarn
|
- name: Get yarn cache path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
- name: Fetching Yarn cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
- name: Run Mocha
|
- name: Run Mocha
|
||||||
run: yarn run mocha
|
run: npm run mocha
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [lint, test]
|
needs: [lint, test]
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Setting up Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: 12.x
|
||||||
cache: yarn
|
- name: Get yarn cache path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
- name: Fetching Yarn cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
env:
|
env:
|
||||||
@@ -80,11 +101,20 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Setting up Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: 12.x
|
||||||
cache: yarn
|
- name: Get yarn cache path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
- name: Fetching Yarn cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
env:
|
env:
|
||||||
|
22
.github/workflows/demo.yaml
vendored
22
.github/workflows/demo.yaml
vendored
@@ -4,22 +4,26 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
|
|
||||||
env:
|
|
||||||
NODE_VERSION: 14
|
|
||||||
NODE_OPTIONS: --max_old_space_size=4096
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out files from GitHub
|
- name: Check out files from GitHub
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
- name: Setting up Node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: 12.x
|
||||||
cache: yarn
|
- name: Get yarn cache path
|
||||||
|
id: yarn-cache-dir-path
|
||||||
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
|
- name: Fetching Yarn cache
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-yarn-
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
env:
|
env:
|
||||||
|
17
.github/workflows/release.yaml
vendored
17
.github/workflows/release.yaml
vendored
@@ -6,9 +6,9 @@ on:
|
|||||||
- published
|
- published
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
WHEELS_TAG: 3.8-alpine3.12
|
||||||
PYTHON_VERSION: 3.8
|
PYTHON_VERSION: 3.8
|
||||||
NODE_VERSION: 14
|
NODE_VERSION: 12.1
|
||||||
NODE_OPTIONS: --max_old_space_size=4096
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
@@ -30,15 +30,7 @@ jobs:
|
|||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: yarn install
|
|
||||||
|
|
||||||
- name: Download Translations
|
|
||||||
run: ./script/translations_download
|
|
||||||
env:
|
|
||||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
|
||||||
- name: Build and release package
|
- name: Build and release package
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install twine
|
python3 -m pip install twine
|
||||||
@@ -72,9 +64,6 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
|
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
|
||||||
tag:
|
|
||||||
- "3.8-alpine3.12"
|
|
||||||
- "3.9-alpine3.13"
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download requirements.txt
|
- name: Download requirements.txt
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
@@ -84,7 +73,7 @@ jobs:
|
|||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: home-assistant/wheels@master
|
uses: home-assistant/wheels@master
|
||||||
with:
|
with:
|
||||||
tag: ${{ matrix.tag }}
|
tag: ${{ env.WHEELS_TAG }}
|
||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
wheels-host: ${{ secrets.WHEELS_HOST }}
|
wheels-host: ${{ secrets.WHEELS_HOST }}
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
|
42
.github/workflows/translations.yaml
vendored
42
.github/workflows/translations.yaml
vendored
@@ -1,6 +1,8 @@
|
|||||||
name: Translations
|
name: Translations
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 0 * * *"
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
@@ -8,7 +10,7 @@ on:
|
|||||||
- src/translations/en.json
|
- src/translations/en.json
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: 14
|
NODE_VERSION: 12
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
upload:
|
upload:
|
||||||
@@ -18,8 +20,46 @@ jobs:
|
|||||||
- name: Checkout the repository
|
- name: Checkout the repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
- name: Upload Translations
|
- name: Upload Translations
|
||||||
run: |
|
run: |
|
||||||
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
|
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
|
||||||
|
|
||||||
./script/translations_upload_base
|
./script/translations_upload_base
|
||||||
|
|
||||||
|
download:
|
||||||
|
name: Download
|
||||||
|
needs: upload
|
||||||
|
if: github.event_name == 'schedule'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout the repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
|
- name: Download Translations
|
||||||
|
run: |
|
||||||
|
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
|
||||||
|
|
||||||
|
npm install
|
||||||
|
./script/translations_download
|
||||||
|
|
||||||
|
- name: Initialize git
|
||||||
|
uses: home-assistant/actions/helpers/git-init@master
|
||||||
|
with:
|
||||||
|
name: GitHub Action
|
||||||
|
email: github-action@users.noreply.github.com
|
||||||
|
|
||||||
|
- name: Update translation
|
||||||
|
run: |
|
||||||
|
git add translations
|
||||||
|
git commit -am "Translation update"
|
||||||
|
git push
|
||||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@@ -8,15 +8,9 @@ hass_frontend/*
|
|||||||
dist
|
dist
|
||||||
|
|
||||||
# yarn
|
# yarn
|
||||||
.yarn/*
|
.yarn
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/sdks
|
|
||||||
!.yarn/versions
|
|
||||||
.pnp.*
|
|
||||||
node_modules/*
|
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
|
node_modules/*
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|
||||||
# Python stuff
|
# Python stuff
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -1,29 +0,0 @@
|
|||||||
diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
|
||||||
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
|
|
||||||
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
|
||||||
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
|
|
||||||
@@ -1,14 +1,15 @@
|
|
||||||
-let _ET, ET;
|
|
||||||
+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);
|
|
||||||
}
|
|
@@ -1,34 +0,0 @@
|
|||||||
diff --git a/lib/legacy/class.js b/lib/legacy/class.js
|
|
||||||
index aee2511be1cd9bf900ee552bc98190c1631c57c0..f2f499d68bf52034cac9c28307c99e8ce6b8417d 100644
|
|
||||||
--- a/lib/legacy/class.js
|
|
||||||
+++ b/lib/legacy/class.js
|
|
||||||
@@ -304,17 +304,23 @@ function GenerateClassFromInfo(info, Base, behaviors) {
|
|
||||||
// only proceed if the generated class' prototype has not been registered.
|
|
||||||
const generatedProto = PolymerGenerated.prototype;
|
|
||||||
if (!generatedProto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', generatedProto))) {
|
|
||||||
- generatedProto.__hasRegisterFinished = true;
|
|
||||||
+ // make sure legacy lifecycle is called on the *element*'s prototype
|
|
||||||
+ // and not the generated class prototype; if the element has been
|
|
||||||
+ // extended, these are *not* the same.
|
|
||||||
+ const proto = Object.getPrototypeOf(this);
|
|
||||||
+ // Only set flag when generated prototype itself is registered,
|
|
||||||
+ // as this element may be extended from, and needs to run `registered`
|
|
||||||
+ // on all behaviors on the subclass as well.
|
|
||||||
+ if (proto === generatedProto) {
|
|
||||||
+ generatedProto.__hasRegisterFinished = true;
|
|
||||||
+ }
|
|
||||||
// ensure superclass is registered first.
|
|
||||||
super._registered();
|
|
||||||
// copy properties onto the generated class lazily if we're optimizing,
|
|
||||||
- if (legacyOptimizations) {
|
|
||||||
+ if (legacyOptimizations && !Object.hasOwnProperty(generatedProto, '__hasCopiedProperties')) {
|
|
||||||
+ generatedProto.__hasCopiedProperties = true;
|
|
||||||
copyPropertiesToProto(generatedProto);
|
|
||||||
}
|
|
||||||
- // make sure legacy lifecycle is called on the *element*'s prototype
|
|
||||||
- // and not the generated class prototype; if the element has been
|
|
||||||
- // extended, these are *not* the same.
|
|
||||||
- const proto = Object.getPrototypeOf(this);
|
|
||||||
let list = lifecycle.beforeRegister;
|
|
||||||
if (list) {
|
|
||||||
for (let i=0; i < list.length; i++) {
|
|
File diff suppressed because one or more lines are too long
8
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
8
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
File diff suppressed because one or more lines are too long
55
.yarn/releases/yarn-2.4.2.cjs
vendored
55
.yarn/releases/yarn-2.4.2.cjs
vendored
File diff suppressed because one or more lines are too long
@@ -1,9 +0,0 @@
|
|||||||
nodeLinker: node-modules
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
|
||||||
spec: "@yarnpkg/plugin-typescript"
|
|
||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-2.4.2.cjs
|
|
@@ -1,170 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
|
|
||||||
module.exports = function inlineConstants(babel, options, cwd) {
|
|
||||||
const t = babel.types;
|
|
||||||
|
|
||||||
if (!Array.isArray(options.modules)) {
|
|
||||||
throw new TypeError(
|
|
||||||
"babel-plugin-inline-constants: expected a `modules` array to be passed"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.resolveExtensions && !Array.isArray(options.resolveExtensions)) {
|
|
||||||
throw new TypeError(
|
|
||||||
"babel-plugin-inline-constants: expected `resolveExtensions` to be an array"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ignoreModuleNotFound = options.ignoreModuleNotFound;
|
|
||||||
const resolveExtensions = options.resolveExtensions;
|
|
||||||
|
|
||||||
const hasRelativeModules = options.modules.some(
|
|
||||||
(module) => module.startsWith(".") || module.startsWith("/")
|
|
||||||
);
|
|
||||||
|
|
||||||
const modules = Object.fromEntries(
|
|
||||||
options.modules.map((module) => {
|
|
||||||
const absolute = module.startsWith(".")
|
|
||||||
? require.resolve(module, { paths: [cwd] })
|
|
||||||
: module;
|
|
||||||
// eslint-disable-next-line import/no-dynamic-require
|
|
||||||
return [absolute, require(absolute)];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const toLiteral = (value) => {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
return t.stringLiteral(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "number") {
|
|
||||||
return t.numericLiteral(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "boolean") {
|
|
||||||
return t.booleanLiteral(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === null) {
|
|
||||||
return t.nullLiteral();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
"babel-plugin-inline-constants: cannot handle non-literal `" + value + "`"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveAbsolute = (value, state, resolveExtensionIndex) => {
|
|
||||||
if (!state.filename) {
|
|
||||||
throw new TypeError(
|
|
||||||
"babel-plugin-inline-constants: expected a `filename` to be set for files"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resolveExtensions && resolveExtensionIndex !== undefined) {
|
|
||||||
value += resolveExtensions[resolveExtensionIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return require.resolve(value, { paths: [path.dirname(state.filename)] });
|
|
||||||
} catch (error) {
|
|
||||||
if (
|
|
||||||
error.code === "MODULE_NOT_FOUND" &&
|
|
||||||
resolveExtensions &&
|
|
||||||
(resolveExtensionIndex === undefined ||
|
|
||||||
resolveExtensionIndex < resolveExtensions.length - 1)
|
|
||||||
) {
|
|
||||||
const resolveExtensionIdx = (resolveExtensionIndex || -1) + 1;
|
|
||||||
return resolveAbsolute(value, state, resolveExtensionIdx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.code === "MODULE_NOT_FOUND" && ignoreModuleNotFound) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const importDeclaration = (p, state) => {
|
|
||||||
if (p.node.type !== "ImportDeclaration") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const absolute =
|
|
||||||
hasRelativeModules && p.node.source.value.startsWith(".")
|
|
||||||
? resolveAbsolute(p.node.source.value, state)
|
|
||||||
: p.node.source.value;
|
|
||||||
|
|
||||||
if (!absolute || !(absolute in modules)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const module = modules[absolute];
|
|
||||||
|
|
||||||
for (const specifier of p.node.specifiers) {
|
|
||||||
if (
|
|
||||||
specifier.type === "ImportDefaultSpecifier" &&
|
|
||||||
specifier.local &&
|
|
||||||
specifier.local.type === "Identifier"
|
|
||||||
) {
|
|
||||||
if (!("default" in module)) {
|
|
||||||
throw new Error(
|
|
||||||
"babel-plugin-inline-constants: cannot access default export from `" +
|
|
||||||
p.node.source.value +
|
|
||||||
"`"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const variableValue = toLiteral(module.default);
|
|
||||||
const variable = t.variableDeclarator(
|
|
||||||
t.identifier(specifier.local.name),
|
|
||||||
variableValue
|
|
||||||
);
|
|
||||||
|
|
||||||
p.insertBefore({
|
|
||||||
type: "VariableDeclaration",
|
|
||||||
kind: "const",
|
|
||||||
declarations: [variable],
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
specifier.type === "ImportSpecifier" &&
|
|
||||||
specifier.imported &&
|
|
||||||
specifier.imported.type === "Identifier" &&
|
|
||||||
specifier.local &&
|
|
||||||
specifier.local.type === "Identifier"
|
|
||||||
) {
|
|
||||||
if (!(specifier.imported.name in module)) {
|
|
||||||
throw new Error(
|
|
||||||
"babel-plugin-inline-constants: cannot access `" +
|
|
||||||
specifier.imported.name +
|
|
||||||
"` from `" +
|
|
||||||
p.node.source.value +
|
|
||||||
"`"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const variableValue = toLiteral(module[specifier.imported.name]);
|
|
||||||
const variable = t.variableDeclarator(
|
|
||||||
t.identifier(specifier.local.name),
|
|
||||||
variableValue
|
|
||||||
);
|
|
||||||
|
|
||||||
p.insertBefore({
|
|
||||||
type: "VariableDeclaration",
|
|
||||||
kind: "const",
|
|
||||||
declarations: [variable],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error("Cannot handle specifier `" + specifier.type + "`");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.remove();
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
visitor: {
|
|
||||||
ImportDeclaration: importDeclaration,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@@ -5,6 +5,8 @@ const paths = require("./paths.js");
|
|||||||
|
|
||||||
// Files from NPM Packages that should not be imported
|
// Files from NPM Packages that should not be imported
|
||||||
module.exports.ignorePackages = ({ latestBuild }) => [
|
module.exports.ignorePackages = ({ latestBuild }) => [
|
||||||
|
// Bloats bundle and it's not used.
|
||||||
|
path.resolve(require.resolve("moment"), "../locale"),
|
||||||
// Part of yaml.js and only used for !!js functions that we don't use
|
// Part of yaml.js and only used for !!js functions that we don't use
|
||||||
require.resolve("esprima"),
|
require.resolve("esprima"),
|
||||||
];
|
];
|
||||||
@@ -18,8 +20,7 @@ module.exports.emptyPackages = ({ latestBuild }) =>
|
|||||||
require.resolve("@polymer/paper-styles/default-theme.js"),
|
require.resolve("@polymer/paper-styles/default-theme.js"),
|
||||||
// Loads stuff from a CDN
|
// Loads stuff from a CDN
|
||||||
require.resolve("@polymer/font-roboto/roboto.js"),
|
require.resolve("@polymer/font-roboto/roboto.js"),
|
||||||
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
|
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
|
||||||
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
|
|
||||||
// Compatibility not needed for latest builds
|
// Compatibility not needed for latest builds
|
||||||
latestBuild &&
|
latestBuild &&
|
||||||
// wrapped in require.resolve so it blows up if file no longer exists
|
// wrapped in require.resolve so it blows up if file no longer exists
|
||||||
@@ -51,29 +52,17 @@ module.exports.terserOptions = (latestBuild) => ({
|
|||||||
|
|
||||||
module.exports.babelOptions = ({ latestBuild }) => ({
|
module.exports.babelOptions = ({ latestBuild }) => ({
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
compact: false,
|
|
||||||
presets: [
|
presets: [
|
||||||
!latestBuild && [
|
!latestBuild && [
|
||||||
"@babel/preset-env",
|
"@babel/preset-env",
|
||||||
{
|
{
|
||||||
useBuiltIns: "entry",
|
useBuiltIns: "entry",
|
||||||
corejs: "3.15",
|
corejs: "3.6",
|
||||||
bugfixes: true,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"@babel/preset-typescript",
|
"@babel/preset-typescript",
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
plugins: [
|
plugins: [
|
||||||
[
|
|
||||||
path.resolve(
|
|
||||||
paths.polymer_dir,
|
|
||||||
"build-scripts/babel-plugins/inline-constants-plugin.js"
|
|
||||||
),
|
|
||||||
{
|
|
||||||
modules: ["@mdi/js"],
|
|
||||||
ignoreModuleNotFound: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
||||||
!latestBuild && [
|
!latestBuild && [
|
||||||
"@babel/plugin-proposal-object-rest-spread",
|
"@babel/plugin-proposal-object-rest-spread",
|
||||||
@@ -86,16 +75,16 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
|||||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||||
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
||||||
["@babel/plugin-proposal-private-methods", { loose: true }],
|
["@babel/plugin-proposal-private-methods", { loose: true }],
|
||||||
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
|
|
||||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
exclude: [
|
|
||||||
// \\ for Windows, / for Mac OS and Linux
|
|
||||||
/node_modules[\\/]core-js/,
|
|
||||||
/node_modules[\\/]webpack[\\/]buildin/,
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Are already ES5, cause warnings when babelified.
|
||||||
|
module.exports.babelExclude = () => [
|
||||||
|
require.resolve("@mdi/js/mdi.js"),
|
||||||
|
require.resolve("hls.js"),
|
||||||
|
];
|
||||||
|
|
||||||
const outputPath = (outputRoot, latestBuild) =>
|
const outputPath = (outputRoot, latestBuild) =>
|
||||||
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
||||||
|
|
||||||
|
@@ -47,8 +47,8 @@ gulp.task(
|
|||||||
gulp.parallel("gen-icons-json", "build-translations"),
|
gulp.parallel("gen-icons-json", "build-translations"),
|
||||||
"copy-static-app",
|
"copy-static-app",
|
||||||
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
||||||
// Don't compress running tests
|
...// Don't compress running tests
|
||||||
...(env.isTest() ? [] : ["compress-app"]),
|
(env.isTest() ? [] : ["compress-app"]),
|
||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
"gen-pages-prod",
|
"gen-pages-prod",
|
||||||
"gen-index-app-prod",
|
"gen-index-app-prod",
|
||||||
|
@@ -302,23 +302,15 @@ gulp.task("gen-index-hassio-prod", async () => {
|
|||||||
|
|
||||||
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
|
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
|
||||||
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
|
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
|
||||||
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.resolve(paths.hassio_output_root, "entrypoint.js"),
|
path.resolve(paths.hassio_output_root, "entrypoint.js"),
|
||||||
`
|
`
|
||||||
function loadES5() {
|
try {
|
||||||
|
new Function("import('${latestEntrypoint}')")();
|
||||||
|
} catch (err) {
|
||||||
var el = document.createElement('script');
|
var el = document.createElement('script');
|
||||||
el.src = '${es5Entrypoint}';
|
el.src = '${es5Entrypoint}';
|
||||||
document.body.appendChild(el);
|
document.body.appendChild(el);
|
||||||
}
|
|
||||||
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
|
|
||||||
loadES5();
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
new Function("import('${latestEntrypoint}')")();
|
|
||||||
} catch (err) {
|
|
||||||
loadES5();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{ encoding: "utf-8" }
|
{ encoding: "utf-8" }
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const cpx = require("cpx");
|
||||||
const fs = require("fs-extra");
|
const fs = require("fs-extra");
|
||||||
const paths = require("../paths");
|
const paths = require("../paths");
|
||||||
|
|
||||||
@@ -61,12 +62,9 @@ function copyLoaderJS(staticDir) {
|
|||||||
function copyFonts(staticDir) {
|
function copyFonts(staticDir) {
|
||||||
const staticPath = genStaticPath(staticDir);
|
const staticPath = genStaticPath(staticDir);
|
||||||
// Local fonts
|
// Local fonts
|
||||||
fs.copySync(
|
cpx.copySync(
|
||||||
npmPath("roboto-fontface/fonts/roboto/"),
|
npmPath("roboto-fontface/fonts/roboto/*.woff2"),
|
||||||
staticPath("fonts/roboto/"),
|
staticPath("fonts/roboto")
|
||||||
{
|
|
||||||
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
// Tasks to run webpack.
|
// Tasks to run webpack.
|
||||||
const fs = require("fs");
|
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
const WebpackDevServer = require("webpack-dev-server");
|
const WebpackDevServer = require("webpack-dev-server");
|
||||||
@@ -19,13 +18,6 @@ const bothBuilds = (createConfigFunc, params) => [
|
|||||||
createConfigFunc({ ...params, latestBuild: false }),
|
createConfigFunc({ ...params, latestBuild: false }),
|
||||||
];
|
];
|
||||||
|
|
||||||
const isWsl =
|
|
||||||
fs.existsSync("/proc/version") &&
|
|
||||||
fs
|
|
||||||
.readFileSync("/proc/version", "utf-8")
|
|
||||||
.toLocaleLowerCase()
|
|
||||||
.includes("microsoft");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{
|
* @param {{
|
||||||
* compiler: import("webpack").Compiler,
|
* compiler: import("webpack").Compiler,
|
||||||
@@ -86,15 +78,8 @@ const prodBuild = (conf) =>
|
|||||||
|
|
||||||
gulp.task("webpack-watch-app", () => {
|
gulp.task("webpack-watch-app", () => {
|
||||||
// This command will run forever because we don't close compiler
|
// This command will run forever because we don't close compiler
|
||||||
webpack(
|
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
|
||||||
process.env.ES5
|
{ ignored: /build-translations/ },
|
||||||
? bothBuilds(createAppConfig, { isProdBuild: false })
|
|
||||||
: createAppConfig({ isProdBuild: false, latestBuild: true })
|
|
||||||
).watch(
|
|
||||||
{
|
|
||||||
ignored: /build-translations/,
|
|
||||||
poll: isWsl,
|
|
||||||
},
|
|
||||||
doneHandler()
|
doneHandler()
|
||||||
);
|
);
|
||||||
gulp.watch(
|
gulp.watch(
|
||||||
@@ -152,7 +137,7 @@ gulp.task("webpack-watch-hassio", () => {
|
|||||||
isProdBuild: false,
|
isProdBuild: false,
|
||||||
latestBuild: true,
|
latestBuild: true,
|
||||||
})
|
})
|
||||||
).watch({ ignored: /build-translations/, poll: isWsl }, doneHandler());
|
).watch({ ignored: /build-translations/ }, doneHandler());
|
||||||
|
|
||||||
gulp.watch(
|
gulp.watch(
|
||||||
path.join(paths.translations_src, "en.json"),
|
path.join(paths.translations_src, "en.json"),
|
||||||
|
@@ -57,6 +57,7 @@ const createRollupConfig = ({
|
|||||||
babel({
|
babel({
|
||||||
...bundle.babelOptions({ latestBuild }),
|
...bundle.babelOptions({ latestBuild }),
|
||||||
extensions,
|
extensions,
|
||||||
|
exclude: bundle.babelExclude(),
|
||||||
babelHelpers: isWDS ? "inline" : "bundled",
|
babelHelpers: isWDS ? "inline" : "bundled",
|
||||||
}),
|
}),
|
||||||
string({
|
string({
|
||||||
|
@@ -47,18 +47,15 @@ const createWebpackConfig = ({
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.m?js$|\.ts$/,
|
test: /\.m?js$|\.ts$/,
|
||||||
|
exclude: bundle.babelExclude(),
|
||||||
use: {
|
use: {
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
options: {
|
options: bundle.babelOptions({ latestBuild }),
|
||||||
...bundle.babelOptions({ latestBuild }),
|
|
||||||
cacheDirectory: !isProdBuild,
|
|
||||||
cacheCompression: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
type: "asset/source",
|
use: "raw-loader",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -70,8 +67,6 @@ const createWebpackConfig = ({
|
|||||||
terserOptions: bundle.terserOptions(latestBuild),
|
terserOptions: bundle.terserOptions(latestBuild),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
|
||||||
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new WebpackManifestPlugin({
|
new WebpackManifestPlugin({
|
||||||
@@ -118,6 +113,16 @@ const createWebpackConfig = ({
|
|||||||
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
||||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||||
),
|
),
|
||||||
|
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one
|
||||||
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
|
new RegExp(
|
||||||
|
path.resolve(
|
||||||
|
paths.polymer_dir,
|
||||||
|
"src/resources/lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
|
||||||
|
),
|
||||||
!isProdBuild && new LogStartCompilePlugin(),
|
!isProdBuild && new LogStartCompilePlugin(),
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
resolve: {
|
resolve: {
|
||||||
@@ -130,13 +135,15 @@ const createWebpackConfig = ({
|
|||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: ({ chunk }) => {
|
filename: ({ chunk }) => {
|
||||||
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
|
if (!isProdBuild || dontHash.has(chunk.name)) {
|
||||||
return `${chunk.name}.js`;
|
return `${chunk.name}.js`;
|
||||||
}
|
}
|
||||||
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
||||||
},
|
},
|
||||||
chunkFilename:
|
chunkFilename:
|
||||||
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
|
isProdBuild && !isStatsBuild
|
||||||
|
? "chunk.[chunkhash].js"
|
||||||
|
: "[name].chunk.js",
|
||||||
path: outputPath,
|
path: outputPath,
|
||||||
publicPath,
|
publicPath,
|
||||||
// To silence warning in worker plugin
|
// To silence warning in worker plugin
|
||||||
|
@@ -139,7 +139,7 @@
|
|||||||
Your authentication credentials or Home Assistant url are never sent
|
Your authentication credentials or Home Assistant url are never sent
|
||||||
to the Cloud. You can validate this behavior in
|
to the Cloud. You can validate this behavior in
|
||||||
<a
|
<a
|
||||||
href="https://github.com/home-assistant/frontend/tree/dev/cast"
|
href="https://github.com/home-assistant/home-assistant-polymer/tree/dev/cast"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>the source code</a
|
>the source code</a
|
||||||
>.
|
>.
|
||||||
|
@@ -5,8 +5,8 @@ import {
|
|||||||
import { castContext } from "../cast_context";
|
import { castContext } from "../cast_context";
|
||||||
|
|
||||||
export const castDemoLovelace: () => LovelaceConfig = () => {
|
export const castDemoLovelace: () => LovelaceConfig = () => {
|
||||||
const touchSupported =
|
const touchSupported = castContext.getDeviceCapabilities()
|
||||||
castContext.getDeviceCapabilities().touch_input_supported;
|
.touch_input_supported;
|
||||||
return {
|
return {
|
||||||
views: [
|
views: [
|
||||||
{
|
{
|
||||||
|
@@ -113,7 +113,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
||||||
},
|
},
|
||||||
state_filter: {
|
state_filter: {
|
||||||
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
on:
|
||||||
|
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||||
off: "brightness(80%) saturate(0.8)",
|
off: "brightness(80%) saturate(0.8)",
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
@@ -195,7 +196,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
||||||
},
|
},
|
||||||
state_filter: {
|
state_filter: {
|
||||||
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
on:
|
||||||
|
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||||
off: "brightness(80%) saturate(0.8)",
|
off: "brightness(80%) saturate(0.8)",
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
@@ -275,7 +277,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
||||||
},
|
},
|
||||||
state_filter: {
|
state_filter: {
|
||||||
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
on:
|
||||||
|
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||||
off: "brightness(80%) saturate(0.8)",
|
off: "brightness(80%) saturate(0.8)",
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
@@ -312,7 +315,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
|||||||
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
||||||
},
|
},
|
||||||
state_filter: {
|
state_filter: {
|
||||||
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
on:
|
||||||
|
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||||
off: "brightness(80%) saturate(0.8)",
|
off: "brightness(80%) saturate(0.8)",
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
import { Lovelace } from "../../../src/panels/lovelace/types";
|
import { Lovelace } from "../../../src/panels/lovelace/types";
|
||||||
import { energyEntities } from "../stubs/entities";
|
|
||||||
import { DemoConfig } from "./types";
|
import { DemoConfig } from "./types";
|
||||||
|
|
||||||
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
|
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
|
||||||
@@ -13,8 +12,9 @@ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
|
|||||||
// eslint-disable-next-line import/no-mutable-exports
|
// eslint-disable-next-line import/no-mutable-exports
|
||||||
export let selectedDemoConfigIndex = 0;
|
export let selectedDemoConfigIndex = 0;
|
||||||
// eslint-disable-next-line import/no-mutable-exports
|
// eslint-disable-next-line import/no-mutable-exports
|
||||||
export let selectedDemoConfig: Promise<DemoConfig> =
|
export let selectedDemoConfig: Promise<DemoConfig> = demoConfigs[
|
||||||
demoConfigs[selectedDemoConfigIndex]();
|
selectedDemoConfigIndex
|
||||||
|
]();
|
||||||
|
|
||||||
export const setDemoConfig = async (
|
export const setDemoConfig = async (
|
||||||
hass: MockHomeAssistant,
|
hass: MockHomeAssistant,
|
||||||
@@ -28,7 +28,6 @@ export const setDemoConfig = async (
|
|||||||
selectedDemoConfig = confProm;
|
selectedDemoConfig = confProm;
|
||||||
|
|
||||||
hass.addEntities(config.entities(hass.localize), true);
|
hass.addEntities(config.entities(hass.localize), true);
|
||||||
hass.addEntities(energyEntities());
|
|
||||||
lovelace.saveConfig(config.lovelace(hass.localize));
|
lovelace.saveConfig(config.lovelace(hass.localize));
|
||||||
hass.mockTheme(config.theme());
|
hass.mockTheme(config.theme());
|
||||||
};
|
};
|
||||||
|
@@ -980,7 +980,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
|
|||||||
icon: "mdi:account-off",
|
icon: "mdi:account-off",
|
||||||
custom_ui_state_card: "state-card-custom-ui",
|
custom_ui_state_card: "state-card-custom-ui",
|
||||||
templates: {
|
templates: {
|
||||||
icon: "if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
|
icon:
|
||||||
|
"if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
|
||||||
icon_color:
|
icon_color:
|
||||||
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
|
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
|
||||||
},
|
},
|
||||||
@@ -1004,7 +1005,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
|
|||||||
icon: "mdi:account-multiple-minus",
|
icon: "mdi:account-multiple-minus",
|
||||||
custom_ui_state_card: "state-card-custom-ui",
|
custom_ui_state_card: "state-card-custom-ui",
|
||||||
templates: {
|
templates: {
|
||||||
icon: "if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
|
icon:
|
||||||
|
"if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
|
||||||
icon_color:
|
icon_color:
|
||||||
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
|
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
|
||||||
},
|
},
|
||||||
|
@@ -19,7 +19,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
@property({ attribute: false }) public hass!: MockHomeAssistant;
|
@property({ attribute: false }) public hass!: MockHomeAssistant;
|
||||||
|
|
||||||
@state() private _switching = false;
|
@state() private _switching?: boolean;
|
||||||
|
|
||||||
private _hidden = localStorage.hide_demo_card;
|
private _hidden = localStorage.hide_demo_card;
|
||||||
|
|
||||||
@@ -27,7 +27,12 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
return this._hidden ? 0 : 2;
|
return this._hidden ? 0 : 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setConfig(_config: LovelaceCardConfig) {}
|
public setConfig(
|
||||||
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
config: LovelaceCardConfig
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
) {}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (this._hidden) {
|
if (this._hidden) {
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import "@polymer/polymer/lib/elements/dom-if";
|
||||||
|
import "@polymer/polymer/lib/elements/dom-repeat";
|
||||||
import "../../src/resources/ha-style";
|
import "../../src/resources/ha-style";
|
||||||
import "../../src/resources/roboto";
|
import "../../src/resources/roboto";
|
||||||
import "../../src/resources/safari-14-attachshadow-patch";
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
|
@@ -20,10 +20,6 @@ import { mockShoppingList } from "./stubs/shopping_list";
|
|||||||
import { mockSystemLog } from "./stubs/system_log";
|
import { mockSystemLog } from "./stubs/system_log";
|
||||||
import { mockTemplate } from "./stubs/template";
|
import { mockTemplate } from "./stubs/template";
|
||||||
import { mockTranslations } from "./stubs/translations";
|
import { mockTranslations } from "./stubs/translations";
|
||||||
import { mockEnergy } from "./stubs/energy";
|
|
||||||
import { mockConfig } from "./stubs/config";
|
|
||||||
import { energyEntities } from "./stubs/entities";
|
|
||||||
import { mockForecastSolar } from "./stubs/forecast_solar";
|
|
||||||
|
|
||||||
class HaDemo extends HomeAssistantAppEl {
|
class HaDemo extends HomeAssistantAppEl {
|
||||||
protected async _initializeHass() {
|
protected async _initializeHass() {
|
||||||
@@ -51,13 +47,8 @@ class HaDemo extends HomeAssistantAppEl {
|
|||||||
mockEvents(hass);
|
mockEvents(hass);
|
||||||
mockMediaPlayer(hass);
|
mockMediaPlayer(hass);
|
||||||
mockFrontend(hass);
|
mockFrontend(hass);
|
||||||
mockEnergy(hass);
|
|
||||||
mockForecastSolar(hass);
|
|
||||||
mockConfig(hass);
|
|
||||||
mockPersistentNotification(hass);
|
mockPersistentNotification(hass);
|
||||||
|
|
||||||
hass.addEntities(energyEntities());
|
|
||||||
|
|
||||||
// Once config is loaded AND localize, set entities and apply theme.
|
// Once config is loaded AND localize, set entities and apply theme.
|
||||||
Promise.all([selectedDemoConfig, localizePromise]).then(
|
Promise.all([selectedDemoConfig, localizePromise]).then(
|
||||||
([conf, localize]) => {
|
([conf, localize]) => {
|
||||||
|
@@ -1,41 +0,0 @@
|
|||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
|
||||||
|
|
||||||
export const mockConfig = (hass: MockHomeAssistant) => {
|
|
||||||
hass.mockAPI("config/config_entries/entry", () => [
|
|
||||||
{
|
|
||||||
entry_id: "co2signal",
|
|
||||||
domain: "co2signal",
|
|
||||||
title: "CO2 Signal",
|
|
||||||
source: "user",
|
|
||||||
state: "loaded",
|
|
||||||
supports_options: false,
|
|
||||||
supports_unload: true,
|
|
||||||
pref_disable_new_entities: false,
|
|
||||||
pref_disable_polling: false,
|
|
||||||
disabled_by: null,
|
|
||||||
reason: null,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
hass.mockWS("config/entity_registry/list", () => [
|
|
||||||
{
|
|
||||||
config_entry_id: "co2signal",
|
|
||||||
device_id: "co2signal",
|
|
||||||
area_id: null,
|
|
||||||
disabled_by: null,
|
|
||||||
entity_id: "sensor.co2_intensity",
|
|
||||||
name: null,
|
|
||||||
icon: null,
|
|
||||||
platform: "co2signal",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
config_entry_id: "co2signal",
|
|
||||||
device_id: "co2signal",
|
|
||||||
area_id: null,
|
|
||||||
disabled_by: null,
|
|
||||||
entity_id: "sensor.grid_fossil_fuel_percentage",
|
|
||||||
name: null,
|
|
||||||
icon: null,
|
|
||||||
platform: "co2signal",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
};
|
|
@@ -1,70 +0,0 @@
|
|||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
|
||||||
|
|
||||||
export const mockEnergy = (hass: MockHomeAssistant) => {
|
|
||||||
hass.mockWS("energy/get_prefs", () => ({
|
|
||||||
energy_sources: [
|
|
||||||
{
|
|
||||||
type: "grid",
|
|
||||||
flow_from: [
|
|
||||||
{
|
|
||||||
stat_energy_from: "sensor.energy_consumption_tarif_1",
|
|
||||||
stat_cost: "sensor.energy_consumption_tarif_1_cost",
|
|
||||||
entity_energy_from: "sensor.energy_consumption_tarif_1",
|
|
||||||
entity_energy_price: null,
|
|
||||||
number_energy_price: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stat_energy_from: "sensor.energy_consumption_tarif_2",
|
|
||||||
stat_cost: "sensor.energy_consumption_tarif_2_cost",
|
|
||||||
entity_energy_from: "sensor.energy_consumption_tarif_2",
|
|
||||||
entity_energy_price: null,
|
|
||||||
number_energy_price: null,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
flow_to: [
|
|
||||||
{
|
|
||||||
stat_energy_to: "sensor.energy_production_tarif_1",
|
|
||||||
stat_compensation: "sensor.energy_production_tarif_1_compensation",
|
|
||||||
entity_energy_to: "sensor.energy_production_tarif_1",
|
|
||||||
entity_energy_price: null,
|
|
||||||
number_energy_price: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stat_energy_to: "sensor.energy_production_tarif_2",
|
|
||||||
stat_compensation: "sensor.energy_production_tarif_2_compensation",
|
|
||||||
entity_energy_to: "sensor.energy_production_tarif_2",
|
|
||||||
entity_energy_price: null,
|
|
||||||
number_energy_price: null,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
cost_adjustment_day: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "solar",
|
|
||||||
stat_energy_from: "sensor.solar_production",
|
|
||||||
config_entry_solar_forecast: ["solar_forecast"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
device_consumption: [
|
|
||||||
{
|
|
||||||
stat_consumption: "sensor.energy_car",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stat_consumption: "sensor.energy_ac",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stat_consumption: "sensor.energy_washing_machine",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stat_consumption: "sensor.energy_dryer",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stat_consumption: "sensor.energy_heat_pump",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
stat_consumption: "sensor.energy_boiler",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}));
|
|
||||||
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
|
|
||||||
};
|
|
@@ -1,143 +0,0 @@
|
|||||||
import { convertEntities } from "../../../src/fake_data/entity";
|
|
||||||
|
|
||||||
export const energyEntities = () =>
|
|
||||||
convertEntities({
|
|
||||||
"sensor.grid_fossil_fuel_percentage": {
|
|
||||||
entity_id: "sensor.grid_fossil_fuel_percentage",
|
|
||||||
state: "88.6",
|
|
||||||
attributes: {
|
|
||||||
unit_of_measurement: "%",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.solar_production": {
|
|
||||||
entity_id: "sensor.solar_production",
|
|
||||||
state: "88.6",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Solar",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_consumption_tarif_1": {
|
|
||||||
entity_id: "sensor.energy_consumption_tarif_1 ",
|
|
||||||
state: "88.6",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Grid consumption low tariff",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_consumption_tarif_2": {
|
|
||||||
entity_id: "sensor.energy_consumption_tarif_2",
|
|
||||||
state: "88.6",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Grid consumption high tariff",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_production_tarif_1": {
|
|
||||||
entity_id: "sensor.energy_production_tarif_1",
|
|
||||||
state: "88.6",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Returned to grid low tariff",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_production_tarif_2": {
|
|
||||||
entity_id: "sensor.energy_production_tarif_2",
|
|
||||||
state: "88.6",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Returned to grid high tariff",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_consumption_tarif_1_cost": {
|
|
||||||
entity_id: "sensor.energy_consumption_tarif_1_cost",
|
|
||||||
state: "2",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
unit_of_measurement: "EUR",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_consumption_tarif_2_cost": {
|
|
||||||
entity_id: "sensor.energy_consumption_tarif_2_cost",
|
|
||||||
state: "2",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
unit_of_measurement: "EUR",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_production_tarif_1_compensation": {
|
|
||||||
entity_id: "sensor.energy_production_tarif_1_compensation",
|
|
||||||
state: "2",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
unit_of_measurement: "EUR",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_production_tarif_2_compensation": {
|
|
||||||
entity_id: "sensor.energy_production_tarif_2_compensation",
|
|
||||||
state: "2",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
unit_of_measurement: "EUR",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_car": {
|
|
||||||
entity_id: "sensor.energy_car",
|
|
||||||
state: "4",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Electric car",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_ac": {
|
|
||||||
entity_id: "sensor.energy_ac",
|
|
||||||
state: "3",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Air conditioning",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_washing_machine": {
|
|
||||||
entity_id: "sensor.energy_washing_machine",
|
|
||||||
state: "6",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Washing machine",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_dryer": {
|
|
||||||
entity_id: "sensor.energy_dryer",
|
|
||||||
state: "5.5",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Dryer",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_heat_pump": {
|
|
||||||
entity_id: "sensor.energy_heat_pump",
|
|
||||||
state: "6",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Heat pump",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"sensor.energy_boiler": {
|
|
||||||
entity_id: "sensor.energy_boiler",
|
|
||||||
state: "7",
|
|
||||||
attributes: {
|
|
||||||
last_reset: "1970-01-01T00:00:00:00+00",
|
|
||||||
friendly_name: "Boiler",
|
|
||||||
unit_of_measurement: "kWh",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,55 +0,0 @@
|
|||||||
import { format, startOfToday, startOfTomorrow } from "date-fns";
|
|
||||||
import { ForecastSolarForecast } from "../../../src/data/forecast_solar";
|
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
|
||||||
|
|
||||||
export const mockForecastSolar = (hass: MockHomeAssistant) => {
|
|
||||||
const todayString = format(startOfToday(), "yyyy-MM-dd");
|
|
||||||
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
|
|
||||||
hass.mockWS(
|
|
||||||
"forecast_solar/forecasts",
|
|
||||||
(): Record<string, ForecastSolarForecast> => ({
|
|
||||||
solar_forecast: {
|
|
||||||
wh_hours: {
|
|
||||||
[`${todayString}T06:00:00`]: 0,
|
|
||||||
[`${todayString}T06:23:00`]: 6,
|
|
||||||
[`${todayString}T06:45:00`]: 39,
|
|
||||||
[`${todayString}T07:00:00`]: 28,
|
|
||||||
[`${todayString}T08:00:00`]: 208,
|
|
||||||
[`${todayString}T09:00:00`]: 352,
|
|
||||||
[`${todayString}T10:00:00`]: 544,
|
|
||||||
[`${todayString}T11:00:00`]: 748,
|
|
||||||
[`${todayString}T12:00:00`]: 1259,
|
|
||||||
[`${todayString}T13:00:00`]: 1361,
|
|
||||||
[`${todayString}T14:00:00`]: 1373,
|
|
||||||
[`${todayString}T15:00:00`]: 1370,
|
|
||||||
[`${todayString}T16:00:00`]: 1186,
|
|
||||||
[`${todayString}T17:00:00`]: 937,
|
|
||||||
[`${todayString}T18:00:00`]: 652,
|
|
||||||
[`${todayString}T19:00:00`]: 370,
|
|
||||||
[`${todayString}T20:00:00`]: 155,
|
|
||||||
[`${todayString}T21:48:00`]: 24,
|
|
||||||
[`${todayString}T22:36:00`]: 0,
|
|
||||||
[`${tomorrowString}T06:01:00`]: 0,
|
|
||||||
[`${tomorrowString}T06:23:00`]: 9,
|
|
||||||
[`${tomorrowString}T06:45:00`]: 47,
|
|
||||||
[`${tomorrowString}T07:00:00`]: 48,
|
|
||||||
[`${tomorrowString}T08:00:00`]: 473,
|
|
||||||
[`${tomorrowString}T09:00:00`]: 827,
|
|
||||||
[`${tomorrowString}T10:00:00`]: 1153,
|
|
||||||
[`${tomorrowString}T11:00:00`]: 1413,
|
|
||||||
[`${tomorrowString}T12:00:00`]: 1590,
|
|
||||||
[`${tomorrowString}T13:00:00`]: 1652,
|
|
||||||
[`${tomorrowString}T14:00:00`]: 1612,
|
|
||||||
[`${tomorrowString}T15:00:00`]: 1438,
|
|
||||||
[`${tomorrowString}T16:00:00`]: 1149,
|
|
||||||
[`${tomorrowString}T17:00:00`]: 830,
|
|
||||||
[`${tomorrowString}T18:00:00`]: 542,
|
|
||||||
[`${tomorrowString}T19:00:00`]: 311,
|
|
||||||
[`${tomorrowString}T20:00:00`]: 140,
|
|
||||||
[`${tomorrowString}T21:47:00`]: 22,
|
|
||||||
[`${tomorrowString}T22:34:00`]: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
|
@@ -1,6 +1,4 @@
|
|||||||
import { addHours, differenceInHours } from "date-fns";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { StatisticValue } from "../../../src/data/history";
|
|
||||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
interface HistoryQueryParams {
|
interface HistoryQueryParams {
|
||||||
@@ -66,215 +64,17 @@ const generateHistory = (state, deltas) => {
|
|||||||
|
|
||||||
const incrementalUnits = ["clients", "queries", "ads"];
|
const incrementalUnits = ["clients", "queries", "ads"];
|
||||||
|
|
||||||
const generateMeanStatistics = (
|
|
||||||
id: string,
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
initValue: number,
|
|
||||||
maxDiff: number
|
|
||||||
) => {
|
|
||||||
const statistics: StatisticValue[] = [];
|
|
||||||
let currentDate = new Date(start);
|
|
||||||
currentDate.setMinutes(0, 0, 0);
|
|
||||||
let lastVal = initValue;
|
|
||||||
const now = new Date();
|
|
||||||
while (end > currentDate && currentDate < now) {
|
|
||||||
const delta = Math.random() * maxDiff;
|
|
||||||
const mean = lastVal + delta;
|
|
||||||
statistics.push({
|
|
||||||
statistic_id: id,
|
|
||||||
start: currentDate.toISOString(),
|
|
||||||
mean,
|
|
||||||
min: mean,
|
|
||||||
max: mean,
|
|
||||||
last_reset: "1970-01-01T00:00:00+00:00",
|
|
||||||
state: mean,
|
|
||||||
sum: null,
|
|
||||||
});
|
|
||||||
lastVal = mean;
|
|
||||||
currentDate = addHours(currentDate, 1);
|
|
||||||
}
|
|
||||||
return statistics;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateSumStatistics = (
|
|
||||||
id: string,
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
initValue: number,
|
|
||||||
maxDiff: number
|
|
||||||
) => {
|
|
||||||
const statistics: StatisticValue[] = [];
|
|
||||||
let currentDate = new Date(start);
|
|
||||||
currentDate.setMinutes(0, 0, 0);
|
|
||||||
let sum = initValue;
|
|
||||||
const now = new Date();
|
|
||||||
while (end > currentDate && currentDate < now) {
|
|
||||||
const add = Math.random() * maxDiff;
|
|
||||||
sum += add;
|
|
||||||
statistics.push({
|
|
||||||
statistic_id: id,
|
|
||||||
start: currentDate.toISOString(),
|
|
||||||
mean: null,
|
|
||||||
min: null,
|
|
||||||
max: null,
|
|
||||||
last_reset: "1970-01-01T00:00:00+00:00",
|
|
||||||
state: initValue + sum,
|
|
||||||
sum,
|
|
||||||
});
|
|
||||||
currentDate = addHours(currentDate, 1);
|
|
||||||
}
|
|
||||||
return statistics;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateCurvedStatistics = (
|
|
||||||
id: string,
|
|
||||||
start: Date,
|
|
||||||
end: Date,
|
|
||||||
initValue: number,
|
|
||||||
maxDiff: number,
|
|
||||||
metered: boolean
|
|
||||||
) => {
|
|
||||||
const statistics: StatisticValue[] = [];
|
|
||||||
let currentDate = new Date(start);
|
|
||||||
currentDate.setMinutes(0, 0, 0);
|
|
||||||
let sum = initValue;
|
|
||||||
const hours = differenceInHours(end, start) - 1;
|
|
||||||
let i = 0;
|
|
||||||
let half = false;
|
|
||||||
const now = new Date();
|
|
||||||
while (end > currentDate && currentDate < now) {
|
|
||||||
const add = Math.random() * maxDiff;
|
|
||||||
sum += i * add;
|
|
||||||
statistics.push({
|
|
||||||
statistic_id: id,
|
|
||||||
start: currentDate.toISOString(),
|
|
||||||
mean: null,
|
|
||||||
min: null,
|
|
||||||
max: null,
|
|
||||||
last_reset: "1970-01-01T00:00:00+00:00",
|
|
||||||
state: initValue + sum,
|
|
||||||
sum: metered ? sum : null,
|
|
||||||
});
|
|
||||||
currentDate = addHours(currentDate, 1);
|
|
||||||
if (!half && i > hours / 2) {
|
|
||||||
half = true;
|
|
||||||
}
|
|
||||||
i += half ? -1 : 1;
|
|
||||||
}
|
|
||||||
return statistics;
|
|
||||||
};
|
|
||||||
|
|
||||||
const statisticsFunctions: Record<
|
|
||||||
string,
|
|
||||||
(id: string, start: Date, end: Date) => StatisticValue[]
|
|
||||||
> = {
|
|
||||||
"sensor.energy_consumption_tarif_1": (id: string, start: Date, end: Date) => {
|
|
||||||
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
|
|
||||||
const morningLow = generateSumStatistics(id, start, morningEnd, 0, 0.7);
|
|
||||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
|
||||||
const morningFinalVal = morningLow.length
|
|
||||||
? morningLow[morningLow.length - 1].sum!
|
|
||||||
: 0;
|
|
||||||
const empty = generateSumStatistics(
|
|
||||||
id,
|
|
||||||
morningEnd,
|
|
||||||
eveningStart,
|
|
||||||
morningFinalVal,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const eveningLow = generateSumStatistics(
|
|
||||||
id,
|
|
||||||
eveningStart,
|
|
||||||
end,
|
|
||||||
morningFinalVal,
|
|
||||||
0.7
|
|
||||||
);
|
|
||||||
return [...morningLow, ...empty, ...eveningLow];
|
|
||||||
},
|
|
||||||
"sensor.energy_consumption_tarif_2": (id: string, start: Date, end: Date) => {
|
|
||||||
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
|
||||||
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
|
|
||||||
const highTarif = generateSumStatistics(
|
|
||||||
id,
|
|
||||||
morningEnd,
|
|
||||||
eveningStart,
|
|
||||||
0,
|
|
||||||
0.3
|
|
||||||
);
|
|
||||||
const highTarifFinalVal = highTarif.length
|
|
||||||
? highTarif[highTarif.length - 1].sum!
|
|
||||||
: 0;
|
|
||||||
const morning = generateSumStatistics(id, start, morningEnd, 0, 0);
|
|
||||||
const evening = generateSumStatistics(
|
|
||||||
id,
|
|
||||||
eveningStart,
|
|
||||||
end,
|
|
||||||
highTarifFinalVal,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
return [...morning, ...highTarif, ...evening];
|
|
||||||
},
|
|
||||||
"sensor.energy_production_tarif_1": (id, start, end) =>
|
|
||||||
generateSumStatistics(id, start, end, 0, 0),
|
|
||||||
"sensor.energy_production_tarif_1_compensation": (id, start, end) =>
|
|
||||||
generateSumStatistics(id, start, end, 0, 0),
|
|
||||||
"sensor.energy_production_tarif_2": (id, start, end) => {
|
|
||||||
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
|
|
||||||
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
|
|
||||||
const production = generateCurvedStatistics(
|
|
||||||
id,
|
|
||||||
productionStart,
|
|
||||||
productionEnd,
|
|
||||||
0,
|
|
||||||
0.15,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
const productionFinalVal = production.length
|
|
||||||
? production[production.length - 1].sum!
|
|
||||||
: 0;
|
|
||||||
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
|
|
||||||
const evening = generateSumStatistics(
|
|
||||||
id,
|
|
||||||
productionEnd,
|
|
||||||
end,
|
|
||||||
productionFinalVal,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
return [...morning, ...production, ...evening];
|
|
||||||
},
|
|
||||||
"sensor.solar_production": (id, start, end) => {
|
|
||||||
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
|
|
||||||
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
|
|
||||||
const production = generateCurvedStatistics(
|
|
||||||
id,
|
|
||||||
productionStart,
|
|
||||||
productionEnd,
|
|
||||||
0,
|
|
||||||
0.3,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
const productionFinalVal = production.length
|
|
||||||
? production[production.length - 1].sum!
|
|
||||||
: 0;
|
|
||||||
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
|
|
||||||
const evening = generateSumStatistics(
|
|
||||||
id,
|
|
||||||
productionEnd,
|
|
||||||
end,
|
|
||||||
productionFinalVal,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
return [...morning, ...production, ...evening];
|
|
||||||
},
|
|
||||||
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
|
|
||||||
generateMeanStatistics(id, start, end, 35, 1.3),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||||
mockHass.mockAPI(
|
mockHass.mockAPI(
|
||||||
new RegExp("history/period/.+"),
|
new RegExp("history/period/.+"),
|
||||||
(hass, _method, path, _parameters) => {
|
(
|
||||||
|
hass,
|
||||||
|
// @ts-ignore
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
// @ts-ignore
|
||||||
|
parameters
|
||||||
|
) => {
|
||||||
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
|
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
|
||||||
const entities = params.filter_entity_id.split(",");
|
const entities = params.filter_entity_id.split(",");
|
||||||
|
|
||||||
@@ -295,7 +95,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
|||||||
const numberState = Number(state.state);
|
const numberState = Number(state.state);
|
||||||
|
|
||||||
if (isNaN(numberState)) {
|
if (isNaN(numberState)) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line
|
||||||
console.log(
|
console.log(
|
||||||
"Ignoring state with unparsable state but with a unit",
|
"Ignoring state with unparsable state but with a unit",
|
||||||
entityId,
|
entityId,
|
||||||
@@ -340,39 +140,4 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
mockHass.mockWS(
|
|
||||||
"history/statistics_during_period",
|
|
||||||
({ statistic_ids, start_time, end_time }, hass) => {
|
|
||||||
const start = new Date(start_time);
|
|
||||||
const end = new Date(end_time);
|
|
||||||
|
|
||||||
const statistics: Record<string, StatisticValue[]> = {};
|
|
||||||
|
|
||||||
statistic_ids.forEach((id: string) => {
|
|
||||||
if (id in statisticsFunctions) {
|
|
||||||
statistics[id] = statisticsFunctions[id](id, start, end);
|
|
||||||
} else {
|
|
||||||
const entityState = hass.states[id];
|
|
||||||
const state = entityState ? Number(entityState.state) : 1;
|
|
||||||
statistics[id] =
|
|
||||||
entityState && "last_reset" in entityState.attributes
|
|
||||||
? generateSumStatistics(
|
|
||||||
id,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
state,
|
|
||||||
state * (state > 80 ? 0.01 : 0.05)
|
|
||||||
)
|
|
||||||
: generateMeanStatistics(
|
|
||||||
id,
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
state,
|
|
||||||
state * (state > 80 ? 0.05 : 0.1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return statistics;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
@@ -10,9 +10,10 @@ export const mockLovelace = (
|
|||||||
localizePromise: Promise<LocalizeFunc>
|
localizePromise: Promise<LocalizeFunc>
|
||||||
) => {
|
) => {
|
||||||
hass.mockWS("lovelace/config", () =>
|
hass.mockWS("lovelace/config", () =>
|
||||||
Promise.all([selectedDemoConfig, localizePromise]).then(
|
Promise.all([
|
||||||
([config, localize]) => config.lovelace(localize)
|
selectedDemoConfig,
|
||||||
)
|
localizePromise,
|
||||||
|
]).then(([config, localize]) => config.lovelace(localize))
|
||||||
);
|
);
|
||||||
|
|
||||||
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
||||||
|
@@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
|
|||||||
body: { message: "Template dev tool does not work in the demo." },
|
body: { message: "Template dev tool does not work in the demo." },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
hass.mockWS("render_template", (msg, _hass, onChange) => {
|
hass.mockWS("render_template", (msg, onChange) => {
|
||||||
onChange!({
|
onChange!({
|
||||||
result: msg.template,
|
result: msg.template,
|
||||||
listeners: { all: false, domains: [], entities: [], time: false },
|
listeners: { all: false, domains: [], entities: [], time: false },
|
||||||
|
@@ -2,12 +2,12 @@ import { html, css, LitElement, TemplateResult } from "lit";
|
|||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
import "../../../src/components/trace/hat-script-graph";
|
import "../../../src/components/trace/hat-script-graph";
|
||||||
import "../../../src/components/trace/hat-trace-timeline";
|
import "../../../src/components/trace/hat-trace-timeline";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
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";
|
||||||
import { DemoTrace } from "../data/traces/types";
|
import { DemoTrace } from "../data/traces/types";
|
||||||
import { basicTrace } from "../data/traces/basic_trace";
|
import { basicTrace } from "../data/traces/basic_trace";
|
||||||
import { motionLightTrace } from "../data/traces/motion-light-trace";
|
import { motionLightTrace } from "../data/traces/motion-light-trace";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
|
||||||
const traces: DemoTrace[] = [basicTrace, motionLightTrace];
|
const traces: DemoTrace[] = [basicTrace, motionLightTrace];
|
||||||
|
|
||||||
|
@@ -2,8 +2,6 @@ import { html, css, LitElement, TemplateResult } from "lit";
|
|||||||
import "../../../src/components/ha-formfield";
|
import "../../../src/components/ha-formfield";
|
||||||
import "../../../src/components/ha-switch";
|
import "../../../src/components/ha-switch";
|
||||||
|
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { IntegrationManifest } from "../../../src/data/integration";
|
import { IntegrationManifest } from "../../../src/data/integration";
|
||||||
|
|
||||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||||
@@ -17,6 +15,8 @@ import type {
|
|||||||
} from "../../../src/panels/config/integrations/ha-config-integrations";
|
} from "../../../src/panels/config/integrations/ha-config-integrations";
|
||||||
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
|
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
|
||||||
import { EntityRegistryEntry } from "../../../src/data/entity_registry";
|
import { EntityRegistryEntry } from "../../../src/data/entity_registry";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
|
||||||
const createConfigEntry = (
|
const createConfigEntry = (
|
||||||
title: string,
|
title: string,
|
||||||
|
@@ -2,6 +2,7 @@ import "@material/mwc-button";
|
|||||||
import { ActionDetail } from "@material/mwc-list";
|
import { ActionDetail } from "@material/mwc-list";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiDotsVertical } from "@mdi/js";
|
import { mdiDotsVertical } from "@mdi/js";
|
||||||
|
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
|
||||||
import { DEFAULT_SCHEMA, Type } from "js-yaml";
|
import { DEFAULT_SCHEMA, Type } from "js-yaml";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -133,7 +134,7 @@ class HassioAddonConfig extends LitElement {
|
|||||||
></ha-form>`
|
></ha-form>`
|
||||||
: html` <ha-yaml-editor
|
: html` <ha-yaml-editor
|
||||||
@value-changed=${this._configChanged}
|
@value-changed=${this._configChanged}
|
||||||
.yamlSchema=${ADDON_YAML_SCHEMA}
|
.schema=${ADDON_YAML_SCHEMA}
|
||||||
></ha-yaml-editor>`}
|
></ha-yaml-editor>`}
|
||||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||||
${!this._yamlMode ||
|
${!this._yamlMode ||
|
||||||
@@ -268,9 +269,6 @@ class HassioAddonConfig extends LitElement {
|
|||||||
|
|
||||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||||
const button = ev.currentTarget as any;
|
const button = ev.currentTarget as any;
|
||||||
const options: Record<string, unknown> = this._yamlMode
|
|
||||||
? this._editor?.value
|
|
||||||
: this._options;
|
|
||||||
const eventdata = {
|
const eventdata = {
|
||||||
success: true,
|
success: true,
|
||||||
response: undefined,
|
response: undefined,
|
||||||
@@ -284,13 +282,13 @@ class HassioAddonConfig extends LitElement {
|
|||||||
const validation = await validateHassioAddonOption(
|
const validation = await validateHassioAddonOption(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.addon.slug,
|
this.addon.slug,
|
||||||
options
|
this._editor?.value
|
||||||
);
|
);
|
||||||
if (!validation.valid) {
|
if (!validation.valid) {
|
||||||
throw Error(validation.message);
|
throw Error(validation.message);
|
||||||
}
|
}
|
||||||
await setHassioAddonOption(this.hass, this.addon.slug, {
|
await setHassioAddonOption(this.hass, this.addon.slug, {
|
||||||
options,
|
options: this._yamlMode ? this._editor?.value : this._options,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._configHasChanged = false;
|
this._configHasChanged = false;
|
||||||
@@ -328,6 +326,10 @@ class HassioAddonConfig extends LitElement {
|
|||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
iron-autogrow-textarea {
|
||||||
|
width: 100%;
|
||||||
|
font-family: var(--code-font-family, monospace);
|
||||||
|
}
|
||||||
.syntaxerror {
|
.syntaxerror {
|
||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ import "../../../../src/components/ha-card";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import "../../../../src/components/ha-circular-progress";
|
import "../../../../src/components/ha-circular-progress";
|
||||||
import "../../../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import {
|
import {
|
||||||
fetchHassioAddonDocumentation,
|
fetchHassioAddonDocumentation,
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
@@ -13,6 +12,7 @@ import { haStyle } from "../../../../src/resources/styles";
|
|||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
import { hassioStyle } from "../../resources/hassio-style";
|
import { hassioStyle } from "../../resources/hassio-style";
|
||||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
|
||||||
@customElement("hassio-addon-documentation-tab")
|
@customElement("hassio-addon-documentation-tab")
|
||||||
class HassioAddonDocumentationDashboard extends LitElement {
|
class HassioAddonDocumentationDashboard extends LitElement {
|
||||||
|
@@ -892,19 +892,10 @@ class HassioAddonInfo extends LitElement {
|
|||||||
|
|
||||||
private async _openChangelog(): Promise<void> {
|
private async _openChangelog(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
let content = await fetchHassioAddonChangelog(this.hass, this.addon.slug);
|
const content = await fetchHassioAddonChangelog(
|
||||||
if (
|
this.hass,
|
||||||
content.includes(`# ${this.addon.version}`) &&
|
this.addon.slug
|
||||||
content.includes(`# ${this.addon.version_latest}`)
|
);
|
||||||
) {
|
|
||||||
const newcontent = content.split(`# ${this.addon.version}`)[0];
|
|
||||||
if (newcontent.includes(`# ${this.addon.version_latest}`)) {
|
|
||||||
// Only change the content if the new version still exist
|
|
||||||
// if the changelog does not have the newests version on top
|
|
||||||
// this will not be true, and we don't modify the content
|
|
||||||
content = newcontent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showHassioMarkdownDialog(this, {
|
showHassioMarkdownDialog(this, {
|
||||||
title: this.supervisor.localize("addon.dashboard.changelog"),
|
title: this.supervisor.localize("addon.dashboard.changelog"),
|
||||||
content,
|
content,
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
import { mdiFolderUpload } from "@mdi/js";
|
import { mdiFolderUpload } from "@mdi/js";
|
||||||
|
import "@polymer/iron-input/iron-input";
|
||||||
import "@polymer/paper-input/paper-input-container";
|
import "@polymer/paper-input/paper-input-container";
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
|
@@ -29,6 +29,7 @@ class SupervisorFormfieldLabel extends LitElement {
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import { formatDate } from "../../../src/common/datetime/format_date";
|
import { formatDate } from "../../../src/common/datetime/format_date";
|
||||||
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
||||||
import { LocalizeFunc } from "../../../src/common/translations/localize";
|
|
||||||
import "../../../src/components/ha-checkbox";
|
import "../../../src/components/ha-checkbox";
|
||||||
import "../../../src/components/ha-formfield";
|
import "../../../src/components/ha-formfield";
|
||||||
import "../../../src/components/ha-radio";
|
import "../../../src/components/ha-radio";
|
||||||
@@ -45,9 +44,6 @@ const _computeFolders = (folders): CheckboxItem[] => {
|
|||||||
if (folders.includes("share")) {
|
if (folders.includes("share")) {
|
||||||
list.push({ slug: "share", name: "Share", checked: false });
|
list.push({ slug: "share", name: "Share", checked: false });
|
||||||
}
|
}
|
||||||
if (folders.includes("media")) {
|
|
||||||
list.push({ slug: "media", name: "Media", checked: false });
|
|
||||||
}
|
|
||||||
if (folders.includes("addons/local")) {
|
if (folders.includes("addons/local")) {
|
||||||
list.push({ slug: "addons/local", name: "Local add-ons", checked: false });
|
list.push({ slug: "addons/local", name: "Local add-ons", checked: false });
|
||||||
}
|
}
|
||||||
@@ -68,8 +64,6 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
|
|||||||
export class SupervisorSnapshotContent extends LitElement {
|
export class SupervisorSnapshotContent extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public localize?: LocalizeFunc;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public supervisor?: Supervisor;
|
@property({ attribute: false }) public supervisor?: Supervisor;
|
||||||
|
|
||||||
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail;
|
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail;
|
||||||
@@ -84,14 +78,10 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public snapshotHasPassword = false;
|
@property({ type: Boolean }) public snapshotHasPassword = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public onboarding = false;
|
|
||||||
|
|
||||||
@property() public snapshotName = "";
|
@property() public snapshotName = "";
|
||||||
|
|
||||||
@property() public snapshotPassword = "";
|
@property() public snapshotPassword = "";
|
||||||
|
|
||||||
@property() public confirmSnapshotPassword = "";
|
|
||||||
|
|
||||||
public willUpdate(changedProps) {
|
public willUpdate(changedProps) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
@@ -111,12 +101,8 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _localize = (string: string) =>
|
|
||||||
this.supervisor?.localize(`snapshot.${string}`) ||
|
|
||||||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.onboarding && !this.supervisor) {
|
if (!this.supervisor) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const foldersSection =
|
const foldersSection =
|
||||||
@@ -128,16 +114,14 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
${this.snapshot
|
${this.snapshot
|
||||||
? html`<div class="details">
|
? html`<div class="details">
|
||||||
${this.snapshot.type === "full"
|
${this.snapshot.type === "full"
|
||||||
? this._localize("full_snapshot")
|
? this.supervisor.localize("snapshot.full_snapshot")
|
||||||
: this._localize("partial_snapshot")}
|
: this.supervisor.localize("snapshot.partial_snapshot")}
|
||||||
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
|
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
|
||||||
${this.hass
|
${formatDateTime(new Date(this.snapshot.date), this.hass.locale)}
|
||||||
? formatDateTime(new Date(this.snapshot.date), this.hass.locale)
|
|
||||||
: this.snapshot.date}
|
|
||||||
</div>`
|
</div>`
|
||||||
: html`<paper-input
|
: html`<paper-input
|
||||||
name="snapshotName"
|
name="snapshotName"
|
||||||
.label=${this.supervisor?.localize("snapshot.name") || "Name"}
|
.label=${this.supervisor.localize("snapshot.name")}
|
||||||
.value=${this.snapshotName}
|
.value=${this.snapshotName}
|
||||||
@value-changed=${this._handleTextValueChanged}
|
@value-changed=${this._handleTextValueChanged}
|
||||||
>
|
>
|
||||||
@@ -145,11 +129,13 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
${!this.snapshot || this.snapshot.type === "full"
|
${!this.snapshot || this.snapshot.type === "full"
|
||||||
? html`<div class="sub-header">
|
? html`<div class="sub-header">
|
||||||
${!this.snapshot
|
${!this.snapshot
|
||||||
? this._localize("type")
|
? this.supervisor.localize("snapshot.type")
|
||||||
: this._localize("select_type")}
|
: this.supervisor.localize("snapshot.select_type")}
|
||||||
</div>
|
</div>
|
||||||
<div class="snapshot-types">
|
<div class="snapshot-types">
|
||||||
<ha-formfield .label=${this._localize("full_snapshot")}>
|
<ha-formfield
|
||||||
|
.label=${this.supervisor.localize("snapshot.full_snapshot")}
|
||||||
|
>
|
||||||
<ha-radio
|
<ha-radio
|
||||||
@change=${this._handleRadioValueChanged}
|
@change=${this._handleRadioValueChanged}
|
||||||
value="full"
|
value="full"
|
||||||
@@ -158,7 +144,9 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
>
|
>
|
||||||
</ha-radio>
|
</ha-radio>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
<ha-formfield .label=${this._localize("partial_snapshot")}>
|
<ha-formfield
|
||||||
|
.label=${this.supervisor!.localize("snapshot.partial_snapshot")}
|
||||||
|
>
|
||||||
<ha-radio
|
<ha-radio
|
||||||
@change=${this._handleRadioValueChanged}
|
@change=${this._handleRadioValueChanged}
|
||||||
value="partial"
|
value="partial"
|
||||||
@@ -169,9 +157,9 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
</div>`
|
</div>`
|
||||||
: ""}
|
: ""}
|
||||||
${this.snapshotType === "partial"
|
${this.snapshot && this.snapshotType === "partial"
|
||||||
? html`<div class="partial-picker">
|
? html`
|
||||||
${this.snapshot && this.snapshot.homeassistant
|
${this.snapshot.homeassistant
|
||||||
? html`
|
? html`
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
.label=${html`<supervisor-formfield-label
|
.label=${html`<supervisor-formfield-label
|
||||||
@@ -191,11 +179,15 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.snapshotType === "partial"
|
||||||
|
? html`
|
||||||
${foldersSection?.templates.length
|
${foldersSection?.templates.length
|
||||||
? html`
|
? html`
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
.label=${html`<supervisor-formfield-label
|
.label=${html`<supervisor-formfield-label
|
||||||
.label=${this._localize("folders")}
|
.label=${this.supervisor.localize("snapshot.folders")}
|
||||||
.iconPath=${mdiFolder}
|
.iconPath=${mdiFolder}
|
||||||
>
|
>
|
||||||
</supervisor-formfield-label>`}
|
</supervisor-formfield-label>`}
|
||||||
@@ -215,7 +207,7 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
.label=${html`<supervisor-formfield-label
|
.label=${html`<supervisor-formfield-label
|
||||||
.label=${this._localize("addons")}
|
.label=${this.supervisor.localize("snapshot.addons")}
|
||||||
.iconPath=${mdiPuzzle}
|
.iconPath=${mdiPuzzle}
|
||||||
>
|
>
|
||||||
</supervisor-formfield-label>`}
|
</supervisor-formfield-label>`}
|
||||||
@@ -231,44 +223,29 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
<div class="section-content">${addonsSection.templates}</div>
|
<div class="section-content">${addonsSection.templates}</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div> `
|
`
|
||||||
: ""}
|
|
||||||
${this.snapshotType === "partial" &&
|
|
||||||
(!this.snapshot || this.snapshotHasPassword)
|
|
||||||
? html`<hr />`
|
|
||||||
: ""}
|
: ""}
|
||||||
${!this.snapshot
|
${!this.snapshot
|
||||||
? html`<ha-formfield
|
? html`<ha-formfield
|
||||||
class="password"
|
.label=${this.supervisor.localize("snapshot.password_protection")}
|
||||||
.label=${this._localize("password_protection")}
|
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this.snapshotHasPassword}
|
.checked=${this.snapshotHasPassword}
|
||||||
@change=${this._toggleHasPassword}
|
@change=${this._toggleHasPassword}
|
||||||
>
|
>
|
||||||
</ha-checkbox>
|
</ha-checkbox
|
||||||
</ha-formfield>`
|
></ha-formfield>`
|
||||||
: ""}
|
: ""}
|
||||||
${this.snapshotHasPassword
|
${this.snapshotHasPassword
|
||||||
? html`
|
? html`
|
||||||
<paper-input
|
<paper-input
|
||||||
.label=${this._localize("password")}
|
.label=${this.supervisor.localize("snapshot.password")}
|
||||||
type="password"
|
type="password"
|
||||||
name="snapshotPassword"
|
name="snapshotPassword"
|
||||||
.value=${this.snapshotPassword}
|
.value=${this.snapshotPassword}
|
||||||
@value-changed=${this._handleTextValueChanged}
|
@value-changed=${this._handleTextValueChanged}
|
||||||
>
|
>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
${!this.snapshot
|
|
||||||
? html` <paper-input
|
|
||||||
.label=${this.supervisor?.localize("confirm_password")}
|
|
||||||
type="password"
|
|
||||||
name="confirmSnapshotPassword"
|
|
||||||
.value=${this.confirmSnapshotPassword}
|
|
||||||
@value-changed=${this._handleTextValueChanged}
|
|
||||||
>
|
|
||||||
</paper-input>`
|
|
||||||
: ""}
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
`;
|
`;
|
||||||
@@ -276,24 +253,21 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.partial-picker ha-formfield {
|
ha-checkbox {
|
||||||
|
--mdc-checkbox-touch-target-size: 16px;
|
||||||
display: block;
|
display: block;
|
||||||
|
margin: 4px 12px 8px 0;
|
||||||
}
|
}
|
||||||
.partial-picker ha-checkbox {
|
ha-formfield {
|
||||||
--mdc-checkbox-touch-target-size: 32px;
|
display: contents;
|
||||||
}
|
|
||||||
.partial-picker {
|
|
||||||
display: block;
|
|
||||||
margin: 0px -6px;
|
|
||||||
}
|
}
|
||||||
supervisor-formfield-label {
|
supervisor-formfield-label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
hr {
|
paper-input[type="password"] {
|
||||||
border-color: var(--divider-color);
|
display: block;
|
||||||
border-bottom: none;
|
margin: 4px 0 4px 16px;
|
||||||
margin: 16px 0;
|
|
||||||
}
|
}
|
||||||
.details {
|
.details {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
@@ -301,15 +275,13 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
.section-content {
|
.section-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-left: 30px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
ha-formfield.password {
|
.security {
|
||||||
display: block;
|
margin-top: 16px;
|
||||||
margin: 0 -14px -16px;
|
|
||||||
}
|
}
|
||||||
.snapshot-types {
|
.snapshot-types {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: -13px;
|
|
||||||
}
|
}
|
||||||
.sub-header {
|
.sub-header {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
@@ -328,9 +300,6 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
|
|
||||||
if (this.snapshotHasPassword) {
|
if (this.snapshotHasPassword) {
|
||||||
data.password = this.snapshotPassword;
|
data.password = this.snapshotPassword;
|
||||||
if (!this.snapshot) {
|
|
||||||
data.confirm_password = this.confirmSnapshotPassword;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.snapshotType === "full") {
|
if (this.snapshotType === "full") {
|
||||||
@@ -362,7 +331,7 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
const addons =
|
const addons =
|
||||||
section === "addons"
|
section === "addons"
|
||||||
? new Map(
|
? new Map(
|
||||||
this.supervisor?.addon.addons.map((item) => [item.slug, item])
|
this.supervisor!.addon.addons.map((item) => [item.slug, item])
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
let checkedItems = 0;
|
let checkedItems = 0;
|
||||||
@@ -372,7 +341,6 @@ export class SupervisorSnapshotContent extends LitElement {
|
|||||||
.label=${item.name}
|
.label=${item.name}
|
||||||
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
|
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
|
||||||
.imageUrl=${section === "addons" &&
|
.imageUrl=${section === "addons" &&
|
||||||
!this.onboarding &&
|
|
||||||
atLeastVersion(this.hass.config.version, 0, 105) &&
|
atLeastVersion(this.hass.config.version, 0, 105) &&
|
||||||
addons?.get(item.slug)?.icon
|
addons?.get(item.slug)?.icon
|
||||||
? `/api/hassio/addons/${item.slug}/icon`
|
? `/api/hassio/addons/${item.slug}/icon`
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
|
import { mdiArrowUpBoldCircle, mdiPlay, mdiPuzzle, mdiStop } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
@@ -17,7 +17,36 @@ class HassioAddons extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
return html`<ha-card
|
||||||
|
.header=${this.supervisor.localize("dashboard.addons")}
|
||||||
|
>
|
||||||
|
<div class="addons" ?narrow=${this.narrow}>
|
||||||
|
${this.supervisor.supervisor.addons.map(
|
||||||
|
(addon) => html`<div
|
||||||
|
class="addon"
|
||||||
|
@click=${this._addonTapped}
|
||||||
|
.addon=${addon}
|
||||||
|
>
|
||||||
|
<div class="icon">
|
||||||
|
<div class="overlay">
|
||||||
|
<ha-svg-icon
|
||||||
|
.title=${addon.state}
|
||||||
|
.path=${addon.state === "started" ? mdiPlay : mdiStop}
|
||||||
|
>
|
||||||
|
</ha-svg-icon>
|
||||||
|
</div>
|
||||||
|
${addon.icon && atLeastVersion(this.hass.config.version, 0, 105)
|
||||||
|
? html`<img src="/api/hassio/addons/${addon.slug}/icon" />`
|
||||||
|
: html`<ha-svg-icon .path=${mdiPuzzle}></ha-svg-icon>`}
|
||||||
|
</div>
|
||||||
|
<div class="name">${addon.name}</div>
|
||||||
|
</div>`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-card>`;
|
||||||
return html`
|
return html`
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h1>${this.supervisor.localize("dashboard.addons")}</h1>
|
<h1>${this.supervisor.localize("dashboard.addons")}</h1>
|
||||||
@@ -88,9 +117,42 @@ class HassioAddons extends LitElement {
|
|||||||
haStyle,
|
haStyle,
|
||||||
hassioStyle,
|
hassioStyle,
|
||||||
css`
|
css`
|
||||||
ha-card {
|
.addons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, auto);
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
.addons[narrow] {
|
||||||
|
grid-template-columns: repeat(2, auto);
|
||||||
|
}
|
||||||
|
.addon {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 100px;
|
||||||
|
padding: 0 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
.icon > *:not(.overlay) {
|
||||||
|
position: relative;
|
||||||
|
max-height: 60px;
|
||||||
|
max-width: 60px;
|
||||||
|
margin: auto;
|
||||||
|
--mdc-icon-size: 60px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
--mdc-icon-size: 24px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
background-color: var(--secondary-background-color);
|
||||||
|
opacity: 0.6;
|
||||||
|
border-radius: 100%;
|
||||||
|
margin-left: 12px;
|
||||||
|
border: 1px var(--secondary-text-color) solid;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import "../../../src/layouts/hass-tabs-subpage";
|
|||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../src/types";
|
import { HomeAssistant, Route } from "../../../src/types";
|
||||||
import { supervisorTabs } from "../hassio-tabs";
|
import { supervisorTabs } from "../hassio-tabs";
|
||||||
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
import "./hassio-addons";
|
import "./hassio-addons";
|
||||||
import "./hassio-update";
|
import "./hassio-update";
|
||||||
|
|
||||||
@@ -32,14 +33,17 @@ class HassioDashboard extends LitElement {
|
|||||||
<span slot="header">
|
<span slot="header">
|
||||||
${this.supervisor.localize("panel.dashboard")}
|
${this.supervisor.localize("panel.dashboard")}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<hassio-update
|
<hassio-update
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.supervisor=${this.supervisor}
|
.supervisor=${this.supervisor}
|
||||||
|
.narrow=${this.narrow}
|
||||||
></hassio-update>
|
></hassio-update>
|
||||||
<hassio-addons
|
<hassio-addons
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.supervisor=${this.supervisor}
|
.supervisor=${this.supervisor}
|
||||||
|
.narrow=${this.narrow}
|
||||||
></hassio-addons>
|
></hassio-addons>
|
||||||
</div>
|
</div>
|
||||||
</hass-tabs-subpage>
|
</hass-tabs-subpage>
|
||||||
@@ -49,9 +53,18 @@ class HassioDashboard extends LitElement {
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
hassioStyle,
|
||||||
css`
|
css`
|
||||||
.content {
|
.content {
|
||||||
margin: 0 auto;
|
display: grid;
|
||||||
|
max-width: 1400px;
|
||||||
|
justify-content: center;
|
||||||
|
grid-template-columns: repeat(2, auto);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.content > * {
|
||||||
|
display: block;
|
||||||
|
min-width: 400px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { mdiHomeAssistant } from "@mdi/js";
|
import { mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -42,11 +42,15 @@ export class HassioUpdate extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
|
|
||||||
private _pendingUpdates = memoizeOne(
|
private _pendingUpdates = memoizeOne(
|
||||||
(supervisor: Supervisor): number =>
|
(supervisor: Supervisor): number =>
|
||||||
Object.keys(supervisor).filter(
|
Object.keys(supervisor).filter(
|
||||||
(value) => supervisor[value].update_available
|
(value) => supervisor[value].update_available
|
||||||
).length
|
).length +
|
||||||
|
supervisor.supervisor.addons.filter((addon) => addon.update_available)
|
||||||
|
.length
|
||||||
);
|
);
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@@ -60,15 +64,38 @@ export class HassioUpdate extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="content">
|
<ha-card
|
||||||
<h1>
|
.header="${this.supervisor.localize(
|
||||||
${this.supervisor.localize(
|
"common.update_available",
|
||||||
"common.update_available",
|
"count",
|
||||||
"count",
|
updatesAvailable + 1
|
||||||
updatesAvailable
|
)}
|
||||||
|
🎉"
|
||||||
|
>
|
||||||
|
${this._renderUpdateRow({
|
||||||
|
type: "os",
|
||||||
|
heading: "Home Assistant Operating system",
|
||||||
|
icon: mdiHomeAssistant,
|
||||||
|
version: "5",
|
||||||
|
version_latest: "6",
|
||||||
|
})}
|
||||||
|
${this.supervisor.addon.addons
|
||||||
|
.filter((addon) => addon.update_available)
|
||||||
|
.map((addon) =>
|
||||||
|
this._renderUpdateRow({
|
||||||
|
type: "addon",
|
||||||
|
heading: addon.name,
|
||||||
|
version: addon.version_latest,
|
||||||
|
version_latest: addon.version,
|
||||||
|
image: addon.icon
|
||||||
|
? `/api/hassio/addons/${addon.slug}/icon`
|
||||||
|
: undefined,
|
||||||
|
icon: mdiPuzzle,
|
||||||
|
})
|
||||||
)}
|
)}
|
||||||
🎉
|
</ha-card>
|
||||||
</h1>
|
<div class="content">
|
||||||
|
<h1></h1>
|
||||||
<div class="card-group">
|
<div class="card-group">
|
||||||
${this._renderUpdateCard(
|
${this._renderUpdateCard(
|
||||||
"Home Assistant Core",
|
"Home Assistant Core",
|
||||||
@@ -86,7 +113,7 @@ export class HassioUpdate extends LitElement {
|
|||||||
"hassio/supervisor/update",
|
"hassio/supervisor/update",
|
||||||
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
|
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
|
||||||
)}
|
)}
|
||||||
${this.supervisor.host.features.includes("haos")
|
${this.supervisor.host.features.includes("hassos")
|
||||||
? this._renderUpdateCard(
|
? this._renderUpdateCard(
|
||||||
"Operating System",
|
"Operating System",
|
||||||
"os",
|
"os",
|
||||||
@@ -100,6 +127,37 @@ export class HassioUpdate extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderUpdateRow(options: {
|
||||||
|
type: "supervisor" | "os" | "core" | "addon";
|
||||||
|
heading: string;
|
||||||
|
version: string;
|
||||||
|
version_latest: string;
|
||||||
|
icon?: string;
|
||||||
|
image?: string;
|
||||||
|
release_notes?: string;
|
||||||
|
slug?: string;
|
||||||
|
}): TemplateResult {
|
||||||
|
return html`<div class="update-row">
|
||||||
|
<paper-icon-item>
|
||||||
|
<div class="icon" slot="item-icon">
|
||||||
|
${options.image && atLeastVersion(this.hass.config.version, 0, 104)
|
||||||
|
? html`<img src="${options.image}" />`
|
||||||
|
: options.icon
|
||||||
|
? html`<ha-svg-icon .path=${options.icon}></ha-svg-icon>`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
<paper-item-body two-line>
|
||||||
|
${options.heading}
|
||||||
|
<div secondary>Version ${options.version_latest} is available</div>
|
||||||
|
</paper-item-body>
|
||||||
|
</paper-icon-item>
|
||||||
|
<div class="update-row-actions" ?narrow=${false}>
|
||||||
|
<mwc-button>Releaese notes</mwc-button>
|
||||||
|
<mwc-button>Update</mwc-button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
private _renderUpdateCard(
|
private _renderUpdateCard(
|
||||||
name: string,
|
name: string,
|
||||||
key: string,
|
key: string,
|
||||||
@@ -231,31 +289,21 @@ export class HassioUpdate extends LitElement {
|
|||||||
haStyle,
|
haStyle,
|
||||||
hassioStyle,
|
hassioStyle,
|
||||||
css`
|
css`
|
||||||
.icon {
|
.update-row,
|
||||||
--mdc-icon-size: 48px;
|
paper-icon-item {
|
||||||
float: right;
|
display: flex;
|
||||||
margin: 0 0 2px 10px;
|
align-items: center;
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
}
|
||||||
.update-heading {
|
|
||||||
font-size: var(--paper-font-subhead_-_font-size);
|
.update-row {
|
||||||
font-weight: 500;
|
padding: 8px;
|
||||||
margin-bottom: 0.5em;
|
justify-content: space-between;
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
}
|
||||||
.card-content {
|
.icon > * {
|
||||||
height: calc(100% - 47px);
|
max-height: 32px;
|
||||||
box-sizing: border-box;
|
max-width: 32px;
|
||||||
}
|
margin-right: 16px;
|
||||||
.card-actions {
|
--mdc-icon-size: 32px;
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
ha-settings-row {
|
|
||||||
padding: 0;
|
|
||||||
--paper-item-body-two-line-min-height: 32px;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -1,194 +0,0 @@
|
|||||||
import { mdiClose } from "@mdi/js";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
|
||||||
import "../../../../src/common/search/search-input";
|
|
||||||
import { compare } from "../../../../src/common/string/compare";
|
|
||||||
import "../../../../src/components/ha-dialog";
|
|
||||||
import "../../../../src/components/ha-expansion-panel";
|
|
||||||
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
|
|
||||||
import { dump } from "../../../../src/resources/js-yaml-dump";
|
|
||||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
|
||||||
import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
|
|
||||||
|
|
||||||
const _filterDevices = memoizeOne(
|
|
||||||
(showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) =>
|
|
||||||
hardware.devices
|
|
||||||
.filter(
|
|
||||||
(device) =>
|
|
||||||
(showAdvanced ||
|
|
||||||
["tty", "gpio", "input"].includes(device.subsystem)) &&
|
|
||||||
(device.by_id?.toLowerCase().includes(filter) ||
|
|
||||||
device.name.toLowerCase().includes(filter) ||
|
|
||||||
device.dev_path.toLocaleLowerCase().includes(filter) ||
|
|
||||||
JSON.stringify(device.attributes)
|
|
||||||
.toLocaleLowerCase()
|
|
||||||
.includes(filter))
|
|
||||||
)
|
|
||||||
.sort((a, b) => compare(a.name, b.name))
|
|
||||||
);
|
|
||||||
|
|
||||||
@customElement("dialog-hassio-hardware")
|
|
||||||
class HassioHardwareDialog extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() private _dialogParams?: HassioHardwareDialogParams;
|
|
||||||
|
|
||||||
@state() private _filter?: string;
|
|
||||||
|
|
||||||
public showDialog(params: HassioHardwareDialogParams) {
|
|
||||||
this._dialogParams = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeDialog() {
|
|
||||||
this._dialogParams = undefined;
|
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
if (!this._dialogParams) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
|
|
||||||
const devices = _filterDevices(
|
|
||||||
this.hass.userData?.showAdvanced || false,
|
|
||||||
this._dialogParams.hardware,
|
|
||||||
(this._filter || "").toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-dialog
|
|
||||||
open
|
|
||||||
scrimClickAction
|
|
||||||
hideActions
|
|
||||||
@closed=${this.closeDialog}
|
|
||||||
.heading=${true}
|
|
||||||
>
|
|
||||||
<div class="header" slot="heading">
|
|
||||||
<h2>
|
|
||||||
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
|
|
||||||
</h2>
|
|
||||||
<mwc-icon-button dialogAction="close">
|
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
<search-input
|
|
||||||
autofocus
|
|
||||||
no-label-float
|
|
||||||
.filter=${this._filter}
|
|
||||||
@value-changed=${this._handleSearchChange}
|
|
||||||
.label=${this._dialogParams.supervisor.localize(
|
|
||||||
"dialog.hardware.search"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</search-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${devices.map(
|
|
||||||
(device) =>
|
|
||||||
html`<ha-expansion-panel
|
|
||||||
.header=${device.name}
|
|
||||||
.secondary=${device.by_id || undefined}
|
|
||||||
outlined
|
|
||||||
>
|
|
||||||
<div class="device-property">
|
|
||||||
<span>
|
|
||||||
${this._dialogParams!.supervisor.localize(
|
|
||||||
"dialog.hardware.subsystem"
|
|
||||||
)}:
|
|
||||||
</span>
|
|
||||||
<span>${device.subsystem}</span>
|
|
||||||
</div>
|
|
||||||
<div class="device-property">
|
|
||||||
<span>
|
|
||||||
${this._dialogParams!.supervisor.localize(
|
|
||||||
"dialog.hardware.device_path"
|
|
||||||
)}:
|
|
||||||
</span>
|
|
||||||
<code>${device.dev_path}</code>
|
|
||||||
</div>
|
|
||||||
${device.by_id
|
|
||||||
? html` <div class="device-property">
|
|
||||||
<span>
|
|
||||||
${this._dialogParams!.supervisor.localize(
|
|
||||||
"dialog.hardware.id"
|
|
||||||
)}:
|
|
||||||
</span>
|
|
||||||
<code>${device.by_id}</code>
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
<div class="attributes">
|
|
||||||
<span>
|
|
||||||
${this._dialogParams!.supervisor.localize(
|
|
||||||
"dialog.hardware.attributes"
|
|
||||||
)}:
|
|
||||||
</span>
|
|
||||||
<pre>${dump(device.attributes, { indent: 2 })}</pre>
|
|
||||||
</div>
|
|
||||||
</ha-expansion-panel>`
|
|
||||||
)}
|
|
||||||
</ha-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleSearchChange(ev: CustomEvent) {
|
|
||||||
this._filter = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyle,
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
mwc-icon-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 16px;
|
|
||||||
top: 10px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
h2 {
|
|
||||||
margin: 18px 42px 0 18px;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-expansion-panel {
|
|
||||||
margin: 4px 0;
|
|
||||||
}
|
|
||||||
pre,
|
|
||||||
code {
|
|
||||||
background-color: var(--markdown-code-background-color, none);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
padding: 16px;
|
|
||||||
overflow: auto;
|
|
||||||
line-height: 1.45;
|
|
||||||
font-family: var(--code-font-family, monospace);
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
font-size: 85%;
|
|
||||||
padding: 0.2em 0.4em;
|
|
||||||
}
|
|
||||||
search-input {
|
|
||||||
margin: 0 16px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.device-property {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.attributes {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"dialog-hassio-hardware": HassioHardwareDialog;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,19 +0,0 @@
|
|||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
|
||||||
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
|
|
||||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
|
||||||
|
|
||||||
export interface HassioHardwareDialogParams {
|
|
||||||
supervisor: Supervisor;
|
|
||||||
hardware: HassioHardwareInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const showHassioHardwareDialog = (
|
|
||||||
element: HTMLElement,
|
|
||||||
dialogParams: HassioHardwareDialogParams
|
|
||||||
): void => {
|
|
||||||
fireEvent(element, "show-dialog", {
|
|
||||||
dialogTag: "dialog-hassio-hardware",
|
|
||||||
dialogImport: () => import("./dialog-hassio-hardware"),
|
|
||||||
dialogParams,
|
|
||||||
});
|
|
||||||
};
|
|
@@ -61,6 +61,10 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
app-toolbar [main-title] {
|
app-toolbar [main-title] {
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
paper-checkbox {
|
||||||
|
display: block;
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
ha-paper-dialog {
|
ha-paper-dialog {
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
@@ -41,8 +41,7 @@ const IP_VERSIONS = ["ipv4", "ipv6"];
|
|||||||
@customElement("dialog-hassio-network")
|
@customElement("dialog-hassio-network")
|
||||||
export class DialogHassioNetwork
|
export class DialogHassioNetwork
|
||||||
extends LitElement
|
extends LitElement
|
||||||
implements HassDialog<HassioNetworkDialogParams>
|
implements HassDialog<HassioNetworkDialogParams> {
|
||||||
{
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||||
@@ -108,7 +107,7 @@ export class DialogHassioNetwork
|
|||||||
</mwc-icon-button>
|
</mwc-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
${this._interfaces.length > 1
|
${this._interfaces.length > 1
|
||||||
? html`<mwc-tab-bar
|
? html` <mwc-tab-bar
|
||||||
.activeIndex=${this._curTabIndex}
|
.activeIndex=${this._curTabIndex}
|
||||||
@MDCTabBar:activated=${this._handleTabActivated}
|
@MDCTabBar:activated=${this._handleTabActivated}
|
||||||
>${this._interfaces.map(
|
>${this._interfaces.map(
|
||||||
@@ -493,7 +492,7 @@ export class DialogHassioNetwork
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleRadioValueChangedAp(ev: CustomEvent): void {
|
private _handleRadioValueChangedAp(ev: CustomEvent): void {
|
||||||
const value = (ev.target as any).value as string as
|
const value = ((ev.target as any).value as string) as
|
||||||
| "open"
|
| "open"
|
||||||
| "wep"
|
| "wep"
|
||||||
| "wpa-psk";
|
| "wpa-psk";
|
||||||
|
@@ -161,9 +161,9 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
|
|
||||||
public focus(): void {
|
public focus(): void {
|
||||||
this.updateComplete.then(() =>
|
this.updateComplete.then(() =>
|
||||||
(
|
(this.shadowRoot?.querySelector(
|
||||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
"[dialogInitialFocus]"
|
||||||
)?.focus()
|
) as HTMLElement)?.focus()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -161,9 +161,9 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
this.updateComplete.then(() =>
|
this.updateComplete.then(() =>
|
||||||
(
|
(this.shadowRoot?.querySelector(
|
||||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
"[dialogInitialFocus]"
|
||||||
)?.focus()
|
) as HTMLElement)?.focus()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -95,25 +95,16 @@ class HassioCreateSnapshotDialog extends LitElement {
|
|||||||
this._creatingSnapshot = true;
|
this._creatingSnapshot = true;
|
||||||
|
|
||||||
this._error = "";
|
this._error = "";
|
||||||
if (snapshotDetails.password && !snapshotDetails.password.length) {
|
if (
|
||||||
|
this._snapshotContent.snapshotHasPassword &&
|
||||||
|
!this._snapshotContent.snapshotPassword.length
|
||||||
|
) {
|
||||||
this._error = this._dialogParams!.supervisor.localize(
|
this._error = this._dialogParams!.supervisor.localize(
|
||||||
"snapshot.enter_password"
|
"snapshot.enter_password"
|
||||||
);
|
);
|
||||||
this._creatingSnapshot = false;
|
this._creatingSnapshot = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
snapshotDetails.password &&
|
|
||||||
snapshotDetails.password !== snapshotDetails.confirm_password
|
|
||||||
) {
|
|
||||||
this._error = this._dialogParams!.supervisor.localize(
|
|
||||||
"snapshot.passwords_not_matching"
|
|
||||||
);
|
|
||||||
this._creatingSnapshot = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete snapshotDetails.confirm_password;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this._snapshotContent.snapshotType === "full") {
|
if (this._snapshotContent.snapshotType === "full") {
|
||||||
|
@@ -12,8 +12,7 @@ import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload"
|
|||||||
@customElement("dialog-hassio-snapshot-upload")
|
@customElement("dialog-hassio-snapshot-upload")
|
||||||
export class DialogHassioSnapshotUpload
|
export class DialogHassioSnapshotUpload
|
||||||
extends LitElement
|
extends LitElement
|
||||||
implements HassDialog<HassioSnapshotUploadDialogParams>
|
implements HassDialog<HassioSnapshotUploadDialogParams> {
|
||||||
{
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _params?: HassioSnapshotUploadDialogParams;
|
@state() private _params?: HassioSnapshotUploadDialogParams;
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { ActionDetail } from "@material/mwc-list";
|
import { ActionDetail } from "@material/mwc-list";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiClose, mdiDotsVertical } from "@mdi/js";
|
import { mdiDotsVertical } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import { slugify } from "../../../../src/common/string/slugify";
|
import { slugify } from "../../../../src/common/string/slugify";
|
||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../../src/components/ha-button-menu";
|
import "../../../../src/components/ha-button-menu";
|
||||||
import "../../../../src/components/ha-header-bar";
|
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-svg-icon";
|
||||||
import { getSignedPath } from "../../../../src/data/auth";
|
import { getSignedPath } from "../../../../src/data/auth";
|
||||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||||
@@ -22,7 +22,6 @@ import {
|
|||||||
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
import { fileDownload } from "../../../../src/util/file_download";
|
|
||||||
import "../../components/supervisor-snapshot-content";
|
import "../../components/supervisor-snapshot-content";
|
||||||
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
|
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
|
||||||
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
||||||
@@ -30,8 +29,7 @@ import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
|||||||
@customElement("dialog-hassio-snapshot")
|
@customElement("dialog-hassio-snapshot")
|
||||||
class HassioSnapshotDialog
|
class HassioSnapshotDialog
|
||||||
extends LitElement
|
extends LitElement
|
||||||
implements HassDialog<HassioSnapshotDialogParams>
|
implements HassDialog<HassioSnapshotDialogParams> {
|
||||||
{
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
@@ -68,24 +66,14 @@ class HassioSnapshotDialog
|
|||||||
open
|
open
|
||||||
scrimClickAction
|
scrimClickAction
|
||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
.heading=${true}
|
.heading=${createCloseHeading(this.hass, this._computeName)}
|
||||||
>
|
>
|
||||||
<div slot="heading">
|
|
||||||
<ha-header-bar>
|
|
||||||
<span slot="title">${this._snapshot.name}</span>
|
|
||||||
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
</ha-header-bar>
|
|
||||||
</div>
|
|
||||||
${this._restoringSnapshot
|
${this._restoringSnapshot
|
||||||
? html` <ha-circular-progress active></ha-circular-progress>`
|
? html` <ha-circular-progress active></ha-circular-progress>`
|
||||||
: html`<supervisor-snapshot-content
|
: html`<supervisor-snapshot-content
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.supervisor=${this._dialogParams.supervisor}
|
.supervisor=${this._dialogParams.supervisor}
|
||||||
.snapshot=${this._snapshot}
|
.snapshot=${this._snapshot}
|
||||||
.onboarding=${this._dialogParams.onboarding || false}
|
|
||||||
.localize=${this._dialogParams.localize}
|
|
||||||
>
|
>
|
||||||
</supervisor-snapshot-content>`}
|
</supervisor-snapshot-content>`}
|
||||||
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
|
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
|
||||||
@@ -98,20 +86,18 @@ class HassioSnapshotDialog
|
|||||||
Restore
|
Restore
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
|
|
||||||
${!this._dialogParams.onboarding
|
<ha-button-menu
|
||||||
? html`<ha-button-menu
|
fixed
|
||||||
fixed
|
slot="primaryAction"
|
||||||
slot="primaryAction"
|
@action=${this._handleMenuAction}
|
||||||
@action=${this._handleMenuAction}
|
@closed=${(ev: Event) => ev.stopPropagation()}
|
||||||
@closed=${(ev: Event) => ev.stopPropagation()}
|
>
|
||||||
>
|
<mwc-icon-button slot="trigger" alt="menu">
|
||||||
<mwc-icon-button slot="trigger" alt="menu">
|
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
</mwc-icon-button>
|
||||||
</mwc-icon-button>
|
<mwc-list-item>Download Snapshot</mwc-list-item>
|
||||||
<mwc-list-item>Download Snapshot</mwc-list-item>
|
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
|
||||||
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
|
</ha-button-menu>
|
||||||
</ha-button-menu>`
|
|
||||||
: ""}
|
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -128,12 +114,6 @@ class HassioSnapshotDialog
|
|||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
ha-header-bar {
|
|
||||||
--mdc-theme-on-primary: var(--primary-text-color);
|
|
||||||
--mdc-theme-primary: var(--mdc-theme-surface);
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -298,7 +278,8 @@ class HassioSnapshotDialog
|
|||||||
if (window.location.href.includes("ui.nabu.casa")) {
|
if (window.location.href.includes("ui.nabu.casa")) {
|
||||||
const confirm = await showConfirmationDialog(this, {
|
const confirm = await showConfirmationDialog(this, {
|
||||||
title: "Potential slow download",
|
title: "Potential slow download",
|
||||||
text: "Downloading snapshots over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
|
text:
|
||||||
|
"Downloading snapshots over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
|
||||||
confirmText: "continue",
|
confirmText: "continue",
|
||||||
dismissText: "cancel",
|
dismissText: "cancel",
|
||||||
});
|
});
|
||||||
@@ -307,11 +288,12 @@ class HassioSnapshotDialog
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileDownload(
|
const a = document.createElement("a");
|
||||||
this,
|
a.href = signedPath.path;
|
||||||
signedPath.path,
|
a.download = `home_assistant_snapshot_${slugify(this._computeName)}.tar`;
|
||||||
`home_assistant_snapshot_${slugify(this._computeName)}.tar`
|
this.shadowRoot!.appendChild(a);
|
||||||
);
|
a.click();
|
||||||
|
this.shadowRoot!.removeChild(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _computeName() {
|
private get _computeName() {
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import { LocalizeFunc } from "../../../../src/common/translations/localize";
|
|
||||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||||
|
|
||||||
export interface HassioSnapshotDialogParams {
|
export interface HassioSnapshotDialogParams {
|
||||||
@@ -7,7 +6,6 @@ export interface HassioSnapshotDialogParams {
|
|||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
onboarding?: boolean;
|
onboarding?: boolean;
|
||||||
supervisor?: Supervisor;
|
supervisor?: Supervisor;
|
||||||
localize?: LocalizeFunc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showHassioSnapshotDialog = (
|
export const showHassioSnapshotDialog = (
|
||||||
|
@@ -49,9 +49,9 @@ class DialogSupervisorUpdate extends LitElement {
|
|||||||
|
|
||||||
public focus(): void {
|
public focus(): void {
|
||||||
this.updateComplete.then(() =>
|
this.updateComplete.then(() =>
|
||||||
(
|
(this.shadowRoot?.querySelector(
|
||||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
"[dialogInitialFocus]"
|
||||||
)?.focus()
|
) as HTMLElement)?.focus()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,8 +158,8 @@ class DialogSupervisorUpdate extends LitElement {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||||
this._error = extractApiErrorMessage(err);
|
this._error = extractApiErrorMessage(err);
|
||||||
this._action = null;
|
|
||||||
}
|
}
|
||||||
|
this._action = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -121,7 +121,7 @@ export class HassioMain extends SupervisorBaseElement {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
themeName =
|
themeName =
|
||||||
(this.hass.selectedTheme as unknown as string) ||
|
((this.hass.selectedTheme as unknown) as string) ||
|
||||||
this.hass.themes.default_theme;
|
this.hass.themes.default_theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
import { sanitizeUrl } from "@braintree/sanitize-url";
|
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { sanitizeUrl } from "@braintree/sanitize-url";
|
||||||
import { navigate } from "../../src/common/navigate";
|
|
||||||
import {
|
import {
|
||||||
createSearchParam,
|
createSearchParam,
|
||||||
extractSearchParamsObject,
|
extractSearchParamsObject,
|
||||||
} from "../../src/common/url/search-params";
|
} from "../../src/common/url/search-params";
|
||||||
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
|
||||||
import "../../src/layouts/hass-error-screen";
|
import "../../src/layouts/hass-error-screen";
|
||||||
import {
|
import {
|
||||||
ParamType,
|
ParamType,
|
||||||
Redirect,
|
Redirect,
|
||||||
Redirects,
|
Redirects,
|
||||||
} from "../../src/panels/my/ha-panel-my";
|
} from "../../src/panels/my/ha-panel-my";
|
||||||
|
import { navigate } from "../../src/common/navigate";
|
||||||
import { HomeAssistant, Route } from "../../src/types";
|
import { HomeAssistant, Route } from "../../src/types";
|
||||||
|
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
|
||||||
const REDIRECTS: Redirects = {
|
const REDIRECTS: Redirects = {
|
||||||
supervisor: {
|
supervisor: {
|
||||||
|
@@ -97,23 +97,16 @@ class HassioIngressView extends LitElement {
|
|||||||
title: requestedAddon,
|
title: requestedAddon,
|
||||||
});
|
});
|
||||||
await nextRender();
|
await nextRender();
|
||||||
navigate("/hassio/store", { replace: true });
|
history.back();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!addonInfo.version) {
|
if (!addonInfo.ingress) {
|
||||||
await showAlertDialog(this, {
|
|
||||||
text: this.supervisor.localize("my.error_addon_not_installed"),
|
|
||||||
title: addonInfo.name,
|
|
||||||
});
|
|
||||||
await nextRender();
|
|
||||||
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
|
|
||||||
} else if (!addonInfo.ingress) {
|
|
||||||
await showAlertDialog(this, {
|
await showAlertDialog(this, {
|
||||||
text: this.supervisor.localize("my.error_addon_no_ingress"),
|
text: this.supervisor.localize("my.error_addon_no_ingress"),
|
||||||
title: addonInfo.name,
|
title: addonInfo.name,
|
||||||
});
|
});
|
||||||
await nextRender();
|
await nextRender();
|
||||||
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
|
history.back();
|
||||||
} else {
|
} else {
|
||||||
navigate(`/hassio/ingress/${addonInfo.slug}`, { replace: true });
|
navigate(`/hassio/ingress/${addonInfo.slug}`, { replace: true });
|
||||||
}
|
}
|
||||||
|
@@ -86,8 +86,10 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
|||||||
const unsubs = Object.keys(this._unsubs);
|
const unsubs = Object.keys(this._unsubs);
|
||||||
for (const collection of Object.keys(this._collections)) {
|
for (const collection of Object.keys(this._collections)) {
|
||||||
if (!unsubs.includes(collection)) {
|
if (!unsubs.includes(collection)) {
|
||||||
this._unsubs[collection] = this._collections[collection].subscribe(
|
this._unsubs[collection] = this._collections[
|
||||||
(data) => this._updateSupervisor({ [collection]: data })
|
collection
|
||||||
|
].subscribe((data) =>
|
||||||
|
this._updateSupervisor({ [collection]: data })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ import "@material/mwc-button";
|
|||||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiDotsVertical } from "@mdi/js";
|
import { mdiDotsVertical } from "@mdi/js";
|
||||||
|
import { dump } from "js-yaml";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -40,8 +41,8 @@ import {
|
|||||||
roundWithOneDecimal,
|
roundWithOneDecimal,
|
||||||
} from "../../../src/util/calculate";
|
} from "../../../src/util/calculate";
|
||||||
import "../components/supervisor-metric";
|
import "../components/supervisor-metric";
|
||||||
|
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||||
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
|
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
|
||||||
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
|
|
||||||
import { hassioStyle } from "../resources/hassio-style";
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
|
|
||||||
@customElement("hassio-host-info")
|
@customElement("hassio-host-info")
|
||||||
@@ -113,7 +114,7 @@ class HassioHostInfo extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
${!this.supervisor.host.features.includes("haos")
|
${!this.supervisor.host.features.includes("hassos")
|
||||||
? html`<ha-settings-row>
|
? html`<ha-settings-row>
|
||||||
<span slot="heading">
|
<span slot="heading">
|
||||||
${this.supervisor.localize("system.host.docker_version")}
|
${this.supervisor.localize("system.host.docker_version")}
|
||||||
@@ -190,7 +191,7 @@ class HassioHostInfo extends LitElement {
|
|||||||
<mwc-list-item>
|
<mwc-list-item>
|
||||||
${this.supervisor.localize("system.host.hardware")}
|
${this.supervisor.localize("system.host.hardware")}
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
${this.supervisor.host.features.includes("haos")
|
${this.supervisor.host.features.includes("hassos")
|
||||||
? html`<mwc-list-item>
|
? html`<mwc-list-item>
|
||||||
${this.supervisor.localize("system.host.import_from_usb")}
|
${this.supervisor.localize("system.host.import_from_usb")}
|
||||||
</mwc-list-item>`
|
</mwc-list-item>`
|
||||||
@@ -228,19 +229,20 @@ class HassioHostInfo extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _showHardware(): Promise<void> {
|
private async _showHardware(): Promise<void> {
|
||||||
let hardware;
|
|
||||||
try {
|
try {
|
||||||
hardware = await fetchHassioHardwareInfo(this.hass);
|
const content = await fetchHassioHardwareInfo(this.hass);
|
||||||
|
showHassioMarkdownDialog(this, {
|
||||||
|
title: this.supervisor.localize("system.host.hardware"),
|
||||||
|
content: `<pre>${dump(content, { indent: 2 })}</pre>`,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: this.supervisor.localize(
|
title: this.supervisor.localize(
|
||||||
"system.host.failed_to_get_hardware_list"
|
"system.host.failed_to_get_hardware_list"
|
||||||
),
|
),
|
||||||
text: extractApiErrorMessage(err),
|
text: extractApiErrorMessage(err),
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
showHassioHardwareDialog(this, { supervisor: this.supervisor, hardware });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _hostReboot(ev: CustomEvent): Promise<void> {
|
private async _hostReboot(ev: CustomEvent): Promise<void> {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
"*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
|
"*.ts": () => "tsc -p tsconfig.json",
|
||||||
|
"*.{js,ts}": "eslint --fix",
|
||||||
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
|
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
|
||||||
};
|
};
|
||||||
|
128
package.json
128
package.json
@@ -42,72 +42,74 @@
|
|||||||
"@fullcalendar/daygrid": "5.1.0",
|
"@fullcalendar/daygrid": "5.1.0",
|
||||||
"@fullcalendar/interaction": "5.1.0",
|
"@fullcalendar/interaction": "5.1.0",
|
||||||
"@fullcalendar/list": "5.1.0",
|
"@fullcalendar/list": "5.1.0",
|
||||||
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
|
"@lit-labs/virtualizer": "^0.6.0",
|
||||||
"@material/chips": "12.0.0-canary.22d29cbb4.0",
|
"@material/chips": "=12.0.0-canary.1a8d06483.0",
|
||||||
"@material/data-table": "12.0.0-canary.22d29cbb4.0",
|
"@material/mwc-button": "canary",
|
||||||
"@material/mwc-button": "0.22.1",
|
"@material/mwc-checkbox": "canary",
|
||||||
"@material/mwc-checkbox": "0.22.1",
|
"@material/mwc-circular-progress": "canary",
|
||||||
"@material/mwc-circular-progress": "0.22.1",
|
"@material/mwc-dialog": "canary",
|
||||||
"@material/mwc-dialog": "0.22.1",
|
"@material/mwc-fab": "canary",
|
||||||
"@material/mwc-fab": "0.22.1",
|
"@material/mwc-formfield": "canary",
|
||||||
"@material/mwc-formfield": "0.22.1",
|
"@material/mwc-icon-button": "canary",
|
||||||
"@material/mwc-icon-button": "0.22.1",
|
"@material/mwc-list": "canary",
|
||||||
"@material/mwc-linear-progress": "0.22.1",
|
"@material/mwc-menu": "canary",
|
||||||
"@material/mwc-list": "0.22.1",
|
"@material/mwc-radio": "canary",
|
||||||
"@material/mwc-menu": "0.22.1",
|
"@material/mwc-ripple": "canary",
|
||||||
"@material/mwc-radio": "0.22.1",
|
"@material/mwc-switch": "canary",
|
||||||
"@material/mwc-ripple": "0.22.1",
|
"@material/mwc-tab": "canary",
|
||||||
"@material/mwc-switch": "0.22.1",
|
"@material/mwc-tab-bar": "canary",
|
||||||
"@material/mwc-tab": "0.22.1",
|
"@material/top-app-bar": "=12.0.0-canary.1a8d06483.0",
|
||||||
"@material/mwc-tab-bar": "0.22.1",
|
|
||||||
"@material/top-app-bar": "12.0.0-canary.22d29cbb4.0",
|
|
||||||
"@mdi/js": "5.9.55",
|
"@mdi/js": "5.9.55",
|
||||||
"@mdi/svg": "5.9.55",
|
"@mdi/svg": "5.9.55",
|
||||||
"@polymer/app-layout": "^3.1.0",
|
"@polymer/app-layout": "^3.0.2",
|
||||||
|
"@polymer/app-storage": "^3.0.2",
|
||||||
|
"@polymer/iron-autogrow-textarea": "^3.0.1",
|
||||||
"@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",
|
||||||
|
"@polymer/iron-image": "^3.0.1",
|
||||||
"@polymer/iron-input": "^3.0.1",
|
"@polymer/iron-input": "^3.0.1",
|
||||||
"@polymer/iron-overlay-behavior": "^3.0.3",
|
"@polymer/iron-label": "^3.0.1",
|
||||||
|
"@polymer/iron-overlay-behavior": "^3.0.2",
|
||||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||||
"@polymer/paper-checkbox": "^3.1.0",
|
"@polymer/paper-checkbox": "^3.1.0",
|
||||||
"@polymer/paper-dialog": "^3.0.1",
|
"@polymer/paper-dialog": "^3.0.1",
|
||||||
"@polymer/paper-dialog-behavior": "^3.0.1",
|
"@polymer/paper-dialog-behavior": "^3.0.1",
|
||||||
"@polymer/paper-dialog-scrollable": "^3.0.1",
|
"@polymer/paper-dialog-scrollable": "^3.0.1",
|
||||||
"@polymer/paper-dropdown-menu": "^3.2.0",
|
"@polymer/paper-dropdown-menu": "^3.0.1",
|
||||||
"@polymer/paper-input": "^3.2.1",
|
"@polymer/paper-input": "^3.0.1",
|
||||||
"@polymer/paper-item": "^3.0.1",
|
"@polymer/paper-item": "^3.0.1",
|
||||||
"@polymer/paper-listbox": "^3.0.1",
|
"@polymer/paper-listbox": "^3.0.1",
|
||||||
"@polymer/paper-menu-button": "^3.1.0",
|
"@polymer/paper-menu-button": "^3.0.1",
|
||||||
"@polymer/paper-progress": "^3.0.1",
|
"@polymer/paper-progress": "^3.0.1",
|
||||||
"@polymer/paper-radio-button": "^3.0.1",
|
"@polymer/paper-radio-button": "^3.0.1",
|
||||||
"@polymer/paper-radio-group": "^3.0.1",
|
"@polymer/paper-radio-group": "^3.0.1",
|
||||||
"@polymer/paper-ripple": "^3.0.2",
|
"@polymer/paper-ripple": "^3.0.1",
|
||||||
"@polymer/paper-slider": "^3.0.1",
|
"@polymer/paper-slider": "^3.0.1",
|
||||||
"@polymer/paper-styles": "^3.0.1",
|
"@polymer/paper-styles": "^3.0.1",
|
||||||
"@polymer/paper-tabs": "^3.1.0",
|
"@polymer/paper-tabs": "^3.0.1",
|
||||||
"@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.1.0",
|
||||||
"@thomasloven/round-slider": "0.5.2",
|
"@thomasloven/round-slider": "0.5.2",
|
||||||
"@vaadin/vaadin-combo-box": "^20.0.1",
|
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||||
"@vaadin/vaadin-date-picker": "^20.0.1",
|
"@vaadin/vaadin-date-picker": "^4.0.7",
|
||||||
"@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.2.0",
|
"@vue/web-component-wrapper": "^1.2.0",
|
||||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||||
"chart.js": "^3.3.2",
|
"chart.js": "^2.9.4",
|
||||||
|
"chartjs-chart-timeline": "^0.4.0",
|
||||||
"comlink": "^4.3.1",
|
"comlink": "^4.3.1",
|
||||||
"core-js": "^3.15.2",
|
"core-js": "^3.6.5",
|
||||||
"cropperjs": "^1.5.11",
|
"cropperjs": "^1.5.11",
|
||||||
"date-fns": "^2.22.1",
|
|
||||||
"deep-clone-simple": "^1.1.1",
|
"deep-clone-simple": "^1.1.1",
|
||||||
"deep-freeze": "^0.0.1",
|
"deep-freeze": "^0.0.1",
|
||||||
"fecha": "^4.2.0",
|
"fecha": "^4.2.0",
|
||||||
"fuse.js": "^6.0.0",
|
"fuse.js": "^6.0.0",
|
||||||
"google-timezones-json": "^1.0.2",
|
"google-timezones-json": "^1.0.2",
|
||||||
"hls.js": "^1.0.7",
|
"hls.js": "^1.0.4",
|
||||||
"home-assistant-js-websocket": "^5.11.1",
|
"home-assistant-js-websocket": "^5.10.0",
|
||||||
"idb-keyval": "^5.0.5",
|
"idb-keyval": "^5.0.5",
|
||||||
"intl-messageformat": "^9.6.16",
|
"intl-messageformat": "^9.6.16",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
@@ -122,7 +124,7 @@
|
|||||||
"proxy-polyfill": "^0.3.1",
|
"proxy-polyfill": "^0.3.1",
|
||||||
"punycode": "^2.1.1",
|
"punycode": "^2.1.1",
|
||||||
"qrcode": "^1.4.4",
|
"qrcode": "^1.4.4",
|
||||||
"regenerator-runtime": "^0.13.8",
|
"regenerator-runtime": "^0.13.2",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"roboto-fontface": "^0.10.0",
|
"roboto-fontface": "^0.10.0",
|
||||||
"sortablejs": "^1.10.2",
|
"sortablejs": "^1.10.2",
|
||||||
@@ -144,17 +146,17 @@
|
|||||||
"xss": "^1.0.9"
|
"xss": "^1.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.14.6",
|
"@babel/core": "^7.14.3",
|
||||||
"@babel/plugin-external-helpers": "^7.14.5",
|
"@babel/plugin-external-helpers": "^7.12.13",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||||
"@babel/plugin-proposal-decorators": "^7.14.5",
|
"@babel/plugin-proposal-decorators": "^7.13.15",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8",
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.14.7",
|
"@babel/plugin-proposal-object-rest-spread": "^7.13.8",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
|
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||||
"@babel/preset-env": "^7.14.7",
|
"@babel/preset-env": "^7.14.2",
|
||||||
"@babel/preset-typescript": "^7.14.5",
|
"@babel/preset-typescript": "^7.13.0",
|
||||||
"@koa/cors": "^3.1.0",
|
"@koa/cors": "^3.1.0",
|
||||||
"@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",
|
||||||
@@ -171,22 +173,22 @@
|
|||||||
"@types/mocha": "^8.2.2",
|
"@types/mocha": "^8.2.2",
|
||||||
"@types/sortablejs": "^1.10.6",
|
"@types/sortablejs": "^1.10.6",
|
||||||
"@types/webspeechapi": "^0.0.29",
|
"@types/webspeechapi": "^0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.28.3",
|
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||||
"@typescript-eslint/parser": "^4.28.3",
|
"@typescript-eslint/parser": "^4.22.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": "^8.2.2",
|
"babel-loader": "^8.1.0",
|
||||||
"chai": "^4.3.4",
|
"chai": "^4.3.4",
|
||||||
|
"cpx": "^1.5.0",
|
||||||
"del": "^4.0.0",
|
"del": "^4.0.0",
|
||||||
"eslint": "^7.30.0",
|
"eslint": "^7.25.0",
|
||||||
"eslint-config-airbnb-typescript": "^12.3.1",
|
"eslint-config-airbnb-typescript": "^12.3.1",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-import-resolver-webpack": "^0.13.1",
|
"eslint-import-resolver-webpack": "^0.13.0",
|
||||||
"eslint-plugin-disable": "^2.0.1",
|
"eslint-plugin-disable": "^2.0.1",
|
||||||
"eslint-plugin-import": "^2.23.4",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-lit": "^1.5.1",
|
"eslint-plugin-lit": "^1.3.0",
|
||||||
"eslint-plugin-prettier": "^3.4.0",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"eslint-plugin-unused-imports": "^1.1.2",
|
|
||||||
"eslint-plugin-wc": "^1.3.0",
|
"eslint-plugin-wc": "^1.3.0",
|
||||||
"fancy-log": "^1.3.3",
|
"fancy-log": "^1.3.3",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
@@ -198,7 +200,7 @@
|
|||||||
"gulp-zopfli-green": "^3.0.1",
|
"gulp-zopfli-green": "^3.0.1",
|
||||||
"html-minifier": "^4.0.0",
|
"html-minifier": "^4.0.0",
|
||||||
"husky": "^1.3.1",
|
"husky": "^1.3.1",
|
||||||
"lint-staged": "^11.0.1",
|
"lint-staged": "^10.5.4",
|
||||||
"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",
|
||||||
@@ -207,7 +209,8 @@
|
|||||||
"mocha": "^8.4.0",
|
"mocha": "^8.4.0",
|
||||||
"object-hash": "^2.0.3",
|
"object-hash": "^2.0.3",
|
||||||
"open": "^7.0.4",
|
"open": "^7.0.4",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.0.4",
|
||||||
|
"raw-loader": "^2.0.0",
|
||||||
"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",
|
||||||
@@ -217,22 +220,23 @@
|
|||||||
"sinon": "^11.0.0",
|
"sinon": "^11.0.0",
|
||||||
"source-map-url": "^0.4.0",
|
"source-map-url": "^0.4.0",
|
||||||
"systemjs": "^6.3.2",
|
"systemjs": "^6.3.2",
|
||||||
"terser-webpack-plugin": "^5.1.4",
|
"terser-webpack-plugin": "^5.1.2",
|
||||||
"ts-lit-plugin": "^1.2.1",
|
"ts-lit-plugin": "^1.2.1",
|
||||||
"ts-mocha": "^8.0.0",
|
"ts-mocha": "^8.0.0",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.2.4",
|
||||||
"vinyl-buffer": "^1.0.1",
|
"vinyl-buffer": "^1.0.1",
|
||||||
"vinyl-source-stream": "^2.0.0",
|
"vinyl-source-stream": "^2.0.0",
|
||||||
"webpack": "^5.43.0",
|
"webpack": "^5.24.1",
|
||||||
"webpack-cli": "^4.7.2",
|
"webpack-cli": "^4.5.0",
|
||||||
"webpack-dev-server": "^3.11.2",
|
"webpack-dev-server": "^3.11.2",
|
||||||
"webpack-manifest-plugin": "^3.1.1",
|
"webpack-manifest-plugin": "^3.0.0",
|
||||||
"workbox-build": "^6.1.5"
|
"workbox-build": "^6.1.5"
|
||||||
},
|
},
|
||||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",
|
||||||
|
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@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",
|
||||||
|
"@polymer/polymer": "3.1.0",
|
||||||
"lit-html": "2.0.0-rc.3",
|
"lit-html": "2.0.0-rc.3",
|
||||||
"lit-element": "3.0.0-rc.2"
|
"lit-element": "3.0.0-rc.2"
|
||||||
},
|
},
|
||||||
|
22
script/core
22
script/core
@@ -9,6 +9,12 @@ if [ -z "${DEVCONTAINER}" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "${CODESPACES}" ]; then
|
||||||
|
WORKSPACE="/root/workspace/frontend"
|
||||||
|
else
|
||||||
|
WORKSPACE="/workspaces/frontend"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z $(which hass) ]; then
|
if [ -z $(which hass) ]; then
|
||||||
echo "Installing Home Asstant core from dev."
|
echo "Installing Home Asstant core from dev."
|
||||||
python3 -m pip install --upgrade \
|
python3 -m pip install --upgrade \
|
||||||
@@ -16,9 +22,9 @@ if [ -z $(which hass) ]; then
|
|||||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "/workspaces/frontend/config" ]; then
|
if [ ! -d "${WORKSPACE}/config" ]; then
|
||||||
echo "Creating default configuration."
|
echo "Creating default configuration."
|
||||||
mkdir -p "/workspaces/frontend/config";
|
mkdir -p "${WORKSPACE}/config";
|
||||||
hass --script ensure_config -c config
|
hass --script ensure_config -c config
|
||||||
echo "demo:
|
echo "demo:
|
||||||
|
|
||||||
@@ -26,24 +32,24 @@ logger:
|
|||||||
default: info
|
default: info
|
||||||
logs:
|
logs:
|
||||||
homeassistant.components.frontend: debug
|
homeassistant.components.frontend: debug
|
||||||
" >> /workspaces/frontend/config/configuration.yaml
|
" >> "${WORKSPACE}/config/configuration.yaml"
|
||||||
|
|
||||||
if [ ! -z "${HASSIO}" ]; then
|
if [ ! -z "${HASSIO}" ]; then
|
||||||
echo "
|
echo "
|
||||||
# frontend:
|
# frontend:
|
||||||
# development_repo: /workspaces/frontend
|
# development_repo: ${WORKSPACE}
|
||||||
|
|
||||||
hassio:
|
hassio:
|
||||||
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
|
||||||
else
|
else
|
||||||
echo "
|
echo "
|
||||||
frontend:
|
frontend:
|
||||||
development_repo: /workspaces/frontend
|
development_repo: ${WORKSPACE}
|
||||||
|
|
||||||
# hassio:
|
# hassio:
|
||||||
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
# development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
hass -c /workspaces/frontend/config
|
hass -c "${WORKSPACE}/config"
|
||||||
|
4
setup.py
4
setup.py
@@ -2,9 +2,9 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20210803.1",
|
version="20210601.1",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/frontend",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
author_email="hello@home-assistant.io",
|
author_email="hello@home-assistant.io",
|
||||||
license="Apache-2.0",
|
license="Apache-2.0",
|
||||||
|
@@ -7,7 +7,6 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import "./ha-password-manager-polyfill";
|
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import "../components/ha-form/ha-form";
|
import "../components/ha-form/ha-form";
|
||||||
import "../components/ha-markdown";
|
import "../components/ha-markdown";
|
||||||
@@ -21,7 +20,7 @@ import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
|||||||
type State = "loading" | "error" | "step";
|
type State = "loading" | "error" | "step";
|
||||||
|
|
||||||
class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||||
@property({ attribute: false }) public authProvider?: AuthProvider;
|
@property() public authProvider?: AuthProvider;
|
||||||
|
|
||||||
@property() public clientId?: string;
|
@property() public clientId?: string;
|
||||||
|
|
||||||
@@ -38,15 +37,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
@state() private _errorMessage?: string;
|
@state() private _errorMessage?: string;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html` <form>${this._renderForm()}</form> `;
|
||||||
<form>${this._renderForm()}</form>
|
|
||||||
<ha-password-manager-polyfill
|
|
||||||
.step=${this._step}
|
|
||||||
.stepData=${this._stepData}
|
|
||||||
@form-submitted=${this._handleSubmit}
|
|
||||||
@value-changed=${this._stepDataChanged}
|
|
||||||
></ha-password-manager-polyfill>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
@@ -240,17 +231,11 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
// 100ms to give all the form elements time to initialize.
|
// 100ms to give all the form elements time to initialize.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const form = this.renderRoot.querySelector("ha-form");
|
const form = this.shadowRoot!.querySelector("ha-form");
|
||||||
if (form) {
|
if (form) {
|
||||||
(form as any).focus();
|
(form as any).focus();
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.renderRoot.querySelector(
|
|
||||||
"ha-password-manager-polyfill"
|
|
||||||
)!.boundingRect = this.getBoundingClientRect();
|
|
||||||
}, 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _stepDataChanged(ev: CustomEvent) {
|
private _stepDataChanged(ev: CustomEvent) {
|
||||||
@@ -344,9 +329,3 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("ha-auth-flow", HaAuthFlow);
|
customElements.define("ha-auth-flow", HaAuthFlow);
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-auth-flow": HaAuthFlow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,110 +0,0 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
|
||||||
import { HaFormSchema } from "../components/ha-form/ha-form";
|
|
||||||
import { DataEntryFlowStep } from "../data/data_entry_flow";
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-password-manager-polyfill": HaPasswordManagerPolyfill;
|
|
||||||
}
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"form-submitted": undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ENABLED_HANDLERS = [
|
|
||||||
"homeassistant",
|
|
||||||
"legacy_api_password",
|
|
||||||
"command_line",
|
|
||||||
];
|
|
||||||
|
|
||||||
@customElement("ha-password-manager-polyfill")
|
|
||||||
export class HaPasswordManagerPolyfill extends LitElement {
|
|
||||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public stepData: any;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public boundingRect?: DOMRect;
|
|
||||||
|
|
||||||
protected createRenderRoot() {
|
|
||||||
// Add under document body so the element isn't placed inside any shadow roots
|
|
||||||
return document.body;
|
|
||||||
}
|
|
||||||
|
|
||||||
private get styles() {
|
|
||||||
return `
|
|
||||||
.password-manager-polyfill {
|
|
||||||
position: absolute;
|
|
||||||
top: ${this.boundingRect?.y || 148}px;
|
|
||||||
left: calc(50% - ${(this.boundingRect?.width || 360) / 2}px);
|
|
||||||
width: ${this.boundingRect?.width || 360}px;
|
|
||||||
opacity: 0;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
.password-manager-polyfill input {
|
|
||||||
width: 100%;
|
|
||||||
height: 62px;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
.password-manager-polyfill input[type="submit"] {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
if (
|
|
||||||
this.step &&
|
|
||||||
this.step.type === "form" &&
|
|
||||||
this.step.step_id === "init" &&
|
|
||||||
ENABLED_HANDLERS.includes(this.step.handler[0])
|
|
||||||
) {
|
|
||||||
return html`
|
|
||||||
<form
|
|
||||||
class="password-manager-polyfill"
|
|
||||||
aria-hidden="true"
|
|
||||||
@submit=${this._handleSubmit}
|
|
||||||
>
|
|
||||||
${this.step.data_schema.map((input) => this.render_input(input))}
|
|
||||||
<input type="submit" />
|
|
||||||
<style>
|
|
||||||
${this.styles}
|
|
||||||
</style>
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
|
|
||||||
private render_input(schema: HaFormSchema): TemplateResult | string {
|
|
||||||
const inputType = schema.name.includes("password") ? "password" : "text";
|
|
||||||
if (schema.type !== "string") {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<input
|
|
||||||
tabindex="-1"
|
|
||||||
.id=${schema.name}
|
|
||||||
.type=${inputType}
|
|
||||||
.value=${this.stepData[schema.name] || ""}
|
|
||||||
@input=${this._valueChanged}
|
|
||||||
/>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleSubmit(ev: Event) {
|
|
||||||
ev.preventDefault();
|
|
||||||
fireEvent(this, "form-submitted");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _valueChanged(ev: Event) {
|
|
||||||
const target = ev.target! as HTMLInputElement;
|
|
||||||
this.stepData = { ...this.stepData, [target.id]: target.value };
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: this.stepData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,63 +0,0 @@
|
|||||||
export const COLORS = [
|
|
||||||
"#377eb8",
|
|
||||||
"#984ea3",
|
|
||||||
"#00d2d5",
|
|
||||||
"#ff7f00",
|
|
||||||
"#af8d00",
|
|
||||||
"#7f80cd",
|
|
||||||
"#b3e900",
|
|
||||||
"#c42e60",
|
|
||||||
"#a65628",
|
|
||||||
"#f781bf",
|
|
||||||
"#8dd3c7",
|
|
||||||
"#bebada",
|
|
||||||
"#fb8072",
|
|
||||||
"#80b1d3",
|
|
||||||
"#fdb462",
|
|
||||||
"#fccde5",
|
|
||||||
"#bc80bd",
|
|
||||||
"#ffed6f",
|
|
||||||
"#c4eaff",
|
|
||||||
"#cf8c00",
|
|
||||||
"#1b9e77",
|
|
||||||
"#d95f02",
|
|
||||||
"#e7298a",
|
|
||||||
"#e6ab02",
|
|
||||||
"#a6761d",
|
|
||||||
"#0097ff",
|
|
||||||
"#00d067",
|
|
||||||
"#f43600",
|
|
||||||
"#4ba93b",
|
|
||||||
"#5779bb",
|
|
||||||
"#927acc",
|
|
||||||
"#97ee3f",
|
|
||||||
"#bf3947",
|
|
||||||
"#9f5b00",
|
|
||||||
"#f48758",
|
|
||||||
"#8caed6",
|
|
||||||
"#f2b94f",
|
|
||||||
"#eff26e",
|
|
||||||
"#e43872",
|
|
||||||
"#d9b100",
|
|
||||||
"#9d7a00",
|
|
||||||
"#698cff",
|
|
||||||
"#d9d9d9",
|
|
||||||
"#00d27e",
|
|
||||||
"#d06800",
|
|
||||||
"#009f82",
|
|
||||||
"#c49200",
|
|
||||||
"#cbe8ff",
|
|
||||||
"#fecddf",
|
|
||||||
"#c27eb6",
|
|
||||||
"#8cd2ce",
|
|
||||||
"#c4b8d9",
|
|
||||||
"#f883b0",
|
|
||||||
"#a49100",
|
|
||||||
"#f48800",
|
|
||||||
"#27d0df",
|
|
||||||
"#a04a9b",
|
|
||||||
];
|
|
||||||
|
|
||||||
export function getColorByIndex(index: number) {
|
|
||||||
return COLORS[index % COLORS.length];
|
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
export const luminosity = (rgb: [number, number, number]): number => {
|
const luminosity = (rgb: [number, number, number]): number => {
|
||||||
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||||
const lum: [number, number, number] = [0, 0, 0];
|
const lum: [number, number, number] = [0, 0, 0];
|
||||||
for (let i = 0; i < rgb.length; i++) {
|
for (let i = 0; i < rgb.length; i++) {
|
||||||
|
@@ -42,7 +42,6 @@ export const FIXED_DOMAIN_ICONS = {
|
|||||||
remote: "hass:remote",
|
remote: "hass:remote",
|
||||||
scene: "hass:palette",
|
scene: "hass:palette",
|
||||||
script: "hass:script-text",
|
script: "hass:script-text",
|
||||||
select: "hass:format-list-bulleted",
|
|
||||||
sensor: "hass:eye",
|
sensor: "hass:eye",
|
||||||
simple_alarm: "hass:bell",
|
simple_alarm: "hass:bell",
|
||||||
sun: "hass:white-balance-sunny",
|
sun: "hass:white-balance-sunny",
|
||||||
@@ -59,11 +58,10 @@ export const FIXED_DEVICE_CLASS_ICONS = {
|
|||||||
current: "hass:current-ac",
|
current: "hass:current-ac",
|
||||||
carbon_dioxide: "mdi:molecule-co2",
|
carbon_dioxide: "mdi:molecule-co2",
|
||||||
carbon_monoxide: "mdi:molecule-co",
|
carbon_monoxide: "mdi:molecule-co",
|
||||||
energy: "hass:lightning-bolt",
|
energy: "hass:flash",
|
||||||
humidity: "hass:water-percent",
|
humidity: "hass:water-percent",
|
||||||
illuminance: "hass:brightness-5",
|
illuminance: "hass:brightness-5",
|
||||||
temperature: "hass:thermometer",
|
temperature: "hass:thermometer",
|
||||||
monetary: "mdi:cash",
|
|
||||||
pressure: "hass:gauge",
|
pressure: "hass:gauge",
|
||||||
power: "hass:flash",
|
power: "hass:flash",
|
||||||
power_factor: "hass:angle-acute",
|
power_factor: "hass:angle-acute",
|
||||||
@@ -85,7 +83,6 @@ export const DOMAINS_WITH_CARD = [
|
|||||||
"number",
|
"number",
|
||||||
"scene",
|
"scene",
|
||||||
"script",
|
"script",
|
||||||
"select",
|
|
||||||
"timer",
|
"timer",
|
||||||
"vacuum",
|
"vacuum",
|
||||||
"water_heater",
|
"water_heater",
|
||||||
@@ -124,7 +121,6 @@ export const DOMAINS_HIDE_MORE_INFO = [
|
|||||||
"input_text",
|
"input_text",
|
||||||
"number",
|
"number",
|
||||||
"scene",
|
"scene",
|
||||||
"select",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Domains that should have the history hidden in the more info dialog. */
|
/** Domains that should have the history hidden in the more info dialog. */
|
||||||
|
@@ -26,9 +26,6 @@ function checkToLocaleStringSupportsOptions() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const toLocaleDateStringSupportsOptions =
|
export const toLocaleDateStringSupportsOptions = checkToLocaleDateStringSupportsOptions();
|
||||||
checkToLocaleDateStringSupportsOptions();
|
export const toLocaleTimeStringSupportsOptions = checkToLocaleTimeStringSupportsOptions();
|
||||||
export const toLocaleTimeStringSupportsOptions =
|
export const toLocaleStringSupportsOptions = checkToLocaleStringSupportsOptions();
|
||||||
checkToLocaleTimeStringSupportsOptions();
|
|
||||||
export const toLocaleStringSupportsOptions =
|
|
||||||
checkToLocaleStringSupportsOptions();
|
|
||||||
|
@@ -17,19 +17,6 @@ export const formatDate = toLocaleDateStringSupportsOptions
|
|||||||
formatDateMem(locale).format(dateObj)
|
formatDateMem(locale).format(dateObj)
|
||||||
: (dateObj: Date) => format(dateObj, "longDate");
|
: (dateObj: Date) => format(dateObj, "longDate");
|
||||||
|
|
||||||
const formatDateShortMem = memoizeOne(
|
|
||||||
(locale: FrontendLocaleData) =>
|
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
|
||||||
day: "numeric",
|
|
||||||
month: "short",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export const formatDateShort = toLocaleDateStringSupportsOptions
|
|
||||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
|
||||||
formatDateShortMem(locale).format(dateObj)
|
|
||||||
: (dateObj: Date) => format(dateObj, "shortDate");
|
|
||||||
|
|
||||||
const formatDateWeekdayMem = memoizeOne(
|
const formatDateWeekdayMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new Intl.DateTimeFormat(locale.language, {
|
new Intl.DateTimeFormat(locale.language, {
|
||||||
|
@@ -82,71 +82,67 @@ class Storage {
|
|||||||
|
|
||||||
const storage = new Storage();
|
const storage = new Storage();
|
||||||
|
|
||||||
export const LocalStorage =
|
export const LocalStorage = (
|
||||||
(
|
storageKey?: string,
|
||||||
storageKey?: string,
|
property?: boolean,
|
||||||
property?: boolean,
|
propertyOptions?: PropertyDeclaration
|
||||||
propertyOptions?: PropertyDeclaration
|
): any => (clsElement: ClassElement) => {
|
||||||
): any =>
|
const key = String(clsElement.key);
|
||||||
(clsElement: ClassElement) => {
|
storageKey = storageKey || String(clsElement.key);
|
||||||
const key = String(clsElement.key);
|
const initVal = clsElement.initializer ? clsElement.initializer() : undefined;
|
||||||
storageKey = storageKey || String(clsElement.key);
|
|
||||||
const initVal = clsElement.initializer
|
|
||||||
? clsElement.initializer()
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
storage.addFromStorage(storageKey);
|
storage.addFromStorage(storageKey);
|
||||||
|
|
||||||
const subscribe = (el: ReactiveElement): UnsubscribeFunc =>
|
const subscribe = (el: ReactiveElement): UnsubscribeFunc =>
|
||||||
storage.subscribeChanges(storageKey!, (oldValue) => {
|
storage.subscribeChanges(storageKey!, (oldValue) => {
|
||||||
el.requestUpdate(clsElement.key, oldValue);
|
el.requestUpdate(clsElement.key, oldValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getValue = (): any =>
|
const getValue = (): any =>
|
||||||
storage.hasKey(storageKey!) ? storage.getValue(storageKey!) : initVal;
|
storage.hasKey(storageKey!) ? storage.getValue(storageKey!) : initVal;
|
||||||
|
|
||||||
const setValue = (el: ReactiveElement, value: any) => {
|
const setValue = (el: ReactiveElement, value: any) => {
|
||||||
let oldValue: unknown | undefined;
|
let oldValue: unknown | undefined;
|
||||||
if (property) {
|
if (property) {
|
||||||
oldValue = getValue();
|
oldValue = getValue();
|
||||||
}
|
}
|
||||||
storage.setValue(storageKey!, value);
|
storage.setValue(storageKey!, value);
|
||||||
if (property) {
|
if (property) {
|
||||||
el.requestUpdate(clsElement.key, oldValue);
|
el.requestUpdate(clsElement.key, oldValue);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
kind: "method",
|
|
||||||
placement: "prototype",
|
|
||||||
key: clsElement.key,
|
|
||||||
descriptor: {
|
|
||||||
set(this: ReactiveElement, value: unknown) {
|
|
||||||
setValue(this, value);
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return getValue();
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
},
|
|
||||||
finisher(cls: typeof ReactiveElement) {
|
|
||||||
if (property) {
|
|
||||||
const connectedCallback = cls.prototype.connectedCallback;
|
|
||||||
const disconnectedCallback = cls.prototype.disconnectedCallback;
|
|
||||||
cls.prototype.connectedCallback = function () {
|
|
||||||
connectedCallback.call(this);
|
|
||||||
this[`__unbsubLocalStorage${key}`] = subscribe(this);
|
|
||||||
};
|
|
||||||
cls.prototype.disconnectedCallback = function () {
|
|
||||||
disconnectedCallback.call(this);
|
|
||||||
this[`__unbsubLocalStorage${key}`]();
|
|
||||||
};
|
|
||||||
cls.createProperty(clsElement.key, {
|
|
||||||
noAccessor: true,
|
|
||||||
...propertyOptions,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
kind: "method",
|
||||||
|
placement: "prototype",
|
||||||
|
key: clsElement.key,
|
||||||
|
descriptor: {
|
||||||
|
set(this: ReactiveElement, value: unknown) {
|
||||||
|
setValue(this, value);
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return getValue();
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
},
|
||||||
|
finisher(cls: typeof ReactiveElement) {
|
||||||
|
if (property) {
|
||||||
|
const connectedCallback = cls.prototype.connectedCallback;
|
||||||
|
const disconnectedCallback = cls.prototype.disconnectedCallback;
|
||||||
|
cls.prototype.connectedCallback = function () {
|
||||||
|
connectedCallback.call(this);
|
||||||
|
this[`__unbsubLocalStorage${key}`] = subscribe(this);
|
||||||
|
};
|
||||||
|
cls.prototype.disconnectedCallback = function () {
|
||||||
|
disconnectedCallback.call(this);
|
||||||
|
this[`__unbsubLocalStorage${key}`]();
|
||||||
|
};
|
||||||
|
cls.createProperty(clsElement.key, {
|
||||||
|
noAccessor: true,
|
||||||
|
...propertyOptions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@@ -1,33 +1,33 @@
|
|||||||
import type { LitElement } from "lit";
|
import type { LitElement } from "lit";
|
||||||
import type { ClassElement } from "../../types";
|
import type { ClassElement } from "../../types";
|
||||||
|
|
||||||
export const restoreScroll =
|
export const restoreScroll = (selector: string): any => (
|
||||||
(selector: string): any =>
|
element: ClassElement
|
||||||
(element: ClassElement) => ({
|
) => ({
|
||||||
kind: "method",
|
kind: "method",
|
||||||
placement: "prototype",
|
placement: "prototype",
|
||||||
key: element.key,
|
key: element.key,
|
||||||
descriptor: {
|
descriptor: {
|
||||||
set(this: LitElement, value: number) {
|
set(this: LitElement, value: number) {
|
||||||
this[`__${String(element.key)}`] = value;
|
this[`__${String(element.key)}`] = value;
|
||||||
},
|
|
||||||
get(this: LitElement) {
|
|
||||||
return this[`__${String(element.key)}`];
|
|
||||||
},
|
|
||||||
enumerable: true,
|
|
||||||
configurable: true,
|
|
||||||
},
|
},
|
||||||
finisher(cls: typeof LitElement) {
|
get(this: LitElement) {
|
||||||
const connectedCallback = cls.prototype.connectedCallback;
|
return this[`__${String(element.key)}`];
|
||||||
cls.prototype.connectedCallback = function () {
|
},
|
||||||
connectedCallback.call(this);
|
enumerable: true,
|
||||||
if (this[element.key]) {
|
configurable: true,
|
||||||
const target = this.renderRoot.querySelector(selector);
|
},
|
||||||
if (!target) {
|
finisher(cls: typeof LitElement) {
|
||||||
return;
|
const connectedCallback = cls.prototype.connectedCallback;
|
||||||
}
|
cls.prototype.connectedCallback = function () {
|
||||||
target.scrollTop = this[element.key];
|
connectedCallback.call(this);
|
||||||
|
if (this[element.key]) {
|
||||||
|
const target = this.renderRoot.querySelector(selector);
|
||||||
|
if (!target) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
target.scrollTop = this[element.key];
|
||||||
},
|
}
|
||||||
});
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
@@ -6,7 +6,8 @@ export type LeafletDrawModuleType = typeof import("leaflet-draw");
|
|||||||
|
|
||||||
export const setupLeafletMap = async (
|
export const setupLeafletMap = async (
|
||||||
mapElement: HTMLElement,
|
mapElement: HTMLElement,
|
||||||
darkMode?: boolean
|
darkMode?: boolean,
|
||||||
|
draw = false
|
||||||
): Promise<[Map, LeafletModuleType, TileLayer]> => {
|
): Promise<[Map, LeafletModuleType, TileLayer]> => {
|
||||||
if (!mapElement.parentNode) {
|
if (!mapElement.parentNode) {
|
||||||
throw new Error("Cannot setup Leaflet map on disconnected element");
|
throw new Error("Cannot setup Leaflet map on disconnected element");
|
||||||
@@ -16,6 +17,10 @@ export const setupLeafletMap = async (
|
|||||||
.default as LeafletModuleType;
|
.default as LeafletModuleType;
|
||||||
Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/";
|
Leaflet.Icon.Default.imagePath = "/static/images/leaflet/images/";
|
||||||
|
|
||||||
|
if (draw) {
|
||||||
|
await import("leaflet-draw");
|
||||||
|
}
|
||||||
|
|
||||||
const map = Leaflet.map(mapElement);
|
const map = Leaflet.map(mapElement);
|
||||||
const style = document.createElement("link");
|
const style = document.createElement("link");
|
||||||
style.setAttribute("href", "/static/images/leaflet/leaflet.css");
|
style.setAttribute("href", "/static/images/leaflet/leaflet.css");
|
||||||
|
@@ -21,16 +21,6 @@ export const computeStateDisplay = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (stateObj.attributes.unit_of_measurement) {
|
if (stateObj.attributes.unit_of_measurement) {
|
||||||
if (stateObj.attributes.device_class === "monetary") {
|
|
||||||
try {
|
|
||||||
return formatNumber(compareState, locale, {
|
|
||||||
style: "currency",
|
|
||||||
currency: stateObj.attributes.unit_of_measurement,
|
|
||||||
});
|
|
||||||
} catch (_err) {
|
|
||||||
// fallback to default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return `${formatNumber(compareState, locale)} ${
|
return `${formatNumber(compareState, locale)} ${
|
||||||
stateObj.attributes.unit_of_measurement
|
stateObj.attributes.unit_of_measurement
|
||||||
}`;
|
}`;
|
||||||
@@ -39,61 +29,37 @@ export const computeStateDisplay = (
|
|||||||
const domain = computeStateDomain(stateObj);
|
const domain = computeStateDomain(stateObj);
|
||||||
|
|
||||||
if (domain === "input_datetime") {
|
if (domain === "input_datetime") {
|
||||||
if (state) {
|
let date: Date;
|
||||||
// If trying to display an explicit state, need to parse the explict state to `Date` then format.
|
if (!stateObj.attributes.has_time) {
|
||||||
// Attributes aren't available, we have to use `state`.
|
|
||||||
try {
|
|
||||||
const components = state.split(" ");
|
|
||||||
if (components.length === 2) {
|
|
||||||
// Date and time.
|
|
||||||
return formatDateTime(new Date(components.join("T")), locale);
|
|
||||||
}
|
|
||||||
if (components.length === 1) {
|
|
||||||
if (state.includes("-")) {
|
|
||||||
// Date only.
|
|
||||||
return formatDate(new Date(`${state}T00:00`), locale);
|
|
||||||
}
|
|
||||||
if (state.includes(":")) {
|
|
||||||
// Time only.
|
|
||||||
const now = new Date();
|
|
||||||
return formatTime(
|
|
||||||
new Date(`${now.toISOString().split("T")[0]}T${state}`),
|
|
||||||
locale
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
} catch {
|
|
||||||
// Formatting methods may throw error if date parsing doesn't go well,
|
|
||||||
// just return the state string in that case.
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
|
||||||
let date: Date;
|
|
||||||
if (!stateObj.attributes.has_time) {
|
|
||||||
date = new Date(
|
|
||||||
stateObj.attributes.year,
|
|
||||||
stateObj.attributes.month - 1,
|
|
||||||
stateObj.attributes.day
|
|
||||||
);
|
|
||||||
return formatDate(date, locale);
|
|
||||||
}
|
|
||||||
if (!stateObj.attributes.has_date) {
|
|
||||||
date = new Date();
|
|
||||||
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
|
||||||
return formatTime(date, locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
date = new Date(
|
date = new Date(
|
||||||
stateObj.attributes.year,
|
stateObj.attributes.year,
|
||||||
stateObj.attributes.month - 1,
|
stateObj.attributes.month - 1,
|
||||||
stateObj.attributes.day,
|
stateObj.attributes.day
|
||||||
|
);
|
||||||
|
return formatDate(date, locale);
|
||||||
|
}
|
||||||
|
if (!stateObj.attributes.has_date) {
|
||||||
|
const now = new Date();
|
||||||
|
date = new Date(
|
||||||
|
// Due to bugs.chromium.org/p/chromium/issues/detail?id=797548
|
||||||
|
// don't use artificial 1970 year.
|
||||||
|
now.getFullYear(),
|
||||||
|
now.getMonth(),
|
||||||
|
now.getDay(),
|
||||||
stateObj.attributes.hour,
|
stateObj.attributes.hour,
|
||||||
stateObj.attributes.minute
|
stateObj.attributes.minute
|
||||||
);
|
);
|
||||||
return formatDateTime(date, locale);
|
return formatTime(date, locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
date = new Date(
|
||||||
|
stateObj.attributes.year,
|
||||||
|
stateObj.attributes.month - 1,
|
||||||
|
stateObj.attributes.day,
|
||||||
|
stateObj.attributes.hour,
|
||||||
|
stateObj.attributes.minute
|
||||||
|
);
|
||||||
|
return formatDateTime(date, locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain === "humidifier") {
|
if (domain === "humidifier") {
|
||||||
|
@@ -43,17 +43,7 @@ export const domainIcon = (
|
|||||||
: "hass:air-humidifier";
|
: "hass:air-humidifier";
|
||||||
|
|
||||||
case "lock":
|
case "lock":
|
||||||
switch (compareState) {
|
return compareState === "unlocked" ? "hass:lock-open" : "hass:lock";
|
||||||
case "unlocked":
|
|
||||||
return "hass:lock-open";
|
|
||||||
case "jammed":
|
|
||||||
return "hass:lock-alert";
|
|
||||||
case "locking":
|
|
||||||
case "unlocking":
|
|
||||||
return "hass:lock-clock";
|
|
||||||
default:
|
|
||||||
return "hass:lock";
|
|
||||||
}
|
|
||||||
|
|
||||||
case "media_player":
|
case "media_player":
|
||||||
return compareState === "playing" ? "hass:cast-connected" : "hass:cast";
|
return compareState === "playing" ? "hass:cast-connected" : "hass:cast";
|
||||||
|
@@ -4,7 +4,7 @@ import { DEFAULT_DOMAIN_ICON } from "../const";
|
|||||||
import { computeDomain } from "./compute_domain";
|
import { computeDomain } from "./compute_domain";
|
||||||
import { domainIcon } from "./domain_icon";
|
import { domainIcon } from "./domain_icon";
|
||||||
|
|
||||||
export const stateIcon = (state?: HassEntity) => {
|
export const stateIcon = (state: HassEntity) => {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return DEFAULT_DOMAIN_ICON;
|
return DEFAULT_DOMAIN_ICON;
|
||||||
}
|
}
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
export const clamp = (value: number, min: number, max: number) =>
|
|
||||||
Math.min(Math.max(value, min), max);
|
|
@@ -1,2 +0,0 @@
|
|||||||
export const round = (value: number, precision = 2): number =>
|
|
||||||
Math.round(value * 10 ** precision) / 10 ** precision;
|
|
@@ -1,16 +1,9 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
import { mdiClose, mdiMagnify } from "@mdi/js";
|
import { mdiClose, mdiMagnify } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import {
|
import { customElement, property } from "lit/decorators";
|
||||||
css,
|
import { classMap } from "lit/directives/class-map";
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
|
||||||
import "../../components/ha-svg-icon";
|
import "../../components/ha-svg-icon";
|
||||||
import { fireEvent } from "../dom/fire_event";
|
import { fireEvent } from "../dom/fire_event";
|
||||||
|
|
||||||
@@ -34,11 +27,18 @@ class SearchInput extends LitElement {
|
|||||||
this.shadowRoot!.querySelector("paper-input")!.focus();
|
this.shadowRoot!.querySelector("paper-input")!.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@query("paper-input", true) private _input!: PaperInputElement;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
|
<style>
|
||||||
|
.no-underline:not(.focused) {
|
||||||
|
--paper-input-container-underline: {
|
||||||
|
display: none;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<paper-input
|
<paper-input
|
||||||
|
class=${classMap({ "no-underline": this.noUnderline })}
|
||||||
.autofocus=${this.autofocus}
|
.autofocus=${this.autofocus}
|
||||||
.label=${this.label || "Search"}
|
.label=${this.label || "Search"}
|
||||||
.value=${this.filter}
|
.value=${this.filter}
|
||||||
@@ -62,19 +62,6 @@ class SearchInput extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
|
||||||
if (
|
|
||||||
changedProps.has("noUnderline") &&
|
|
||||||
(this.noUnderline || changedProps.get("noUnderline") !== undefined)
|
|
||||||
) {
|
|
||||||
(
|
|
||||||
this._input.inputElement!.parentElement!.shadowRoot!.querySelector(
|
|
||||||
"div.unfocused-line"
|
|
||||||
) as HTMLElement
|
|
||||||
).style.display = this.noUnderline ? "none" : "block";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _filterChanged(value: string) {
|
private async _filterChanged(value: string) {
|
||||||
fireEvent(this, "value-changed", { value: String(value) });
|
fireEvent(this, "value-changed", { value: String(value) });
|
||||||
}
|
}
|
||||||
|
@@ -1,22 +1,4 @@
|
|||||||
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
||||||
import { round } from "../number/round";
|
|
||||||
|
|
||||||
export const numberFormatToLocale = (
|
|
||||||
localeOptions: FrontendLocaleData
|
|
||||||
): string | string[] | undefined => {
|
|
||||||
switch (localeOptions.number_format) {
|
|
||||||
case NumberFormat.comma_decimal:
|
|
||||||
return ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
|
|
||||||
case NumberFormat.decimal_comma:
|
|
||||||
return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
|
|
||||||
case NumberFormat.space_comma:
|
|
||||||
return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
|
|
||||||
case NumberFormat.system:
|
|
||||||
return undefined;
|
|
||||||
default:
|
|
||||||
return localeOptions.language;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility.
|
* Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility.
|
||||||
@@ -27,12 +9,27 @@ export const numberFormatToLocale = (
|
|||||||
*/
|
*/
|
||||||
export const formatNumber = (
|
export const formatNumber = (
|
||||||
num: string | number,
|
num: string | number,
|
||||||
localeOptions?: FrontendLocaleData,
|
locale?: FrontendLocaleData,
|
||||||
options?: Intl.NumberFormatOptions
|
options?: Intl.NumberFormatOptions
|
||||||
): string => {
|
): string => {
|
||||||
const locale = localeOptions
|
let format: string | string[] | undefined;
|
||||||
? numberFormatToLocale(localeOptions)
|
|
||||||
: undefined;
|
switch (locale?.number_format) {
|
||||||
|
case NumberFormat.comma_decimal:
|
||||||
|
format = ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
|
||||||
|
break;
|
||||||
|
case NumberFormat.decimal_comma:
|
||||||
|
format = ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
|
||||||
|
break;
|
||||||
|
case NumberFormat.space_comma:
|
||||||
|
format = ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
|
||||||
|
break;
|
||||||
|
case NumberFormat.system:
|
||||||
|
format = undefined;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
format = locale?.language;
|
||||||
|
}
|
||||||
|
|
||||||
// Polyfill for Number.isNaN, which is more reliable than the global isNaN()
|
// Polyfill for Number.isNaN, which is more reliable than the global isNaN()
|
||||||
Number.isNaN =
|
Number.isNaN =
|
||||||
@@ -42,13 +39,13 @@ export const formatNumber = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
localeOptions?.number_format !== NumberFormat.none &&
|
|
||||||
!Number.isNaN(Number(num)) &&
|
!Number.isNaN(Number(num)) &&
|
||||||
Intl
|
Intl &&
|
||||||
|
locale?.number_format !== NumberFormat.none
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
return new Intl.NumberFormat(
|
return new Intl.NumberFormat(
|
||||||
locale,
|
format,
|
||||||
getDefaultFormatOptions(num, options)
|
getDefaultFormatOptions(num, options)
|
||||||
).format(Number(num));
|
).format(Number(num));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -61,12 +58,7 @@ export const formatNumber = (
|
|||||||
).format(Number(num));
|
).format(Number(num));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof num === "string") {
|
return num.toString();
|
||||||
return num;
|
|
||||||
}
|
|
||||||
return `${round(num, options?.maximumFractionDigits).toString()}${
|
|
||||||
options?.style === "currency" ? ` ${options.currency}` : ""
|
|
||||||
}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,10 +70,7 @@ const getDefaultFormatOptions = (
|
|||||||
num: string | number,
|
num: string | number,
|
||||||
options?: Intl.NumberFormatOptions
|
options?: Intl.NumberFormatOptions
|
||||||
): Intl.NumberFormatOptions => {
|
): Intl.NumberFormatOptions => {
|
||||||
const defaultOptions: Intl.NumberFormatOptions = {
|
const defaultOptions: Intl.NumberFormatOptions = options || {};
|
||||||
maximumFractionDigits: 2,
|
|
||||||
...options,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof num !== "string") {
|
if (typeof num !== "string") {
|
||||||
return defaultOptions;
|
return defaultOptions;
|
||||||
|
@@ -6,7 +6,6 @@
|
|||||||
// 3. Disallow dates based on week number.
|
// 3. Disallow dates based on week number.
|
||||||
// 4. Disallow dates only consisting of a year.
|
// 4. Disallow dates only consisting of a year.
|
||||||
// https://regex101.com/r/kc5C14/3
|
// https://regex101.com/r/kc5C14/3
|
||||||
const regexp =
|
const regexp = /^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])[T| ](((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)(\8[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)$/;
|
||||||
/^\d{4}-(0[1-9]|1[0-2])-([12]\d|0[1-9]|3[01])[T| ](((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([.,]\d+(?!:))?)(\8[0-5]\d([.,]\d+)?)?([zZ]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)$/;
|
|
||||||
|
|
||||||
export const isTimestamp = (input: string): boolean => regexp.test(input);
|
export const isTimestamp = (input: string): boolean => regexp.test(input);
|
||||||
|
@@ -29,28 +29,31 @@ export const iconColorCSS = css`
|
|||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="climate"][data-state="cooling"] {
|
ha-icon[data-domain="climate"][data-state="cooling"] {
|
||||||
color: var(--cool-color, var(--state-climate-cool-color));
|
color: var(--cool-color, #2b9af9);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="climate"][data-state="heating"] {
|
ha-icon[data-domain="climate"][data-state="heating"] {
|
||||||
color: var(--heat-color, var(--state-climate-heat-color));
|
color: var(--heat-color, #ff8100);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="climate"][data-state="drying"] {
|
ha-icon[data-domain="climate"][data-state="drying"] {
|
||||||
color: var(--dry-color, var(--state-climate-dry-color));
|
color: var(--dry-color, #efbd07);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="alarm_control_panel"] {
|
ha-icon[data-domain="alarm_control_panel"] {
|
||||||
color: var(--alarm-color-armed, var(--label-badge-red));
|
color: var(--alarm-color-armed, var(--label-badge-red));
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
|
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
|
||||||
color: var(--alarm-color-disarmed, var(--label-badge-green));
|
color: var(--alarm-color-disarmed, var(--label-badge-green));
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
ha-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
||||||
ha-icon[data-domain="alarm_control_panel"][data-state="arming"] {
|
ha-icon[data-domain="alarm_control_panel"][data-state="arming"] {
|
||||||
color: var(--alarm-color-pending, var(--label-badge-yellow));
|
color: var(--alarm-color-pending, var(--label-badge-yellow));
|
||||||
animation: pulse 1s infinite;
|
animation: pulse 1s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
|
ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
|
||||||
color: var(--alarm-color-triggered, var(--label-badge-red));
|
color: var(--alarm-color-triggered, var(--label-badge-red));
|
||||||
animation: pulse 1s infinite;
|
animation: pulse 1s infinite;
|
||||||
@@ -70,11 +73,11 @@ export const iconColorCSS = css`
|
|||||||
|
|
||||||
ha-icon[data-domain="plant"][data-state="problem"],
|
ha-icon[data-domain="plant"][data-state="problem"],
|
||||||
ha-icon[data-domain="zwave"][data-state="dead"] {
|
ha-icon[data-domain="zwave"][data-state="dead"] {
|
||||||
color: var(--state-icon-error-color);
|
color: var(--error-state-color, #db4437);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Color the icon if unavailable */
|
/* Color the icon if unavailable */
|
||||||
ha-icon[data-state="unavailable"] {
|
ha-icon[data-state="unavailable"] {
|
||||||
color: var(--state-unavailable-color);
|
color: var(--state-icon-unavailable-color);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -5,20 +5,32 @@
|
|||||||
// as much as it can, without ever going more than once per `wait` duration;
|
// as much as it can, without ever going more than once per `wait` duration;
|
||||||
// but if you'd like to disable the execution on the leading edge, pass
|
// but if you'd like to disable the execution on the leading edge, pass
|
||||||
// `false for leading`. To disable execution on the trailing edge, ditto.
|
// `false for leading`. To disable execution on the trailing edge, ditto.
|
||||||
export const throttle = <T extends any[]>(
|
export const throttle = <T extends (...args) => unknown>(
|
||||||
func: (...args: T) => void,
|
func: T,
|
||||||
wait: number,
|
wait: number,
|
||||||
leading = true,
|
leading = true,
|
||||||
trailing = true
|
trailing = true
|
||||||
) => {
|
): T => {
|
||||||
let timeout: number | undefined;
|
let timeout: number | undefined;
|
||||||
let previous = 0;
|
let previous = 0;
|
||||||
return (...args: T): void => {
|
let context: any;
|
||||||
const later = () => {
|
let args: any;
|
||||||
previous = leading === false ? 0 : Date.now();
|
const later = () => {
|
||||||
timeout = undefined;
|
previous = leading === false ? 0 : Date.now();
|
||||||
func(...args);
|
timeout = undefined;
|
||||||
};
|
func.apply(context, args);
|
||||||
|
if (!timeout) {
|
||||||
|
context = null;
|
||||||
|
args = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// @ts-ignore
|
||||||
|
return function (...argmnts) {
|
||||||
|
// @ts-ignore
|
||||||
|
// @typescript-eslint/no-this-alias
|
||||||
|
context = this;
|
||||||
|
args = argmnts;
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (!previous && leading === false) {
|
if (!previous && leading === false) {
|
||||||
previous = now;
|
previous = now;
|
||||||
@@ -30,7 +42,7 @@ export const throttle = <T extends any[]>(
|
|||||||
timeout = undefined;
|
timeout = undefined;
|
||||||
}
|
}
|
||||||
previous = now;
|
previous = now;
|
||||||
func(...args);
|
func.apply(context, args);
|
||||||
} else if (!timeout && trailing !== false) {
|
} else if (!timeout && trailing !== false) {
|
||||||
timeout = window.setTimeout(later, remaining);
|
timeout = window.setTimeout(later, remaining);
|
||||||
}
|
}
|
||||||
|
@@ -64,18 +64,18 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
|
|||||||
this.hass
|
this.hass
|
||||||
.callService(this.domain, this.service, this.serviceData)
|
.callService(this.domain, this.service, this.serviceData)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
function () {
|
||||||
el.progress = false;
|
el.progress = false;
|
||||||
el.$.progress.actionSuccess();
|
el.$.progress.actionSuccess();
|
||||||
eventData.success = true;
|
eventData.success = true;
|
||||||
},
|
},
|
||||||
() => {
|
function () {
|
||||||
el.progress = false;
|
el.progress = false;
|
||||||
el.$.progress.actionError();
|
el.$.progress.actionError();
|
||||||
eventData.success = false;
|
eventData.success = false;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(function () {
|
||||||
el.fire("hass-service-called", eventData);
|
el.fire("hass-service-called", eventData);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ import { customElement, property, query } from "lit/decorators";
|
|||||||
import "../ha-circular-progress";
|
import "../ha-circular-progress";
|
||||||
|
|
||||||
@customElement("ha-progress-button")
|
@customElement("ha-progress-button")
|
||||||
export class HaProgressButton extends LitElement {
|
class HaProgressButton extends LitElement {
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public progress = false;
|
@property({ type: Boolean }) public progress = false;
|
||||||
|
@@ -1,197 +0,0 @@
|
|||||||
import { _adapters } from "chart.js";
|
|
||||||
import {
|
|
||||||
startOfSecond,
|
|
||||||
startOfMinute,
|
|
||||||
startOfHour,
|
|
||||||
startOfDay,
|
|
||||||
startOfWeek,
|
|
||||||
startOfMonth,
|
|
||||||
startOfQuarter,
|
|
||||||
startOfYear,
|
|
||||||
addMilliseconds,
|
|
||||||
addSeconds,
|
|
||||||
addMinutes,
|
|
||||||
addHours,
|
|
||||||
addDays,
|
|
||||||
addWeeks,
|
|
||||||
addMonths,
|
|
||||||
addQuarters,
|
|
||||||
addYears,
|
|
||||||
differenceInMilliseconds,
|
|
||||||
differenceInSeconds,
|
|
||||||
differenceInMinutes,
|
|
||||||
differenceInHours,
|
|
||||||
differenceInDays,
|
|
||||||
differenceInWeeks,
|
|
||||||
differenceInMonths,
|
|
||||||
differenceInQuarters,
|
|
||||||
differenceInYears,
|
|
||||||
endOfSecond,
|
|
||||||
endOfMinute,
|
|
||||||
endOfHour,
|
|
||||||
endOfDay,
|
|
||||||
endOfWeek,
|
|
||||||
endOfMonth,
|
|
||||||
endOfQuarter,
|
|
||||||
endOfYear,
|
|
||||||
} from "date-fns";
|
|
||||||
import { formatDate, formatDateShort } from "../../common/datetime/format_date";
|
|
||||||
import {
|
|
||||||
formatDateTime,
|
|
||||||
formatDateTimeWithSeconds,
|
|
||||||
} from "../../common/datetime/format_date_time";
|
|
||||||
import {
|
|
||||||
formatTime,
|
|
||||||
formatTimeWithSeconds,
|
|
||||||
} from "../../common/datetime/format_time";
|
|
||||||
|
|
||||||
const FORMATS = {
|
|
||||||
datetime: "datetime",
|
|
||||||
datetimeseconds: "datetimeseconds",
|
|
||||||
millisecond: "millisecond",
|
|
||||||
second: "second",
|
|
||||||
minute: "minute",
|
|
||||||
hour: "hour",
|
|
||||||
day: "day",
|
|
||||||
week: "week",
|
|
||||||
month: "month",
|
|
||||||
quarter: "quarter",
|
|
||||||
year: "year",
|
|
||||||
};
|
|
||||||
|
|
||||||
_adapters._date.override({
|
|
||||||
formats: () => FORMATS,
|
|
||||||
parse: (value: Date | number) => {
|
|
||||||
if (!(value instanceof Date)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return value.getTime();
|
|
||||||
},
|
|
||||||
format: function (time, fmt: keyof typeof FORMATS) {
|
|
||||||
switch (fmt) {
|
|
||||||
case "datetime":
|
|
||||||
return formatDateTime(new Date(time), this.options.locale);
|
|
||||||
case "datetimeseconds":
|
|
||||||
return formatDateTimeWithSeconds(new Date(time), this.options.locale);
|
|
||||||
case "millisecond":
|
|
||||||
return formatTimeWithSeconds(new Date(time), this.options.locale);
|
|
||||||
case "second":
|
|
||||||
return formatTimeWithSeconds(new Date(time), this.options.locale);
|
|
||||||
case "minute":
|
|
||||||
return formatTime(new Date(time), this.options.locale);
|
|
||||||
case "hour":
|
|
||||||
return formatTime(new Date(time), this.options.locale);
|
|
||||||
case "day":
|
|
||||||
return formatDateShort(new Date(time), this.options.locale);
|
|
||||||
case "week":
|
|
||||||
return formatDate(new Date(time), this.options.locale);
|
|
||||||
case "month":
|
|
||||||
return formatDate(new Date(time), this.options.locale);
|
|
||||||
case "quarter":
|
|
||||||
return formatDate(new Date(time), this.options.locale);
|
|
||||||
case "year":
|
|
||||||
return formatDate(new Date(time), this.options.locale);
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// @ts-ignore
|
|
||||||
add: (time, amount, unit) => {
|
|
||||||
switch (unit) {
|
|
||||||
case "millisecond":
|
|
||||||
return addMilliseconds(time, amount);
|
|
||||||
case "second":
|
|
||||||
return addSeconds(time, amount);
|
|
||||||
case "minute":
|
|
||||||
return addMinutes(time, amount);
|
|
||||||
case "hour":
|
|
||||||
return addHours(time, amount);
|
|
||||||
case "day":
|
|
||||||
return addDays(time, amount);
|
|
||||||
case "week":
|
|
||||||
return addWeeks(time, amount);
|
|
||||||
case "month":
|
|
||||||
return addMonths(time, amount);
|
|
||||||
case "quarter":
|
|
||||||
return addQuarters(time, amount);
|
|
||||||
case "year":
|
|
||||||
return addYears(time, amount);
|
|
||||||
default:
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
diff: (max, min, unit) => {
|
|
||||||
switch (unit) {
|
|
||||||
case "millisecond":
|
|
||||||
return differenceInMilliseconds(max, min);
|
|
||||||
case "second":
|
|
||||||
return differenceInSeconds(max, min);
|
|
||||||
case "minute":
|
|
||||||
return differenceInMinutes(max, min);
|
|
||||||
case "hour":
|
|
||||||
return differenceInHours(max, min);
|
|
||||||
case "day":
|
|
||||||
return differenceInDays(max, min);
|
|
||||||
case "week":
|
|
||||||
return differenceInWeeks(max, min);
|
|
||||||
case "month":
|
|
||||||
return differenceInMonths(max, min);
|
|
||||||
case "quarter":
|
|
||||||
return differenceInQuarters(max, min);
|
|
||||||
case "year":
|
|
||||||
return differenceInYears(max, min);
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// @ts-ignore
|
|
||||||
startOf: (time, unit, weekday) => {
|
|
||||||
switch (unit) {
|
|
||||||
case "second":
|
|
||||||
return startOfSecond(time);
|
|
||||||
case "minute":
|
|
||||||
return startOfMinute(time);
|
|
||||||
case "hour":
|
|
||||||
return startOfHour(time);
|
|
||||||
case "day":
|
|
||||||
return startOfDay(time);
|
|
||||||
case "week":
|
|
||||||
return startOfWeek(time);
|
|
||||||
case "isoWeek":
|
|
||||||
return startOfWeek(time, {
|
|
||||||
weekStartsOn: +weekday! as 0 | 1 | 2 | 3 | 4 | 5 | 6,
|
|
||||||
});
|
|
||||||
case "month":
|
|
||||||
return startOfMonth(time);
|
|
||||||
case "quarter":
|
|
||||||
return startOfQuarter(time);
|
|
||||||
case "year":
|
|
||||||
return startOfYear(time);
|
|
||||||
default:
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// @ts-ignore
|
|
||||||
endOf: (time, unit) => {
|
|
||||||
switch (unit) {
|
|
||||||
case "second":
|
|
||||||
return endOfSecond(time);
|
|
||||||
case "minute":
|
|
||||||
return endOfMinute(time);
|
|
||||||
case "hour":
|
|
||||||
return endOfHour(time);
|
|
||||||
case "day":
|
|
||||||
return endOfDay(time);
|
|
||||||
case "week":
|
|
||||||
return endOfWeek(time);
|
|
||||||
case "month":
|
|
||||||
return endOfMonth(time);
|
|
||||||
case "quarter":
|
|
||||||
return endOfQuarter(time);
|
|
||||||
case "year":
|
|
||||||
return endOfYear(time);
|
|
||||||
default:
|
|
||||||
return time;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,349 +0,0 @@
|
|||||||
import type {
|
|
||||||
Chart,
|
|
||||||
ChartType,
|
|
||||||
ChartData,
|
|
||||||
ChartOptions,
|
|
||||||
TooltipModel,
|
|
||||||
} from "chart.js";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { styleMap } from "lit/directives/style-map";
|
|
||||||
import { clamp } from "../../common/number/clamp";
|
|
||||||
|
|
||||||
interface Tooltip extends TooltipModel<any> {
|
|
||||||
top: string;
|
|
||||||
left: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ha-chart-base")
|
|
||||||
export default class HaChartBase extends LitElement {
|
|
||||||
public chart?: Chart;
|
|
||||||
|
|
||||||
@property({ attribute: "chart-type", reflect: true })
|
|
||||||
public chartType: ChartType = "line";
|
|
||||||
|
|
||||||
@property({ attribute: false }) public data: ChartData = { datasets: [] };
|
|
||||||
|
|
||||||
@property({ attribute: false }) public options?: ChartOptions;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public plugins?: any[];
|
|
||||||
|
|
||||||
@property({ type: Number }) public height?: number;
|
|
||||||
|
|
||||||
@state() private _chartHeight?: number;
|
|
||||||
|
|
||||||
@state() private _tooltip?: Tooltip;
|
|
||||||
|
|
||||||
@state() private _hiddenDatasets: Set<number> = new Set();
|
|
||||||
|
|
||||||
protected firstUpdated() {
|
|
||||||
this._setupChart();
|
|
||||||
this.data.datasets.forEach((dataset, index) => {
|
|
||||||
if (dataset.hidden) {
|
|
||||||
this._hiddenDatasets.add(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues): void {
|
|
||||||
super.willUpdate(changedProps);
|
|
||||||
|
|
||||||
if (!this.hasUpdated || !this.chart) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (changedProps.has("plugins")) {
|
|
||||||
this.chart.destroy();
|
|
||||||
this._setupChart();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (changedProps.has("chartType")) {
|
|
||||||
this.chart.config.type = this.chartType;
|
|
||||||
}
|
|
||||||
if (changedProps.has("data")) {
|
|
||||||
this.chart.data = this.data;
|
|
||||||
}
|
|
||||||
if (changedProps.has("options")) {
|
|
||||||
this.chart.options = this._createOptions();
|
|
||||||
}
|
|
||||||
this.chart.update("none");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`
|
|
||||||
${this.options?.plugins?.legend?.display === true
|
|
||||||
? html`<div class="chartLegend">
|
|
||||||
<ul>
|
|
||||||
${this.data.datasets.map(
|
|
||||||
(dataset, index) => html`<li
|
|
||||||
.datasetIndex=${index}
|
|
||||||
@click=${this._legendClick}
|
|
||||||
class=${classMap({
|
|
||||||
hidden: this._hiddenDatasets.has(index),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="bullet"
|
|
||||||
style=${styleMap({
|
|
||||||
backgroundColor: dataset.backgroundColor as string,
|
|
||||||
borderColor: dataset.borderColor as string,
|
|
||||||
})}
|
|
||||||
></div>
|
|
||||||
${dataset.label}
|
|
||||||
</li>`
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
<div
|
|
||||||
class="chartContainer"
|
|
||||||
style=${styleMap({
|
|
||||||
height: `${this.height ?? this._chartHeight}px`,
|
|
||||||
overflow: this._chartHeight ? "initial" : "hidden",
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<canvas></canvas>
|
|
||||||
${this._tooltip
|
|
||||||
? html`<div
|
|
||||||
class="chartTooltip ${classMap({ [this._tooltip.yAlign]: true })}"
|
|
||||||
style=${styleMap({
|
|
||||||
top: this._tooltip.top,
|
|
||||||
left: this._tooltip.left,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div class="title">${this._tooltip.title}</div>
|
|
||||||
${this._tooltip.beforeBody
|
|
||||||
? html`<div class="beforeBody">
|
|
||||||
${this._tooltip.beforeBody}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
<div>
|
|
||||||
<ul>
|
|
||||||
${this._tooltip.body.map(
|
|
||||||
(item, i) => html`<li>
|
|
||||||
<div
|
|
||||||
class="bullet"
|
|
||||||
style=${styleMap({
|
|
||||||
backgroundColor: this._tooltip!.labelColors[i]
|
|
||||||
.backgroundColor as string,
|
|
||||||
borderColor: this._tooltip!.labelColors[i]
|
|
||||||
.borderColor as string,
|
|
||||||
})}
|
|
||||||
></div>
|
|
||||||
${item.lines.join("\n")}
|
|
||||||
</li>`
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
${this._tooltip.footer.length
|
|
||||||
? html`<div class="footer">
|
|
||||||
${this._tooltip.footer.map((item) => html`${item}<br />`)}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _setupChart() {
|
|
||||||
const ctx: CanvasRenderingContext2D = this.renderRoot
|
|
||||||
.querySelector("canvas")!
|
|
||||||
.getContext("2d")!;
|
|
||||||
|
|
||||||
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
|
|
||||||
|
|
||||||
const computedStyles = getComputedStyle(this);
|
|
||||||
|
|
||||||
ChartConstructor.defaults.borderColor =
|
|
||||||
computedStyles.getPropertyValue("--divider-color");
|
|
||||||
ChartConstructor.defaults.color = computedStyles.getPropertyValue(
|
|
||||||
"--secondary-text-color"
|
|
||||||
);
|
|
||||||
|
|
||||||
this.chart = new ChartConstructor(ctx, {
|
|
||||||
type: this.chartType,
|
|
||||||
data: this.data,
|
|
||||||
options: this._createOptions(),
|
|
||||||
plugins: this._createPlugins(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createOptions() {
|
|
||||||
return {
|
|
||||||
...this.options,
|
|
||||||
plugins: {
|
|
||||||
...this.options?.plugins,
|
|
||||||
tooltip: {
|
|
||||||
...this.options?.plugins?.tooltip,
|
|
||||||
enabled: false,
|
|
||||||
external: (context) => this._handleTooltip(context),
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
...this.options?.plugins?.legend,
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createPlugins() {
|
|
||||||
return [
|
|
||||||
...(this.plugins || []),
|
|
||||||
{
|
|
||||||
id: "afterRenderHook",
|
|
||||||
afterRender: (chart) => {
|
|
||||||
this._chartHeight = chart.height;
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
...this.options?.plugins?.legend,
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private _legendClick(ev) {
|
|
||||||
if (!this.chart) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const index = ev.currentTarget.datasetIndex;
|
|
||||||
if (this.chart.isDatasetVisible(index)) {
|
|
||||||
this.chart.setDatasetVisibility(index, false);
|
|
||||||
this._hiddenDatasets.add(index);
|
|
||||||
} else {
|
|
||||||
this.chart.setDatasetVisibility(index, true);
|
|
||||||
this._hiddenDatasets.delete(index);
|
|
||||||
}
|
|
||||||
this.chart.update("none");
|
|
||||||
this.requestUpdate("_hiddenDatasets");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleTooltip(context: {
|
|
||||||
chart: Chart;
|
|
||||||
tooltip: TooltipModel<any>;
|
|
||||||
}) {
|
|
||||||
if (context.tooltip.opacity === 0) {
|
|
||||||
this._tooltip = undefined;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._tooltip = {
|
|
||||||
...context.tooltip,
|
|
||||||
top: this.chart!.canvas.offsetTop + context.tooltip.caretY + 12 + "px",
|
|
||||||
left:
|
|
||||||
this.chart!.canvas.offsetLeft +
|
|
||||||
clamp(context.tooltip.caretX, 100, this.clientWidth - 100) -
|
|
||||||
100 +
|
|
||||||
"px",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public updateChart = (): void => {
|
|
||||||
if (this.chart) {
|
|
||||||
this.chart.update();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.chartContainer {
|
|
||||||
overflow: hidden;
|
|
||||||
height: 0;
|
|
||||||
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
canvas {
|
|
||||||
max-height: var(--chart-max-height, 400px);
|
|
||||||
}
|
|
||||||
.chartLegend {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.chartLegend li {
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-flex;
|
|
||||||
padding: 0 8px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
.chartLegend .hidden {
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
.chartLegend .bullet,
|
|
||||||
.chartTooltip .bullet {
|
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: inline-block;
|
|
||||||
height: 16px;
|
|
||||||
margin-right: 6px;
|
|
||||||
width: 16px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
.chartTooltip .bullet {
|
|
||||||
align-self: baseline;
|
|
||||||
}
|
|
||||||
:host([rtl]) .chartLegend .bullet,
|
|
||||||
:host([rtl]) .chartTooltip .bullet {
|
|
||||||
margin-right: inherit;
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
.chartTooltip {
|
|
||||||
padding: 8px;
|
|
||||||
font-size: 90%;
|
|
||||||
position: absolute;
|
|
||||||
background: rgba(80, 80, 80, 0.9);
|
|
||||||
color: white;
|
|
||||||
border-radius: 4px;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 1000;
|
|
||||||
width: 200px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
:host([rtl]) .chartTooltip {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
.chartLegend ul,
|
|
||||||
.chartTooltip ul {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 0px;
|
|
||||||
margin: 8px 0 0 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.chartTooltip ul {
|
|
||||||
margin: 0 4px;
|
|
||||||
}
|
|
||||||
.chartTooltip li {
|
|
||||||
display: flex;
|
|
||||||
white-space: pre-line;
|
|
||||||
align-items: center;
|
|
||||||
line-height: 16px;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
.chartTooltip .title {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.chartTooltip .footer {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.chartTooltip .beforeBody {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 300;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-chart-base": HaChartBase;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,400 +0,0 @@
|
|||||||
import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
|
||||||
import { html, LitElement, PropertyValues } from "lit";
|
|
||||||
import { property, state } from "lit/decorators";
|
|
||||||
import { getColorByIndex } from "../../common/color/colors";
|
|
||||||
import {
|
|
||||||
formatNumber,
|
|
||||||
numberFormatToLocale,
|
|
||||||
} from "../../common/string/format_number";
|
|
||||||
import { LineChartEntity, LineChartState } from "../../data/history";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import "./ha-chart-base";
|
|
||||||
|
|
||||||
const safeParseFloat = (value) => {
|
|
||||||
const parsed = parseFloat(value);
|
|
||||||
return isFinite(parsed) ? parsed : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
class StateHistoryChartLine extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public data: LineChartEntity[] = [];
|
|
||||||
|
|
||||||
@property() public names: boolean | Record<string, string> = false;
|
|
||||||
|
|
||||||
@property() public unit?: string;
|
|
||||||
|
|
||||||
@property() public identifier?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public isSingleDevice = false;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public endTime?: Date;
|
|
||||||
|
|
||||||
@state() private _chartData?: ChartData<"line">;
|
|
||||||
|
|
||||||
@state() private _chartOptions?: ChartOptions<"line">;
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`
|
|
||||||
<ha-chart-base
|
|
||||||
.data=${this._chartData}
|
|
||||||
.options=${this._chartOptions}
|
|
||||||
chart-type="line"
|
|
||||||
></ha-chart-base>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._chartOptions = {
|
|
||||||
parsing: false,
|
|
||||||
animation: false,
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: "time",
|
|
||||||
adapters: {
|
|
||||||
date: {
|
|
||||||
locale: this.hass.locale,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
maxRotation: 0,
|
|
||||||
sampleSize: 5,
|
|
||||||
autoSkipPadding: 20,
|
|
||||||
major: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
font: (context) =>
|
|
||||||
context.tick && context.tick.major
|
|
||||||
? ({ weight: "bold" } as any)
|
|
||||||
: {},
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
tooltipFormat: "datetimeseconds",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
ticks: {
|
|
||||||
maxTicksLimit: 7,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: this.unit,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
mode: "nearest",
|
|
||||||
callbacks: {
|
|
||||||
label: (context) =>
|
|
||||||
`${context.dataset.label}: ${formatNumber(
|
|
||||||
context.parsed.y,
|
|
||||||
this.hass.locale
|
|
||||||
)} ${this.unit}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
filler: {
|
|
||||||
propagate: true,
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: !this.isSingleDevice,
|
|
||||||
labels: {
|
|
||||||
usePointStyle: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
mode: "nearest",
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
line: {
|
|
||||||
tension: 0.1,
|
|
||||||
borderWidth: 1.5,
|
|
||||||
},
|
|
||||||
point: {
|
|
||||||
hitRadius: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// @ts-expect-error
|
|
||||||
locale: numberFormatToLocale(this.hass.locale),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (changedProps.has("data")) {
|
|
||||||
this._generateData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _generateData() {
|
|
||||||
let colorIndex = 0;
|
|
||||||
const computedStyles = getComputedStyle(this);
|
|
||||||
const entityStates = this.data;
|
|
||||||
const datasets: ChartDataset<"line">[] = [];
|
|
||||||
let endTime: Date;
|
|
||||||
|
|
||||||
if (entityStates.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
endTime =
|
|
||||||
this.endTime ||
|
|
||||||
// Get the highest date from the last date of each device
|
|
||||||
new Date(
|
|
||||||
Math.max(
|
|
||||||
...entityStates.map((devSts) =>
|
|
||||||
new Date(
|
|
||||||
devSts.states[devSts.states.length - 1].last_changed
|
|
||||||
).getTime()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (endTime > new Date()) {
|
|
||||||
endTime = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
const names = this.names || {};
|
|
||||||
entityStates.forEach((states) => {
|
|
||||||
const domain = states.domain;
|
|
||||||
const name = names[states.entity_id] || states.name;
|
|
||||||
// array containing [value1, value2, etc]
|
|
||||||
let prevValues: any[] | null = null;
|
|
||||||
|
|
||||||
const data: ChartDataset<"line">[] = [];
|
|
||||||
|
|
||||||
const pushData = (timestamp: Date, datavalues: any[] | null) => {
|
|
||||||
if (!datavalues) return;
|
|
||||||
if (timestamp > endTime) {
|
|
||||||
// Drop datapoints that are after the requested endTime. This could happen if
|
|
||||||
// endTime is "now" and client time is not in sync with server time.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
data.forEach((d, i) => {
|
|
||||||
if (datavalues[i] === null && prevValues && prevValues[i] !== null) {
|
|
||||||
// null data values show up as gaps in the chart.
|
|
||||||
// If the current value for the dataset is null and the previous
|
|
||||||
// value of the data set is not null, then add an 'end' point
|
|
||||||
// to the chart for the previous value. Otherwise the gap will
|
|
||||||
// be too big. It will go from the start of the previous data
|
|
||||||
// value until the start of the next data value.
|
|
||||||
d.data.push({ x: timestamp.getTime(), y: prevValues[i] });
|
|
||||||
}
|
|
||||||
d.data.push({ x: timestamp.getTime(), y: datavalues[i] });
|
|
||||||
});
|
|
||||||
prevValues = datavalues;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addDataSet = (
|
|
||||||
nameY: string,
|
|
||||||
step = false,
|
|
||||||
fill = false,
|
|
||||||
color?: string
|
|
||||||
) => {
|
|
||||||
if (!color) {
|
|
||||||
color = getColorByIndex(colorIndex);
|
|
||||||
colorIndex++;
|
|
||||||
}
|
|
||||||
data.push({
|
|
||||||
label: nameY,
|
|
||||||
fill: fill ? "origin" : false,
|
|
||||||
borderColor: color,
|
|
||||||
backgroundColor: color + "7F",
|
|
||||||
stepped: step ? "before" : false,
|
|
||||||
pointRadius: 0,
|
|
||||||
data: [],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
domain === "thermostat" ||
|
|
||||||
domain === "climate" ||
|
|
||||||
domain === "water_heater"
|
|
||||||
) {
|
|
||||||
const hasHvacAction = states.states.some(
|
|
||||||
(entityState) => entityState.attributes?.hvac_action
|
|
||||||
);
|
|
||||||
|
|
||||||
const isHeating =
|
|
||||||
domain === "climate" && hasHvacAction
|
|
||||||
? (entityState: LineChartState) =>
|
|
||||||
entityState.attributes?.hvac_action === "heating"
|
|
||||||
: (entityState: LineChartState) => entityState.state === "heat";
|
|
||||||
const isCooling =
|
|
||||||
domain === "climate" && hasHvacAction
|
|
||||||
? (entityState: LineChartState) =>
|
|
||||||
entityState.attributes?.hvac_action === "cooling"
|
|
||||||
: (entityState: LineChartState) => entityState.state === "cool";
|
|
||||||
|
|
||||||
const hasHeat = states.states.some(isHeating);
|
|
||||||
const hasCool = states.states.some(isCooling);
|
|
||||||
// We differentiate between thermostats that have a target temperature
|
|
||||||
// range versus ones that have just a target temperature
|
|
||||||
|
|
||||||
// Using step chart by step-before so manually interpolation not needed.
|
|
||||||
const hasTargetRange = states.states.some(
|
|
||||||
(entityState) =>
|
|
||||||
entityState.attributes &&
|
|
||||||
entityState.attributes.target_temp_high !==
|
|
||||||
entityState.attributes.target_temp_low
|
|
||||||
);
|
|
||||||
addDataSet(
|
|
||||||
`${this.hass.localize("ui.card.climate.current_temperature", {
|
|
||||||
name: name,
|
|
||||||
})}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
if (hasHeat) {
|
|
||||||
addDataSet(
|
|
||||||
`${this.hass.localize("ui.card.climate.heating", { name: name })}`,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
computedStyles.getPropertyValue("--state-climate-heat-color")
|
|
||||||
);
|
|
||||||
// The "heating" series uses steppedArea to shade the area below the current
|
|
||||||
// temperature when the thermostat is calling for heat.
|
|
||||||
}
|
|
||||||
if (hasCool) {
|
|
||||||
addDataSet(
|
|
||||||
`${this.hass.localize("ui.card.climate.cooling", { name: name })}`,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
computedStyles.getPropertyValue("--state-climate-cool-color")
|
|
||||||
);
|
|
||||||
// The "cooling" series uses steppedArea to shade the area below the current
|
|
||||||
// temperature when the thermostat is calling for heat.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasTargetRange) {
|
|
||||||
addDataSet(
|
|
||||||
`${this.hass.localize("ui.card.climate.target_temperature_mode", {
|
|
||||||
name: name,
|
|
||||||
mode: this.hass.localize("ui.card.climate.high"),
|
|
||||||
})}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
addDataSet(
|
|
||||||
`${this.hass.localize("ui.card.climate.target_temperature_mode", {
|
|
||||||
name: name,
|
|
||||||
mode: this.hass.localize("ui.card.climate.low"),
|
|
||||||
})}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
addDataSet(
|
|
||||||
`${this.hass.localize("ui.card.climate.target_temperature_entity", {
|
|
||||||
name: name,
|
|
||||||
})}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
states.states.forEach((entityState) => {
|
|
||||||
if (!entityState.attributes) return;
|
|
||||||
const curTemp = safeParseFloat(
|
|
||||||
entityState.attributes.current_temperature
|
|
||||||
);
|
|
||||||
const series = [curTemp];
|
|
||||||
if (hasHeat) {
|
|
||||||
series.push(isHeating(entityState) ? curTemp : null);
|
|
||||||
}
|
|
||||||
if (hasCool) {
|
|
||||||
series.push(isCooling(entityState) ? curTemp : null);
|
|
||||||
}
|
|
||||||
if (hasTargetRange) {
|
|
||||||
const targetHigh = safeParseFloat(
|
|
||||||
entityState.attributes.target_temp_high
|
|
||||||
);
|
|
||||||
const targetLow = safeParseFloat(
|
|
||||||
entityState.attributes.target_temp_low
|
|
||||||
);
|
|
||||||
series.push(targetHigh, targetLow);
|
|
||||||
pushData(new Date(entityState.last_changed), series);
|
|
||||||
} else {
|
|
||||||
const target = safeParseFloat(entityState.attributes.temperature);
|
|
||||||
series.push(target);
|
|
||||||
pushData(new Date(entityState.last_changed), series);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (domain === "humidifier") {
|
|
||||||
addDataSet(
|
|
||||||
`${this.hass.localize("ui.card.humidifier.target_humidity_entity", {
|
|
||||||
name: name,
|
|
||||||
})}`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
addDataSet(
|
|
||||||
`${this.hass.localize("ui.card.humidifier.on_entity", {
|
|
||||||
name: name,
|
|
||||||
})}`,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
states.states.forEach((entityState) => {
|
|
||||||
if (!entityState.attributes) return;
|
|
||||||
const target = safeParseFloat(entityState.attributes.humidity);
|
|
||||||
const series = [target];
|
|
||||||
series.push(entityState.state === "on" ? target : null);
|
|
||||||
pushData(new Date(entityState.last_changed), series);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Only disable interpolation for sensors
|
|
||||||
const isStep = domain === "sensor";
|
|
||||||
addDataSet(name, isStep);
|
|
||||||
|
|
||||||
let lastValue: number;
|
|
||||||
let lastDate: Date;
|
|
||||||
let lastNullDate: Date | null = null;
|
|
||||||
|
|
||||||
// Process chart data.
|
|
||||||
// When state is `unknown`, calculate the value and break the line.
|
|
||||||
states.states.forEach((entityState) => {
|
|
||||||
const value = safeParseFloat(entityState.state);
|
|
||||||
const date = new Date(entityState.last_changed);
|
|
||||||
if (value !== null && lastNullDate) {
|
|
||||||
const dateTime = date.getTime();
|
|
||||||
const lastNullDateTime = lastNullDate.getTime();
|
|
||||||
const lastDateTime = lastDate?.getTime();
|
|
||||||
const tmpValue =
|
|
||||||
(value - lastValue) *
|
|
||||||
((lastNullDateTime - lastDateTime) /
|
|
||||||
(dateTime - lastDateTime)) +
|
|
||||||
lastValue;
|
|
||||||
pushData(lastNullDate, [tmpValue]);
|
|
||||||
pushData(new Date(lastNullDateTime + 1), [null]);
|
|
||||||
pushData(date, [value]);
|
|
||||||
lastDate = date;
|
|
||||||
lastValue = value;
|
|
||||||
lastNullDate = null;
|
|
||||||
} else if (value !== null && lastNullDate === null) {
|
|
||||||
pushData(date, [value]);
|
|
||||||
lastDate = date;
|
|
||||||
lastValue = value;
|
|
||||||
} else if (
|
|
||||||
value === null &&
|
|
||||||
lastNullDate === null &&
|
|
||||||
lastValue !== undefined
|
|
||||||
) {
|
|
||||||
lastNullDate = date;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an entry for final values
|
|
||||||
pushData(endTime, prevValues);
|
|
||||||
|
|
||||||
// Concat two arrays
|
|
||||||
Array.prototype.push.apply(datasets, data);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._chartData = {
|
|
||||||
datasets,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define("state-history-chart-line", StateHistoryChartLine);
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"state-history-chart-line": StateHistoryChartLine;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,322 +0,0 @@
|
|||||||
import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { getColorByIndex } from "../../common/color/colors";
|
|
||||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
|
||||||
import { numberFormatToLocale } from "../../common/string/format_number";
|
|
||||||
import { computeRTL } from "../../common/util/compute_rtl";
|
|
||||||
import { TimelineEntity } from "../../data/history";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import "./ha-chart-base";
|
|
||||||
import type { TimeLineData } from "./timeline-chart/const";
|
|
||||||
|
|
||||||
/** Binary sensor device classes for which the static colors for on/off need to be inverted.
|
|
||||||
* List the ones were "off" = good or normal state = should be rendered "green".
|
|
||||||
*/
|
|
||||||
const BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED = new Set([
|
|
||||||
"battery",
|
|
||||||
"door",
|
|
||||||
"garage_door",
|
|
||||||
"gas",
|
|
||||||
"lock",
|
|
||||||
"opening",
|
|
||||||
"problem",
|
|
||||||
"safety",
|
|
||||||
"smoke",
|
|
||||||
"window",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const STATIC_STATE_COLORS = new Set([
|
|
||||||
"on",
|
|
||||||
"off",
|
|
||||||
"home",
|
|
||||||
"not_home",
|
|
||||||
"unavailable",
|
|
||||||
"unknown",
|
|
||||||
"idle",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const stateColorMap: Map<string, string> = new Map();
|
|
||||||
|
|
||||||
let colorIndex = 0;
|
|
||||||
|
|
||||||
const invertOnOff = (entityState?: HassEntity) =>
|
|
||||||
entityState &&
|
|
||||||
computeDomain(entityState.entity_id) === "binary_sensor" &&
|
|
||||||
"device_class" in entityState.attributes &&
|
|
||||||
BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED.has(
|
|
||||||
entityState.attributes.device_class!
|
|
||||||
);
|
|
||||||
|
|
||||||
const getColor = (
|
|
||||||
stateString: string,
|
|
||||||
entityState: HassEntity,
|
|
||||||
computedStyles: CSSStyleDeclaration
|
|
||||||
) => {
|
|
||||||
if (invertOnOff(entityState)) {
|
|
||||||
stateString = stateString === "on" ? "off" : "on";
|
|
||||||
}
|
|
||||||
if (stateColorMap.has(stateString)) {
|
|
||||||
return stateColorMap.get(stateString);
|
|
||||||
}
|
|
||||||
if (STATIC_STATE_COLORS.has(stateString)) {
|
|
||||||
const color = computedStyles.getPropertyValue(
|
|
||||||
`--state-${stateString}-color`
|
|
||||||
);
|
|
||||||
stateColorMap.set(stateString, color);
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
const color = getColorByIndex(colorIndex);
|
|
||||||
colorIndex++;
|
|
||||||
stateColorMap.set(stateString, color);
|
|
||||||
return color;
|
|
||||||
};
|
|
||||||
|
|
||||||
@customElement("state-history-chart-timeline")
|
|
||||||
export class StateHistoryChartTimeline extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public data: TimelineEntity[] = [];
|
|
||||||
|
|
||||||
@property() public names: boolean | Record<string, string> = false;
|
|
||||||
|
|
||||||
@property() public unit?: string;
|
|
||||||
|
|
||||||
@property() public identifier?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public isSingleDevice = false;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public endTime?: Date;
|
|
||||||
|
|
||||||
@state() private _chartData?: ChartData<"timeline">;
|
|
||||||
|
|
||||||
@state() private _chartOptions?: ChartOptions<"timeline">;
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`
|
|
||||||
<ha-chart-base
|
|
||||||
.data=${this._chartData}
|
|
||||||
.options=${this._chartOptions}
|
|
||||||
.height=${this.data.length * 30 + 30}
|
|
||||||
chart-type="timeline"
|
|
||||||
></ha-chart-base>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._chartOptions = {
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
parsing: false,
|
|
||||||
animation: false,
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: "timeline",
|
|
||||||
position: "bottom",
|
|
||||||
adapters: {
|
|
||||||
date: {
|
|
||||||
locale: this.hass.locale,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
autoSkip: true,
|
|
||||||
maxRotation: 0,
|
|
||||||
sampleSize: 5,
|
|
||||||
autoSkipPadding: 20,
|
|
||||||
major: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
font: (context) =>
|
|
||||||
context.tick && context.tick.major
|
|
||||||
? ({ weight: "bold" } as any)
|
|
||||||
: {},
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
offset: false,
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
tooltipFormat: "datetimeseconds",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
type: "category",
|
|
||||||
barThickness: 20,
|
|
||||||
offset: true,
|
|
||||||
grid: {
|
|
||||||
display: false,
|
|
||||||
drawBorder: false,
|
|
||||||
drawTicks: false,
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
display: this.data.length !== 1,
|
|
||||||
},
|
|
||||||
afterSetDimensions: (y) => {
|
|
||||||
y.maxWidth = y.chart.width * 0.18;
|
|
||||||
},
|
|
||||||
position: computeRTL(this.hass) ? "right" : "left",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
mode: "nearest",
|
|
||||||
callbacks: {
|
|
||||||
title: (context) =>
|
|
||||||
context![0].chart!.data!.labels![
|
|
||||||
context[0].datasetIndex
|
|
||||||
] as string,
|
|
||||||
beforeBody: (context) => context[0].dataset.label || "",
|
|
||||||
label: (item) => {
|
|
||||||
const d = item.dataset.data[item.dataIndex] as TimeLineData;
|
|
||||||
return [
|
|
||||||
d.label || "",
|
|
||||||
formatDateTimeWithSeconds(d.start, this.hass.locale),
|
|
||||||
formatDateTimeWithSeconds(d.end, this.hass.locale),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
labelColor: (item) => ({
|
|
||||||
borderColor: (item.dataset.data[item.dataIndex] as TimeLineData)
|
|
||||||
.color!,
|
|
||||||
backgroundColor: (
|
|
||||||
item.dataset.data[item.dataIndex] as TimeLineData
|
|
||||||
).color!,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
filler: {
|
|
||||||
propagate: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// @ts-expect-error
|
|
||||||
locale: numberFormatToLocale(this.hass.locale),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (changedProps.has("data")) {
|
|
||||||
this._generateData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _generateData() {
|
|
||||||
const computedStyles = getComputedStyle(this);
|
|
||||||
let stateHistory = this.data;
|
|
||||||
|
|
||||||
if (!stateHistory) {
|
|
||||||
stateHistory = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = new Date(
|
|
||||||
stateHistory.reduce(
|
|
||||||
(minTime, stateInfo) =>
|
|
||||||
Math.min(minTime, new Date(stateInfo.data[0].last_changed).getTime()),
|
|
||||||
new Date().getTime()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// end time is Math.max(startTime, last_event)
|
|
||||||
let endTime =
|
|
||||||
this.endTime ||
|
|
||||||
new Date(
|
|
||||||
stateHistory.reduce(
|
|
||||||
(maxTime, stateInfo) =>
|
|
||||||
Math.max(
|
|
||||||
maxTime,
|
|
||||||
new Date(
|
|
||||||
stateInfo.data[stateInfo.data.length - 1].last_changed
|
|
||||||
).getTime()
|
|
||||||
),
|
|
||||||
startTime.getTime()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (endTime > new Date()) {
|
|
||||||
endTime = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
const labels: string[] = [];
|
|
||||||
const datasets: ChartDataset<"timeline">[] = [];
|
|
||||||
const names = this.names || {};
|
|
||||||
// stateHistory is a list of lists of sorted state objects
|
|
||||||
stateHistory.forEach((stateInfo) => {
|
|
||||||
let newLastChanged: Date;
|
|
||||||
let prevState: string | null = null;
|
|
||||||
let locState: string | null = null;
|
|
||||||
let prevLastChanged = startTime;
|
|
||||||
const entityDisplay: string =
|
|
||||||
names[stateInfo.entity_id] || stateInfo.name;
|
|
||||||
|
|
||||||
const dataRow: TimeLineData[] = [];
|
|
||||||
stateInfo.data.forEach((entityState) => {
|
|
||||||
let newState: string | null = entityState.state;
|
|
||||||
const timeStamp = new Date(entityState.last_changed);
|
|
||||||
if (!newState) {
|
|
||||||
newState = null;
|
|
||||||
}
|
|
||||||
if (timeStamp > endTime) {
|
|
||||||
// Drop datapoints that are after the requested endTime. This could happen if
|
|
||||||
// endTime is 'now' and client time is not in sync with server time.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (prevState === null) {
|
|
||||||
prevState = newState;
|
|
||||||
locState = entityState.state_localize;
|
|
||||||
prevLastChanged = new Date(entityState.last_changed);
|
|
||||||
} else if (newState !== prevState) {
|
|
||||||
newLastChanged = new Date(entityState.last_changed);
|
|
||||||
|
|
||||||
dataRow.push({
|
|
||||||
start: prevLastChanged,
|
|
||||||
end: newLastChanged,
|
|
||||||
label: locState,
|
|
||||||
color: getColor(
|
|
||||||
prevState,
|
|
||||||
this.hass.states[stateInfo.entity_id],
|
|
||||||
computedStyles
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
prevState = newState;
|
|
||||||
locState = entityState.state_localize;
|
|
||||||
prevLastChanged = newLastChanged;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (prevState !== null) {
|
|
||||||
dataRow.push({
|
|
||||||
start: prevLastChanged,
|
|
||||||
end: endTime,
|
|
||||||
label: locState,
|
|
||||||
color: getColor(
|
|
||||||
prevState,
|
|
||||||
this.hass.states[stateInfo.entity_id],
|
|
||||||
computedStyles
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
datasets.push({
|
|
||||||
data: dataRow,
|
|
||||||
label: stateInfo.entity_id,
|
|
||||||
});
|
|
||||||
labels.push(entityDisplay);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._chartData = {
|
|
||||||
labels: labels,
|
|
||||||
datasets: datasets,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
ha-chart-base {
|
|
||||||
--chart-max-height: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"state-history-chart-timeline": StateHistoryChartTimeline;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,402 +0,0 @@
|
|||||||
import type {
|
|
||||||
ChartData,
|
|
||||||
ChartDataset,
|
|
||||||
ChartOptions,
|
|
||||||
ChartType,
|
|
||||||
} from "chart.js";
|
|
||||||
import {
|
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { getColorByIndex } from "../../common/color/colors";
|
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
|
||||||
import {
|
|
||||||
formatNumber,
|
|
||||||
numberFormatToLocale,
|
|
||||||
} from "../../common/string/format_number";
|
|
||||||
import {
|
|
||||||
getStatisticIds,
|
|
||||||
Statistics,
|
|
||||||
statisticsHaveType,
|
|
||||||
StatisticsMetaData,
|
|
||||||
StatisticType,
|
|
||||||
} from "../../data/history";
|
|
||||||
import type { HomeAssistant } from "../../types";
|
|
||||||
import "./ha-chart-base";
|
|
||||||
|
|
||||||
@customElement("statistics-chart")
|
|
||||||
class StatisticsChart extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public statisticsData!: Statistics;
|
|
||||||
|
|
||||||
@property({ type: Array }) public statisticIds?: StatisticsMetaData[];
|
|
||||||
|
|
||||||
@property() public names: boolean | Record<string, string> = false;
|
|
||||||
|
|
||||||
@property() public unit?: string;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public endTime?: Date;
|
|
||||||
|
|
||||||
@property({ type: Array }) public statTypes: Array<StatisticType> = [
|
|
||||||
"sum",
|
|
||||||
"min",
|
|
||||||
"mean",
|
|
||||||
"max",
|
|
||||||
];
|
|
||||||
|
|
||||||
@property() public chartType: ChartType = "line";
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public isLoadingData = false;
|
|
||||||
|
|
||||||
@state() private _chartData: ChartData = { datasets: [] };
|
|
||||||
|
|
||||||
@state() private _chartOptions?: ChartOptions;
|
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
|
||||||
return changedProps.size > 1 || !changedProps.has("hass");
|
|
||||||
}
|
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._createOptions();
|
|
||||||
}
|
|
||||||
if (changedProps.has("statisticsData") || changedProps.has("statTypes")) {
|
|
||||||
this._generateData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
if (!isComponentLoaded(this.hass, "history")) {
|
|
||||||
return html`<div class="info">
|
|
||||||
${this.hass.localize("ui.components.history_charts.history_disabled")}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isLoadingData && !this.statisticsData) {
|
|
||||||
return html`<div class="info">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.components.statistics_charts.loading_statistics"
|
|
||||||
)}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.statisticsData || !Object.keys(this.statisticsData).length) {
|
|
||||||
return html`<div class="info">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.components.statistics_charts.no_statistics_found"
|
|
||||||
)}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-chart-base
|
|
||||||
.data=${this._chartData}
|
|
||||||
.options=${this._chartOptions}
|
|
||||||
.chartType=${this.chartType}
|
|
||||||
></ha-chart-base>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _createOptions() {
|
|
||||||
this._chartOptions = {
|
|
||||||
parsing: false,
|
|
||||||
animation: false,
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: "time",
|
|
||||||
adapters: {
|
|
||||||
date: {
|
|
||||||
locale: this.hass.locale,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
maxRotation: 0,
|
|
||||||
sampleSize: 5,
|
|
||||||
autoSkipPadding: 20,
|
|
||||||
major: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
font: (context) =>
|
|
||||||
context.tick && context.tick.major
|
|
||||||
? ({ weight: "bold" } as any)
|
|
||||||
: {},
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
tooltipFormat: "datetime",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
beginAtZero: false,
|
|
||||||
ticks: {
|
|
||||||
maxTicksLimit: 7,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: this.unit,
|
|
||||||
text: this.unit,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
tooltip: {
|
|
||||||
mode: "nearest",
|
|
||||||
callbacks: {
|
|
||||||
label: (context) =>
|
|
||||||
`${context.dataset.label}: ${formatNumber(
|
|
||||||
context.parsed.y,
|
|
||||||
this.hass.locale
|
|
||||||
)} ${
|
|
||||||
// @ts-ignore
|
|
||||||
context.dataset.unit || ""
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
filler: {
|
|
||||||
propagate: true,
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: true,
|
|
||||||
labels: {
|
|
||||||
usePointStyle: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
mode: "nearest",
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
line: {
|
|
||||||
tension: 0.4,
|
|
||||||
borderWidth: 1.5,
|
|
||||||
},
|
|
||||||
bar: { borderWidth: 1.5, borderRadius: 4 },
|
|
||||||
point: {
|
|
||||||
hitRadius: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// @ts-expect-error
|
|
||||||
locale: numberFormatToLocale(this.hass.locale),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _getStatisticIds() {
|
|
||||||
this.statisticIds = await getStatisticIds(this.hass);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _generateData() {
|
|
||||||
if (!this.statisticsData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.statisticIds) {
|
|
||||||
await this._getStatisticIds();
|
|
||||||
}
|
|
||||||
|
|
||||||
let colorIndex = 0;
|
|
||||||
const statisticsData = Object.values(this.statisticsData);
|
|
||||||
const totalDataSets: ChartDataset<"line">[] = [];
|
|
||||||
let endTime: Date;
|
|
||||||
|
|
||||||
if (statisticsData.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
endTime =
|
|
||||||
this.endTime ||
|
|
||||||
// Get the highest date from the last date of each statistic
|
|
||||||
new Date(
|
|
||||||
Math.max(
|
|
||||||
...statisticsData.map((stats) =>
|
|
||||||
new Date(stats[stats.length - 1].start).getTime()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (endTime > new Date()) {
|
|
||||||
endTime = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
let unit: string | undefined | null;
|
|
||||||
|
|
||||||
const names = this.names || {};
|
|
||||||
statisticsData.forEach((stats) => {
|
|
||||||
const firstStat = stats[0];
|
|
||||||
let name = names[firstStat.statistic_id];
|
|
||||||
if (!name) {
|
|
||||||
const entityState = this.hass.states[firstStat.statistic_id];
|
|
||||||
if (entityState) {
|
|
||||||
name = computeStateName(entityState);
|
|
||||||
} else {
|
|
||||||
name = firstStat.statistic_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const meta = this.statisticIds!.find(
|
|
||||||
(stat) => stat.statistic_id === firstStat.statistic_id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!this.unit) {
|
|
||||||
if (unit === undefined) {
|
|
||||||
unit = meta?.unit_of_measurement;
|
|
||||||
} else if (unit !== meta?.unit_of_measurement) {
|
|
||||||
unit = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// array containing [value1, value2, etc]
|
|
||||||
let prevValues: Array<number | null> | null = null;
|
|
||||||
|
|
||||||
// The datasets for the current statistic
|
|
||||||
const statDataSets: ChartDataset<"line">[] = [];
|
|
||||||
|
|
||||||
const pushData = (
|
|
||||||
timestamp: Date,
|
|
||||||
dataValues: Array<number | null> | null
|
|
||||||
) => {
|
|
||||||
if (!dataValues) return;
|
|
||||||
if (timestamp > endTime) {
|
|
||||||
// Drop datapoints that are after the requested endTime. This could happen if
|
|
||||||
// endTime is "now" and client time is not in sync with server time.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
statDataSets.forEach((d, i) => {
|
|
||||||
if (dataValues[i] === null && prevValues && prevValues[i] !== null) {
|
|
||||||
// null data values show up as gaps in the chart.
|
|
||||||
// If the current value for the dataset is null and the previous
|
|
||||||
// value of the data set is not null, then add an 'end' point
|
|
||||||
// to the chart for the previous value. Otherwise the gap will
|
|
||||||
// be too big. It will go from the start of the previous data
|
|
||||||
// value until the start of the next data value.
|
|
||||||
d.data.push({ x: timestamp.getTime(), y: prevValues[i]! });
|
|
||||||
}
|
|
||||||
d.data.push({ x: timestamp.getTime(), y: dataValues[i]! });
|
|
||||||
});
|
|
||||||
prevValues = dataValues;
|
|
||||||
};
|
|
||||||
|
|
||||||
const color = getColorByIndex(colorIndex);
|
|
||||||
colorIndex++;
|
|
||||||
|
|
||||||
const statTypes: this["statTypes"] = [];
|
|
||||||
|
|
||||||
const drawBands =
|
|
||||||
this.statTypes.includes("mean") && statisticsHaveType(stats, "mean");
|
|
||||||
|
|
||||||
const sortedTypes = drawBands
|
|
||||||
? [...this.statTypes].sort((a, b) => {
|
|
||||||
if (a === "min" || b === "max") {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a === "max" || b === "min") {
|
|
||||||
return +1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
})
|
|
||||||
: this.statTypes;
|
|
||||||
|
|
||||||
sortedTypes.forEach((type) => {
|
|
||||||
if (statisticsHaveType(stats, type)) {
|
|
||||||
const band = drawBands && (type === "min" || type === "max");
|
|
||||||
statTypes.push(type);
|
|
||||||
statDataSets.push({
|
|
||||||
label: `${name} (${this.hass.localize(
|
|
||||||
`ui.components.statistics_charts.statistic_types.${type}`
|
|
||||||
)})
|
|
||||||
`,
|
|
||||||
fill: drawBands
|
|
||||||
? type === "min"
|
|
||||||
? "+1"
|
|
||||||
: type === "max"
|
|
||||||
? "-1"
|
|
||||||
: false
|
|
||||||
: false,
|
|
||||||
borderColor: band ? color + "7F" : color,
|
|
||||||
backgroundColor: band ? color + "3F" : color + "7F",
|
|
||||||
pointRadius: 0,
|
|
||||||
data: [],
|
|
||||||
// @ts-ignore
|
|
||||||
unit: meta?.unit_of_measurement,
|
|
||||||
band,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let prevDate: Date | null = null;
|
|
||||||
// Process chart data.
|
|
||||||
let initVal: number | null = null;
|
|
||||||
let prevSum: number | null = null;
|
|
||||||
stats.forEach((stat) => {
|
|
||||||
const date = new Date(stat.start);
|
|
||||||
if (prevDate === date) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
prevDate = date;
|
|
||||||
const dataValues: Array<number | null> = [];
|
|
||||||
statTypes.forEach((type) => {
|
|
||||||
let val: number | null;
|
|
||||||
if (type === "sum") {
|
|
||||||
if (!initVal) {
|
|
||||||
initVal = val = stat.state;
|
|
||||||
prevSum = stat.sum;
|
|
||||||
} else {
|
|
||||||
val = initVal + ((stat.sum || 0) - prevSum!);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val = stat[type];
|
|
||||||
}
|
|
||||||
dataValues.push(val !== null ? Math.round(val * 100) / 100 : null);
|
|
||||||
});
|
|
||||||
pushData(date, dataValues);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add an entry for final values
|
|
||||||
pushData(endTime, prevValues);
|
|
||||||
|
|
||||||
// Concat two arrays
|
|
||||||
Array.prototype.push.apply(totalDataSets, statDataSets);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (unit !== null) {
|
|
||||||
this._chartOptions = {
|
|
||||||
...this._chartOptions,
|
|
||||||
scales: {
|
|
||||||
...this._chartOptions!.scales,
|
|
||||||
y: {
|
|
||||||
...(this._chartOptions!.scales!.y as Record<string, unknown>),
|
|
||||||
title: { display: unit, text: unit },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this._chartData = {
|
|
||||||
datasets: totalDataSets,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
min-height: 60px;
|
|
||||||
}
|
|
||||||
.info {
|
|
||||||
text-align: center;
|
|
||||||
line-height: 60px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"statistics-chart": StatisticsChart;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,18 +0,0 @@
|
|||||||
export interface TimeLineData {
|
|
||||||
start: Date;
|
|
||||||
end: Date;
|
|
||||||
label?: string | null;
|
|
||||||
color?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "chart.js" {
|
|
||||||
interface ChartTypeRegistry {
|
|
||||||
timeline: {
|
|
||||||
chartOptions: BarControllerChartOptions;
|
|
||||||
datasetOptions: BarControllerDatasetOptions;
|
|
||||||
defaultDataPoint: TimeLineData;
|
|
||||||
parsedDataType: any;
|
|
||||||
scales: "timeline";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,59 +0,0 @@
|
|||||||
import { BarElement, BarOptions, BarProps } from "chart.js";
|
|
||||||
import { hex2rgb } from "../../../common/color/convert-color";
|
|
||||||
import { luminosity } from "../../../common/color/rgb";
|
|
||||||
|
|
||||||
export interface TextBarProps extends BarProps {
|
|
||||||
text?: string | null;
|
|
||||||
options?: Partial<TextBaroptions>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TextBaroptions extends BarOptions {
|
|
||||||
textPad?: number;
|
|
||||||
textColor?: string;
|
|
||||||
backgroundColor: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TextBarElement extends BarElement {
|
|
||||||
static id = "textbar";
|
|
||||||
|
|
||||||
draw(ctx) {
|
|
||||||
super.draw(ctx);
|
|
||||||
const options = this.options as TextBaroptions;
|
|
||||||
const { x, y, base, width, text } = (
|
|
||||||
this as BarElement<TextBarProps, TextBaroptions>
|
|
||||||
).getProps(["x", "y", "base", "width", "text"]);
|
|
||||||
|
|
||||||
if (!text) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
const textRect = ctx.measureText(text);
|
|
||||||
if (
|
|
||||||
textRect.width === 0 ||
|
|
||||||
textRect.width + (options.textPad || 4) + 2 > width
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const textColor =
|
|
||||||
options.textColor ||
|
|
||||||
(options.backgroundColor &&
|
|
||||||
(luminosity(hex2rgb(options.backgroundColor)) > 0.5 ? "#000" : "#fff"));
|
|
||||||
|
|
||||||
// ctx.font = "12px arial";
|
|
||||||
ctx.fillStyle = textColor;
|
|
||||||
ctx.lineWidth = 0;
|
|
||||||
ctx.strokeStyle = textColor;
|
|
||||||
ctx.textBaseline = "middle";
|
|
||||||
ctx.fillText(
|
|
||||||
text,
|
|
||||||
x - width / 2 + (options.textPad || 4),
|
|
||||||
y + (base - y) / 2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
tooltipPosition(useFinalPosition: boolean) {
|
|
||||||
const { x, y, base } = this.getProps(["x", "y", "base"], useFinalPosition);
|
|
||||||
return { x, y: y + (base - y) / 2 };
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,160 +0,0 @@
|
|||||||
import { BarController, BarElement } from "chart.js";
|
|
||||||
import { TimeLineData } from "./const";
|
|
||||||
import { TextBarProps } from "./textbar-element";
|
|
||||||
|
|
||||||
function parseValue(entry, item, vScale, i) {
|
|
||||||
const startValue = vScale.parse(entry.start, i);
|
|
||||||
const endValue = vScale.parse(entry.end, i);
|
|
||||||
const min = Math.min(startValue, endValue);
|
|
||||||
const max = Math.max(startValue, endValue);
|
|
||||||
let barStart = min;
|
|
||||||
let barEnd = max;
|
|
||||||
|
|
||||||
if (Math.abs(min) > Math.abs(max)) {
|
|
||||||
barStart = max;
|
|
||||||
barEnd = min;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store `barEnd` (furthest away from origin) as parsed value,
|
|
||||||
// to make stacking straight forward
|
|
||||||
item[vScale.axis] = barEnd;
|
|
||||||
|
|
||||||
item._custom = {
|
|
||||||
barStart,
|
|
||||||
barEnd,
|
|
||||||
start: startValue,
|
|
||||||
end: endValue,
|
|
||||||
min,
|
|
||||||
max,
|
|
||||||
};
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TimelineController extends BarController {
|
|
||||||
static id = "timeline";
|
|
||||||
|
|
||||||
static defaults = {
|
|
||||||
dataElementType: "textbar",
|
|
||||||
dataElementOptions: ["text", "textColor", "textPadding"],
|
|
||||||
elements: {
|
|
||||||
showText: true,
|
|
||||||
textPadding: 4,
|
|
||||||
minBarWidth: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
layout: {
|
|
||||||
padding: {
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static overrides = {
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
parseObjectData(meta, data, start, count) {
|
|
||||||
const iScale = meta.iScale;
|
|
||||||
const vScale = meta.vScale;
|
|
||||||
const labels = iScale.getLabels();
|
|
||||||
const singleScale = iScale === vScale;
|
|
||||||
const parsed: any[] = [];
|
|
||||||
let i;
|
|
||||||
let ilen;
|
|
||||||
let item;
|
|
||||||
let entry;
|
|
||||||
|
|
||||||
for (i = start, ilen = start + count; i < ilen; ++i) {
|
|
||||||
entry = data[i];
|
|
||||||
item = {};
|
|
||||||
item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
|
|
||||||
parsed.push(parseValue(entry, item, vScale, i));
|
|
||||||
}
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
getLabelAndValue(index) {
|
|
||||||
const meta = this._cachedMeta;
|
|
||||||
const { vScale } = meta;
|
|
||||||
const data = this.getDataset().data[index] as TimeLineData;
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: vScale!.getLabelForValue(this.index) || "",
|
|
||||||
value: data.label || "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
updateElements(
|
|
||||||
bars: BarElement[],
|
|
||||||
start: number,
|
|
||||||
count: number,
|
|
||||||
mode: "reset" | "resize" | "none" | "hide" | "show" | "normal" | "active"
|
|
||||||
) {
|
|
||||||
const vScale = this._cachedMeta.vScale!;
|
|
||||||
const iScale = this._cachedMeta.iScale!;
|
|
||||||
const dataset = this.getDataset();
|
|
||||||
|
|
||||||
const firstOpts = this.resolveDataElementOptions(start, mode);
|
|
||||||
const sharedOptions = this.getSharedOptions(firstOpts);
|
|
||||||
const includeOptions = this.includeOptions(mode, sharedOptions!);
|
|
||||||
|
|
||||||
const horizontal = vScale.isHorizontal();
|
|
||||||
|
|
||||||
this.updateSharedOptions(sharedOptions!, mode, firstOpts);
|
|
||||||
|
|
||||||
for (let index = start; index < start + count; index++) {
|
|
||||||
const data = dataset.data[index] as TimeLineData;
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const y = vScale.getPixelForValue(this.index);
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const xStart = iScale.getPixelForValue(data.start.getTime());
|
|
||||||
// @ts-ignore
|
|
||||||
const xEnd = iScale.getPixelForValue(data.end.getTime());
|
|
||||||
const width = xEnd - xStart;
|
|
||||||
|
|
||||||
const height = 10;
|
|
||||||
|
|
||||||
const properties: TextBarProps = {
|
|
||||||
horizontal,
|
|
||||||
x: xStart + width / 2, // Center of the bar
|
|
||||||
y: y - height, // Top of bar
|
|
||||||
width,
|
|
||||||
height: 0,
|
|
||||||
base: y + height, // Bottom of bar,
|
|
||||||
// Text
|
|
||||||
text: data.label,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (includeOptions) {
|
|
||||||
properties.options =
|
|
||||||
sharedOptions || this.resolveDataElementOptions(index, mode);
|
|
||||||
|
|
||||||
properties.options = {
|
|
||||||
...properties.options,
|
|
||||||
backgroundColor: data.color,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateElement(bars[index], index, properties as any, mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
removeHoverStyle(_element, _datasetIndex, _index) {
|
|
||||||
// this._setStyle(element, index, 'active', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
setHoverStyle(_element, _datasetIndex, _index) {
|
|
||||||
// this._setStyle(element, index, 'active', true);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,55 +0,0 @@
|
|||||||
import { TimeScale } from "chart.js";
|
|
||||||
import { TimeLineData } from "./const";
|
|
||||||
|
|
||||||
export class TimeLineScale extends TimeScale {
|
|
||||||
static id = "timeline";
|
|
||||||
|
|
||||||
static defaults = {
|
|
||||||
position: "bottom",
|
|
||||||
tooltips: {
|
|
||||||
mode: "nearest",
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
autoSkip: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
determineDataLimits() {
|
|
||||||
const options = this.options;
|
|
||||||
// @ts-ignore
|
|
||||||
const adapter = this._adapter;
|
|
||||||
const unit = options.time.unit || "day";
|
|
||||||
let { min, max } = this.getUserBounds();
|
|
||||||
|
|
||||||
const chart = this.chart;
|
|
||||||
|
|
||||||
// Convert data to timestamps
|
|
||||||
chart.data.datasets.forEach((dataset, index) => {
|
|
||||||
if (!chart.isDatasetVisible(index)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const data of dataset.data as TimeLineData[]) {
|
|
||||||
let timestamp0 = adapter.parse(data.start, this);
|
|
||||||
let timestamp1 = adapter.parse(data.end, this);
|
|
||||||
if (timestamp0 > timestamp1) {
|
|
||||||
[timestamp0, timestamp1] = [timestamp1, timestamp0];
|
|
||||||
}
|
|
||||||
if (min > timestamp0 && timestamp0) {
|
|
||||||
min = timestamp0;
|
|
||||||
}
|
|
||||||
if (max < timestamp1 && timestamp1) {
|
|
||||||
max = timestamp1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// In case there is no valid min/max, var's use today limits
|
|
||||||
min =
|
|
||||||
isFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
|
|
||||||
max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit);
|
|
||||||
|
|
||||||
// Make sure that max is strictly higher than min (required by the lookup table)
|
|
||||||
this.min = Math.min(min, max - 1);
|
|
||||||
this.max = Math.max(min + 1, max);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,168 +0,0 @@
|
|||||||
export const createCurrencyListEl = () => {
|
|
||||||
const list = document.createElement("datalist");
|
|
||||||
list.id = "currencies";
|
|
||||||
for (const currency of [
|
|
||||||
"AED",
|
|
||||||
"AFN",
|
|
||||||
"ALL",
|
|
||||||
"AMD",
|
|
||||||
"ANG",
|
|
||||||
"AOA",
|
|
||||||
"ARS",
|
|
||||||
"AUD",
|
|
||||||
"AWG",
|
|
||||||
"AZN",
|
|
||||||
"BAM",
|
|
||||||
"BBD",
|
|
||||||
"BDT",
|
|
||||||
"BGN",
|
|
||||||
"BHD",
|
|
||||||
"BIF",
|
|
||||||
"BMD",
|
|
||||||
"BND",
|
|
||||||
"BOB",
|
|
||||||
"BRL",
|
|
||||||
"BSD",
|
|
||||||
"BTN",
|
|
||||||
"BWP",
|
|
||||||
"BYR",
|
|
||||||
"BZD",
|
|
||||||
"CAD",
|
|
||||||
"CDF",
|
|
||||||
"CHF",
|
|
||||||
"CLP",
|
|
||||||
"CNY",
|
|
||||||
"COP",
|
|
||||||
"CRC",
|
|
||||||
"CUP",
|
|
||||||
"CVE",
|
|
||||||
"CZK",
|
|
||||||
"DJF",
|
|
||||||
"DKK",
|
|
||||||
"DOP",
|
|
||||||
"DZD",
|
|
||||||
"EGP",
|
|
||||||
"ERN",
|
|
||||||
"ETB",
|
|
||||||
"EUR",
|
|
||||||
"FJD",
|
|
||||||
"FKP",
|
|
||||||
"GBP",
|
|
||||||
"GEL",
|
|
||||||
"GHS",
|
|
||||||
"GIP",
|
|
||||||
"GMD",
|
|
||||||
"GNF",
|
|
||||||
"GTQ",
|
|
||||||
"GYD",
|
|
||||||
"HKD",
|
|
||||||
"HNL",
|
|
||||||
"HRK",
|
|
||||||
"HTG",
|
|
||||||
"HUF",
|
|
||||||
"IDR",
|
|
||||||
"ILS",
|
|
||||||
"INR",
|
|
||||||
"IQD",
|
|
||||||
"IRR",
|
|
||||||
"ISK",
|
|
||||||
"JMD",
|
|
||||||
"JOD",
|
|
||||||
"JPY",
|
|
||||||
"KES",
|
|
||||||
"KGS",
|
|
||||||
"KHR",
|
|
||||||
"KMF",
|
|
||||||
"KPW",
|
|
||||||
"KRW",
|
|
||||||
"KWD",
|
|
||||||
"KYD",
|
|
||||||
"KZT",
|
|
||||||
"LAK",
|
|
||||||
"LBP",
|
|
||||||
"LKR",
|
|
||||||
"LRD",
|
|
||||||
"LSL",
|
|
||||||
"LTL",
|
|
||||||
"LYD",
|
|
||||||
"MAD",
|
|
||||||
"MDL",
|
|
||||||
"MGA",
|
|
||||||
"MKD",
|
|
||||||
"MMK",
|
|
||||||
"MNT",
|
|
||||||
"MOP",
|
|
||||||
"MRO",
|
|
||||||
"MUR",
|
|
||||||
"MVR",
|
|
||||||
"MWK",
|
|
||||||
"MXN",
|
|
||||||
"MYR",
|
|
||||||
"MZN",
|
|
||||||
"NAD",
|
|
||||||
"NGN",
|
|
||||||
"NIO",
|
|
||||||
"NOK",
|
|
||||||
"NPR",
|
|
||||||
"NZD",
|
|
||||||
"OMR",
|
|
||||||
"PAB",
|
|
||||||
"PEN",
|
|
||||||
"PGK",
|
|
||||||
"PHP",
|
|
||||||
"PKR",
|
|
||||||
"PLN",
|
|
||||||
"PYG",
|
|
||||||
"QAR",
|
|
||||||
"RON",
|
|
||||||
"RSD",
|
|
||||||
"RUB",
|
|
||||||
"RWF",
|
|
||||||
"SAR",
|
|
||||||
"SBD",
|
|
||||||
"SCR",
|
|
||||||
"SDG",
|
|
||||||
"SEK",
|
|
||||||
"SGD",
|
|
||||||
"SHP",
|
|
||||||
"SLL",
|
|
||||||
"SOS",
|
|
||||||
"SRD",
|
|
||||||
"SSP",
|
|
||||||
"STD",
|
|
||||||
"SYP",
|
|
||||||
"SZL",
|
|
||||||
"THB",
|
|
||||||
"TJS",
|
|
||||||
"TMT",
|
|
||||||
"TND",
|
|
||||||
"TOP",
|
|
||||||
"TRY",
|
|
||||||
"TTD",
|
|
||||||
"TWD",
|
|
||||||
"TZS",
|
|
||||||
"UAH",
|
|
||||||
"UGX",
|
|
||||||
"USD",
|
|
||||||
"UYU",
|
|
||||||
"UZS",
|
|
||||||
"VEF",
|
|
||||||
"VND",
|
|
||||||
"VUV",
|
|
||||||
"WST",
|
|
||||||
"XAF",
|
|
||||||
"XCD",
|
|
||||||
"XOF",
|
|
||||||
"XPF",
|
|
||||||
"YER",
|
|
||||||
"ZAR",
|
|
||||||
"ZMK",
|
|
||||||
"ZWL",
|
|
||||||
]) {
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = currency;
|
|
||||||
option.innerHTML = currency;
|
|
||||||
list.appendChild(option);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
};
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user