mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-11 10:19:25 +00:00
Compare commits
13 Commits
20210803.1
...
waitfor-in
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d82f87f72f | ||
![]() |
4aa759f7f5 | ||
![]() |
774393a515 | ||
![]() |
9ced09035f | ||
![]() |
6988cc6170 | ||
![]() |
56a3c8c0c0 | ||
![]() |
1519355d76 | ||
![]() |
72d086210d | ||
![]() |
f812d25024 | ||
![]() |
821be14dcb | ||
![]() |
6eac30a022 | ||
![]() |
eac156d53b | ||
![]() |
b169ae55e1 |
@@ -35,51 +35,55 @@
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"class-methods-use-this": "off",
|
||||
"new-cap": "off",
|
||||
"prefer-template": "off",
|
||||
"object-shorthand": "off",
|
||||
"func-names": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"strict": "off",
|
||||
"no-plusplus": "off",
|
||||
"no-bitwise": "error",
|
||||
"comma-dangle": "off",
|
||||
"vars-on-top": "off",
|
||||
"no-continue": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-multi-assign": "off",
|
||||
"no-console": "error",
|
||||
"radix": "off",
|
||||
"no-alert": "off",
|
||||
"no-nested-ternary": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"class-methods-use-this": 0,
|
||||
"new-cap": 0,
|
||||
"prefer-template": 0,
|
||||
"object-shorthand": 0,
|
||||
"func-names": 0,
|
||||
"prefer-arrow-callback": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"strict": 0,
|
||||
"prefer-spread": 0,
|
||||
"no-plusplus": 0,
|
||||
"no-bitwise": 2,
|
||||
"comma-dangle": 0,
|
||||
"vars-on-top": 0,
|
||||
"no-continue": 0,
|
||||
"no-param-reassign": 0,
|
||||
"no-multi-assign": 0,
|
||||
"no-console": 2,
|
||||
"radix": 0,
|
||||
"no-alert": 0,
|
||||
"no-return-await": 0,
|
||||
"no-nested-ternary": 0,
|
||||
"prefer-destructuring": 0,
|
||||
"no-restricted-globals": [2, "event"],
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"import/prefer-default-export": "off",
|
||||
"import/no-default-export": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"import/no-cycle": "off",
|
||||
"prefer-promise-reject-errors": 0,
|
||||
"import/order": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-cycle": 0,
|
||||
"import/extensions": [
|
||||
"error",
|
||||
2,
|
||||
"ignorePackages",
|
||||
{ "ts": "never", "js": "never" }
|
||||
],
|
||||
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
|
||||
"object-curly-newline": "off",
|
||||
"default-case": "off",
|
||||
"wc/no-self-class": "off",
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/camelcase": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"object-curly-newline": 0,
|
||||
"default-case": 0,
|
||||
"wc/no-self-class": 0,
|
||||
"no-shadow": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"@typescript-eslint/ban-ts-comment": 0,
|
||||
"@typescript-eslint/no-use-before-define": 0,
|
||||
"@typescript-eslint/no-non-null-assertion": 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/no-unused-vars": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types": 0,
|
||||
"@typescript-eslint/no-shadow": ["error"],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"off",
|
||||
0,
|
||||
{
|
||||
"selector": "default",
|
||||
"format": ["camelCase", "snake_case"],
|
||||
@@ -97,20 +101,9 @@
|
||||
"format": ["PascalCase"]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"unused-imports/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"vars": "all",
|
||||
"varsIgnorePattern": "^_",
|
||||
"args": "after-used",
|
||||
"argsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true
|
||||
}
|
||||
],
|
||||
"unused-imports/no-unused-imports": "error",
|
||||
"lit/attribute-value-entities": "off"
|
||||
"lit/attribute-value-entities": 0
|
||||
},
|
||||
"plugins": ["disable", "unused-imports"],
|
||||
"processor": "disable/disable"
|
||||
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
|
||||
"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
|
||||
- master
|
||||
|
||||
env:
|
||||
NODE_VERSION: 14
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
@@ -37,35 +42,51 @@ jobs:
|
||||
run: yarn run lint:types
|
||||
- name: Run prettier
|
||||
run: yarn run lint:prettier
|
||||
- name: Check for duplicate dependencies
|
||||
run: yarn dedupe --check
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Run Mocha
|
||||
run: yarn run mocha
|
||||
run: npm run mocha
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
@@ -80,11 +101,20 @@ jobs:
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
|
22
.github/workflows/demo.yaml
vendored
22
.github/workflows/demo.yaml
vendored
@@ -4,22 +4,26 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
env:
|
||||
NODE_VERSION: 14
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
|
11
.github/workflows/release.yaml
vendored
11
.github/workflows/release.yaml
vendored
@@ -7,8 +7,7 @@ on:
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: 3.8
|
||||
NODE_VERSION: 14
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
NODE_VERSION: 12.1
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -30,15 +29,7 @@ jobs:
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Download Translations
|
||||
run: ./script/translations_download
|
||||
env:
|
||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||
- name: Build and release package
|
||||
run: |
|
||||
python3 -m pip install twine
|
||||
|
42
.github/workflows/translations.yaml
vendored
42
.github/workflows/translations.yaml
vendored
@@ -1,6 +1,8 @@
|
||||
name: Translations
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
@@ -8,7 +10,7 @@ on:
|
||||
- src/translations/en.json
|
||||
|
||||
env:
|
||||
NODE_VERSION: 14
|
||||
NODE_VERSION: 12
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
@@ -18,8 +20,46 @@ jobs:
|
||||
- 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: Upload Translations
|
||||
run: |
|
||||
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
|
||||
|
||||
./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
|
||||
|
||||
# yarn
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
node_modules/*
|
||||
.yarn
|
||||
yarn-error.log
|
||||
node_modules/*
|
||||
npm-debug.log
|
||||
|
||||
# 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,
|
||||
},
|
||||
};
|
||||
};
|
@@ -18,8 +18,7 @@ module.exports.emptyPackages = ({ latestBuild }) =>
|
||||
require.resolve("@polymer/paper-styles/default-theme.js"),
|
||||
// Loads stuff from a CDN
|
||||
require.resolve("@polymer/font-roboto/roboto.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
|
||||
// Compatibility not needed for latest builds
|
||||
latestBuild &&
|
||||
// wrapped in require.resolve so it blows up if file no longer exists
|
||||
@@ -57,23 +56,12 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
"@babel/preset-env",
|
||||
{
|
||||
useBuiltIns: "entry",
|
||||
corejs: "3.15",
|
||||
bugfixes: true,
|
||||
corejs: "3.6",
|
||||
},
|
||||
],
|
||||
"@babel/preset-typescript",
|
||||
].filter(Boolean),
|
||||
plugins: [
|
||||
[
|
||||
path.resolve(
|
||||
paths.polymer_dir,
|
||||
"build-scripts/babel-plugins/inline-constants-plugin.js"
|
||||
),
|
||||
{
|
||||
modules: ["@mdi/js"],
|
||||
ignoreModuleNotFound: true,
|
||||
},
|
||||
],
|
||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
||||
!latestBuild && [
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
@@ -86,14 +74,8 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
|
||||
["@babel/plugin-proposal-private-methods", { loose: true }],
|
||||
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
|
||||
["@babel/plugin-proposal-class-properties", { loose: true }],
|
||||
].filter(Boolean),
|
||||
exclude: [
|
||||
// \\ for Windows, / for Mac OS and Linux
|
||||
/node_modules[\\/]core-js/,
|
||||
/node_modules[\\/]webpack[\\/]buildin/,
|
||||
],
|
||||
});
|
||||
|
||||
const outputPath = (outputRoot, latestBuild) =>
|
||||
|
@@ -47,8 +47,8 @@ gulp.task(
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-app",
|
||||
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
|
||||
// Don't compress running tests
|
||||
...(env.isTest() ? [] : ["compress-app"]),
|
||||
...// Don't compress running tests
|
||||
(env.isTest() ? [] : ["compress-app"]),
|
||||
gulp.parallel(
|
||||
"gen-pages-prod",
|
||||
"gen-index-app-prod",
|
||||
|
@@ -302,23 +302,15 @@ gulp.task("gen-index-hassio-prod", async () => {
|
||||
|
||||
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
|
||||
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
|
||||
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
|
||||
fs.writeFileSync(
|
||||
path.resolve(paths.hassio_output_root, "entrypoint.js"),
|
||||
`
|
||||
function loadES5() {
|
||||
try {
|
||||
new Function("import('${latestEntrypoint}')")();
|
||||
} catch (err) {
|
||||
var el = document.createElement('script');
|
||||
el.src = '${es5Entrypoint}';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
|
||||
loadES5();
|
||||
} else {
|
||||
try {
|
||||
new Function("import('${latestEntrypoint}')")();
|
||||
} catch (err) {
|
||||
loadES5();
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ encoding: "utf-8" }
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const cpx = require("cpx");
|
||||
const fs = require("fs-extra");
|
||||
const paths = require("../paths");
|
||||
|
||||
@@ -61,12 +62,9 @@ function copyLoaderJS(staticDir) {
|
||||
function copyFonts(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
// Local fonts
|
||||
fs.copySync(
|
||||
npmPath("roboto-fontface/fonts/roboto/"),
|
||||
staticPath("fonts/roboto/"),
|
||||
{
|
||||
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
|
||||
}
|
||||
cpx.copySync(
|
||||
npmPath("roboto-fontface/fonts/roboto/*.woff2"),
|
||||
staticPath("fonts/roboto")
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -86,15 +86,8 @@ const prodBuild = (conf) =>
|
||||
|
||||
gulp.task("webpack-watch-app", () => {
|
||||
// This command will run forever because we don't close compiler
|
||||
webpack(
|
||||
process.env.ES5
|
||||
? bothBuilds(createAppConfig, { isProdBuild: false })
|
||||
: createAppConfig({ isProdBuild: false, latestBuild: true })
|
||||
).watch(
|
||||
{
|
||||
ignored: /build-translations/,
|
||||
poll: isWsl,
|
||||
},
|
||||
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
|
||||
{ ignored: /build-translations/, poll: isWsl },
|
||||
doneHandler()
|
||||
);
|
||||
gulp.watch(
|
||||
|
@@ -49,16 +49,12 @@ const createWebpackConfig = ({
|
||||
test: /\.m?js$|\.ts$/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
...bundle.babelOptions({ latestBuild }),
|
||||
cacheDirectory: !isProdBuild,
|
||||
cacheCompression: false,
|
||||
},
|
||||
options: bundle.babelOptions({ latestBuild }),
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
type: "asset/source",
|
||||
use: "raw-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -70,8 +66,6 @@ const createWebpackConfig = ({
|
||||
terserOptions: bundle.terserOptions(latestBuild),
|
||||
}),
|
||||
],
|
||||
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||
},
|
||||
plugins: [
|
||||
new WebpackManifestPlugin({
|
||||
@@ -118,6 +112,16 @@ const createWebpackConfig = ({
|
||||
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
|
||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||
),
|
||||
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one
|
||||
new webpack.NormalModuleReplacementPlugin(
|
||||
new RegExp(
|
||||
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(),
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
@@ -130,13 +134,15 @@ const createWebpackConfig = ({
|
||||
},
|
||||
output: {
|
||||
filename: ({ chunk }) => {
|
||||
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
|
||||
if (!isProdBuild || dontHash.has(chunk.name)) {
|
||||
return `${chunk.name}.js`;
|
||||
}
|
||||
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
||||
},
|
||||
chunkFilename:
|
||||
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
|
||||
isProdBuild && !isStatsBuild
|
||||
? "chunk.[chunkhash].js"
|
||||
: "[name].chunk.js",
|
||||
path: outputPath,
|
||||
publicPath,
|
||||
// To silence warning in worker plugin
|
||||
|
@@ -139,7 +139,7 @@
|
||||
Your authentication credentials or Home Assistant url are never sent
|
||||
to the Cloud. You can validate this behavior in
|
||||
<a
|
||||
href="https://github.com/home-assistant/frontend/tree/dev/cast"
|
||||
href="https://github.com/home-assistant/home-assistant-polymer/tree/dev/cast"
|
||||
target="_blank"
|
||||
>the source code</a
|
||||
>.
|
||||
|
@@ -5,8 +5,8 @@ import {
|
||||
import { castContext } from "../cast_context";
|
||||
|
||||
export const castDemoLovelace: () => LovelaceConfig = () => {
|
||||
const touchSupported =
|
||||
castContext.getDeviceCapabilities().touch_input_supported;
|
||||
const touchSupported = castContext.getDeviceCapabilities()
|
||||
.touch_input_supported;
|
||||
return {
|
||||
views: [
|
||||
{
|
||||
|
@@ -113,7 +113,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
||||
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
||||
},
|
||||
state_filter: {
|
||||
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||
on:
|
||||
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||
off: "brightness(80%) saturate(0.8)",
|
||||
},
|
||||
style: {
|
||||
@@ -195,7 +196,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
||||
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
||||
},
|
||||
state_filter: {
|
||||
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||
on:
|
||||
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||
off: "brightness(80%) saturate(0.8)",
|
||||
},
|
||||
style: {
|
||||
@@ -275,7 +277,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
||||
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
||||
},
|
||||
state_filter: {
|
||||
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||
on:
|
||||
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||
off: "brightness(80%) saturate(0.8)",
|
||||
},
|
||||
style: {
|
||||
@@ -312,7 +315,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
||||
on: "/assets/arsaboo/icons/light_bulb_on.png",
|
||||
},
|
||||
state_filter: {
|
||||
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||
on:
|
||||
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
|
||||
off: "brightness(80%) saturate(0.8)",
|
||||
},
|
||||
style: {
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
import { Lovelace } from "../../../src/panels/lovelace/types";
|
||||
import { energyEntities } from "../stubs/entities";
|
||||
import { DemoConfig } from "./types";
|
||||
|
||||
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
|
||||
@@ -13,8 +12,9 @@ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
export let selectedDemoConfigIndex = 0;
|
||||
// eslint-disable-next-line import/no-mutable-exports
|
||||
export let selectedDemoConfig: Promise<DemoConfig> =
|
||||
demoConfigs[selectedDemoConfigIndex]();
|
||||
export let selectedDemoConfig: Promise<DemoConfig> = demoConfigs[
|
||||
selectedDemoConfigIndex
|
||||
]();
|
||||
|
||||
export const setDemoConfig = async (
|
||||
hass: MockHomeAssistant,
|
||||
@@ -28,7 +28,6 @@ export const setDemoConfig = async (
|
||||
selectedDemoConfig = confProm;
|
||||
|
||||
hass.addEntities(config.entities(hass.localize), true);
|
||||
hass.addEntities(energyEntities());
|
||||
lovelace.saveConfig(config.lovelace(hass.localize));
|
||||
hass.mockTheme(config.theme());
|
||||
};
|
||||
|
@@ -980,7 +980,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
|
||||
icon: "mdi:account-off",
|
||||
custom_ui_state_card: "state-card-custom-ui",
|
||||
templates: {
|
||||
icon: "if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
|
||||
icon:
|
||||
"if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
|
||||
icon_color:
|
||||
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
|
||||
},
|
||||
@@ -1004,7 +1005,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
|
||||
icon: "mdi:account-multiple-minus",
|
||||
custom_ui_state_card: "state-card-custom-ui",
|
||||
templates: {
|
||||
icon: "if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
|
||||
icon:
|
||||
"if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
|
||||
icon_color:
|
||||
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
|
||||
},
|
||||
|
@@ -19,7 +19,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@property({ attribute: false }) public hass!: MockHomeAssistant;
|
||||
|
||||
@state() private _switching = false;
|
||||
@state() private _switching?: boolean;
|
||||
|
||||
private _hidden = localStorage.hide_demo_card;
|
||||
|
||||
@@ -27,7 +27,12 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
return this._hidden ? 0 : 2;
|
||||
}
|
||||
|
||||
public setConfig(_config: LovelaceCardConfig) {}
|
||||
public setConfig(
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
config: LovelaceCardConfig
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._hidden) {
|
||||
|
@@ -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/roboto";
|
||||
import "../../src/resources/safari-14-attachshadow-patch";
|
||||
|
@@ -20,10 +20,6 @@ import { mockShoppingList } from "./stubs/shopping_list";
|
||||
import { mockSystemLog } from "./stubs/system_log";
|
||||
import { mockTemplate } from "./stubs/template";
|
||||
import { mockTranslations } from "./stubs/translations";
|
||||
import { mockEnergy } from "./stubs/energy";
|
||||
import { mockConfig } from "./stubs/config";
|
||||
import { energyEntities } from "./stubs/entities";
|
||||
import { mockForecastSolar } from "./stubs/forecast_solar";
|
||||
|
||||
class HaDemo extends HomeAssistantAppEl {
|
||||
protected async _initializeHass() {
|
||||
@@ -51,13 +47,8 @@ class HaDemo extends HomeAssistantAppEl {
|
||||
mockEvents(hass);
|
||||
mockMediaPlayer(hass);
|
||||
mockFrontend(hass);
|
||||
mockEnergy(hass);
|
||||
mockForecastSolar(hass);
|
||||
mockConfig(hass);
|
||||
mockPersistentNotification(hass);
|
||||
|
||||
hass.addEntities(energyEntities());
|
||||
|
||||
// Once config is loaded AND localize, set entities and apply theme.
|
||||
Promise.all([selectedDemoConfig, localizePromise]).then(
|
||||
([conf, localize]) => {
|
||||
|
@@ -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 { StatisticValue } from "../../../src/data/history";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
interface HistoryQueryParams {
|
||||
@@ -66,215 +64,17 @@ const generateHistory = (state, deltas) => {
|
||||
|
||||
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) => {
|
||||
mockHass.mockAPI(
|
||||
new RegExp("history/period/.+"),
|
||||
(hass, _method, path, _parameters) => {
|
||||
(
|
||||
hass,
|
||||
// @ts-ignore
|
||||
method,
|
||||
path,
|
||||
// @ts-ignore
|
||||
parameters
|
||||
) => {
|
||||
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
|
||||
const entities = params.filter_entity_id.split(",");
|
||||
|
||||
@@ -295,7 +95,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||
const numberState = Number(state.state);
|
||||
|
||||
if (isNaN(numberState)) {
|
||||
// eslint-disable-next-line no-console
|
||||
// eslint-disable-next-line
|
||||
console.log(
|
||||
"Ignoring state with unparsable state but with a unit",
|
||||
entityId,
|
||||
@@ -340,39 +140,4 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||
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>
|
||||
) => {
|
||||
hass.mockWS("lovelace/config", () =>
|
||||
Promise.all([selectedDemoConfig, localizePromise]).then(
|
||||
([config, localize]) => config.lovelace(localize)
|
||||
)
|
||||
Promise.all([
|
||||
selectedDemoConfig,
|
||||
localizePromise,
|
||||
]).then(([config, localize]) => config.lovelace(localize))
|
||||
);
|
||||
|
||||
hass.mockWS("lovelace/config/save", () => Promise.resolve());
|
||||
|
@@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
|
||||
body: { message: "Template dev tool does not work in the demo." },
|
||||
})
|
||||
);
|
||||
hass.mockWS("render_template", (msg, _hass, onChange) => {
|
||||
hass.mockWS("render_template", (msg, onChange) => {
|
||||
onChange!({
|
||||
result: msg.template,
|
||||
listeners: { all: false, domains: [], entities: [], time: false },
|
||||
|
@@ -2,12 +2,12 @@ import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/trace/hat-script-graph";
|
||||
import "../../../src/components/trace/hat-trace-timeline";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { DemoTrace } from "../data/traces/types";
|
||||
import { basicTrace } from "../data/traces/basic_trace";
|
||||
import { motionLightTrace } from "../data/traces/motion-light-trace";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
|
||||
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-switch";
|
||||
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { IntegrationManifest } from "../../../src/data/integration";
|
||||
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
@@ -17,6 +15,8 @@ import type {
|
||||
} from "../../../src/panels/config/integrations/ha-config-integrations";
|
||||
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
|
||||
import { EntityRegistryEntry } from "../../../src/data/entity_registry";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
|
||||
const createConfigEntry = (
|
||||
title: string,
|
||||
|
@@ -2,6 +2,7 @@ import "@material/mwc-button";
|
||||
import { ActionDetail } from "@material/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
|
||||
import { DEFAULT_SCHEMA, Type } from "js-yaml";
|
||||
import {
|
||||
css,
|
||||
@@ -328,6 +329,10 @@ class HassioAddonConfig extends LitElement {
|
||||
color: var(--error-color);
|
||||
margin-top: 16px;
|
||||
}
|
||||
iron-autogrow-textarea {
|
||||
width: 100%;
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
.syntaxerror {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ import "../../../../src/components/ha-card";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import {
|
||||
fetchHassioAddonDocumentation,
|
||||
HassioAddonDetails,
|
||||
@@ -13,6 +12,7 @@ import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
|
||||
@customElement("hassio-addon-documentation-tab")
|
||||
class HassioAddonDocumentationDashboard extends LitElement {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiFolderUpload } from "@mdi/js";
|
||||
import "@polymer/iron-input/iron-input";
|
||||
import "@polymer/paper-input/paper-input-container";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
|
@@ -86,7 +86,7 @@ export class HassioUpdate extends LitElement {
|
||||
"hassio/supervisor/update",
|
||||
`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(
|
||||
"Operating System",
|
||||
"os",
|
||||
|
@@ -61,6 +61,10 @@ class HassioMarkdownDialog extends LitElement {
|
||||
app-toolbar [main-title] {
|
||||
margin-left: 16px;
|
||||
}
|
||||
paper-checkbox {
|
||||
display: block;
|
||||
margin: 4px;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-paper-dialog {
|
||||
max-height: 100%;
|
||||
|
@@ -41,8 +41,7 @@ const IP_VERSIONS = ["ipv4", "ipv6"];
|
||||
@customElement("dialog-hassio-network")
|
||||
export class DialogHassioNetwork
|
||||
extends LitElement
|
||||
implements HassDialog<HassioNetworkDialogParams>
|
||||
{
|
||||
implements HassDialog<HassioNetworkDialogParams> {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
@@ -108,7 +107,7 @@ export class DialogHassioNetwork
|
||||
</mwc-icon-button>
|
||||
</ha-header-bar>
|
||||
${this._interfaces.length > 1
|
||||
? html`<mwc-tab-bar
|
||||
? html` <mwc-tab-bar
|
||||
.activeIndex=${this._curTabIndex}
|
||||
@MDCTabBar:activated=${this._handleTabActivated}
|
||||
>${this._interfaces.map(
|
||||
@@ -493,7 +492,7 @@ export class DialogHassioNetwork
|
||||
}
|
||||
|
||||
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"
|
||||
| "wep"
|
||||
| "wpa-psk";
|
||||
|
@@ -161,9 +161,9 @@ class HassioRegistriesDialog extends LitElement {
|
||||
|
||||
public focus(): void {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
(this.shadowRoot?.querySelector(
|
||||
"[dialogInitialFocus]"
|
||||
) as HTMLElement)?.focus()
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -161,9 +161,9 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
|
||||
public focus() {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
(this.shadowRoot?.querySelector(
|
||||
"[dialogInitialFocus]"
|
||||
) as HTMLElement)?.focus()
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -12,8 +12,7 @@ import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload"
|
||||
@customElement("dialog-hassio-snapshot-upload")
|
||||
export class DialogHassioSnapshotUpload
|
||||
extends LitElement
|
||||
implements HassDialog<HassioSnapshotUploadDialogParams>
|
||||
{
|
||||
implements HassDialog<HassioSnapshotUploadDialogParams> {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: HassioSnapshotUploadDialogParams;
|
||||
|
@@ -30,8 +30,7 @@ import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
||||
@customElement("dialog-hassio-snapshot")
|
||||
class HassioSnapshotDialog
|
||||
extends LitElement
|
||||
implements HassDialog<HassioSnapshotDialogParams>
|
||||
{
|
||||
implements HassDialog<HassioSnapshotDialogParams> {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _error?: string;
|
||||
@@ -298,7 +297,8 @@ class HassioSnapshotDialog
|
||||
if (window.location.href.includes("ui.nabu.casa")) {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
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",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
|
@@ -49,9 +49,9 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
|
||||
public focus(): void {
|
||||
this.updateComplete.then(() =>
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
(this.shadowRoot?.querySelector(
|
||||
"[dialogInitialFocus]"
|
||||
) as HTMLElement)?.focus()
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -121,7 +121,7 @@ export class HassioMain extends SupervisorBaseElement {
|
||||
}
|
||||
} else {
|
||||
themeName =
|
||||
(this.hass.selectedTheme as unknown as string) ||
|
||||
((this.hass.selectedTheme as unknown) as string) ||
|
||||
this.hass.themes.default_theme;
|
||||
}
|
||||
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { sanitizeUrl } from "@braintree/sanitize-url";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import { sanitizeUrl } from "@braintree/sanitize-url";
|
||||
import {
|
||||
createSearchParam,
|
||||
extractSearchParamsObject,
|
||||
} from "../../src/common/url/search-params";
|
||||
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
||||
import "../../src/layouts/hass-error-screen";
|
||||
import {
|
||||
ParamType,
|
||||
Redirect,
|
||||
Redirects,
|
||||
} from "../../src/panels/my/ha-panel-my";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
|
||||
const REDIRECTS: Redirects = {
|
||||
supervisor: {
|
||||
|
@@ -27,20 +27,26 @@ import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-subpage";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
|
||||
const STATUS_BAD_GATEWAY = 502;
|
||||
const TIMEOUT = 60000;
|
||||
|
||||
@customElement("hassio-ingress-view")
|
||||
class HassioIngressView extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property() public route!: Route;
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
@property() public ingressPanel = false;
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean }) public ingressPanel = false;
|
||||
|
||||
@state() private _addon?: HassioAddonDetails;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public narrow = false;
|
||||
@state() private _resolveIngressStatus?: number;
|
||||
|
||||
private _resolveIngressTime?: number;
|
||||
|
||||
private _sessionKeepAlive?: number;
|
||||
|
||||
@@ -51,11 +57,76 @@ class HassioIngressView extends LitElement {
|
||||
clearInterval(this._sessionKeepAlive);
|
||||
this._sessionKeepAlive = undefined;
|
||||
}
|
||||
|
||||
this._resolveIngressStatus = undefined;
|
||||
this._resolveIngressTime = undefined;
|
||||
}
|
||||
|
||||
|
||||
private async _resolveURL(addonSlug: string): Promise<void> {
|
||||
await this._fetchData(addonSlug);
|
||||
if (!this._addon) {
|
||||
window.setTimeout(async () => {
|
||||
this._resolveURL(addonSlug);
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._resolveIngressTime) {
|
||||
this._resolveIngressTime = new Date().getTime();
|
||||
}
|
||||
|
||||
if (
|
||||
this._resolveIngressStatus &&
|
||||
this._resolveIngressStatus !== STATUS_BAD_GATEWAY
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this._resolveIngressTime &&
|
||||
new Date().getTime() > this._resolveIngressTime + TIMEOUT
|
||||
) {
|
||||
await showAlertDialog(this, {
|
||||
text:
|
||||
this.hass.localize("ingress.timeout") ||
|
||||
"Timeout while waiting for add-on to start, check the add-on logs",
|
||||
title: this._addon.name,
|
||||
confirmText:
|
||||
this.hass.localize("ingress.go_to_logs") || "Go to add-on logs",
|
||||
});
|
||||
await nextRender();
|
||||
navigate(`/hassio/addon/${this._addon.slug}/logs`, { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(this._addon.ingress_url!);
|
||||
this._resolveIngressStatus = response.status;
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
}
|
||||
window.setTimeout(async () => {
|
||||
await this._resolveURL(this._addon!.slug);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._addon) {
|
||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||
if (!this._addon || this._resolveIngressStatus === STATUS_BAD_GATEWAY) {
|
||||
return html`
|
||||
<hass-loading-screen
|
||||
.narrow=${this.narrow}
|
||||
.header=${this._addon?.name}
|
||||
>
|
||||
${this._resolveIngressStatus === STATUS_BAD_GATEWAY
|
||||
? html`<p>
|
||||
${this.hass.localize("ingress.waiting") ||
|
||||
"Waiting for add-on to start"}
|
||||
</p>`
|
||||
: ""}
|
||||
</hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
const iframe = html`<iframe src=${this._addon.ingress_url!}></iframe>`;
|
||||
@@ -134,20 +205,22 @@ class HassioIngressView extends LitElement {
|
||||
const oldAddon = oldRoute ? oldRoute.path.substr(1) : undefined;
|
||||
|
||||
if (addon && addon !== oldAddon) {
|
||||
this._fetchData(addon);
|
||||
this._resolveURL(addon);
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchData(addonSlug: string) {
|
||||
const createSessionPromise = createHassioSession(this.hass);
|
||||
|
||||
let addon;
|
||||
let addon: HassioAddonDetails;
|
||||
|
||||
try {
|
||||
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
|
||||
} catch (err) {
|
||||
await showAlertDialog(this, {
|
||||
text: "Unable to fetch add-on info to start Ingress",
|
||||
text:
|
||||
this.hass.localize("ingress.unable_to_fetch") ||
|
||||
"Unable to fetch add-on info to start Ingress",
|
||||
title: "Supervisor",
|
||||
});
|
||||
await nextRender();
|
||||
@@ -157,7 +230,9 @@ class HassioIngressView extends LitElement {
|
||||
|
||||
if (!addon.ingress_url) {
|
||||
await showAlertDialog(this, {
|
||||
text: "Add-on does not support Ingress",
|
||||
text:
|
||||
this.hass.localize("ingress.no_ingress") ||
|
||||
"Add-on does not support Ingress",
|
||||
title: addon.name,
|
||||
});
|
||||
await nextRender();
|
||||
@@ -167,7 +242,9 @@ class HassioIngressView extends LitElement {
|
||||
|
||||
if (addon.state !== "started") {
|
||||
await showAlertDialog(this, {
|
||||
text: "Add-on is not running. Please start it first",
|
||||
text:
|
||||
this.hass.localize("ingress.not_running") ||
|
||||
"The add-on is not running, please start it.",
|
||||
title: addon.name,
|
||||
});
|
||||
await nextRender();
|
||||
@@ -181,7 +258,9 @@ class HassioIngressView extends LitElement {
|
||||
session = await createSessionPromise;
|
||||
} catch (err) {
|
||||
await showAlertDialog(this, {
|
||||
text: "Unable to create an Ingress session",
|
||||
text:
|
||||
this.hass.localize("ingress.unable_to_create") ||
|
||||
"Unable to create an Ingress session",
|
||||
title: addon.name,
|
||||
});
|
||||
await nextRender();
|
||||
@@ -189,16 +268,15 @@ class HassioIngressView extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._sessionKeepAlive) {
|
||||
clearInterval(this._sessionKeepAlive);
|
||||
if (!this._sessionKeepAlive) {
|
||||
this._sessionKeepAlive = window.setInterval(async () => {
|
||||
try {
|
||||
await validateHassioSession(this.hass, session);
|
||||
} catch (err) {
|
||||
session = await createHassioSession(this.hass);
|
||||
}
|
||||
}, 60000);
|
||||
}
|
||||
this._sessionKeepAlive = window.setInterval(async () => {
|
||||
try {
|
||||
await validateHassioSession(this.hass, session);
|
||||
} catch (err) {
|
||||
session = await createHassioSession(this.hass);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
this._addon = addon;
|
||||
}
|
||||
|
@@ -86,8 +86,10 @@ export class SupervisorBaseElement extends urlSyncMixin(
|
||||
const unsubs = Object.keys(this._unsubs);
|
||||
for (const collection of Object.keys(this._collections)) {
|
||||
if (!unsubs.includes(collection)) {
|
||||
this._unsubs[collection] = this._collections[collection].subscribe(
|
||||
(data) => this._updateSupervisor({ [collection]: data })
|
||||
this._unsubs[collection] = this._collections[
|
||||
collection
|
||||
].subscribe((data) =>
|
||||
this._updateSupervisor({ [collection]: data })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -113,7 +113,7 @@ class HassioHostInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
${!this.supervisor.host.features.includes("haos")
|
||||
${!this.supervisor.host.features.includes("hassos")
|
||||
? html`<ha-settings-row>
|
||||
<span slot="heading">
|
||||
${this.supervisor.localize("system.host.docker_version")}
|
||||
@@ -190,7 +190,7 @@ class HassioHostInfo extends LitElement {
|
||||
<mwc-list-item>
|
||||
${this.supervisor.localize("system.host.hardware")}
|
||||
</mwc-list-item>
|
||||
${this.supervisor.host.features.includes("haos")
|
||||
${this.supervisor.host.features.includes("hassos")
|
||||
? html`<mwc-list-item>
|
||||
${this.supervisor.localize("system.host.import_from_usb")}
|
||||
</mwc-list-item>`
|
||||
|
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
"*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
|
||||
"*.{js,ts}": "eslint --fix",
|
||||
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
|
||||
};
|
||||
|
123
package.json
123
package.json
@@ -42,63 +42,64 @@
|
||||
"@fullcalendar/daygrid": "5.1.0",
|
||||
"@fullcalendar/interaction": "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",
|
||||
"@material/chips": "12.0.0-canary.22d29cbb4.0",
|
||||
"@material/data-table": "12.0.0-canary.22d29cbb4.0",
|
||||
"@material/mwc-button": "0.22.1",
|
||||
"@material/mwc-checkbox": "0.22.1",
|
||||
"@material/mwc-circular-progress": "0.22.1",
|
||||
"@material/mwc-dialog": "0.22.1",
|
||||
"@material/mwc-fab": "0.22.1",
|
||||
"@material/mwc-formfield": "0.22.1",
|
||||
"@material/mwc-icon-button": "0.22.1",
|
||||
"@material/mwc-linear-progress": "0.22.1",
|
||||
"@material/mwc-list": "0.22.1",
|
||||
"@material/mwc-menu": "0.22.1",
|
||||
"@material/mwc-radio": "0.22.1",
|
||||
"@material/mwc-ripple": "0.22.1",
|
||||
"@material/mwc-switch": "0.22.1",
|
||||
"@material/mwc-tab": "0.22.1",
|
||||
"@material/mwc-tab-bar": "0.22.1",
|
||||
"@material/top-app-bar": "12.0.0-canary.22d29cbb4.0",
|
||||
"@lit-labs/virtualizer": "^0.6.0",
|
||||
"@material/chips": "=12.0.0-canary.1a8d06483.0",
|
||||
"@material/mwc-button": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-checkbox": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-circular-progress": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-dialog": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-fab": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-formfield": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-icon-button": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-linear-progress": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-list": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-menu": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-radio": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-ripple": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-switch": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-tab": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-tab-bar": "0.22.0-canary.cc04657a.0",
|
||||
"@material/top-app-bar": "=12.0.0-canary.1a8d06483.0",
|
||||
"@mdi/js": "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-icon": "^3.0.1",
|
||||
"@polymer/iron-input": "^3.0.1",
|
||||
"@polymer/iron-overlay-behavior": "^3.0.3",
|
||||
"@polymer/iron-overlay-behavior": "^3.0.2",
|
||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||
"@polymer/paper-checkbox": "^3.1.0",
|
||||
"@polymer/paper-dialog": "^3.0.1",
|
||||
"@polymer/paper-dialog-behavior": "^3.0.1",
|
||||
"@polymer/paper-dialog-scrollable": "^3.0.1",
|
||||
"@polymer/paper-dropdown-menu": "^3.2.0",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@polymer/paper-dropdown-menu": "^3.0.1",
|
||||
"@polymer/paper-input": "^3.0.1",
|
||||
"@polymer/paper-item": "^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-radio-button": "^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-styles": "^3.0.1",
|
||||
"@polymer/paper-tabs": "^3.1.0",
|
||||
"@polymer/paper-tabs": "^3.0.1",
|
||||
"@polymer/paper-toast": "^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",
|
||||
"@vaadin/vaadin-combo-box": "^20.0.1",
|
||||
"@vaadin/vaadin-date-picker": "^20.0.1",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.7",
|
||||
"@vibrant/color": "^3.2.1-alpha.1",
|
||||
"@vibrant/core": "^3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
|
||||
"@vue/web-component-wrapper": "^1.2.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"chart.js": "^3.3.2",
|
||||
"comlink": "^4.3.1",
|
||||
"core-js": "^3.15.2",
|
||||
"core-js": "^3.6.5",
|
||||
"cropperjs": "^1.5.11",
|
||||
"date-fns": "^2.22.1",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
@@ -106,8 +107,8 @@
|
||||
"fecha": "^4.2.0",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.0.7",
|
||||
"home-assistant-js-websocket": "^5.11.1",
|
||||
"hls.js": "^1.0.5",
|
||||
"home-assistant-js-websocket": "^5.10.0",
|
||||
"idb-keyval": "^5.0.5",
|
||||
"intl-messageformat": "^9.6.16",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -122,7 +123,7 @@
|
||||
"proxy-polyfill": "^0.3.1",
|
||||
"punycode": "^2.1.1",
|
||||
"qrcode": "^1.4.4",
|
||||
"regenerator-runtime": "^0.13.8",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"sortablejs": "^1.10.2",
|
||||
@@ -144,17 +145,17 @@
|
||||
"xss": "^1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/plugin-external-helpers": "^7.14.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-proposal-decorators": "^7.14.5",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.14.7",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
|
||||
"@babel/core": "^7.14.3",
|
||||
"@babel/plugin-external-helpers": "^7.12.13",
|
||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.13.15",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.13.8",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||
"@babel/preset-env": "^7.14.7",
|
||||
"@babel/preset-typescript": "^7.14.5",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@koa/cors": "^3.1.0",
|
||||
"@open-wc/dev-server-hmr": "^0.0.2",
|
||||
"@rollup/plugin-babel": "^5.2.1",
|
||||
@@ -171,22 +172,22 @@
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/sortablejs": "^1.10.6",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.3",
|
||||
"@typescript-eslint/parser": "^4.28.3",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"@web/dev-server": "^0.0.24",
|
||||
"@web/dev-server-rollup": "^0.2.11",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-loader": "^8.1.0",
|
||||
"chai": "^4.3.4",
|
||||
"cpx": "^1.5.0",
|
||||
"del": "^4.0.0",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint-config-airbnb-typescript": "^12.3.1",
|
||||
"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-import": "^2.23.4",
|
||||
"eslint-plugin-lit": "^1.5.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-lit": "^1.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-unused-imports": "^1.1.2",
|
||||
"eslint-plugin-wc": "^1.3.0",
|
||||
"fancy-log": "^1.3.3",
|
||||
"fs-extra": "^7.0.1",
|
||||
@@ -198,7 +199,7 @@
|
||||
"gulp-zopfli-green": "^3.0.1",
|
||||
"html-minifier": "^4.0.0",
|
||||
"husky": "^1.3.1",
|
||||
"lint-staged": "^11.0.1",
|
||||
"lint-staged": "^10.5.4",
|
||||
"lit-analyzer": "^1.2.1",
|
||||
"lodash.template": "^4.5.0",
|
||||
"magic-string": "^0.25.7",
|
||||
@@ -207,7 +208,8 @@
|
||||
"mocha": "^8.4.0",
|
||||
"object-hash": "^2.0.3",
|
||||
"open": "^7.0.4",
|
||||
"prettier": "^2.3.2",
|
||||
"prettier": "^2.0.4",
|
||||
"raw-loader": "^2.0.0",
|
||||
"require-dir": "^1.2.0",
|
||||
"rollup": "^2.8.2",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
@@ -217,22 +219,23 @@
|
||||
"sinon": "^11.0.0",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"terser-webpack-plugin": "^5.1.2",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typescript": "^4.3.5",
|
||||
"typescript": "^4.2.4",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"webpack": "^5.43.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack": "^5.24.1",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-manifest-plugin": "^3.1.1",
|
||||
"webpack-manifest-plugin": "^3.0.0",
|
||||
"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": {
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"lit-html": "2.0.0-rc.3",
|
||||
"lit-element": "3.0.0-rc.2"
|
||||
},
|
||||
|
22
script/core
22
script/core
@@ -9,6 +9,12 @@ if [ -z "${DEVCONTAINER}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -z "${CODESPACES}" ]; then
|
||||
WORKSPACE="/root/workspace/frontend"
|
||||
else
|
||||
WORKSPACE="/workspaces/frontend"
|
||||
fi
|
||||
|
||||
if [ -z $(which hass) ]; then
|
||||
echo "Installing Home Asstant core from dev."
|
||||
python3 -m pip install --upgrade \
|
||||
@@ -16,9 +22,9 @@ if [ -z $(which hass) ]; then
|
||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||
fi
|
||||
|
||||
if [ ! -d "/workspaces/frontend/config" ]; then
|
||||
if [ ! -d "${WORKSPACE}/config" ]; then
|
||||
echo "Creating default configuration."
|
||||
mkdir -p "/workspaces/frontend/config";
|
||||
mkdir -p "${WORKSPACE}/config";
|
||||
hass --script ensure_config -c config
|
||||
echo "demo:
|
||||
|
||||
@@ -26,24 +32,24 @@ logger:
|
||||
default: info
|
||||
logs:
|
||||
homeassistant.components.frontend: debug
|
||||
" >> /workspaces/frontend/config/configuration.yaml
|
||||
" >> "${WORKSPACE}/config/configuration.yaml"
|
||||
|
||||
if [ ! -z "${HASSIO}" ]; then
|
||||
echo "
|
||||
# frontend:
|
||||
# development_repo: /workspaces/frontend
|
||||
# development_repo: ${WORKSPACE}
|
||||
|
||||
hassio:
|
||||
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
|
||||
else
|
||||
echo "
|
||||
frontend:
|
||||
development_repo: /workspaces/frontend
|
||||
development_repo: ${WORKSPACE}
|
||||
|
||||
# hassio:
|
||||
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
# development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
|
||||
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(
|
||||
name="home-assistant-frontend",
|
||||
version="20210803.1",
|
||||
version="20210603.0",
|
||||
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_email="hello@home-assistant.io",
|
||||
license="Apache-2.0",
|
||||
|
@@ -59,11 +59,10 @@ export const FIXED_DEVICE_CLASS_ICONS = {
|
||||
current: "hass:current-ac",
|
||||
carbon_dioxide: "mdi:molecule-co2",
|
||||
carbon_monoxide: "mdi:molecule-co",
|
||||
energy: "hass:lightning-bolt",
|
||||
energy: "hass:flash",
|
||||
humidity: "hass:water-percent",
|
||||
illuminance: "hass:brightness-5",
|
||||
temperature: "hass:thermometer",
|
||||
monetary: "mdi:cash",
|
||||
pressure: "hass:gauge",
|
||||
power: "hass:flash",
|
||||
power_factor: "hass:angle-acute",
|
||||
|
@@ -26,9 +26,6 @@ function checkToLocaleStringSupportsOptions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
export const toLocaleDateStringSupportsOptions =
|
||||
checkToLocaleDateStringSupportsOptions();
|
||||
export const toLocaleTimeStringSupportsOptions =
|
||||
checkToLocaleTimeStringSupportsOptions();
|
||||
export const toLocaleStringSupportsOptions =
|
||||
checkToLocaleStringSupportsOptions();
|
||||
export const toLocaleDateStringSupportsOptions = checkToLocaleDateStringSupportsOptions();
|
||||
export const toLocaleTimeStringSupportsOptions = checkToLocaleTimeStringSupportsOptions();
|
||||
export const toLocaleStringSupportsOptions = checkToLocaleStringSupportsOptions();
|
||||
|
@@ -82,71 +82,67 @@ class Storage {
|
||||
|
||||
const storage = new Storage();
|
||||
|
||||
export const LocalStorage =
|
||||
(
|
||||
storageKey?: string,
|
||||
property?: boolean,
|
||||
propertyOptions?: PropertyDeclaration
|
||||
): any =>
|
||||
(clsElement: ClassElement) => {
|
||||
const key = String(clsElement.key);
|
||||
storageKey = storageKey || String(clsElement.key);
|
||||
const initVal = clsElement.initializer
|
||||
? clsElement.initializer()
|
||||
: undefined;
|
||||
export const LocalStorage = (
|
||||
storageKey?: string,
|
||||
property?: boolean,
|
||||
propertyOptions?: PropertyDeclaration
|
||||
): any => (clsElement: ClassElement) => {
|
||||
const key = String(clsElement.key);
|
||||
storageKey = storageKey || String(clsElement.key);
|
||||
const initVal = clsElement.initializer ? clsElement.initializer() : undefined;
|
||||
|
||||
storage.addFromStorage(storageKey);
|
||||
storage.addFromStorage(storageKey);
|
||||
|
||||
const subscribe = (el: ReactiveElement): UnsubscribeFunc =>
|
||||
storage.subscribeChanges(storageKey!, (oldValue) => {
|
||||
el.requestUpdate(clsElement.key, oldValue);
|
||||
});
|
||||
const subscribe = (el: ReactiveElement): UnsubscribeFunc =>
|
||||
storage.subscribeChanges(storageKey!, (oldValue) => {
|
||||
el.requestUpdate(clsElement.key, oldValue);
|
||||
});
|
||||
|
||||
const getValue = (): any =>
|
||||
storage.hasKey(storageKey!) ? storage.getValue(storageKey!) : initVal;
|
||||
const getValue = (): any =>
|
||||
storage.hasKey(storageKey!) ? storage.getValue(storageKey!) : initVal;
|
||||
|
||||
const setValue = (el: ReactiveElement, value: any) => {
|
||||
let oldValue: unknown | undefined;
|
||||
if (property) {
|
||||
oldValue = getValue();
|
||||
}
|
||||
storage.setValue(storageKey!, value);
|
||||
if (property) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
const setValue = (el: ReactiveElement, value: any) => {
|
||||
let oldValue: unknown | undefined;
|
||||
if (property) {
|
||||
oldValue = getValue();
|
||||
}
|
||||
storage.setValue(storageKey!, value);
|
||||
if (property) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@@ -1,33 +1,33 @@
|
||||
import type { LitElement } from "lit";
|
||||
import type { ClassElement } from "../../types";
|
||||
|
||||
export const restoreScroll =
|
||||
(selector: string): any =>
|
||||
(element: ClassElement) => ({
|
||||
kind: "method",
|
||||
placement: "prototype",
|
||||
key: element.key,
|
||||
descriptor: {
|
||||
set(this: LitElement, value: number) {
|
||||
this[`__${String(element.key)}`] = value;
|
||||
},
|
||||
get(this: LitElement) {
|
||||
return this[`__${String(element.key)}`];
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
export const restoreScroll = (selector: string): any => (
|
||||
element: ClassElement
|
||||
) => ({
|
||||
kind: "method",
|
||||
placement: "prototype",
|
||||
key: element.key,
|
||||
descriptor: {
|
||||
set(this: LitElement, value: number) {
|
||||
this[`__${String(element.key)}`] = value;
|
||||
},
|
||||
finisher(cls: typeof LitElement) {
|
||||
const connectedCallback = cls.prototype.connectedCallback;
|
||||
cls.prototype.connectedCallback = function () {
|
||||
connectedCallback.call(this);
|
||||
if (this[element.key]) {
|
||||
const target = this.renderRoot.querySelector(selector);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
target.scrollTop = this[element.key];
|
||||
get(this: LitElement) {
|
||||
return this[`__${String(element.key)}`];
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
finisher(cls: typeof LitElement) {
|
||||
const connectedCallback = cls.prototype.connectedCallback;
|
||||
cls.prototype.connectedCallback = function () {
|
||||
connectedCallback.call(this);
|
||||
if (this[element.key]) {
|
||||
const target = this.renderRoot.querySelector(selector);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
target.scrollTop = this[element.key];
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -21,16 +21,6 @@ export const computeStateDisplay = (
|
||||
}
|
||||
|
||||
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)} ${
|
||||
stateObj.attributes.unit_of_measurement
|
||||
}`;
|
||||
|
@@ -43,17 +43,7 @@ export const domainIcon = (
|
||||
: "hass:air-humidifier";
|
||||
|
||||
case "lock":
|
||||
switch (compareState) {
|
||||
case "unlocked":
|
||||
return "hass:lock-open";
|
||||
case "jammed":
|
||||
return "hass:lock-alert";
|
||||
case "locking":
|
||||
case "unlocking":
|
||||
return "hass:lock-clock";
|
||||
default:
|
||||
return "hass:lock";
|
||||
}
|
||||
return compareState === "unlocked" ? "hass:lock-open" : "hass:lock";
|
||||
|
||||
case "media_player":
|
||||
return compareState === "playing" ? "hass:cast-connected" : "hass:cast";
|
||||
|
@@ -4,7 +4,7 @@ import { DEFAULT_DOMAIN_ICON } from "../const";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { domainIcon } from "./domain_icon";
|
||||
|
||||
export const stateIcon = (state?: HassEntity) => {
|
||||
export const stateIcon = (state: HassEntity) => {
|
||||
if (!state) {
|
||||
return DEFAULT_DOMAIN_ICON;
|
||||
}
|
||||
|
@@ -1,2 +0,0 @@
|
||||
export const round = (value: number, precision = 2): number =>
|
||||
Math.round(value * 10 ** precision) / 10 ** precision;
|
@@ -67,11 +67,9 @@ class SearchInput extends LitElement {
|
||||
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";
|
||||
(this._input.inputElement!.parentElement!.shadowRoot!.querySelector(
|
||||
"div.unfocused-line"
|
||||
) as HTMLElement).style.display = this.noUnderline ? "none" : "block";
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,22 +1,4 @@
|
||||
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.
|
||||
@@ -27,12 +9,27 @@ export const numberFormatToLocale = (
|
||||
*/
|
||||
export const formatNumber = (
|
||||
num: string | number,
|
||||
localeOptions?: FrontendLocaleData,
|
||||
locale?: FrontendLocaleData,
|
||||
options?: Intl.NumberFormatOptions
|
||||
): string => {
|
||||
const locale = localeOptions
|
||||
? numberFormatToLocale(localeOptions)
|
||||
: undefined;
|
||||
let format: string | string[] | 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()
|
||||
Number.isNaN =
|
||||
@@ -42,13 +39,13 @@ export const formatNumber = (
|
||||
};
|
||||
|
||||
if (
|
||||
localeOptions?.number_format !== NumberFormat.none &&
|
||||
!Number.isNaN(Number(num)) &&
|
||||
Intl
|
||||
Intl &&
|
||||
locale?.number_format !== NumberFormat.none
|
||||
) {
|
||||
try {
|
||||
return new Intl.NumberFormat(
|
||||
locale,
|
||||
format,
|
||||
getDefaultFormatOptions(num, options)
|
||||
).format(Number(num));
|
||||
} catch (error) {
|
||||
@@ -61,12 +58,7 @@ export const formatNumber = (
|
||||
).format(Number(num));
|
||||
}
|
||||
}
|
||||
if (typeof num === "string") {
|
||||
return num;
|
||||
}
|
||||
return `${round(num, options?.maximumFractionDigits).toString()}${
|
||||
options?.style === "currency" ? ` ${options.currency}` : ""
|
||||
}`;
|
||||
return num.toString();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -78,10 +70,7 @@ const getDefaultFormatOptions = (
|
||||
num: string | number,
|
||||
options?: Intl.NumberFormatOptions
|
||||
): Intl.NumberFormatOptions => {
|
||||
const defaultOptions: Intl.NumberFormatOptions = {
|
||||
maximumFractionDigits: 2,
|
||||
...options,
|
||||
};
|
||||
const defaultOptions: Intl.NumberFormatOptions = options || {};
|
||||
|
||||
if (typeof num !== "string") {
|
||||
return defaultOptions;
|
||||
|
@@ -6,7 +6,6 @@
|
||||
// 3. Disallow dates based on week number.
|
||||
// 4. Disallow dates only consisting of a year.
|
||||
// https://regex101.com/r/kc5C14/3
|
||||
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)?)?)$/;
|
||||
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)?)?)$/;
|
||||
|
||||
export const isTimestamp = (input: string): boolean => regexp.test(input);
|
||||
|
@@ -64,18 +64,18 @@ class HaCallServiceButton extends EventsMixin(PolymerElement) {
|
||||
this.hass
|
||||
.callService(this.domain, this.service, this.serviceData)
|
||||
.then(
|
||||
() => {
|
||||
function () {
|
||||
el.progress = false;
|
||||
el.$.progress.actionSuccess();
|
||||
eventData.success = true;
|
||||
},
|
||||
() => {
|
||||
function () {
|
||||
el.progress = false;
|
||||
el.$.progress.actionError();
|
||||
eventData.success = false;
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
.then(function () {
|
||||
el.fire("hass-service-called", eventData);
|
||||
});
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import { customElement, property, query } from "lit/decorators";
|
||||
import "../ha-circular-progress";
|
||||
|
||||
@customElement("ha-progress-button")
|
||||
export class HaProgressButton extends LitElement {
|
||||
class HaProgressButton extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public progress = false;
|
||||
|
@@ -20,21 +20,19 @@ interface Tooltip extends TooltipModel<any> {
|
||||
export default class HaChartBase extends LitElement {
|
||||
public chart?: Chart;
|
||||
|
||||
@property({ attribute: "chart-type", reflect: true })
|
||||
@property()
|
||||
public chartType: ChartType = "line";
|
||||
|
||||
@property({ attribute: false }) public data: ChartData = { datasets: [] };
|
||||
@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;
|
||||
@property({ attribute: false })
|
||||
public options?: ChartOptions;
|
||||
|
||||
@state() private _tooltip?: Tooltip;
|
||||
|
||||
@state() private _height?: string;
|
||||
|
||||
@state() private _hiddenDatasets: Set<number> = new Set();
|
||||
|
||||
protected firstUpdated() {
|
||||
@@ -52,14 +50,11 @@ export default class HaChartBase extends LitElement {
|
||||
if (!this.hasUpdated || !this.chart) {
|
||||
return;
|
||||
}
|
||||
if (changedProps.has("plugins")) {
|
||||
this.chart.destroy();
|
||||
this._setupChart();
|
||||
return;
|
||||
}
|
||||
if (changedProps.has("chartType")) {
|
||||
|
||||
if (changedProps.has("type")) {
|
||||
this.chart.config.type = this.chartType;
|
||||
}
|
||||
|
||||
if (changedProps.has("data")) {
|
||||
this.chart.data = this.data;
|
||||
}
|
||||
@@ -72,7 +67,7 @@ export default class HaChartBase extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
${this.options?.plugins?.legend?.display === true
|
||||
? html`<div class="chartLegend">
|
||||
? html` <div class="chartLegend">
|
||||
<ul>
|
||||
${this.data.datasets.map(
|
||||
(dataset, index) => html`<li
|
||||
@@ -98,8 +93,11 @@ export default class HaChartBase extends LitElement {
|
||||
<div
|
||||
class="chartContainer"
|
||||
style=${styleMap({
|
||||
height: `${this.height ?? this._chartHeight}px`,
|
||||
overflow: this._chartHeight ? "initial" : "hidden",
|
||||
height:
|
||||
this.chartType === "timeline"
|
||||
? `${this.data.datasets.length * 30 + 30}px`
|
||||
: this._height,
|
||||
overflow: this._height ? "initial" : "hidden",
|
||||
})}
|
||||
>
|
||||
<canvas></canvas>
|
||||
@@ -135,11 +133,6 @@ export default class HaChartBase extends LitElement {
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
${this._tooltip.footer.length
|
||||
? html`<div class="footer">
|
||||
${this._tooltip.footer.map((item) => html`${item}<br />`)}
|
||||
</div>`
|
||||
: ""}
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -151,21 +144,18 @@ export default class HaChartBase extends LitElement {
|
||||
.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, {
|
||||
this.chart = new (await import("../../resources/chartjs")).Chart(ctx, {
|
||||
type: this.chartType,
|
||||
data: this.data,
|
||||
options: this._createOptions(),
|
||||
plugins: this._createPlugins(),
|
||||
plugins: [
|
||||
{
|
||||
id: "afterRenderHook",
|
||||
afterRender: (chart) => {
|
||||
this._height = `${chart.height}px`;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -187,22 +177,6 @@ export default class HaChartBase extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -254,9 +228,6 @@ export default class HaChartBase extends LitElement {
|
||||
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;
|
||||
}
|
||||
@@ -281,7 +252,7 @@ export default class HaChartBase extends LitElement {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
margin-right: 6px;
|
||||
margin-right: 4px;
|
||||
width: 16px;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
@@ -289,10 +260,9 @@ export default class HaChartBase extends LitElement {
|
||||
.chartTooltip .bullet {
|
||||
align-self: baseline;
|
||||
}
|
||||
:host([rtl]) .chartLegend .bullet,
|
||||
:host([rtl]) .chartTooltip .bullet {
|
||||
margin-right: inherit;
|
||||
margin-left: 6px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.chartTooltip {
|
||||
padding: 8px;
|
||||
@@ -324,15 +294,11 @@ export default class HaChartBase extends LitElement {
|
||||
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;
|
||||
|
@@ -2,25 +2,16 @@ 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({ type: Boolean }) public names = false;
|
||||
|
||||
@property() public unit?: string;
|
||||
|
||||
@@ -39,7 +30,7 @@ class StateHistoryChartLine extends LitElement {
|
||||
<ha-chart-base
|
||||
.data=${this._chartData}
|
||||
.options=${this._chartOptions}
|
||||
chart-type="line"
|
||||
chartType="line"
|
||||
></ha-chart-base>
|
||||
`;
|
||||
}
|
||||
@@ -88,10 +79,7 @@ class StateHistoryChartLine extends LitElement {
|
||||
mode: "nearest",
|
||||
callbacks: {
|
||||
label: (context) =>
|
||||
`${context.dataset.label}: ${formatNumber(
|
||||
context.parsed.y,
|
||||
this.hass.locale
|
||||
)} ${this.unit}`,
|
||||
`${context.dataset.label}: ${context.parsed.y} ${this.unit}`,
|
||||
},
|
||||
},
|
||||
filler: {
|
||||
@@ -116,8 +104,6 @@ class StateHistoryChartLine extends LitElement {
|
||||
hitRadius: 5,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
if (changedProps.has("data")) {
|
||||
@@ -128,23 +114,29 @@ class StateHistoryChartLine extends LitElement {
|
||||
private _generateData() {
|
||||
let colorIndex = 0;
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const entityStates = this.data;
|
||||
const deviceStates = this.data;
|
||||
const datasets: ChartDataset<"line">[] = [];
|
||||
let endTime: Date;
|
||||
|
||||
if (entityStates.length === 0) {
|
||||
if (deviceStates.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
function safeParseFloat(value) {
|
||||
const parsed = parseFloat(value);
|
||||
return isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
endTime =
|
||||
this.endTime ||
|
||||
// Get the highest date from the last date of each device
|
||||
new Date(
|
||||
Math.max(
|
||||
...entityStates.map((devSts) =>
|
||||
Math.max.apply(
|
||||
null,
|
||||
deviceStates.map((devSts) =>
|
||||
new Date(
|
||||
devSts.states[devSts.states.length - 1].last_changed
|
||||
).getTime()
|
||||
).getMilliseconds()
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -153,7 +145,7 @@ class StateHistoryChartLine extends LitElement {
|
||||
}
|
||||
|
||||
const names = this.names || {};
|
||||
entityStates.forEach((states) => {
|
||||
deviceStates.forEach((states) => {
|
||||
const domain = states.domain;
|
||||
const name = names[states.entity_id] || states.name;
|
||||
// array containing [value1, value2, etc]
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { 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";
|
||||
@@ -80,7 +79,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public data: TimelineEntity[] = [];
|
||||
|
||||
@property() public names: boolean | Record<string, string> = false;
|
||||
@property({ type: Boolean }) public names = false;
|
||||
|
||||
@property() public unit?: string;
|
||||
|
||||
@@ -99,8 +98,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
<ha-chart-base
|
||||
.data=${this._chartData}
|
||||
.options=${this._chartOptions}
|
||||
.height=${this.data.length * 30 + 30}
|
||||
chart-type="timeline"
|
||||
chartType="timeline"
|
||||
></ha-chart-base>
|
||||
`;
|
||||
}
|
||||
@@ -178,9 +176,9 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
labelColor: (item) => ({
|
||||
borderColor: (item.dataset.data[item.dataIndex] as TimeLineData)
|
||||
.color!,
|
||||
backgroundColor: (
|
||||
item.dataset.data[item.dataIndex] as TimeLineData
|
||||
).color!,
|
||||
backgroundColor: (item.dataset.data[
|
||||
item.dataIndex
|
||||
] as TimeLineData).color!,
|
||||
}),
|
||||
},
|
||||
},
|
||||
@@ -188,8 +186,6 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
propagate: true,
|
||||
},
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
};
|
||||
}
|
||||
if (changedProps.has("data")) {
|
||||
@@ -305,14 +301,6 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
datasets: datasets,
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-chart-base {
|
||||
--chart-max-height: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -10,6 +10,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { HistoryResult } from "../../data/history";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-circular-progress";
|
||||
import "./state-history-chart-line";
|
||||
import "./state-history-chart-timeline";
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -19,9 +19,10 @@ export class TextBarElement extends BarElement {
|
||||
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"]);
|
||||
const { x, y, base, width, text } = (this as BarElement<
|
||||
TextBarProps,
|
||||
TextBaroptions
|
||||
>).getProps(["x", "y", "base", "width", "text"]);
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
|
@@ -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;
|
||||
};
|
@@ -1,4 +1,4 @@
|
||||
import { Layout1d, scroll } from "@lit-labs/virtualizer";
|
||||
import { Layout1d, scroll } from "../../resources/lit-virtualizer";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import {
|
||||
css,
|
||||
@@ -10,10 +10,10 @@ import {
|
||||
} from "lit";
|
||||
import {
|
||||
customElement,
|
||||
eventOptions,
|
||||
property,
|
||||
query,
|
||||
state,
|
||||
query,
|
||||
eventOptions,
|
||||
} from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
@@ -360,8 +360,9 @@ export class HaDataTable extends LitElement {
|
||||
.rowId=${row[this.id]}
|
||||
@click=${this._handleRowClick}
|
||||
class="mdc-data-table__row ${classMap({
|
||||
"mdc-data-table__row--selected":
|
||||
this._checkedRows.includes(String(row[this.id])),
|
||||
"mdc-data-table__row--selected": this._checkedRows.includes(
|
||||
String(row[this.id])
|
||||
),
|
||||
clickable: this.clickable,
|
||||
})}"
|
||||
aria-selected=${ifDefined(
|
||||
@@ -405,15 +406,17 @@ export class HaDataTable extends LitElement {
|
||||
"mdc-data-table__cell--icon": Boolean(
|
||||
column.type === "icon"
|
||||
),
|
||||
"mdc-data-table__cell--icon-button":
|
||||
Boolean(column.type === "icon-button"),
|
||||
"mdc-data-table__cell--icon-button": Boolean(
|
||||
column.type === "icon-button"
|
||||
),
|
||||
grows: Boolean(column.grows),
|
||||
forceLTR: Boolean(column.forceLTR),
|
||||
})}"
|
||||
style=${column.width
|
||||
? styleMap({
|
||||
[column.grows ? "minWidth" : "width"]:
|
||||
column.width,
|
||||
[column.grows
|
||||
? "minWidth"
|
||||
: "width"]: column.width,
|
||||
maxWidth: column.maxWidth
|
||||
? column.maxWidth
|
||||
: "",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -39,6 +38,7 @@ import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-svg-icon";
|
||||
import "./ha-devices-picker";
|
||||
import { ComboBoxLitRenderer, comboBoxRenderer } from "lit-vaadin-helpers";
|
||||
|
||||
interface DevicesByArea {
|
||||
[areaId: string]: AreaDevices;
|
||||
@@ -52,27 +52,20 @@ interface AreaDevices {
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<AreaDevices> = (item) => html`<style>
|
||||
paper-item {
|
||||
width: 100%;
|
||||
margin: -10px 0;
|
||||
padding: 0;
|
||||
margin: -10px;
|
||||
margin-left: 0;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
mwc-icon-button {
|
||||
float: right;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding-left: 2px;
|
||||
margin-right: -2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
:host(:not([selected])) ha-svg-icon {
|
||||
.devices {
|
||||
display: none;
|
||||
}
|
||||
:host([selected]) paper-item {
|
||||
margin-left: 10px;
|
||||
.devices.visible {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||
<paper-item>
|
||||
<paper-item-body two-line="">
|
||||
<div class="name">${item.name}</div>
|
||||
|
@@ -11,8 +11,6 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
import { mdiCheck } from "@mdi/js";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { compare } from "../../common/string/compare";
|
||||
@@ -35,6 +33,7 @@ import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
|
||||
interface Device {
|
||||
name: string;
|
||||
@@ -48,27 +47,10 @@ export type HaDevicePickerDeviceFilterFunc = (
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<Device> = (item) => html`<style>
|
||||
paper-item {
|
||||
margin: -10px 0;
|
||||
padding: 0;
|
||||
margin: -10px;
|
||||
margin-left: 0;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding-left: 2px;
|
||||
margin-right: -2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
:host(:not([selected])) ha-svg-icon {
|
||||
display: none;
|
||||
}
|
||||
:host([selected]) paper-item {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||
<paper-item>
|
||||
<paper-item-body two-line>
|
||||
${item.name}
|
||||
|
@@ -12,7 +12,7 @@ import type { HaEntityPickerEntityFilterFunc } from "./ha-entity-picker";
|
||||
class HaEntitiesPickerLight extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Array }) public value?: string[];
|
||||
@property() public value?: string[];
|
||||
|
||||
/**
|
||||
* Show entities from specific domains.
|
||||
@@ -30,22 +30,6 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
@property({ type: Array, attribute: "exclude-domains" })
|
||||
public excludeDomains?: string[];
|
||||
|
||||
/**
|
||||
* Show only entities of these device classes.
|
||||
* @type {Array}
|
||||
* @attr include-device-classes
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
/**
|
||||
* Show only entities with these unit of measuments.
|
||||
* @type {Array}
|
||||
* @attr include-unit-of-measurement
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-unit-of-measurement" })
|
||||
public includeUnitOfMeasurement?: string[];
|
||||
|
||||
@property({ attribute: "picked-entity-label" })
|
||||
public pickedEntityLabel?: string;
|
||||
|
||||
@@ -67,8 +51,6 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||
.entityFilter=${this._entityFilter}
|
||||
.value=${entityId}
|
||||
.label=${this.pickedEntityLabel}
|
||||
@@ -82,8 +64,6 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||
.entityFilter=${this._entityFilter}
|
||||
.label=${this.pickEntityLabel}
|
||||
@value-changed=${this._addEntity}
|
||||
@@ -101,11 +81,11 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
}
|
||||
|
||||
private async _updateEntities(entities) {
|
||||
this.value = entities;
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: entities,
|
||||
});
|
||||
|
||||
this.value = entities;
|
||||
}
|
||||
|
||||
private _entityChanged(event: PolymerChangedEvent<string>) {
|
||||
@@ -118,22 +98,20 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const currentEntities = this._currentEntities;
|
||||
if (!newValue || currentEntities.includes(newValue)) {
|
||||
this._updateEntities(currentEntities.filter((ent) => ent !== curValue));
|
||||
return;
|
||||
if (newValue === "") {
|
||||
this._updateEntities(
|
||||
this._currentEntities.filter((ent) => ent !== curValue)
|
||||
);
|
||||
} else {
|
||||
this._updateEntities(
|
||||
this._currentEntities.map((ent) => (ent === curValue ? newValue : ent))
|
||||
);
|
||||
}
|
||||
this._updateEntities(
|
||||
currentEntities.map((ent) => (ent === curValue ? newValue : ent))
|
||||
);
|
||||
}
|
||||
|
||||
private async _addEntity(event: PolymerChangedEvent<string>) {
|
||||
event.stopPropagation();
|
||||
const toAdd = event.detail.value;
|
||||
if (!toAdd) {
|
||||
return;
|
||||
}
|
||||
(event.currentTarget as any).value = "";
|
||||
if (!toAdd) {
|
||||
return;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
||||
@@ -25,27 +25,10 @@ export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
|
||||
paper-item {
|
||||
margin: -5px -10px;
|
||||
padding: 0;
|
||||
margin: -10px;
|
||||
margin-left: 0;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding-left: 2px;
|
||||
margin-right: -2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
:host(:not([selected])) ha-svg-icon {
|
||||
display: none;
|
||||
}
|
||||
:host([selected]) paper-item {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||
<paper-item>${formatAttributeName(item)}</paper-item>`;
|
||||
|
||||
@customElement("ha-entity-attribute-picker")
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@@ -28,25 +28,10 @@ export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<HassEntity> = (item) => html`<style>
|
||||
paper-icon-item {
|
||||
margin: -10px;
|
||||
padding: 0;
|
||||
margin: -8px;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding-left: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
:host(:not([selected])) ha-svg-icon {
|
||||
display: none;
|
||||
}
|
||||
:host([selected]) paper-icon-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||
<paper-icon-item>
|
||||
<state-badge slot="item-icon" .stateObj=${item}></state-badge>
|
||||
<paper-item-body two-line="">
|
||||
@@ -57,8 +42,6 @@ const rowRenderer: ComboBoxLitRenderer<HassEntity> = (item) => html`<style>
|
||||
|
||||
@customElement("ha-entity-picker")
|
||||
export class HaEntityPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
@@ -66,6 +49,8 @@ export class HaEntityPicker extends LitElement {
|
||||
@property({ type: Boolean, attribute: "allow-custom-entity" })
|
||||
public allowCustomEntity;
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string;
|
||||
@@ -94,14 +79,6 @@ export class HaEntityPicker extends LitElement {
|
||||
@property({ type: Array, attribute: "include-device-classes" })
|
||||
public includeDeviceClasses?: string[];
|
||||
|
||||
/**
|
||||
* Show only entities with these unit of measuments.
|
||||
* @type {Array}
|
||||
* @attr include-unit-of-measurement
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-unit-of-measurement" })
|
||||
public includeUnitOfMeasurement?: string[];
|
||||
|
||||
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||
|
||||
@property({ type: Boolean }) public hideClearIcon = false;
|
||||
@@ -133,8 +110,7 @@ export class HaEntityPicker extends LitElement {
|
||||
includeDomains: this["includeDomains"],
|
||||
excludeDomains: this["excludeDomains"],
|
||||
entityFilter: this["entityFilter"],
|
||||
includeDeviceClasses: this["includeDeviceClasses"],
|
||||
includeUnitOfMeasurement: this["includeUnitOfMeasurement"]
|
||||
includeDeviceClasses: this["includeDeviceClasses"]
|
||||
) => {
|
||||
let states: HassEntity[] = [];
|
||||
|
||||
@@ -167,18 +143,6 @@ export class HaEntityPicker extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
if (includeUnitOfMeasurement) {
|
||||
states = states.filter(
|
||||
(stateObj) =>
|
||||
// We always want to include the entity of the current value
|
||||
stateObj.entity_id === this.value ||
|
||||
(stateObj.attributes.unit_of_measurement &&
|
||||
includeUnitOfMeasurement.includes(
|
||||
stateObj.attributes.unit_of_measurement
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
if (entityFilter) {
|
||||
states = states.filter(
|
||||
(stateObj) =>
|
||||
@@ -220,7 +184,7 @@ export class HaEntityPicker extends LitElement {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (!this._initedStates || (changedProps.has("_opened") && this._opened)) {
|
||||
this._states = this._getStates(
|
||||
this._opened,
|
||||
@@ -228,24 +192,23 @@ export class HaEntityPicker extends LitElement {
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
this.entityFilter,
|
||||
this.includeDeviceClasses,
|
||||
this.includeUnitOfMeasurement
|
||||
this.includeDeviceClasses
|
||||
);
|
||||
if (this._initedStates) {
|
||||
(this.comboBox as any).filteredItems = this._states;
|
||||
}
|
||||
(this.comboBox as any).filteredItems = this._states;
|
||||
this._initedStates = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
item-value-path="entity_id"
|
||||
item-label-path="entity_id"
|
||||
.value=${this._value}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.filteredItems=${this._states}
|
||||
${comboBoxRenderer(rowRenderer)}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
|
@@ -1,289 +0,0 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiCheck } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { getStatisticIds, StatisticsMetaData } from "../../data/history";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import "../ha-combo-box";
|
||||
import type { HaComboBox } from "../ha-combo-box";
|
||||
import "../ha-svg-icon";
|
||||
import "./state-badge";
|
||||
|
||||
@customElement("ha-statistic-picker")
|
||||
export class HaStatisticPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property({ attribute: "statistic-types" })
|
||||
public statisticTypes?: "mean" | "sum";
|
||||
|
||||
@property({ type: Array }) public statisticIds?: StatisticsMetaData[];
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
/**
|
||||
* Show only statistics with these unit of measuments.
|
||||
* @type {Array}
|
||||
* @attr include-unit-of-measurement
|
||||
*/
|
||||
@property({ type: Array, attribute: "include-unit-of-measurement" })
|
||||
public includeUnitOfMeasurement?: string[];
|
||||
|
||||
/**
|
||||
* Show only statistics on entities.
|
||||
* @type {Boolean}
|
||||
* @attr entities-only
|
||||
*/
|
||||
@property({ type: Boolean, attribute: "entities-only" })
|
||||
public entitiesOnly = false;
|
||||
|
||||
@state() private _opened?: boolean;
|
||||
|
||||
@query("ha-combo-box", true) public comboBox!: HaComboBox;
|
||||
|
||||
private _init = false;
|
||||
|
||||
private _rowRenderer: ComboBoxLitRenderer<{
|
||||
id: string;
|
||||
name: string;
|
||||
state?: HassEntity;
|
||||
}> = (item) => html`<style>
|
||||
paper-icon-item {
|
||||
padding: 0;
|
||||
margin: -8px;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding-left: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
:host(:not([selected])) ha-svg-icon {
|
||||
display: none;
|
||||
}
|
||||
:host([selected]) paper-icon-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||
<paper-icon-item>
|
||||
<state-badge slot="item-icon" .stateObj=${item.state}></state-badge>
|
||||
<paper-item-body two-line="">
|
||||
${item.name}
|
||||
<span secondary
|
||||
>${item.id === "" || item.id === "__missing"
|
||||
? html`<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="${documentationUrl(this.hass, "/more-info/statistics/")}"
|
||||
>${this.hass.localize(
|
||||
"ui.components.statistic-picker.learn_more"
|
||||
)}</a
|
||||
>`
|
||||
: item.id}</span
|
||||
>
|
||||
</paper-item-body>
|
||||
</paper-icon-item>`;
|
||||
|
||||
private _getStatistics = memoizeOne(
|
||||
(
|
||||
statisticIds: StatisticsMetaData[],
|
||||
includeUnitOfMeasurement?: string[],
|
||||
entitiesOnly?: boolean
|
||||
): Array<{ id: string; name: string; state?: HassEntity }> => {
|
||||
if (!statisticIds.length) {
|
||||
return [
|
||||
{
|
||||
id: "",
|
||||
name: this.hass.localize(
|
||||
"ui.components.statistic-picker.no_statistics"
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (includeUnitOfMeasurement) {
|
||||
statisticIds = statisticIds.filter((meta) =>
|
||||
includeUnitOfMeasurement.includes(meta.unit_of_measurement)
|
||||
);
|
||||
}
|
||||
|
||||
const output: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
state?: HassEntity;
|
||||
}> = [];
|
||||
statisticIds.forEach((meta) => {
|
||||
const entityState = this.hass.states[meta.statistic_id];
|
||||
if (!entityState) {
|
||||
if (!entitiesOnly) {
|
||||
output.push({ id: meta.statistic_id, name: meta.statistic_id });
|
||||
}
|
||||
return;
|
||||
}
|
||||
output.push({
|
||||
id: meta.statistic_id,
|
||||
name: computeStateName(entityState),
|
||||
state: entityState,
|
||||
});
|
||||
});
|
||||
|
||||
if (!output.length) {
|
||||
return [
|
||||
{
|
||||
id: "",
|
||||
name: this.hass.localize("ui.components.statistic-picker.no_match"),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (output.length > 1) {
|
||||
output.sort((a, b) => compare(a.name || "", b.name || ""));
|
||||
}
|
||||
|
||||
output.push({
|
||||
id: "__missing",
|
||||
name: this.hass.localize(
|
||||
"ui.components.statistic-picker.missing_entity"
|
||||
),
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
);
|
||||
|
||||
public open() {
|
||||
this.comboBox?.open();
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this.comboBox?.focus();
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
if (
|
||||
(!this.hasUpdated && !this.statisticIds) ||
|
||||
changedProps.has("statisticTypes")
|
||||
) {
|
||||
this._getStatisticIds();
|
||||
}
|
||||
if (
|
||||
(!this._init && this.statisticIds) ||
|
||||
(changedProps.has("_opened") && this._opened)
|
||||
) {
|
||||
this._init = true;
|
||||
if (this.hasUpdated) {
|
||||
(this.comboBox as any).items = this._getStatistics(
|
||||
this.statisticIds!,
|
||||
this.includeUnitOfMeasurement,
|
||||
this.entitiesOnly
|
||||
);
|
||||
} else {
|
||||
this.updateComplete.then(() => {
|
||||
(this.comboBox as any).items = this._getStatistics(
|
||||
this.statisticIds!,
|
||||
this.includeUnitOfMeasurement,
|
||||
this.entitiesOnly
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.label=${this.label === undefined && this.hass
|
||||
? this.hass.localize("ui.components.statistic-picker.statistic")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.renderer=${this._rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
item-value-path="id"
|
||||
item-id-path="id"
|
||||
item-label-path="name"
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._statisticChanged}
|
||||
></ha-combo-box>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _getStatisticIds() {
|
||||
this.statisticIds = await getStatisticIds(this.hass, this.statisticTypes);
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
private _statisticChanged(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
let newValue = ev.detail.value;
|
||||
if (newValue === "__missing") {
|
||||
newValue = "";
|
||||
}
|
||||
|
||||
if (newValue !== this._value) {
|
||||
this._setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
setTimeout(() => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "change");
|
||||
}, 0);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
paper-input > mwc-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-statistic-picker": HaStatisticPicker;
|
||||
}
|
||||
}
|
@@ -1,112 +0,0 @@
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { PolymerChangedEvent } from "../../polymer-types";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "./ha-statistic-picker";
|
||||
|
||||
@customElement("ha-statistics-picker")
|
||||
class HaStatisticsPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Array }) public value?: string[];
|
||||
|
||||
@property({ type: Array }) public statisticIds?: string[];
|
||||
|
||||
@property({ attribute: "statistic-types" })
|
||||
public statisticTypes?: "mean" | "sum";
|
||||
|
||||
@property({ attribute: "picked-statistic-label" })
|
||||
public pickedStatisticLabel?: string;
|
||||
|
||||
@property({ attribute: "pick-statistic-label" })
|
||||
public pickStatisticLabel?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this._currentStatistics.map(
|
||||
(statisticId) => html`
|
||||
<div>
|
||||
<ha-statistic-picker
|
||||
.curValue=${statisticId}
|
||||
.hass=${this.hass}
|
||||
.value=${statisticId}
|
||||
.statisticTypes=${this.statisticTypes}
|
||||
.statisticIds=${this.statisticIds}
|
||||
.label=${this.pickedStatisticLabel}
|
||||
@value-changed=${this._statisticChanged}
|
||||
></ha-statistic-picker>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<div>
|
||||
<ha-statistic-picker
|
||||
.hass=${this.hass}
|
||||
.statisticTypes=${this.statisticTypes}
|
||||
.statisticIds=${this.statisticIds}
|
||||
.label=${this.pickStatisticLabel}
|
||||
@value-changed=${this._addStatistic}
|
||||
></ha-statistic-picker>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _currentStatistics() {
|
||||
return this.value || [];
|
||||
}
|
||||
|
||||
private async _updateStatistics(entities) {
|
||||
this.value = entities;
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: entities,
|
||||
});
|
||||
}
|
||||
|
||||
private _statisticChanged(event: PolymerChangedEvent<string>) {
|
||||
event.stopPropagation();
|
||||
const oldValue = (event.currentTarget as any).curValue;
|
||||
const newValue = event.detail.value;
|
||||
if (newValue === oldValue) {
|
||||
return;
|
||||
}
|
||||
const currentStatistics = this._currentStatistics;
|
||||
if (!newValue || currentStatistics.includes(newValue)) {
|
||||
this._updateStatistics(
|
||||
currentStatistics.filter((ent) => ent !== oldValue)
|
||||
);
|
||||
return;
|
||||
}
|
||||
this._updateStatistics(
|
||||
currentStatistics.map((ent) => (ent === oldValue ? newValue : ent))
|
||||
);
|
||||
}
|
||||
|
||||
private async _addStatistic(event: PolymerChangedEvent<string>) {
|
||||
event.stopPropagation();
|
||||
const toAdd = event.detail.value;
|
||||
if (!toAdd) {
|
||||
return;
|
||||
}
|
||||
(event.currentTarget as any).value = "";
|
||||
if (!toAdd) {
|
||||
return;
|
||||
}
|
||||
const currentEntities = this._currentStatistics;
|
||||
if (currentEntities.includes(toAdd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateStatistics([...currentEntities, toAdd]);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-statistics-picker": HaStatisticsPicker;
|
||||
}
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
import { mdiCheck } from "@mdi/js";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { compare } from "../common/string/compare";
|
||||
@@ -11,33 +9,14 @@ import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { HaComboBox } from "./ha-combo-box";
|
||||
import { ComboBoxLitRenderer } from "lit-vaadin-helpers";
|
||||
|
||||
const rowRenderer: ComboBoxLitRenderer<HassioAddonInfo> = (item) => html`<style>
|
||||
paper-item {
|
||||
margin: -10px 0;
|
||||
padding: 0;
|
||||
margin: -10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
:host([selected]) paper-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding-left: 2px;
|
||||
margin-right: -2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
:host(:not([selected])) ha-svg-icon {
|
||||
display: none;
|
||||
}
|
||||
:host([selected]) paper-icon-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||
<paper-item>
|
||||
<paper-item-body two-line>
|
||||
${item.name}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@@ -48,27 +48,13 @@ const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
|
||||
item
|
||||
) => html`<style>
|
||||
paper-item {
|
||||
margin: -10px 0;
|
||||
padding: 0;
|
||||
margin: -10px;
|
||||
margin-left: 0;
|
||||
}
|
||||
#content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding-left: 2px;
|
||||
margin-right: -2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
:host(:not([selected])) ha-svg-icon {
|
||||
display: none;
|
||||
}
|
||||
:host([selected]) paper-item {
|
||||
margin-left: 10px;
|
||||
paper-item.add-new {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
|
||||
<paper-item class=${classMap({ "add-new": item.area_id === "add_new" })}>
|
||||
<paper-item-body two-line>${item.name}</paper-item-body>
|
||||
</paper-item>`;
|
||||
|
@@ -2,7 +2,6 @@ import "@material/mwc-menu";
|
||||
import type { Corner, Menu } from "@material/mwc-menu";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
|
||||
@customElement("ha-button-menu")
|
||||
export class HaButtonMenu extends LitElement {
|
||||
@property() public corner: Corner = "TOP_START";
|
||||
|
@@ -1,15 +1,12 @@
|
||||
import { Dialog } from "@material/mwc-dialog";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, TemplateResult } from "lit";
|
||||
import { css, CSSResultGroup, html } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
|
||||
export const createCloseHeading = (
|
||||
hass: HomeAssistant,
|
||||
title: string | TemplateResult
|
||||
) => html`
|
||||
export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||
<span class="header_title">${title}</span>
|
||||
<mwc-icon-button
|
||||
aria-label=${hass.localize("ui.dialogs.generic.close")}
|
||||
|
@@ -43,9 +43,9 @@ export class HaFileUpload extends LitElement {
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("_drag") && !this.uploading) {
|
||||
(
|
||||
this.shadowRoot!.querySelector("paper-input-container") as any
|
||||
)._setFocused(this._drag);
|
||||
(this.shadowRoot!.querySelector(
|
||||
"paper-input-container"
|
||||
) as any)._setFocused(this._drag);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-slider/paper-slider";
|
||||
import type { PaperSliderElement } from "@polymer/paper-slider/paper-slider";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { HaCheckbox } from "../ha-checkbox";
|
||||
import "../ha-slider";
|
||||
import type { HaSlider } from "../ha-slider";
|
||||
import {
|
||||
HaFormElement,
|
||||
HaFormIntegerData,
|
||||
@@ -87,7 +88,9 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const value = Number((ev.target as PaperInputElement | HaSlider).value);
|
||||
const value = Number(
|
||||
(ev.target as PaperInputElement | PaperSliderElement).value
|
||||
);
|
||||
if (this._value === value) {
|
||||
return;
|
||||
}
|
||||
|
@@ -94,9 +94,8 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
||||
|
||||
protected firstUpdated() {
|
||||
this.updateComplete.then(() => {
|
||||
const input = (
|
||||
this.shadowRoot?.querySelector("paper-input")?.inputElement as any
|
||||
)?.inputElement;
|
||||
const input = (this.shadowRoot?.querySelector("paper-input")
|
||||
?.inputElement as any)?.inputElement;
|
||||
if (input) {
|
||||
input.style.textOverflow = "ellipsis";
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { Formfield } from "@material/mwc-formfield";
|
||||
import { css, CSSResultGroup } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-formfield")
|
||||
// @ts-expect-error
|
||||
export class HaFormfield extends Formfield {
|
||||
|
@@ -6,18 +6,15 @@ import { formatNumber } from "../common/string/format_number";
|
||||
import { afterNextRender } from "../common/util/render-status";
|
||||
import { FrontendLocaleData } from "../data/translation";
|
||||
import { getValueInPercentage, normalize } from "../util/calculate";
|
||||
import { isSafari } from "../util/is_safari";
|
||||
|
||||
// Workaround for https://github.com/home-assistant/frontend/issues/6467
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
const getAngle = (value: number, min: number, max: number) => {
|
||||
const percentage = getValueInPercentage(normalize(value, min, max), min, max);
|
||||
return (percentage * 180) / 100;
|
||||
};
|
||||
|
||||
export interface LevelDefinition {
|
||||
level: number;
|
||||
stroke: string;
|
||||
}
|
||||
|
||||
@customElement("ha-gauge")
|
||||
export class Gauge extends LitElement {
|
||||
@property({ type: Number }) public min = 0;
|
||||
@@ -26,14 +23,8 @@ export class Gauge extends LitElement {
|
||||
|
||||
@property({ type: Number }) public value = 0;
|
||||
|
||||
@property({ type: String }) public valueText?: string;
|
||||
|
||||
@property() public locale!: FrontendLocaleData;
|
||||
|
||||
@property({ type: Boolean }) public needle?: boolean;
|
||||
|
||||
@property() public levels?: LevelDefinition[];
|
||||
|
||||
@property() public label = "";
|
||||
|
||||
@state() private _angle = 0;
|
||||
@@ -62,63 +53,23 @@ export class Gauge extends LitElement {
|
||||
protected render() {
|
||||
return svg`
|
||||
<svg viewBox="0 0 100 50" class="gauge">
|
||||
${
|
||||
!this.needle || !this.levels
|
||||
? svg`<path
|
||||
<path
|
||||
class="dial"
|
||||
d="M 10 50 A 40 40 0 0 1 90 50"
|
||||
></path>`
|
||||
: ""
|
||||
}
|
||||
|
||||
></path>
|
||||
<path
|
||||
class="value"
|
||||
d="M 90 50.001 A 40 40 0 0 1 10 50"
|
||||
style=${ifDefined(
|
||||
!isSafari
|
||||
? styleMap({ transform: `rotate(${this._angle}deg)` })
|
||||
: undefined
|
||||
)}
|
||||
transform=${ifDefined(
|
||||
isSafari ? `rotate(${this._angle} 50 50)` : undefined
|
||||
)}
|
||||
>
|
||||
${
|
||||
this.levels
|
||||
? this.levels
|
||||
.sort((a, b) => a.level - b.level)
|
||||
.map((level) => {
|
||||
const angle = getAngle(level.level, this.min, this.max);
|
||||
return svg`<path
|
||||
stroke="${level.stroke}"
|
||||
class="level"
|
||||
d="M
|
||||
${50 - 40 * Math.cos((angle * Math.PI) / 180)}
|
||||
${50 - 40 * Math.sin((angle * Math.PI) / 180)}
|
||||
A 40 40 0 0 1 90 50
|
||||
"
|
||||
></path>`;
|
||||
})
|
||||
: ""
|
||||
}
|
||||
${
|
||||
this.needle
|
||||
? svg`<path
|
||||
class="needle"
|
||||
d="M 25 47.5 L 2.5 50 L 25 52.5 z"
|
||||
style=${ifDefined(
|
||||
!isSafari
|
||||
? styleMap({ transform: `rotate(${this._angle}deg)` })
|
||||
: undefined
|
||||
)}
|
||||
transform=${ifDefined(
|
||||
isSafari ? `rotate(${this._angle} 50 50)` : undefined
|
||||
)}
|
||||
>
|
||||
`
|
||||
: svg`<path
|
||||
class="value"
|
||||
d="M 90 50.001 A 40 40 0 0 1 10 50"
|
||||
style=${ifDefined(
|
||||
!isSafari
|
||||
? styleMap({ transform: `rotate(${this._angle}deg)` })
|
||||
: undefined
|
||||
)}
|
||||
transform=${ifDefined(
|
||||
isSafari ? `rotate(${this._angle} 50 50)` : undefined
|
||||
)}
|
||||
>`
|
||||
}
|
||||
${
|
||||
// Workaround for https://github.com/home-assistant/frontend/issues/6467
|
||||
isSafari
|
||||
? svg`<animateTransform
|
||||
attributeName="transform"
|
||||
@@ -133,9 +84,7 @@ export class Gauge extends LitElement {
|
||||
</svg>
|
||||
<svg class="text">
|
||||
<text class="value-text">
|
||||
${this.valueText || formatNumber(this.value, this.locale)} ${
|
||||
this.label
|
||||
}
|
||||
${formatNumber(this.value, this.locale)} ${this.label}
|
||||
</text>
|
||||
</svg>`;
|
||||
}
|
||||
@@ -169,15 +118,6 @@ export class Gauge extends LitElement {
|
||||
transform-origin: 50% 100%;
|
||||
transition: all 1s ease 0s;
|
||||
}
|
||||
.needle {
|
||||
fill: var(--primary-text-color);
|
||||
transform-origin: 50% 100%;
|
||||
transition: all 1s ease 0s;
|
||||
}
|
||||
.level {
|
||||
fill: none;
|
||||
stroke-width: 15;
|
||||
}
|
||||
.gauge {
|
||||
display: block;
|
||||
}
|
||||
|
@@ -96,7 +96,7 @@ class HaHLSPlayer extends LitElement {
|
||||
const useExoPlayerPromise = this._getUseExoPlayer();
|
||||
const masterPlaylistPromise = fetch(this.url);
|
||||
|
||||
const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min"))
|
||||
const Hls: typeof HlsType = (await import("hls.js/dist/hls.light.min.js"))
|
||||
.default;
|
||||
let hlsSupported = Hls.isSupported();
|
||||
|
||||
@@ -117,8 +117,7 @@ class HaHLSPlayer extends LitElement {
|
||||
|
||||
// Parse playlist assuming it is a master playlist. Match group 1 is whether hevc, match group 2 is regular playlist url
|
||||
// See https://tools.ietf.org/html/rfc8216 for HLS spec details
|
||||
const playlistRegexp =
|
||||
/#EXT-X-STREAM-INF:.*?(?:CODECS=".*?(hev1|hvc1)?\..*?".*?)?(?:\n|\r\n)(.+)/g;
|
||||
const playlistRegexp = /#EXT-X-STREAM-INF:.*?(?:CODECS=".*?(hev1|hvc1)?\..*?".*?)?(?:\n|\r\n)(.+)/g;
|
||||
const match = playlistRegexp.exec(masterPlaylist);
|
||||
const matchTwice = playlistRegexp.exec(masterPlaylist);
|
||||
|
||||
|
@@ -33,10 +33,10 @@ class HaLabelBadge extends LitElement {
|
||||
>
|
||||
<slot>
|
||||
${this.icon && !this.value && !this.image
|
||||
? html`<ha-icon .icon=${this.icon}></ha-icon>`
|
||||
? html` <ha-icon .icon=${this.icon}></ha-icon> `
|
||||
: ""}
|
||||
${this.value && !this.image
|
||||
? html`<span>${this.value}</span>`
|
||||
? html` <span>${this.value}</span> `
|
||||
: ""}
|
||||
</slot>
|
||||
</div>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiImagePlus } from "@mdi/js";
|
||||
import "@polymer/iron-input/iron-input";
|
||||
import "@polymer/paper-input/paper-input-container";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { Radio } from "@material/mwc-radio";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-radio")
|
||||
export class HaRadio extends Radio {
|
||||
public firstUpdated() {
|
||||
|
@@ -153,8 +153,9 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
</h3>
|
||||
<ul>
|
||||
${this._related.entity.map((entityId) => {
|
||||
const entity: HassEntity | undefined =
|
||||
this.hass.states[entityId];
|
||||
const entity: HassEntity | undefined = this.hass.states[
|
||||
entityId
|
||||
];
|
||||
if (!entity) {
|
||||
return "";
|
||||
}
|
||||
@@ -202,8 +203,9 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
<h3>${this.hass.localize("ui.components.related-items.scene")}:</h3>
|
||||
<ul>
|
||||
${this._related.scene.map((sceneId) => {
|
||||
const scene: SceneEntity | undefined =
|
||||
this.hass.states[sceneId];
|
||||
const scene: SceneEntity | undefined = this.hass.states[
|
||||
sceneId
|
||||
];
|
||||
if (!scene) {
|
||||
return "";
|
||||
}
|
||||
@@ -229,8 +231,9 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
</h3>
|
||||
<ul>
|
||||
${this._related.automation.map((automationId) => {
|
||||
const automation: HassEntity | undefined =
|
||||
this.hass.states[automationId];
|
||||
const automation: HassEntity | undefined = this.hass.states[
|
||||
automationId
|
||||
];
|
||||
if (!automation) {
|
||||
return "";
|
||||
}
|
||||
@@ -257,8 +260,9 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
</h3>
|
||||
<ul>
|
||||
${this._related.script.map((scriptId) => {
|
||||
const script: HassEntity | undefined =
|
||||
this.hass.states[scriptId];
|
||||
const script: HassEntity | undefined = this.hass.states[
|
||||
scriptId
|
||||
];
|
||||
if (!script) {
|
||||
return "";
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user