mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-11 18:29:27 +00:00
Compare commits
246 Commits
checkbox-s
...
20210818.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2270d8a795 | ||
![]() |
f4dcce6d6c | ||
![]() |
b802a410b9 | ||
![]() |
9e3d339ec5 | ||
![]() |
fb97a98b97 | ||
![]() |
72773f3bc8 | ||
![]() |
b0fd93e0c3 | ||
![]() |
7aa2ec78f2 | ||
![]() |
047e856a61 | ||
![]() |
dbe209e3f2 | ||
![]() |
e0d23ee6cf | ||
![]() |
7a35f46370 | ||
![]() |
4a4465efb6 | ||
![]() |
3a112531cc | ||
![]() |
456209dded | ||
![]() |
2556b0d157 | ||
![]() |
1e8903fd76 | ||
![]() |
ad9f18c231 | ||
![]() |
63e3de00cb | ||
![]() |
d06ffeeede | ||
![]() |
3479fb9d94 | ||
![]() |
304bd002ae | ||
![]() |
5dad18c85f | ||
![]() |
19e4c0657a | ||
![]() |
44548fdc33 | ||
![]() |
d8929074b5 | ||
![]() |
e11532ae92 | ||
![]() |
eff48acdf4 | ||
![]() |
a9850f9641 | ||
![]() |
aab0b8a3ce | ||
![]() |
b12e062d94 | ||
![]() |
b36e342f15 | ||
![]() |
f686816c86 | ||
![]() |
dc50e54afc | ||
![]() |
3897e3d452 | ||
![]() |
2557b03b11 | ||
![]() |
29d29a337f | ||
![]() |
34f8e5e28d | ||
![]() |
afd1a68c62 | ||
![]() |
dbcf1cb907 | ||
![]() |
9ca64f9789 | ||
![]() |
4c247ac49d | ||
![]() |
e8a406526b | ||
![]() |
7fcea16c6b | ||
![]() |
028b799d2c | ||
![]() |
3485296e23 | ||
![]() |
03078cdd45 | ||
![]() |
740310800d | ||
![]() |
6d7c558482 | ||
![]() |
fdb10515c3 | ||
![]() |
5a0f13c485 | ||
![]() |
80b330ad7b | ||
![]() |
d929e1c134 | ||
![]() |
5e40dcdc38 | ||
![]() |
1dd3e2a83b | ||
![]() |
a62742fad9 | ||
![]() |
1f9c45b11c | ||
![]() |
68bec5e158 | ||
![]() |
37f1bd7d63 | ||
![]() |
5ba24211e2 | ||
![]() |
d699647418 | ||
![]() |
b246502cb6 | ||
![]() |
9b33ead8aa | ||
![]() |
32e8c1dc6d | ||
![]() |
e09ef7862e | ||
![]() |
85420304d0 | ||
![]() |
1c097a669d | ||
![]() |
4e1497c5da | ||
![]() |
49d426675f | ||
![]() |
dc6ac668b4 | ||
![]() |
4ee24b0845 | ||
![]() |
ba20aef206 | ||
![]() |
41ef6133c1 | ||
![]() |
50bd5ee8f7 | ||
![]() |
285f3fe330 | ||
![]() |
4d01199986 | ||
![]() |
bcc0052fe0 | ||
![]() |
4b592d81bd | ||
![]() |
884e323288 | ||
![]() |
78b799dd05 | ||
![]() |
847fa2e700 | ||
![]() |
481a79e311 | ||
![]() |
f19f2ff321 | ||
![]() |
6dc4d7bb70 | ||
![]() |
83460a34f4 | ||
![]() |
2adbb47373 | ||
![]() |
2159a5419a | ||
![]() |
044d6a15d9 | ||
![]() |
b6055062c6 | ||
![]() |
6fd85e043b | ||
![]() |
e07ac52356 | ||
![]() |
0f16ba9325 | ||
![]() |
378e6d28bc | ||
![]() |
539d2b880c | ||
![]() |
2982adbfa7 | ||
![]() |
5147dff670 | ||
![]() |
2cdf78c504 | ||
![]() |
cfad45b7c2 | ||
![]() |
5234e9bce5 | ||
![]() |
0ed5454d02 | ||
![]() |
03080973be | ||
![]() |
a4f51b0cb3 | ||
![]() |
749079c1c3 | ||
![]() |
ae10ff42e1 | ||
![]() |
d4cbdab4a3 | ||
![]() |
1bd6392a4c | ||
![]() |
1531e99528 | ||
![]() |
6e7af18494 | ||
![]() |
e1bae65aeb | ||
![]() |
65bfdf94c9 | ||
![]() |
9a92825954 | ||
![]() |
469faf509b | ||
![]() |
f87d4a5ab6 | ||
![]() |
9c31b749d7 | ||
![]() |
521d5df064 | ||
![]() |
4d330fba8a | ||
![]() |
2e04a55d5c | ||
![]() |
0d9d0aa18b | ||
![]() |
f8dd1795bc | ||
![]() |
1d7007584c | ||
![]() |
8cd9f891fb | ||
![]() |
6ab0f1db57 | ||
![]() |
37d754d069 | ||
![]() |
e12b194d41 | ||
![]() |
07d7fa26fe | ||
![]() |
73b9b87ef3 | ||
![]() |
0c0091375c | ||
![]() |
21a29ed3a5 | ||
![]() |
a6312f4279 | ||
![]() |
f459abdf85 | ||
![]() |
586fa1d0f0 | ||
![]() |
bf4192a1f0 | ||
![]() |
ac31eedf65 | ||
![]() |
b05dc5141c | ||
![]() |
32c6fb14dd | ||
![]() |
982c940381 | ||
![]() |
a7a8aaa887 | ||
![]() |
bf83a9980e | ||
![]() |
11be603ed3 | ||
![]() |
a432cf8405 | ||
![]() |
9dd6b3b72d | ||
![]() |
faca62b55f | ||
![]() |
a339de89f5 | ||
![]() |
e40c90e9c0 | ||
![]() |
3f447bb8a7 | ||
![]() |
21dca3fbf8 | ||
![]() |
1078bb4287 | ||
![]() |
daeed06e70 | ||
![]() |
1206e2d75f | ||
![]() |
cc81239b9d | ||
![]() |
e797c01761 | ||
![]() |
12f7366968 | ||
![]() |
b7fd7abe85 | ||
![]() |
a5e1f3d165 | ||
![]() |
212d047ada | ||
![]() |
2e16127fde | ||
![]() |
e8b53a619d | ||
![]() |
df1ca1fd96 | ||
![]() |
8f85132d48 | ||
![]() |
349144599c | ||
![]() |
979093923b | ||
![]() |
137f8ad4cb | ||
![]() |
c1f462b8f8 | ||
![]() |
6701c4c371 | ||
![]() |
fc6e459c09 | ||
![]() |
9e28b3447e | ||
![]() |
5c737e1969 | ||
![]() |
569765e77e | ||
![]() |
bc0d63ed12 | ||
![]() |
02f9893522 | ||
![]() |
b4bbe63f0f | ||
![]() |
fabbcac99f | ||
![]() |
b1b5ab6949 | ||
![]() |
4b9487183b | ||
![]() |
de5a817953 | ||
![]() |
4970f640fa | ||
![]() |
18996535b7 | ||
![]() |
2a1e31b5e9 | ||
![]() |
8ca9a0f409 | ||
![]() |
fcc89a67ba | ||
![]() |
1f377d43c5 | ||
![]() |
30d6c68908 | ||
![]() |
dc781da93a | ||
![]() |
36c20e4348 | ||
![]() |
4466950bb8 | ||
![]() |
be29828454 | ||
![]() |
7bab245073 | ||
![]() |
f5dcf0b760 | ||
![]() |
8141f78a92 | ||
![]() |
be244b3d00 | ||
![]() |
805f5ff9b6 | ||
![]() |
76daeb7e55 | ||
![]() |
9594c8106e | ||
![]() |
ed4052365c | ||
![]() |
377ebadc10 | ||
![]() |
ed4809b71e | ||
![]() |
db37dffdbb | ||
![]() |
13cab7e301 | ||
![]() |
0a50fc66e5 | ||
![]() |
a3d1a3566d | ||
![]() |
ba0be927ed | ||
![]() |
4260606267 | ||
![]() |
4665db4f27 | ||
![]() |
43503ba085 | ||
![]() |
0a83a704f1 | ||
![]() |
08de941c90 | ||
![]() |
62228ef144 | ||
![]() |
9731257782 | ||
![]() |
4ec9c9c16e | ||
![]() |
45436731e2 | ||
![]() |
27730e65e7 | ||
![]() |
a4aba93d57 | ||
![]() |
d93db16963 | ||
![]() |
c327fe11b8 | ||
![]() |
4fbc31d0b0 | ||
![]() |
9a4a1cb4ec | ||
![]() |
202d6957bc | ||
![]() |
14fcff7774 | ||
![]() |
2c9aa1cab4 | ||
![]() |
7745c10d07 | ||
![]() |
c1d571de42 | ||
![]() |
cecb66451c | ||
![]() |
0ef3421fa2 | ||
![]() |
f88e238d41 | ||
![]() |
ce3c8f264d | ||
![]() |
9fbd594f37 | ||
![]() |
5ad95cad90 | ||
![]() |
7e507b40c4 | ||
![]() |
446a9b5c02 | ||
![]() |
e02e61384e | ||
![]() |
5deb570fdf | ||
![]() |
915c46f144 | ||
![]() |
30d6c5eaf3 | ||
![]() |
6e50d1166a | ||
![]() |
8666e6baae | ||
![]() |
2db9f33c41 | ||
![]() |
3d788d6056 | ||
![]() |
7560f98d70 | ||
![]() |
1533c22d5c | ||
![]() |
cf03f103ab | ||
![]() |
4a8a7c997e | ||
![]() |
9612bc78fe | ||
![]() |
2b86137388 | ||
![]() |
8fdbe447c1 | ||
![]() |
764ae7e0b6 | ||
![]() |
6b7e78320d |
@@ -35,55 +35,51 @@
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"class-methods-use-this": 0,
|
||||
"new-cap": 0,
|
||||
"prefer-template": 0,
|
||||
"object-shorthand": 0,
|
||||
"func-names": 0,
|
||||
"prefer-arrow-callback": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"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,
|
||||
"class-methods-use-this": "off",
|
||||
"new-cap": "off",
|
||||
"prefer-template": "off",
|
||||
"object-shorthand": "off",
|
||||
"func-names": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"strict": "off",
|
||||
"no-plusplus": "off",
|
||||
"no-bitwise": "error",
|
||||
"comma-dangle": "off",
|
||||
"vars-on-top": "off",
|
||||
"no-continue": "off",
|
||||
"no-param-reassign": "off",
|
||||
"no-multi-assign": "off",
|
||||
"no-console": "error",
|
||||
"radix": "off",
|
||||
"no-alert": "off",
|
||||
"no-nested-ternary": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"no-restricted-globals": [2, "event"],
|
||||
"prefer-promise-reject-errors": 0,
|
||||
"import/order": 0,
|
||||
"import/prefer-default-export": 0,
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-cycle": 0,
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"import/prefer-default-export": "off",
|
||||
"import/no-default-export": "off",
|
||||
"import/no-unresolved": "off",
|
||||
"import/no-cycle": "off",
|
||||
"import/extensions": [
|
||||
2,
|
||||
"error",
|
||||
"ignorePackages",
|
||||
{ "ts": "never", "js": "never" }
|
||||
],
|
||||
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
|
||||
"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,
|
||||
"object-curly-newline": "off",
|
||||
"default-case": "off",
|
||||
"wc/no-self-class": "off",
|
||||
"no-shadow": "off",
|
||||
"@typescript-eslint/camelcase": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-shadow": ["error"],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
0,
|
||||
"off",
|
||||
{
|
||||
"selector": "default",
|
||||
"format": ["camelCase", "snake_case"],
|
||||
@@ -101,9 +97,20 @@
|
||||
"format": ["PascalCase"]
|
||||
}
|
||||
],
|
||||
"lit/attribute-value-entities": 0
|
||||
"@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"
|
||||
},
|
||||
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
|
||||
"processor": "disable/disable",
|
||||
"ignorePatterns": ["src/resources/lit-virtualizer/*"]
|
||||
"plugins": ["disable", "unused-imports"],
|
||||
"processor": "disable/disable"
|
||||
}
|
||||
|
76
.github/workflows/ci.yaml
vendored
76
.github/workflows/ci.yaml
vendored
@@ -10,26 +10,21 @@ 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: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
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-
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
@@ -42,51 +37,35 @@ 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: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
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-
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Run Mocha
|
||||
run: npm run mocha
|
||||
run: yarn run mocha
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
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-
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
@@ -101,20 +80,11 @@ jobs:
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
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-
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
|
22
.github/workflows/demo.yaml
vendored
22
.github/workflows/demo.yaml
vendored
@@ -4,26 +4,22 @@ 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: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
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-
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
|
11
.github/workflows/release.yaml
vendored
11
.github/workflows/release.yaml
vendored
@@ -7,7 +7,8 @@ on:
|
||||
|
||||
env:
|
||||
PYTHON_VERSION: 3.8
|
||||
NODE_VERSION: 12.1
|
||||
NODE_VERSION: 14
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -29,7 +30,15 @@ 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,8 +1,6 @@
|
||||
name: Translations
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 0 * * *"
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
@@ -10,7 +8,7 @@ on:
|
||||
- src/translations/en.json
|
||||
|
||||
env:
|
||||
NODE_VERSION: 12
|
||||
NODE_VERSION: 14
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
@@ -20,46 +18,8 @@ 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,9 +8,15 @@ hass_frontend/*
|
||||
dist
|
||||
|
||||
# yarn
|
||||
.yarn
|
||||
yarn-error.log
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
node_modules/*
|
||||
yarn-error.log
|
||||
npm-debug.log
|
||||
|
||||
# Python stuff
|
||||
|
1536
.yarn/patches/@lit-labs/virtualizer/0.7.0.patch
Normal file
1536
.yarn/patches/@lit-labs/virtualizer/0.7.0.patch
Normal file
File diff suppressed because one or more lines are too long
29
.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch
Normal file
29
.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch
Normal file
@@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
34
.yarn/patches/@polymer/polymer/pr-5569.patch
Normal file
34
.yarn/patches/@polymer/polymer/pr-5569.patch
Normal file
@@ -0,0 +1,34 @@
|
||||
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++) {
|
77
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
Normal file
77
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
8
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
Normal file
8
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
55
.yarn/releases/yarn-2.4.2.cjs
vendored
Executable file
55
.yarn/releases/yarn-2.4.2.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
9
.yarnrc.yml
Normal file
9
.yarnrc.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
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
|
170
build-scripts/babel-plugins/inline-constants-plugin.js
Normal file
170
build-scripts/babel-plugins/inline-constants-plugin.js
Normal file
@@ -0,0 +1,170 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require("path");
|
||||
|
||||
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
|
||||
module.exports = function inlineConstants(babel, options, cwd) {
|
||||
const t = babel.types;
|
||||
|
||||
if (!Array.isArray(options.modules)) {
|
||||
throw new TypeError(
|
||||
"babel-plugin-inline-constants: expected a `modules` array to be passed"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.resolveExtensions && !Array.isArray(options.resolveExtensions)) {
|
||||
throw new TypeError(
|
||||
"babel-plugin-inline-constants: expected `resolveExtensions` to be an array"
|
||||
);
|
||||
}
|
||||
|
||||
const ignoreModuleNotFound = options.ignoreModuleNotFound;
|
||||
const resolveExtensions = options.resolveExtensions;
|
||||
|
||||
const hasRelativeModules = options.modules.some(
|
||||
(module) => module.startsWith(".") || module.startsWith("/")
|
||||
);
|
||||
|
||||
const modules = Object.fromEntries(
|
||||
options.modules.map((module) => {
|
||||
const absolute = module.startsWith(".")
|
||||
? require.resolve(module, { paths: [cwd] })
|
||||
: module;
|
||||
// eslint-disable-next-line import/no-dynamic-require
|
||||
return [absolute, require(absolute)];
|
||||
})
|
||||
);
|
||||
|
||||
const toLiteral = (value) => {
|
||||
if (typeof value === "string") {
|
||||
return t.stringLiteral(value);
|
||||
}
|
||||
|
||||
if (typeof value === "number") {
|
||||
return t.numericLiteral(value);
|
||||
}
|
||||
|
||||
if (typeof value === "boolean") {
|
||||
return t.booleanLiteral(value);
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
return t.nullLiteral();
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"babel-plugin-inline-constants: cannot handle non-literal `" + value + "`"
|
||||
);
|
||||
};
|
||||
|
||||
const resolveAbsolute = (value, state, resolveExtensionIndex) => {
|
||||
if (!state.filename) {
|
||||
throw new TypeError(
|
||||
"babel-plugin-inline-constants: expected a `filename` to be set for files"
|
||||
);
|
||||
}
|
||||
|
||||
if (resolveExtensions && resolveExtensionIndex !== undefined) {
|
||||
value += resolveExtensions[resolveExtensionIndex];
|
||||
}
|
||||
|
||||
try {
|
||||
return require.resolve(value, { paths: [path.dirname(state.filename)] });
|
||||
} catch (error) {
|
||||
if (
|
||||
error.code === "MODULE_NOT_FOUND" &&
|
||||
resolveExtensions &&
|
||||
(resolveExtensionIndex === undefined ||
|
||||
resolveExtensionIndex < resolveExtensions.length - 1)
|
||||
) {
|
||||
const resolveExtensionIdx = (resolveExtensionIndex || -1) + 1;
|
||||
return resolveAbsolute(value, state, resolveExtensionIdx);
|
||||
}
|
||||
|
||||
if (error.code === "MODULE_NOT_FOUND" && ignoreModuleNotFound) {
|
||||
return undefined;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const importDeclaration = (p, state) => {
|
||||
if (p.node.type !== "ImportDeclaration") {
|
||||
return;
|
||||
}
|
||||
const absolute =
|
||||
hasRelativeModules && p.node.source.value.startsWith(".")
|
||||
? resolveAbsolute(p.node.source.value, state)
|
||||
: p.node.source.value;
|
||||
|
||||
if (!absolute || !(absolute in modules)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const module = modules[absolute];
|
||||
|
||||
for (const specifier of p.node.specifiers) {
|
||||
if (
|
||||
specifier.type === "ImportDefaultSpecifier" &&
|
||||
specifier.local &&
|
||||
specifier.local.type === "Identifier"
|
||||
) {
|
||||
if (!("default" in module)) {
|
||||
throw new Error(
|
||||
"babel-plugin-inline-constants: cannot access default export from `" +
|
||||
p.node.source.value +
|
||||
"`"
|
||||
);
|
||||
}
|
||||
|
||||
const variableValue = toLiteral(module.default);
|
||||
const variable = t.variableDeclarator(
|
||||
t.identifier(specifier.local.name),
|
||||
variableValue
|
||||
);
|
||||
|
||||
p.insertBefore({
|
||||
type: "VariableDeclaration",
|
||||
kind: "const",
|
||||
declarations: [variable],
|
||||
});
|
||||
} else if (
|
||||
specifier.type === "ImportSpecifier" &&
|
||||
specifier.imported &&
|
||||
specifier.imported.type === "Identifier" &&
|
||||
specifier.local &&
|
||||
specifier.local.type === "Identifier"
|
||||
) {
|
||||
if (!(specifier.imported.name in module)) {
|
||||
throw new Error(
|
||||
"babel-plugin-inline-constants: cannot access `" +
|
||||
specifier.imported.name +
|
||||
"` from `" +
|
||||
p.node.source.value +
|
||||
"`"
|
||||
);
|
||||
}
|
||||
|
||||
const variableValue = toLiteral(module[specifier.imported.name]);
|
||||
const variable = t.variableDeclarator(
|
||||
t.identifier(specifier.local.name),
|
||||
variableValue
|
||||
);
|
||||
|
||||
p.insertBefore({
|
||||
type: "VariableDeclaration",
|
||||
kind: "const",
|
||||
declarations: [variable],
|
||||
});
|
||||
} else {
|
||||
throw new Error("Cannot handle specifier `" + specifier.type + "`");
|
||||
}
|
||||
}
|
||||
p.remove();
|
||||
};
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
ImportDeclaration: importDeclaration,
|
||||
},
|
||||
};
|
||||
};
|
@@ -5,8 +5,6 @@ const paths = require("./paths.js");
|
||||
|
||||
// Files from NPM Packages that should not be imported
|
||||
module.exports.ignorePackages = ({ latestBuild }) => [
|
||||
// Bloats bundle and it's not used.
|
||||
path.resolve(require.resolve("moment"), "../locale"),
|
||||
// Part of yaml.js and only used for !!js functions that we don't use
|
||||
require.resolve("esprima"),
|
||||
];
|
||||
@@ -20,7 +18,8 @@ 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/font-roboto.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
|
||||
// Compatibility not needed for latest builds
|
||||
latestBuild &&
|
||||
// wrapped in require.resolve so it blows up if file no longer exists
|
||||
@@ -58,12 +57,23 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
"@babel/preset-env",
|
||||
{
|
||||
useBuiltIns: "entry",
|
||||
corejs: "3.6",
|
||||
corejs: "3.15",
|
||||
bugfixes: true,
|
||||
},
|
||||
],
|
||||
"@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",
|
||||
@@ -76,8 +86,14 @@ 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,15 +302,23 @@ 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"),
|
||||
`
|
||||
try {
|
||||
new Function("import('${latestEntrypoint}')")();
|
||||
} catch (err) {
|
||||
function loadES5() {
|
||||
var el = document.createElement('script');
|
||||
el.src = '${es5Entrypoint}';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
|
||||
loadES5();
|
||||
} else {
|
||||
try {
|
||||
new Function("import('${latestEntrypoint}')")();
|
||||
} catch (err) {
|
||||
loadES5();
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ encoding: "utf-8" }
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const cpx = require("cpx");
|
||||
const fs = require("fs-extra");
|
||||
const paths = require("../paths");
|
||||
|
||||
@@ -13,8 +12,10 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
|
||||
const copyFileDir = (fromFile, toDir) =>
|
||||
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
|
||||
|
||||
const genStaticPath = (staticDir) => (...parts) =>
|
||||
path.resolve(staticDir, ...parts);
|
||||
const genStaticPath =
|
||||
(staticDir) =>
|
||||
(...parts) =>
|
||||
path.resolve(staticDir, ...parts);
|
||||
|
||||
function copyTranslations(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
@@ -62,9 +63,12 @@ function copyLoaderJS(staticDir) {
|
||||
function copyFonts(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
// Local fonts
|
||||
cpx.copySync(
|
||||
npmPath("roboto-fontface/fonts/roboto/*.woff2"),
|
||||
staticPath("fonts/roboto")
|
||||
fs.copySync(
|
||||
npmPath("roboto-fontface/fonts/roboto/"),
|
||||
staticPath("fonts/roboto/"),
|
||||
{
|
||||
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -19,10 +19,12 @@ const bothBuilds = (createConfigFunc, params) => [
|
||||
createConfigFunc({ ...params, latestBuild: false }),
|
||||
];
|
||||
|
||||
const isWsl = fs
|
||||
.readFileSync("/proc/version", "utf-8")
|
||||
.toLocaleLowerCase()
|
||||
.includes("microsoft");
|
||||
const isWsl =
|
||||
fs.existsSync("/proc/version") &&
|
||||
fs
|
||||
.readFileSync("/proc/version", "utf-8")
|
||||
.toLocaleLowerCase()
|
||||
.includes("microsoft");
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
@@ -84,8 +86,15 @@ const prodBuild = (conf) =>
|
||||
|
||||
gulp.task("webpack-watch-app", () => {
|
||||
// This command will run forever because we don't close compiler
|
||||
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
|
||||
{ ignored: /build-translations/, poll: isWsl },
|
||||
webpack(
|
||||
process.env.ES5
|
||||
? bothBuilds(createAppConfig, { isProdBuild: false })
|
||||
: createAppConfig({ isProdBuild: false, latestBuild: true })
|
||||
).watch(
|
||||
{
|
||||
ignored: /build-translations/,
|
||||
poll: isWsl,
|
||||
},
|
||||
doneHandler()
|
||||
);
|
||||
gulp.watch(
|
||||
|
@@ -49,12 +49,16 @@ const createWebpackConfig = ({
|
||||
test: /\.m?js$|\.ts$/,
|
||||
use: {
|
||||
loader: "babel-loader",
|
||||
options: bundle.babelOptions({ latestBuild }),
|
||||
options: {
|
||||
...bundle.babelOptions({ latestBuild }),
|
||||
cacheDirectory: !isProdBuild,
|
||||
cacheCompression: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: "raw-loader",
|
||||
type: "asset/source",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -66,6 +70,8 @@ const createWebpackConfig = ({
|
||||
terserOptions: bundle.terserOptions(latestBuild),
|
||||
}),
|
||||
],
|
||||
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||
},
|
||||
plugins: [
|
||||
new WebpackManifestPlugin({
|
||||
@@ -112,16 +118,6 @@ 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: {
|
||||
@@ -134,15 +130,13 @@ const createWebpackConfig = ({
|
||||
},
|
||||
output: {
|
||||
filename: ({ chunk }) => {
|
||||
if (!isProdBuild || dontHash.has(chunk.name)) {
|
||||
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
|
||||
return `${chunk.name}.js`;
|
||||
}
|
||||
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
||||
},
|
||||
chunkFilename:
|
||||
isProdBuild && !isStatsBuild
|
||||
? "chunk.[chunkhash].js"
|
||||
: "[name].chunk.js",
|
||||
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].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/home-assistant-polymer/tree/dev/cast"
|
||||
href="https://github.com/home-assistant/frontend/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: [
|
||||
{
|
||||
|
@@ -29,6 +29,11 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Energy distribution today",
|
||||
type: "energy-distribution",
|
||||
link_dashboard: true,
|
||||
},
|
||||
{
|
||||
type: "thermostat",
|
||||
entity: "climate.upstairs",
|
||||
@@ -113,8 +118,7 @@ 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: {
|
||||
@@ -196,8 +200,7 @@ 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: {
|
||||
@@ -277,8 +280,7 @@ 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: {
|
||||
@@ -315,8 +317,7 @@ 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,5 +1,6 @@
|
||||
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>> = [
|
||||
@@ -12,9 +13,8 @@ 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,6 +28,7 @@ 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,8 +980,7 @@ 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",
|
||||
},
|
||||
@@ -1005,8 +1004,7 @@ 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?: boolean;
|
||||
@state() private _switching = false;
|
||||
|
||||
private _hidden = localStorage.hide_demo_card;
|
||||
|
||||
@@ -27,12 +27,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
return this._hidden ? 0 : 2;
|
||||
}
|
||||
|
||||
public setConfig(
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
config: LovelaceCardConfig
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
) {}
|
||||
public setConfig(_config: LovelaceCardConfig) {}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._hidden) {
|
||||
|
@@ -1,5 +1,3 @@
|
||||
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,6 +20,10 @@ 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() {
|
||||
@@ -47,8 +51,13 @@ 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]) => {
|
||||
|
41
demo/src/stubs/config.ts
Normal file
41
demo/src/stubs/config.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
|
||||
export const mockConfig = (hass: MockHomeAssistant) => {
|
||||
hass.mockAPI("config/config_entries/entry", () => [
|
||||
{
|
||||
entry_id: "co2signal",
|
||||
domain: "co2signal",
|
||||
title: "CO2 Signal",
|
||||
source: "user",
|
||||
state: "loaded",
|
||||
supports_options: false,
|
||||
supports_unload: true,
|
||||
pref_disable_new_entities: false,
|
||||
pref_disable_polling: false,
|
||||
disabled_by: null,
|
||||
reason: null,
|
||||
},
|
||||
]);
|
||||
hass.mockWS("config/entity_registry/list", () => [
|
||||
{
|
||||
config_entry_id: "co2signal",
|
||||
device_id: "co2signal",
|
||||
area_id: null,
|
||||
disabled_by: null,
|
||||
entity_id: "sensor.co2_intensity",
|
||||
name: null,
|
||||
icon: null,
|
||||
platform: "co2signal",
|
||||
},
|
||||
{
|
||||
config_entry_id: "co2signal",
|
||||
device_id: "co2signal",
|
||||
area_id: null,
|
||||
disabled_by: null,
|
||||
entity_id: "sensor.grid_fossil_fuel_percentage",
|
||||
name: null,
|
||||
icon: null,
|
||||
platform: "co2signal",
|
||||
},
|
||||
]);
|
||||
};
|
83
demo/src/stubs/energy.ts
Normal file
83
demo/src/stubs/energy.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
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"],
|
||||
},
|
||||
/* {
|
||||
type: "battery",
|
||||
stat_energy_from: "sensor.battery_output",
|
||||
stat_energy_to: "sensor.battery_input",
|
||||
}, */
|
||||
{
|
||||
type: "gas",
|
||||
stat_energy_from: "sensor.energy_gas",
|
||||
stat_cost: "sensor.energy_gas_cost",
|
||||
entity_energy_from: "sensor.energy_gas",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
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: [] }));
|
||||
};
|
178
demo/src/stubs/entities.ts
Normal file
178
demo/src/stubs/entities.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
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.battery_input": {
|
||||
entity_id: "sensor.battery_input",
|
||||
state: "4",
|
||||
attributes: {
|
||||
last_reset: "1970-01-01T00:00:00:00+00",
|
||||
friendly_name: "Battery Input",
|
||||
unit_of_measurement: "kWh",
|
||||
},
|
||||
},
|
||||
"sensor.battery_output": {
|
||||
entity_id: "sensor.battery_output",
|
||||
state: "3",
|
||||
attributes: {
|
||||
last_reset: "1970-01-01T00:00:00:00+00",
|
||||
friendly_name: "Battery Output",
|
||||
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_gas_cost": {
|
||||
entity_id: "sensor.energy_gas_cost",
|
||||
state: "2",
|
||||
attributes: {
|
||||
last_reset: "1970-01-01T00:00:00:00+00",
|
||||
unit_of_measurement: "EUR",
|
||||
},
|
||||
},
|
||||
"sensor.energy_gas": {
|
||||
entity_id: "sensor.energy_gas",
|
||||
state: "4",
|
||||
attributes: {
|
||||
last_reset: "1970-01-01T00:00:00:00+00",
|
||||
friendly_name: "Gas",
|
||||
unit_of_measurement: "m³",
|
||||
},
|
||||
},
|
||||
"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",
|
||||
},
|
||||
},
|
||||
});
|
55
demo/src/stubs/forecast_solar.ts
Normal file
55
demo/src/stubs/forecast_solar.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
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,4 +1,6 @@
|
||||
import { addHours, differenceInHours, endOfDay } 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 {
|
||||
@@ -64,17 +66,219 @@ 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 - Math.random() * maxDiff,
|
||||
max: mean + Math.random() * maxDiff,
|
||||
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 dayEnd = new Date(endOfDay(productionEnd));
|
||||
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,
|
||||
dayEnd,
|
||||
productionFinalVal,
|
||||
0
|
||||
);
|
||||
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 1);
|
||||
return [...morning, ...production, ...evening, ...rest];
|
||||
},
|
||||
"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 dayEnd = new Date(endOfDay(productionEnd));
|
||||
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,
|
||||
dayEnd,
|
||||
productionFinalVal,
|
||||
0
|
||||
);
|
||||
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 2);
|
||||
return [...morning, ...production, ...evening, ...rest];
|
||||
},
|
||||
"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,
|
||||
// @ts-ignore
|
||||
method,
|
||||
path,
|
||||
// @ts-ignore
|
||||
parameters
|
||||
) => {
|
||||
(hass, _method, path, _parameters) => {
|
||||
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
|
||||
const entities = params.filter_entity_id.split(",");
|
||||
|
||||
@@ -95,7 +299,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||
const numberState = Number(state.state);
|
||||
|
||||
if (isNaN(numberState)) {
|
||||
// eslint-disable-next-line
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
"Ignoring state with unparsable state but with a unit",
|
||||
entityId,
|
||||
@@ -140,4 +344,40 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
|
||||
return results;
|
||||
}
|
||||
);
|
||||
mockHass.mockWS("history/list_statistic_ids", () => []);
|
||||
mockHass.mockWS(
|
||||
"history/statistics_during_period",
|
||||
({ statistic_ids, start_time, end_time }, hass) => {
|
||||
const start = new Date(start_time);
|
||||
const end = end_time ? new Date(end_time) : new Date();
|
||||
|
||||
const statistics: Record<string, StatisticValue[]> = {};
|
||||
|
||||
statistic_ids.forEach((id: string) => {
|
||||
if (id in statisticsFunctions) {
|
||||
statistics[id] = statisticsFunctions[id](id, start, end);
|
||||
} 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,10 +10,9 @@ 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, onChange) => {
|
||||
hass.mockWS("render_template", (msg, _hass, 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,6 +2,8 @@ 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";
|
||||
@@ -15,8 +17,6 @@ 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,7 +2,6 @@ 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,
|
||||
@@ -134,7 +133,7 @@ class HassioAddonConfig extends LitElement {
|
||||
></ha-form>`
|
||||
: html` <ha-yaml-editor
|
||||
@value-changed=${this._configChanged}
|
||||
.schema=${ADDON_YAML_SCHEMA}
|
||||
.yamlSchema=${ADDON_YAML_SCHEMA}
|
||||
></ha-yaml-editor>`}
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${!this._yamlMode ||
|
||||
@@ -269,6 +268,9 @@ class HassioAddonConfig extends LitElement {
|
||||
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
const options: Record<string, unknown> = this._yamlMode
|
||||
? this._editor?.value
|
||||
: this._options;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
@@ -282,13 +284,13 @@ class HassioAddonConfig extends LitElement {
|
||||
const validation = await validateHassioAddonOption(
|
||||
this.hass,
|
||||
this.addon.slug,
|
||||
this._editor?.value
|
||||
options
|
||||
);
|
||||
if (!validation.valid) {
|
||||
throw Error(validation.message);
|
||||
}
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, {
|
||||
options: this._yamlMode ? this._editor?.value : this._options,
|
||||
options,
|
||||
});
|
||||
|
||||
this._configHasChanged = false;
|
||||
@@ -326,10 +328,6 @@ 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,6 +2,7 @@ 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,
|
||||
@@ -12,7 +13,6 @@ 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 {
|
||||
|
@@ -892,10 +892,19 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
private async _openChangelog(): Promise<void> {
|
||||
try {
|
||||
const content = await fetchHassioAddonChangelog(
|
||||
this.hass,
|
||||
this.addon.slug
|
||||
);
|
||||
let content = await fetchHassioAddonChangelog(this.hass, this.addon.slug);
|
||||
if (
|
||||
content.includes(`# ${this.addon.version}`) &&
|
||||
content.includes(`# ${this.addon.version_latest}`)
|
||||
) {
|
||||
const newcontent = content.split(`# ${this.addon.version}`)[0];
|
||||
if (newcontent.includes(`# ${this.addon.version_latest}`)) {
|
||||
// Only change the content if the new version still exist
|
||||
// if the changelog does not have the newests version on top
|
||||
// this will not be true, and we don't modify the content
|
||||
content = newcontent;
|
||||
}
|
||||
}
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: this.supervisor.localize("addon.dashboard.changelog"),
|
||||
content,
|
||||
@@ -977,9 +986,8 @@ class HassioAddonInfo extends LitElement {
|
||||
showDialogSupervisorUpdate(this, {
|
||||
supervisor: this.supervisor,
|
||||
name: this.addon.name,
|
||||
slug: this.addon.slug,
|
||||
version: this.addon.version_latest,
|
||||
snapshotParams: {
|
||||
backupParams: {
|
||||
name: `addon_${this.addon.slug}_${this.addon.version}`,
|
||||
addons: [this.addon.slug],
|
||||
homeassistant: false,
|
||||
@@ -1141,6 +1149,7 @@ class HassioAddonInfo extends LitElement {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
img.logo {
|
||||
max-width: 100%;
|
||||
max-height: 60px;
|
||||
margin: 16px 0;
|
||||
display: block;
|
||||
@@ -1150,10 +1159,10 @@ class HassioAddonInfo extends LitElement {
|
||||
display: flex;
|
||||
}
|
||||
ha-svg-icon.running {
|
||||
color: var(--paper-green-400);
|
||||
color: var(--success-color);
|
||||
}
|
||||
ha-svg-icon.stopped {
|
||||
color: var(--google-red-300);
|
||||
color: var(--error-color);
|
||||
}
|
||||
ha-call-api-button {
|
||||
font-weight: 500;
|
||||
|
@@ -25,12 +25,12 @@ import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-fab";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioSnapshots,
|
||||
fetchHassioBackups,
|
||||
friendlyFolderName,
|
||||
HassioSnapshot,
|
||||
reloadHassioSnapshots,
|
||||
removeSnapshot,
|
||||
} from "../../../src/data/hassio/snapshot";
|
||||
HassioBackup,
|
||||
reloadHassioBackups,
|
||||
removeBackup,
|
||||
} from "../../../src/data/hassio/backup";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -40,14 +40,14 @@ import "../../../src/layouts/hass-tabs-subpage-data-table";
|
||||
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot";
|
||||
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
|
||||
import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload";
|
||||
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
|
||||
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
|
||||
import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-snapshots")
|
||||
export class HassioSnapshots extends LitElement {
|
||||
@customElement("hassio-backups")
|
||||
export class HassioBackups extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
@@ -58,9 +58,9 @@ export class HassioSnapshots extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@state() private _selectedSnapshots: string[] = [];
|
||||
@state() private _selectedBackups: string[] = [];
|
||||
|
||||
@state() private _snapshots?: HassioSnapshot[] = [];
|
||||
@state() private _backups?: HassioBackup[] = [];
|
||||
|
||||
@query("hass-tabs-subpage-data-table", true)
|
||||
private _dataTable!: HaTabsSubpageDataTable;
|
||||
@@ -75,26 +75,26 @@ export class HassioSnapshots extends LitElement {
|
||||
}
|
||||
|
||||
public async refreshData() {
|
||||
await reloadHassioSnapshots(this.hass);
|
||||
await this.fetchSnapshots();
|
||||
await reloadHassioBackups(this.hass);
|
||||
await this.fetchBackups();
|
||||
}
|
||||
|
||||
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => {
|
||||
if (snapshot.type === "full") {
|
||||
return this.supervisor.localize("snapshot.full_snapshot");
|
||||
private _computeBackupContent = (backup: HassioBackup): string => {
|
||||
if (backup.type === "full") {
|
||||
return this.supervisor.localize("backup.full_backup");
|
||||
}
|
||||
const content: string[] = [];
|
||||
if (snapshot.content.homeassistant) {
|
||||
if (backup.content.homeassistant) {
|
||||
content.push("Home Assistant");
|
||||
}
|
||||
if (snapshot.content.folders.length !== 0) {
|
||||
for (const folder of snapshot.content.folders) {
|
||||
if (backup.content.folders.length !== 0) {
|
||||
for (const folder of backup.content.folders) {
|
||||
content.push(friendlyFolderName[folder] || folder);
|
||||
}
|
||||
}
|
||||
|
||||
if (snapshot.content.addons.length !== 0) {
|
||||
for (const addon of snapshot.content.addons) {
|
||||
if (backup.content.addons.length !== 0) {
|
||||
for (const addon of backup.content.addons) {
|
||||
content.push(
|
||||
this.supervisor.supervisor.addons.find(
|
||||
(entry) => entry.slug === addon
|
||||
@@ -117,16 +117,16 @@ export class HassioSnapshots extends LitElement {
|
||||
private _columns = memoizeOne(
|
||||
(narrow: boolean): DataTableColumnContainer => ({
|
||||
name: {
|
||||
title: this.supervisor?.localize("snapshot.name") || "",
|
||||
title: this.supervisor?.localize("backup.name") || "",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
grows: true,
|
||||
template: (entry: string, snapshot: any) =>
|
||||
html`${entry || snapshot.slug}
|
||||
<div class="secondary">${snapshot.secondary}</div>`,
|
||||
template: (entry: string, backup: any) =>
|
||||
html`${entry || backup.slug}
|
||||
<div class="secondary">${backup.secondary}</div>`,
|
||||
},
|
||||
date: {
|
||||
title: this.supervisor?.localize("snapshot.created") || "",
|
||||
title: this.supervisor?.localize("backup.created") || "",
|
||||
width: "15%",
|
||||
direction: "desc",
|
||||
hidden: narrow,
|
||||
@@ -143,10 +143,10 @@ export class HassioSnapshots extends LitElement {
|
||||
})
|
||||
);
|
||||
|
||||
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) =>
|
||||
snapshots.map((snapshot) => ({
|
||||
...snapshot,
|
||||
secondary: this._computeSnapshotContent(snapshot),
|
||||
private _backupData = memoizeOne((backups: HassioBackup[]) =>
|
||||
backups.map((backup) => ({
|
||||
...backup,
|
||||
secondary: this._computeBackupContent(backup),
|
||||
}))
|
||||
);
|
||||
|
||||
@@ -160,11 +160,11 @@ export class HassioSnapshots extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.localizeFunc=${this.supervisor.localize}
|
||||
.searchLabel=${this.supervisor.localize("search")}
|
||||
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")}
|
||||
.noDataText=${this.supervisor.localize("backup.no_backups")}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.columns=${this._columns(this.narrow)}
|
||||
.data=${this._snapshotData(this._snapshots || [])}
|
||||
.data=${this._backupData(this._backups || [])}
|
||||
id="slug"
|
||||
@row-click=${this._handleRowClicked}
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
@@ -187,12 +187,12 @@ export class HassioSnapshots extends LitElement {
|
||||
</mwc-list-item>
|
||||
${atLeastVersion(this.hass.config.version, 0, 116)
|
||||
? html`<mwc-list-item>
|
||||
${this.supervisor?.localize("snapshot.upload_snapshot")}
|
||||
${this.supervisor?.localize("backup.upload_backup")}
|
||||
</mwc-list-item>`
|
||||
: ""}
|
||||
</ha-button-menu>
|
||||
|
||||
${this._selectedSnapshots.length
|
||||
${this._selectedBackups.length
|
||||
? html`<div
|
||||
class=${classMap({
|
||||
"header-toolbar": this.narrow,
|
||||
@@ -201,8 +201,8 @@ export class HassioSnapshots extends LitElement {
|
||||
slot="header"
|
||||
>
|
||||
<p class="selected-txt">
|
||||
${this.supervisor.localize("snapshot.selected", {
|
||||
number: this._selectedSnapshots.length,
|
||||
${this.supervisor.localize("backup.selected", {
|
||||
number: this._selectedBackups.length,
|
||||
})}
|
||||
</p>
|
||||
<div class="header-btns">
|
||||
@@ -212,7 +212,7 @@ export class HassioSnapshots extends LitElement {
|
||||
@click=${this._deleteSelected}
|
||||
class="warning"
|
||||
>
|
||||
${this.supervisor.localize("snapshot.delete_selected")}
|
||||
${this.supervisor.localize("backup.delete_selected")}
|
||||
</mwc-button>
|
||||
`
|
||||
: html`
|
||||
@@ -224,7 +224,7 @@ export class HassioSnapshots extends LitElement {
|
||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<paper-tooltip animation-delay="0" for="delete-btn">
|
||||
${this.supervisor.localize("snapshot.delete_selected")}
|
||||
${this.supervisor.localize("backup.delete_selected")}
|
||||
</paper-tooltip>
|
||||
`}
|
||||
</div>
|
||||
@@ -233,8 +233,8 @@ export class HassioSnapshots extends LitElement {
|
||||
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
@click=${this._createSnapshot}
|
||||
.label=${this.supervisor.localize("snapshot.create_snapshot")}
|
||||
@click=${this._createBackup}
|
||||
.label=${this.supervisor.localize("backup.create_backup")}
|
||||
extended
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||
@@ -249,7 +249,7 @@ export class HassioSnapshots extends LitElement {
|
||||
this.refreshData();
|
||||
break;
|
||||
case 1:
|
||||
this._showUploadSnapshotDialog();
|
||||
this._showUploadBackupDialog();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -257,33 +257,33 @@ export class HassioSnapshots extends LitElement {
|
||||
private _handleSelectionChanged(
|
||||
ev: HASSDomEvent<SelectionChangedEvent>
|
||||
): void {
|
||||
this._selectedSnapshots = ev.detail.value;
|
||||
this._selectedBackups = ev.detail.value;
|
||||
}
|
||||
|
||||
private _showUploadSnapshotDialog() {
|
||||
showSnapshotUploadDialog(this, {
|
||||
showSnapshot: (slug: string) =>
|
||||
showHassioSnapshotDialog(this, {
|
||||
private _showUploadBackupDialog() {
|
||||
showBackupUploadDialog(this, {
|
||||
showBackup: (slug: string) =>
|
||||
showHassioBackupDialog(this, {
|
||||
slug,
|
||||
supervisor: this.supervisor,
|
||||
onDelete: () => this.fetchSnapshots(),
|
||||
onDelete: () => this.fetchBackups(),
|
||||
}),
|
||||
reloadSnapshot: () => this.refreshData(),
|
||||
reloadBackup: () => this.refreshData(),
|
||||
});
|
||||
}
|
||||
|
||||
private async fetchSnapshots() {
|
||||
await reloadHassioSnapshots(this.hass);
|
||||
this._snapshots = await fetchHassioSnapshots(this.hass);
|
||||
private async fetchBackups() {
|
||||
await reloadHassioBackups(this.hass);
|
||||
this._backups = await fetchHassioBackups(this.hass);
|
||||
}
|
||||
|
||||
private async _deleteSelected() {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: this.supervisor.localize("snapshot.delete_snapshot_title"),
|
||||
text: this.supervisor.localize("snapshot.delete_snapshot_text", {
|
||||
number: this._selectedSnapshots.length,
|
||||
title: this.supervisor.localize("backup.delete_backup_title"),
|
||||
text: this.supervisor.localize("backup.delete_backup_text", {
|
||||
number: this._selectedBackups.length,
|
||||
}),
|
||||
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"),
|
||||
confirmText: this.supervisor.localize("backup.delete_backup_confirm"),
|
||||
});
|
||||
|
||||
if (!confirm) {
|
||||
@@ -292,44 +292,44 @@ export class HassioSnapshots extends LitElement {
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug))
|
||||
this._selectedBackups.map((slug) => removeBackup(this.hass, slug))
|
||||
);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor.localize("snapshot.failed_to_delete"),
|
||||
title: this.supervisor.localize("backup.failed_to_delete"),
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
return;
|
||||
}
|
||||
await reloadHassioSnapshots(this.hass);
|
||||
this._snapshots = await fetchHassioSnapshots(this.hass);
|
||||
await reloadHassioBackups(this.hass);
|
||||
this._backups = await fetchHassioBackups(this.hass);
|
||||
this._dataTable.clearSelection();
|
||||
}
|
||||
|
||||
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const slug = ev.detail.id;
|
||||
showHassioSnapshotDialog(this, {
|
||||
showHassioBackupDialog(this, {
|
||||
slug,
|
||||
supervisor: this.supervisor,
|
||||
onDelete: () => this.fetchSnapshots(),
|
||||
onDelete: () => this.fetchBackups(),
|
||||
});
|
||||
}
|
||||
|
||||
private _createSnapshot() {
|
||||
private _createBackup() {
|
||||
if (this.supervisor!.info.state !== "running") {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor!.localize("snapshot.could_not_create"),
|
||||
title: this.supervisor!.localize("backup.could_not_create"),
|
||||
text: this.supervisor!.localize(
|
||||
"snapshot.create_blocked_not_running",
|
||||
"backup.create_blocked_not_running",
|
||||
"state",
|
||||
this.supervisor!.info.state
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
showHassioCreateSnapshotDialog(this, {
|
||||
showHassioCreateBackupDialog(this, {
|
||||
supervisor: this.supervisor!,
|
||||
onCreate: () => this.fetchSnapshots(),
|
||||
onCreate: () => this.fetchBackups(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -378,6 +378,6 @@ export class HassioSnapshots extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-snapshots": HassioSnapshots;
|
||||
"hassio-backups": HassioBackups;
|
||||
}
|
||||
}
|
@@ -41,16 +41,16 @@ class HassioAnsiToHtml extends LitElement {
|
||||
text-decoration: underline line-through;
|
||||
}
|
||||
.fg-red {
|
||||
color: rgb(222, 56, 43);
|
||||
color: var(--error-color);
|
||||
}
|
||||
.fg-green {
|
||||
color: rgb(57, 181, 74);
|
||||
color: var(--success-color);
|
||||
}
|
||||
.fg-yellow {
|
||||
color: rgb(255, 199, 6);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
.fg-blue {
|
||||
color: rgb(0, 111, 184);
|
||||
color: var(--info-color);
|
||||
}
|
||||
.fg-magenta {
|
||||
color: rgb(118, 38, 113);
|
||||
@@ -65,16 +65,16 @@ class HassioAnsiToHtml extends LitElement {
|
||||
background-color: rgb(0, 0, 0);
|
||||
}
|
||||
.bg-red {
|
||||
background-color: rgb(222, 56, 43);
|
||||
background-color: var(--error-color);
|
||||
}
|
||||
.bg-green {
|
||||
background-color: rgb(57, 181, 74);
|
||||
background-color: var(--success-color);
|
||||
}
|
||||
.bg-yellow {
|
||||
background-color: rgb(255, 199, 6);
|
||||
background-color: var(--warning-color);
|
||||
}
|
||||
.bg-blue {
|
||||
background-color: rgb(0, 111, 184);
|
||||
background-color: var(--info-color);
|
||||
}
|
||||
.bg-magenta {
|
||||
background-color: rgb(118, 38, 113);
|
||||
|
@@ -80,14 +80,14 @@ class HassioCardContent extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-svg-icon.update {
|
||||
color: var(--paper-orange-400);
|
||||
color: var(--warning-color);
|
||||
}
|
||||
ha-svg-icon.running,
|
||||
ha-svg-icon.installed {
|
||||
color: var(--paper-green-400);
|
||||
color: var(--success-color);
|
||||
}
|
||||
ha-svg-icon.hassupdate,
|
||||
ha-svg-icon.snapshot {
|
||||
ha-svg-icon.backup {
|
||||
color: var(--paper-item-icon-color);
|
||||
}
|
||||
ha-svg-icon.not_available {
|
||||
@@ -122,7 +122,7 @@ class HassioCardContent extends LitElement {
|
||||
}
|
||||
.dot {
|
||||
position: absolute;
|
||||
background-color: var(--paper-orange-400);
|
||||
background-color: var(--warning-color);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
top: 8px;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
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";
|
||||
@@ -9,23 +8,20 @@ import "../../../src/components/ha-circular-progress";
|
||||
import "../../../src/components/ha-file-upload";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
HassioSnapshot,
|
||||
uploadSnapshot,
|
||||
} from "../../../src/data/hassio/snapshot";
|
||||
import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
|
||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"snapshot-uploaded": { snapshot: HassioSnapshot };
|
||||
"backup-uploaded": { backup: HassioBackup };
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
|
||||
|
||||
@customElement("hassio-upload-snapshot")
|
||||
export class HassioUploadSnapshot extends LitElement {
|
||||
@customElement("hassio-upload-backup")
|
||||
export class HassioUploadBackup extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@state() public value: string | null = null;
|
||||
@@ -38,7 +34,7 @@ export class HassioUploadSnapshot extends LitElement {
|
||||
.uploading=${this._uploading}
|
||||
.icon=${mdiFolderUpload}
|
||||
accept="application/x-tar"
|
||||
label="Upload snapshot"
|
||||
label="Upload backup"
|
||||
@file-picked=${this._uploadFile}
|
||||
auto-open-file-dialog
|
||||
></ha-file-upload>
|
||||
@@ -50,10 +46,10 @@ export class HassioUploadSnapshot extends LitElement {
|
||||
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
showAlertDialog(this, {
|
||||
title: "Snapshot file is too big",
|
||||
title: "Backup file is too big",
|
||||
text: html`The maximum allowed filesize is 1GB.<br />
|
||||
<a
|
||||
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-snapshot-on-a-new-install"
|
||||
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-backup-on-a-new-install"
|
||||
target="_blank"
|
||||
>Have a look here on how to restore it.</a
|
||||
>`,
|
||||
@@ -65,15 +61,15 @@ export class HassioUploadSnapshot extends LitElement {
|
||||
if (!["application/x-tar"].includes(file.type)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Unsupported file format",
|
||||
text: "Please choose a Home Assistant snapshot file (.tar)",
|
||||
text: "Please choose a Home Assistant backup file (.tar)",
|
||||
confirmText: "ok",
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._uploading = true;
|
||||
try {
|
||||
const snapshot = await uploadSnapshot(this.hass, file);
|
||||
fireEvent(this, "snapshot-uploaded", { snapshot: snapshot.data });
|
||||
const backup = await uploadBackup(this.hass, file);
|
||||
fireEvent(this, "backup-uploaded", { backup: backup.data });
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Upload failed",
|
||||
@@ -88,6 +84,6 @@ export class HassioUploadSnapshot extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hassio-upload-snapshot": HassioUploadSnapshot;
|
||||
"hassio-upload-backup": HassioUploadBackup;
|
||||
}
|
||||
}
|
@@ -11,10 +11,10 @@ import "../../../src/components/ha-formfield";
|
||||
import "../../../src/components/ha-radio";
|
||||
import type { HaRadio } from "../../../src/components/ha-radio";
|
||||
import {
|
||||
HassioFullSnapshotCreateParams,
|
||||
HassioPartialSnapshotCreateParams,
|
||||
HassioSnapshotDetail,
|
||||
} from "../../../src/data/hassio/snapshot";
|
||||
HassioFullBackupCreateParams,
|
||||
HassioPartialBackupCreateParams,
|
||||
HassioBackupDetail,
|
||||
} from "../../../src/data/hassio/backup";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { PolymerChangedEvent } from "../../../src/polymer-types";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
@@ -64,17 +64,17 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
|
||||
}))
|
||||
.sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
|
||||
@customElement("supervisor-snapshot-content")
|
||||
export class SupervisorSnapshotContent extends LitElement {
|
||||
@customElement("supervisor-backup-content")
|
||||
export class SupervisorBackupContent extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public localize?: LocalizeFunc;
|
||||
|
||||
@property({ attribute: false }) public supervisor?: Supervisor;
|
||||
|
||||
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail;
|
||||
@property({ attribute: false }) public backup?: HassioBackupDetail;
|
||||
|
||||
@property() public snapshotType: HassioSnapshotDetail["type"] = "full";
|
||||
@property() public backupType: HassioBackupDetail["type"] = "full";
|
||||
|
||||
@property({ attribute: false }) public folders?: CheckboxItem[];
|
||||
|
||||
@@ -82,37 +82,35 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public homeAssistant = false;
|
||||
|
||||
@property({ type: Boolean }) public snapshotHasPassword = false;
|
||||
@property({ type: Boolean }) public backupHasPassword = false;
|
||||
|
||||
@property({ type: Boolean }) public onboarding = false;
|
||||
|
||||
@property() public snapshotName = "";
|
||||
@property() public backupName = "";
|
||||
|
||||
@property() public snapshotPassword = "";
|
||||
@property() public backupPassword = "";
|
||||
|
||||
@property() public confirmSnapshotPassword = "";
|
||||
@property() public confirmBackupPassword = "";
|
||||
|
||||
public willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
this.folders = _computeFolders(
|
||||
this.snapshot
|
||||
? this.snapshot.folders
|
||||
this.backup
|
||||
? this.backup.folders
|
||||
: ["homeassistant", "ssl", "share", "media", "addons/local"]
|
||||
);
|
||||
this.addons = _computeAddons(
|
||||
this.snapshot
|
||||
? this.snapshot.addons
|
||||
: this.supervisor?.supervisor.addons
|
||||
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
|
||||
);
|
||||
this.snapshotType = this.snapshot?.type || "full";
|
||||
this.snapshotName = this.snapshot?.name || "";
|
||||
this.snapshotHasPassword = this.snapshot?.protected || false;
|
||||
this.backupType = this.backup?.type || "full";
|
||||
this.backupName = this.backup?.name || "";
|
||||
this.backupHasPassword = this.backup?.protected || false;
|
||||
}
|
||||
}
|
||||
|
||||
private _localize = (string: string) =>
|
||||
this.supervisor?.localize(`snapshot.${string}`) ||
|
||||
this.supervisor?.localize(`backup.${string}`) ||
|
||||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -120,64 +118,64 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
const foldersSection =
|
||||
this.snapshotType === "partial" ? this._getSection("folders") : undefined;
|
||||
this.backupType === "partial" ? this._getSection("folders") : undefined;
|
||||
const addonsSection =
|
||||
this.snapshotType === "partial" ? this._getSection("addons") : undefined;
|
||||
this.backupType === "partial" ? this._getSection("addons") : undefined;
|
||||
|
||||
return html`
|
||||
${this.snapshot
|
||||
${this.backup
|
||||
? html`<div class="details">
|
||||
${this.snapshot.type === "full"
|
||||
? this._localize("full_snapshot")
|
||||
: this._localize("partial_snapshot")}
|
||||
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
|
||||
${this.backup.type === "full"
|
||||
? this._localize("full_backup")
|
||||
: this._localize("partial_backup")}
|
||||
(${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br />
|
||||
${this.hass
|
||||
? formatDateTime(new Date(this.snapshot.date), this.hass.locale)
|
||||
: this.snapshot.date}
|
||||
? formatDateTime(new Date(this.backup.date), this.hass.locale)
|
||||
: this.backup.date}
|
||||
</div>`
|
||||
: html`<paper-input
|
||||
name="snapshotName"
|
||||
.label=${this.supervisor?.localize("snapshot.name") || "Name"}
|
||||
.value=${this.snapshotName}
|
||||
name="backupName"
|
||||
.label=${this._localize("name")}
|
||||
.value=${this.backupName}
|
||||
@value-changed=${this._handleTextValueChanged}
|
||||
>
|
||||
</paper-input>`}
|
||||
${!this.snapshot || this.snapshot.type === "full"
|
||||
${!this.backup || this.backup.type === "full"
|
||||
? html`<div class="sub-header">
|
||||
${!this.snapshot
|
||||
${!this.backup
|
||||
? this._localize("type")
|
||||
: this._localize("select_type")}
|
||||
</div>
|
||||
<div class="snapshot-types">
|
||||
<ha-formfield .label=${this._localize("full_snapshot")}>
|
||||
<div class="backup-types">
|
||||
<ha-formfield .label=${this._localize("full_backup")}>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
value="full"
|
||||
name="snapshotType"
|
||||
.checked=${this.snapshotType === "full"}
|
||||
name="backupType"
|
||||
.checked=${this.backupType === "full"}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield .label=${this._localize("partial_snapshot")}>
|
||||
<ha-formfield .label=${this._localize("partial_backup")}>
|
||||
<ha-radio
|
||||
@change=${this._handleRadioValueChanged}
|
||||
value="partial"
|
||||
name="snapshotType"
|
||||
.checked=${this.snapshotType === "partial"}
|
||||
name="backupType"
|
||||
.checked=${this.backupType === "partial"}
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
</div>`
|
||||
: ""}
|
||||
${this.snapshotType === "partial"
|
||||
${this.backupType === "partial"
|
||||
? html`<div class="partial-picker">
|
||||
${this.snapshot && this.snapshot.homeassistant
|
||||
${this.backup && this.backup.homeassistant
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
label="Home Assistant"
|
||||
.iconPath=${mdiHomeAssistant}
|
||||
.version=${this.snapshot.homeassistant}
|
||||
.version=${this.backup.homeassistant}
|
||||
>
|
||||
</supervisor-formfield-label>`}
|
||||
>
|
||||
@@ -233,38 +231,38 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
: ""}
|
||||
</div> `
|
||||
: ""}
|
||||
${this.snapshotType === "partial" &&
|
||||
(!this.snapshot || this.snapshotHasPassword)
|
||||
${this.backupType === "partial" &&
|
||||
(!this.backup || this.backupHasPassword)
|
||||
? html`<hr />`
|
||||
: ""}
|
||||
${!this.snapshot
|
||||
${!this.backup
|
||||
? html`<ha-formfield
|
||||
class="password"
|
||||
.label=${this._localize("password_protection")}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.snapshotHasPassword}
|
||||
.checked=${this.backupHasPassword}
|
||||
@change=${this._toggleHasPassword}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>`
|
||||
: ""}
|
||||
${this.snapshotHasPassword
|
||||
${this.backupHasPassword
|
||||
? html`
|
||||
<paper-input
|
||||
.label=${this._localize("password")}
|
||||
type="password"
|
||||
name="snapshotPassword"
|
||||
.value=${this.snapshotPassword}
|
||||
name="backupPassword"
|
||||
.value=${this.backupPassword}
|
||||
@value-changed=${this._handleTextValueChanged}
|
||||
>
|
||||
</paper-input>
|
||||
${!this.snapshot
|
||||
${!this.backup
|
||||
? html` <paper-input
|
||||
.label=${this.supervisor?.localize("confirm_password")}
|
||||
.label=${this._localize("confirm_password")}
|
||||
type="password"
|
||||
name="confirmSnapshotPassword"
|
||||
.value=${this.confirmSnapshotPassword}
|
||||
name="confirmBackupPassword"
|
||||
.value=${this.confirmBackupPassword}
|
||||
@value-changed=${this._handleTextValueChanged}
|
||||
>
|
||||
</paper-input>`
|
||||
@@ -307,7 +305,7 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
display: block;
|
||||
margin: 0 -14px -16px;
|
||||
}
|
||||
.snapshot-types {
|
||||
.backup-types {
|
||||
display: flex;
|
||||
margin-left: -13px;
|
||||
}
|
||||
@@ -317,23 +315,23 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
public snapshotDetails():
|
||||
| HassioPartialSnapshotCreateParams
|
||||
| HassioFullSnapshotCreateParams {
|
||||
public backupDetails():
|
||||
| HassioPartialBackupCreateParams
|
||||
| HassioFullBackupCreateParams {
|
||||
const data: any = {};
|
||||
|
||||
if (!this.snapshot) {
|
||||
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale);
|
||||
if (!this.backup) {
|
||||
data.name = this.backupName || formatDate(new Date(), this.hass.locale);
|
||||
}
|
||||
|
||||
if (this.snapshotHasPassword) {
|
||||
data.password = this.snapshotPassword;
|
||||
if (!this.snapshot) {
|
||||
data.confirm_password = this.confirmSnapshotPassword;
|
||||
if (this.backupHasPassword) {
|
||||
data.password = this.backupPassword;
|
||||
if (!this.backup) {
|
||||
data.confirm_password = this.confirmBackupPassword;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.snapshotType === "full") {
|
||||
if (this.backupType === "full") {
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -415,7 +413,7 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
}
|
||||
|
||||
private _toggleHasPassword(): void {
|
||||
this.snapshotHasPassword = !this.snapshotHasPassword;
|
||||
this.backupHasPassword = !this.backupHasPassword;
|
||||
}
|
||||
|
||||
private _toggleSection(ev): void {
|
||||
@@ -445,6 +443,6 @@ export class SupervisorSnapshotContent extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"supervisor-snapshot-content": SupervisorSnapshotContent;
|
||||
"supervisor-backup-content": SupervisorBackupContent;
|
||||
}
|
||||
}
|
@@ -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("hassos")
|
||||
${this.supervisor.host.features.includes("haos")
|
||||
? this._renderUpdateCard(
|
||||
"Operating System",
|
||||
"os",
|
||||
@@ -161,9 +161,8 @@ export class HassioUpdate extends LitElement {
|
||||
showDialogSupervisorUpdate(this, {
|
||||
supervisor: this.supervisor,
|
||||
name: "Home Assistant Core",
|
||||
slug: "core",
|
||||
version: this.supervisor.core.version_latest,
|
||||
snapshotParams: {
|
||||
backupParams: {
|
||||
name: `core_${this.supervisor.core.version}`,
|
||||
folders: ["homeassistant"],
|
||||
homeassistant: true,
|
||||
|
@@ -6,19 +6,20 @@ import "../../../../src/components/ha-header-bar";
|
||||
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-upload-snapshot";
|
||||
import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload";
|
||||
import "../../components/hassio-upload-backup";
|
||||
import { HassioBackupUploadDialogParams } from "./show-dialog-backup-upload";
|
||||
|
||||
@customElement("dialog-hassio-snapshot-upload")
|
||||
export class DialogHassioSnapshotUpload
|
||||
@customElement("dialog-hassio-backup-upload")
|
||||
export class DialogHassioBackupUpload
|
||||
extends LitElement
|
||||
implements HassDialog<HassioSnapshotUploadDialogParams> {
|
||||
implements HassDialog<HassioBackupUploadDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: HassioSnapshotUploadDialogParams;
|
||||
@state() private _params?: HassioBackupUploadDialogParams;
|
||||
|
||||
public async showDialog(
|
||||
params: HassioSnapshotUploadDialogParams
|
||||
params: HassioBackupUploadDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
await this.updateComplete;
|
||||
@@ -26,8 +27,8 @@ export class DialogHassioSnapshotUpload
|
||||
|
||||
public closeDialog(): void {
|
||||
if (this._params && !this._params.onboarding) {
|
||||
if (this._params.reloadSnapshot) {
|
||||
this._params.reloadSnapshot();
|
||||
if (this._params.reloadBackup) {
|
||||
this._params.reloadBackup();
|
||||
}
|
||||
}
|
||||
this._params = undefined;
|
||||
@@ -50,23 +51,23 @@ export class DialogHassioSnapshotUpload
|
||||
>
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title"> Upload snapshot </span>
|
||||
<span slot="title"> Upload backup </span>
|
||||
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</ha-header-bar>
|
||||
</div>
|
||||
<hassio-upload-snapshot
|
||||
@snapshot-uploaded=${this._snapshotUploaded}
|
||||
<hassio-upload-backup
|
||||
@backup-uploaded=${this._backupUploaded}
|
||||
.hass=${this.hass}
|
||||
></hassio-upload-snapshot>
|
||||
></hassio-upload-backup>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _snapshotUploaded(ev) {
|
||||
const snapshot = ev.detail.snapshot;
|
||||
this._params?.showSnapshot(snapshot.slug);
|
||||
private _backupUploaded(ev) {
|
||||
const backup = ev.detail.backup;
|
||||
this._params?.showBackup(backup.slug);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
@@ -93,6 +94,6 @@ export class DialogHassioSnapshotUpload
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-hassio-snapshot-upload": DialogHassioSnapshotUpload;
|
||||
"dialog-hassio-backup-upload": DialogHassioBackupUpload;
|
||||
}
|
||||
}
|
140
hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts → hassio/src/dialogs/backup/dialog-hassio-backup.ts
Executable file → Normal file
140
hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts → hassio/src/dialogs/backup/dialog-hassio-backup.ts
Executable file → Normal file
@@ -12,9 +12,9 @@ import "../../../../src/components/ha-svg-icon";
|
||||
import { getSignedPath } from "../../../../src/data/auth";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioSnapshotInfo,
|
||||
HassioSnapshotDetail,
|
||||
} from "../../../../src/data/hassio/snapshot";
|
||||
fetchHassioBackupInfo,
|
||||
HassioBackupDetail,
|
||||
} from "../../../../src/data/hassio/backup";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -23,43 +23,45 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { fileDownload } from "../../../../src/util/file_download";
|
||||
import "../../components/supervisor-snapshot-content";
|
||||
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
|
||||
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
|
||||
import "../../components/supervisor-backup-content";
|
||||
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
|
||||
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
|
||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||
|
||||
@customElement("dialog-hassio-snapshot")
|
||||
class HassioSnapshotDialog
|
||||
@customElement("dialog-hassio-backup")
|
||||
class HassioBackupDialog
|
||||
extends LitElement
|
||||
implements HassDialog<HassioSnapshotDialogParams> {
|
||||
implements HassDialog<HassioBackupDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _snapshot?: HassioSnapshotDetail;
|
||||
@state() private _backup?: HassioBackupDetail;
|
||||
|
||||
@state() private _dialogParams?: HassioSnapshotDialogParams;
|
||||
@state() private _dialogParams?: HassioBackupDialogParams;
|
||||
|
||||
@state() private _restoringSnapshot = false;
|
||||
@state() private _restoringBackup = false;
|
||||
|
||||
@query("supervisor-snapshot-content")
|
||||
private _snapshotContent!: SupervisorSnapshotContent;
|
||||
@query("supervisor-backup-content")
|
||||
private _backupContent!: SupervisorBackupContent;
|
||||
|
||||
public async showDialog(params: HassioSnapshotDialogParams) {
|
||||
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
|
||||
public async showDialog(params: HassioBackupDialogParams) {
|
||||
this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
|
||||
this._dialogParams = params;
|
||||
this._restoringSnapshot = false;
|
||||
this._restoringBackup = false;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._snapshot = undefined;
|
||||
this._backup = undefined;
|
||||
this._dialogParams = undefined;
|
||||
this._restoringSnapshot = false;
|
||||
this._restoringBackup = false;
|
||||
this._error = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._dialogParams || !this._snapshot) {
|
||||
if (!this._dialogParams || !this._backup) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
@@ -71,26 +73,26 @@ class HassioSnapshotDialog
|
||||
>
|
||||
<div slot="heading">
|
||||
<ha-header-bar>
|
||||
<span slot="title">${this._snapshot.name}</span>
|
||||
<span slot="title">${this._backup.name}</span>
|
||||
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</ha-header-bar>
|
||||
</div>
|
||||
${this._restoringSnapshot
|
||||
${this._restoringBackup
|
||||
? html` <ha-circular-progress active></ha-circular-progress>`
|
||||
: html`<supervisor-snapshot-content
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
.snapshot=${this._snapshot}
|
||||
.backup=${this._backup}
|
||||
.onboarding=${this._dialogParams.onboarding || false}
|
||||
.localize=${this._dialogParams.localize}
|
||||
>
|
||||
</supervisor-snapshot-content>`}
|
||||
</supervisor-backup-content>`}
|
||||
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
|
||||
|
||||
<mwc-button
|
||||
.disabled=${this._restoringSnapshot}
|
||||
.disabled=${this._restoringBackup}
|
||||
slot="secondaryAction"
|
||||
@click=${this._restoreClicked}
|
||||
>
|
||||
@@ -107,8 +109,8 @@ class HassioSnapshotDialog
|
||||
<mwc-icon-button slot="trigger" alt="menu">
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item>Download Snapshot</mwc-list-item>
|
||||
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
|
||||
<mwc-list-item>Download Backup</mwc-list-item>
|
||||
<mwc-list-item class="error">Delete Backup</mwc-list-item>
|
||||
</ha-button-menu>`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
@@ -149,30 +151,30 @@ class HassioSnapshotDialog
|
||||
}
|
||||
|
||||
private async _restoreClicked() {
|
||||
const snapshotDetails = this._snapshotContent.snapshotDetails();
|
||||
this._restoringSnapshot = true;
|
||||
if (this._snapshotContent.snapshotType === "full") {
|
||||
await this._fullRestoreClicked(snapshotDetails);
|
||||
const backupDetails = this._backupContent.backupDetails();
|
||||
this._restoringBackup = true;
|
||||
if (this._backupContent.backupType === "full") {
|
||||
await this._fullRestoreClicked(backupDetails);
|
||||
} else {
|
||||
await this._partialRestoreClicked(snapshotDetails);
|
||||
await this._partialRestoreClicked(backupDetails);
|
||||
}
|
||||
this._restoringSnapshot = false;
|
||||
this._restoringBackup = false;
|
||||
}
|
||||
|
||||
private async _partialRestoreClicked(snapshotDetails) {
|
||||
private async _partialRestoreClicked(backupDetails) {
|
||||
if (
|
||||
this._dialogParams?.supervisor !== undefined &&
|
||||
this._dialogParams?.supervisor.info.state !== "running"
|
||||
) {
|
||||
await showAlertDialog(this, {
|
||||
title: "Could not restore snapshot",
|
||||
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
|
||||
title: "Could not restore backup",
|
||||
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want partially to restore this snapshot?",
|
||||
title: "Are you sure you want partially to restore this backup?",
|
||||
confirmText: "restore",
|
||||
dismissText: "cancel",
|
||||
}))
|
||||
@@ -185,8 +187,12 @@ class HassioSnapshotDialog
|
||||
.callApi(
|
||||
"POST",
|
||||
|
||||
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
|
||||
snapshotDetails
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/restore/partial`,
|
||||
backupDetails
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
@@ -198,29 +204,29 @@ class HassioSnapshotDialog
|
||||
);
|
||||
} else {
|
||||
fireEvent(this, "restoring");
|
||||
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, {
|
||||
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(snapshotDetails),
|
||||
body: JSON.stringify(backupDetails),
|
||||
});
|
||||
this.closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fullRestoreClicked(snapshotDetails) {
|
||||
private async _fullRestoreClicked(backupDetails) {
|
||||
if (
|
||||
this._dialogParams?.supervisor !== undefined &&
|
||||
this._dialogParams?.supervisor.info.state !== "running"
|
||||
) {
|
||||
await showAlertDialog(this, {
|
||||
title: "Could not restore snapshot",
|
||||
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
|
||||
title: "Could not restore backup",
|
||||
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title:
|
||||
"Are you sure you want to wipe your system and restore this snapshot?",
|
||||
"Are you sure you want to wipe your system and restore this backup?",
|
||||
confirmText: "restore",
|
||||
dismissText: "cancel",
|
||||
}))
|
||||
@@ -232,8 +238,12 @@ class HassioSnapshotDialog
|
||||
this.hass
|
||||
.callApi(
|
||||
"POST",
|
||||
`hassio/snapshots/${this._snapshot!.slug}/restore/full`,
|
||||
snapshotDetails
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/restore/full`,
|
||||
backupDetails
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
@@ -245,9 +255,9 @@ class HassioSnapshotDialog
|
||||
);
|
||||
} else {
|
||||
fireEvent(this, "restoring");
|
||||
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, {
|
||||
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(snapshotDetails),
|
||||
body: JSON.stringify(backupDetails),
|
||||
});
|
||||
this.closeDialog();
|
||||
}
|
||||
@@ -256,7 +266,7 @@ class HassioSnapshotDialog
|
||||
private async _deleteClicked() {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want to delete this snapshot?",
|
||||
title: "Are you sure you want to delete this backup?",
|
||||
confirmText: "delete",
|
||||
dismissText: "cancel",
|
||||
}))
|
||||
@@ -266,7 +276,14 @@ class HassioSnapshotDialog
|
||||
|
||||
this.hass
|
||||
|
||||
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
|
||||
.callApi(
|
||||
atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST",
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/remove`
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
if (this._dialogParams!.onDelete) {
|
||||
@@ -285,7 +302,11 @@ class HassioSnapshotDialog
|
||||
try {
|
||||
signedPath = await getSignedPath(
|
||||
this.hass,
|
||||
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
|
||||
`/api/hassio/${
|
||||
atLeastVersion(this.hass.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/download`
|
||||
);
|
||||
} catch (err) {
|
||||
await showAlertDialog(this, {
|
||||
@@ -297,8 +318,7 @@ 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 backups 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",
|
||||
});
|
||||
@@ -310,19 +330,19 @@ class HassioSnapshotDialog
|
||||
fileDownload(
|
||||
this,
|
||||
signedPath.path,
|
||||
`home_assistant_snapshot_${slugify(this._computeName)}.tar`
|
||||
`home_assistant_backup_${slugify(this._computeName)}.tar`
|
||||
);
|
||||
}
|
||||
|
||||
private get _computeName() {
|
||||
return this._snapshot
|
||||
? this._snapshot.name || this._snapshot.slug
|
||||
: "Unnamed snapshot";
|
||||
return this._backup
|
||||
? this._backup.name || this._backup.slug
|
||||
: "Unnamed backup";
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-hassio-snapshot": HassioSnapshotDialog;
|
||||
"dialog-hassio-backup": HassioBackupDialog;
|
||||
}
|
||||
}
|
80
hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts → hassio/src/dialogs/backup/dialog-hassio-create-backup.ts
Executable file → Normal file
80
hassio/src/dialogs/snapshot/dialog-hassio-create-snapshot.ts → hassio/src/dialogs/backup/dialog-hassio-create-backup.ts
Executable file → Normal file
@@ -6,37 +6,37 @@ import "../../../../src/components/buttons/ha-progress-button";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
createHassioFullSnapshot,
|
||||
createHassioPartialSnapshot,
|
||||
} from "../../../../src/data/hassio/snapshot";
|
||||
createHassioFullBackup,
|
||||
createHassioPartialBackup,
|
||||
} from "../../../../src/data/hassio/backup";
|
||||
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/supervisor-snapshot-content";
|
||||
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
|
||||
import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot";
|
||||
import "../../components/supervisor-backup-content";
|
||||
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
|
||||
import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup";
|
||||
|
||||
@customElement("dialog-hassio-create-snapshot")
|
||||
class HassioCreateSnapshotDialog extends LitElement {
|
||||
@customElement("dialog-hassio-create-backup")
|
||||
class HassioCreateBackupDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _dialogParams?: HassioCreateSnapshotDialogParams;
|
||||
@state() private _dialogParams?: HassioCreateBackupDialogParams;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _creatingSnapshot = false;
|
||||
@state() private _creatingBackup = false;
|
||||
|
||||
@query("supervisor-snapshot-content")
|
||||
private _snapshotContent!: SupervisorSnapshotContent;
|
||||
@query("supervisor-backup-content")
|
||||
private _backupContent!: SupervisorBackupContent;
|
||||
|
||||
public showDialog(params: HassioCreateSnapshotDialogParams) {
|
||||
public showDialog(params: HassioCreateBackupDialogParams) {
|
||||
this._dialogParams = params;
|
||||
this._creatingSnapshot = false;
|
||||
this._creatingBackup = false;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._dialogParams = undefined;
|
||||
this._creatingSnapshot = false;
|
||||
this._creatingBackup = false;
|
||||
this._error = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
@@ -52,74 +52,74 @@ class HassioCreateSnapshotDialog extends LitElement {
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._dialogParams.supervisor.localize("snapshot.create_snapshot")
|
||||
this._dialogParams.supervisor.localize("backup.create_backup")
|
||||
)}
|
||||
>
|
||||
${this._creatingSnapshot
|
||||
${this._creatingBackup
|
||||
? html` <ha-circular-progress active></ha-circular-progress>`
|
||||
: html`<supervisor-snapshot-content
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
>
|
||||
</supervisor-snapshot-content>`}
|
||||
</supervisor-backup-content>`}
|
||||
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
|
||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
||||
${this._dialogParams.supervisor.localize("common.close")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
.disabled=${this._creatingSnapshot}
|
||||
.disabled=${this._creatingBackup}
|
||||
slot="primaryAction"
|
||||
@click=${this._createSnapshot}
|
||||
@click=${this._createBackup}
|
||||
>
|
||||
${this._dialogParams.supervisor.localize("snapshot.create")}
|
||||
${this._dialogParams.supervisor.localize("backup.create")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _createSnapshot(): Promise<void> {
|
||||
private async _createBackup(): Promise<void> {
|
||||
if (this._dialogParams!.supervisor.info.state !== "running") {
|
||||
showAlertDialog(this, {
|
||||
title: this._dialogParams!.supervisor.localize(
|
||||
"snapshot.could_not_create"
|
||||
"backup.could_not_create"
|
||||
),
|
||||
text: this._dialogParams!.supervisor.localize(
|
||||
"snapshot.create_blocked_not_running",
|
||||
"backup.create_blocked_not_running",
|
||||
"state",
|
||||
this._dialogParams!.supervisor.info.state
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const snapshotDetails = this._snapshotContent.snapshotDetails();
|
||||
this._creatingSnapshot = true;
|
||||
const backupDetails = this._backupContent.backupDetails();
|
||||
this._creatingBackup = true;
|
||||
|
||||
this._error = "";
|
||||
if (snapshotDetails.password && !snapshotDetails.password.length) {
|
||||
if (backupDetails.password && !backupDetails.password.length) {
|
||||
this._error = this._dialogParams!.supervisor.localize(
|
||||
"snapshot.enter_password"
|
||||
"backup.enter_password"
|
||||
);
|
||||
this._creatingSnapshot = false;
|
||||
this._creatingBackup = false;
|
||||
return;
|
||||
}
|
||||
if (
|
||||
snapshotDetails.password &&
|
||||
snapshotDetails.password !== snapshotDetails.confirm_password
|
||||
backupDetails.password &&
|
||||
backupDetails.password !== backupDetails.confirm_password
|
||||
) {
|
||||
this._error = this._dialogParams!.supervisor.localize(
|
||||
"snapshot.passwords_not_matching"
|
||||
"backup.passwords_not_matching"
|
||||
);
|
||||
this._creatingSnapshot = false;
|
||||
this._creatingBackup = false;
|
||||
return;
|
||||
}
|
||||
|
||||
delete snapshotDetails.confirm_password;
|
||||
delete backupDetails.confirm_password;
|
||||
|
||||
try {
|
||||
if (this._snapshotContent.snapshotType === "full") {
|
||||
await createHassioFullSnapshot(this.hass, snapshotDetails);
|
||||
if (this._backupContent.backupType === "full") {
|
||||
await createHassioFullBackup(this.hass, backupDetails);
|
||||
} else {
|
||||
await createHassioPartialSnapshot(this.hass, snapshotDetails);
|
||||
await createHassioPartialBackup(this.hass, backupDetails);
|
||||
}
|
||||
|
||||
this._dialogParams!.onCreate();
|
||||
@@ -127,7 +127,7 @@ class HassioCreateSnapshotDialog extends LitElement {
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
}
|
||||
this._creatingSnapshot = false;
|
||||
this._creatingBackup = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -146,6 +146,6 @@ class HassioCreateSnapshotDialog extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog;
|
||||
"dialog-hassio-create-backup": HassioCreateBackupDialog;
|
||||
}
|
||||
}
|
19
hassio/src/dialogs/backup/show-dialog-backup-upload.ts
Normal file
19
hassio/src/dialogs/backup/show-dialog-backup-upload.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "./dialog-hassio-backup-upload";
|
||||
|
||||
export interface HassioBackupUploadDialogParams {
|
||||
showBackup: (slug: string) => void;
|
||||
reloadBackup?: () => Promise<void>;
|
||||
onboarding?: boolean;
|
||||
}
|
||||
|
||||
export const showBackupUploadDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: HassioBackupUploadDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-backup-upload",
|
||||
dialogImport: () => import("./dialog-hassio-backup-upload"),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -2,7 +2,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { LocalizeFunc } from "../../../../src/common/translations/localize";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
|
||||
export interface HassioSnapshotDialogParams {
|
||||
export interface HassioBackupDialogParams {
|
||||
slug: string;
|
||||
onDelete?: () => void;
|
||||
onboarding?: boolean;
|
||||
@@ -10,13 +10,13 @@ export interface HassioSnapshotDialogParams {
|
||||
localize?: LocalizeFunc;
|
||||
}
|
||||
|
||||
export const showHassioSnapshotDialog = (
|
||||
export const showHassioBackupDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: HassioSnapshotDialogParams
|
||||
dialogParams: HassioBackupDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-snapshot",
|
||||
dialogImport: () => import("./dialog-hassio-snapshot"),
|
||||
dialogTag: "dialog-hassio-backup",
|
||||
dialogImport: () => import("./dialog-hassio-backup"),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -1,18 +1,18 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||
|
||||
export interface HassioCreateSnapshotDialogParams {
|
||||
export interface HassioCreateBackupDialogParams {
|
||||
supervisor: Supervisor;
|
||||
onCreate: () => void;
|
||||
}
|
||||
|
||||
export const showHassioCreateSnapshotDialog = (
|
||||
export const showHassioCreateBackupDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: HassioCreateSnapshotDialogParams
|
||||
dialogParams: HassioCreateBackupDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-create-snapshot",
|
||||
dialogImport: () => import("./dialog-hassio-create-snapshot"),
|
||||
dialogTag: "dialog-hassio-create-backup",
|
||||
dialogImport: () => import("./dialog-hassio-create-backup"),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -61,10 +61,6 @@ 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,7 +41,8 @@ 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;
|
||||
@@ -107,7 +108,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(
|
||||
@@ -492,7 +493,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()
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,19 +0,0 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "./dialog-hassio-snapshot-upload";
|
||||
|
||||
export interface HassioSnapshotUploadDialogParams {
|
||||
showSnapshot: (slug: string) => void;
|
||||
reloadSnapshot?: () => Promise<void>;
|
||||
onboarding?: boolean;
|
||||
}
|
||||
|
||||
export const showSnapshotUploadDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: HassioSnapshotUploadDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-hassio-snapshot-upload",
|
||||
dialogImport: () => import("./dialog-hassio-snapshot-upload"),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@@ -2,32 +2,19 @@ import "@material/mwc-button/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-checkbox";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoreSupervisorError,
|
||||
} from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
SupervisorFrontendPrefrences,
|
||||
fetchSupervisorFrontendPreferences,
|
||||
saveSupervisorFrontendPreferences,
|
||||
} from "../../../../src/data/supervisor/supervisor";
|
||||
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
|
||||
import { createHassioPartialBackup } from "../../../../src/data/hassio/backup";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
const snapshot_before_update = memoizeOne(
|
||||
(slug: string, frontendPrefrences: SupervisorFrontendPrefrences) =>
|
||||
slug in frontendPrefrences.snapshot_before_update
|
||||
? frontendPrefrences.snapshot_before_update[slug]
|
||||
: true
|
||||
);
|
||||
|
||||
@customElement("dialog-supervisor-update")
|
||||
class DialogSupervisorUpdate extends LitElement {
|
||||
@@ -35,12 +22,12 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _action: "snapshot" | "update" | null = null;
|
||||
@state() private _createBackup = true;
|
||||
|
||||
@state() private _action: "backup" | "update" | null = null;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _frontendPrefrences?: SupervisorFrontendPrefrences;
|
||||
|
||||
@state()
|
||||
private _dialogParams?: SupervisorDialogSupervisorUpdateParams;
|
||||
|
||||
@@ -49,30 +36,27 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
): Promise<void> {
|
||||
this._opened = true;
|
||||
this._dialogParams = params;
|
||||
this._frontendPrefrences = await fetchSupervisorFrontendPreferences(
|
||||
this.hass
|
||||
);
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._action = null;
|
||||
this._createBackup = true;
|
||||
this._error = undefined;
|
||||
this._dialogParams = undefined;
|
||||
this._frontendPrefrences = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.updateComplete.then(() =>
|
||||
(this.shadowRoot?.querySelector(
|
||||
"[dialogInitialFocus]"
|
||||
) as HTMLElement)?.focus()
|
||||
(
|
||||
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
|
||||
)?.focus()
|
||||
);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._dialogParams || !this._frontendPrefrences) {
|
||||
if (!this._dialogParams) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
@@ -98,28 +82,24 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
</div>
|
||||
|
||||
<ha-settings-row>
|
||||
<ha-checkbox
|
||||
.checked=${snapshot_before_update(
|
||||
this._dialogParams.slug,
|
||||
this._frontendPrefrences
|
||||
)}
|
||||
haptic
|
||||
@click=${this._toggleSnapshot}
|
||||
slot="prefix"
|
||||
>
|
||||
</ha-checkbox>
|
||||
<span slot="heading">
|
||||
${this._dialogParams.supervisor.localize(
|
||||
"dialog.update.snapshot"
|
||||
"dialog.update.backup"
|
||||
)}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this._dialogParams.supervisor.localize(
|
||||
"dialog.update.create_snapshot",
|
||||
"dialog.update.create_backup",
|
||||
"name",
|
||||
this._dialogParams.name
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
.checked=${this._createBackup}
|
||||
haptic
|
||||
@click=${this._toggleBackup}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-settings-row>
|
||||
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
${this._dialogParams.supervisor.localize("common.cancel")}
|
||||
@@ -143,7 +123,7 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
this._dialogParams.version
|
||||
)
|
||||
: this._dialogParams.supervisor.localize(
|
||||
"dialog.update.snapshotting",
|
||||
"dialog.update.creating_backup",
|
||||
"name",
|
||||
this._dialogParams.name
|
||||
)}
|
||||
@@ -153,32 +133,17 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _toggleSnapshot(): Promise<void> {
|
||||
this._frontendPrefrences!.snapshot_before_update[
|
||||
this._dialogParams!.slug
|
||||
] = !snapshot_before_update(
|
||||
this._dialogParams!.slug,
|
||||
this._frontendPrefrences!
|
||||
);
|
||||
|
||||
await saveSupervisorFrontendPreferences(
|
||||
this.hass,
|
||||
this._frontendPrefrences!
|
||||
);
|
||||
private _toggleBackup() {
|
||||
this._createBackup = !this._createBackup;
|
||||
}
|
||||
|
||||
private async _update() {
|
||||
if (
|
||||
snapshot_before_update(
|
||||
this._dialogParams!.slug,
|
||||
this._frontendPrefrences!
|
||||
)
|
||||
) {
|
||||
this._action = "snapshot";
|
||||
if (this._createBackup) {
|
||||
this._action = "backup";
|
||||
try {
|
||||
await createHassioPartialSnapshot(
|
||||
await createHassioPartialBackup(
|
||||
this.hass,
|
||||
this._dialogParams!.snapshotParams
|
||||
this._dialogParams!.backupParams
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
@@ -193,8 +158,8 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
} catch (err) {
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._action = null;
|
||||
}
|
||||
this._action = null;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -5,8 +5,7 @@ export interface SupervisorDialogSupervisorUpdateParams {
|
||||
supervisor: Supervisor;
|
||||
name: string;
|
||||
version: string;
|
||||
slug: string;
|
||||
snapshotParams: any;
|
||||
backupParams: any;
|
||||
updateHandler: () => Promise<void>;
|
||||
}
|
||||
|
||||
|
@@ -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 { html, LitElement, TemplateResult } from "lit";
|
||||
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 {
|
||||
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: {
|
||||
@@ -26,7 +26,10 @@ const REDIRECTS: Redirects = {
|
||||
redirect: "/hassio/system",
|
||||
},
|
||||
supervisor_snapshots: {
|
||||
redirect: "/hassio/snapshots",
|
||||
redirect: "/hassio/backups",
|
||||
},
|
||||
supervisor_backups: {
|
||||
redirect: "/hassio/backups",
|
||||
},
|
||||
supervisor_store: {
|
||||
redirect: "/hassio/store",
|
||||
|
@@ -9,7 +9,7 @@ import "./addon-store/hassio-addon-store";
|
||||
// Don't codesplit it, that way the dashboard always loads fast.
|
||||
import "./dashboard/hassio-dashboard";
|
||||
// Don't codesplit the others, because it breaks the UI when pushed to a Pi
|
||||
import "./snapshots/hassio-snapshots";
|
||||
import "./backups/hassio-backups";
|
||||
import "./system/hassio-system";
|
||||
|
||||
@customElement("hassio-panel-router")
|
||||
@@ -23,6 +23,8 @@ class HassioPanelRouter extends HassRouterPage {
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
beforeRender: (page: string) =>
|
||||
page === "snapshots" ? "backups" : undefined,
|
||||
routes: {
|
||||
dashboard: {
|
||||
tag: "hassio-dashboard",
|
||||
@@ -30,8 +32,8 @@ class HassioPanelRouter extends HassRouterPage {
|
||||
store: {
|
||||
tag: "hassio-addon-store",
|
||||
},
|
||||
snapshots: {
|
||||
tag: "hassio-snapshots",
|
||||
backups: {
|
||||
tag: "hassio-backups",
|
||||
},
|
||||
system: {
|
||||
tag: "hassio-system",
|
||||
|
@@ -23,6 +23,8 @@ class HassioRouter extends HassRouterPage {
|
||||
protected routerOptions: RouterOptions = {
|
||||
// Hass.io has a page with tabs, so we route all non-matching routes to it.
|
||||
defaultPage: "dashboard",
|
||||
beforeRender: (page: string) =>
|
||||
page === "snapshots" ? "backups" : undefined,
|
||||
initialLoad: () => this._redirectIngress(),
|
||||
showLoading: true,
|
||||
routes: {
|
||||
@@ -30,7 +32,7 @@ class HassioRouter extends HassRouterPage {
|
||||
tag: "hassio-panel",
|
||||
cache: true,
|
||||
},
|
||||
snapshots: "dashboard",
|
||||
backups: "dashboard",
|
||||
store: "dashboard",
|
||||
system: "dashboard",
|
||||
addon: {
|
||||
|
@@ -13,8 +13,8 @@ export const supervisorTabs: PageNavigation[] = [
|
||||
iconPath: mdiStore,
|
||||
},
|
||||
{
|
||||
translationKey: "panel.snapshots",
|
||||
path: `/hassio/snapshots`,
|
||||
translationKey: "panel.backups",
|
||||
path: `/hassio/backups`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
|
@@ -86,10 +86,8 @@ 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 })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -164,9 +164,8 @@ class HassioCoreInfo extends LitElement {
|
||||
showDialogSupervisorUpdate(this, {
|
||||
supervisor: this.supervisor,
|
||||
name: "Home Assistant Core",
|
||||
slug: "core",
|
||||
version: this.supervisor.core.version_latest,
|
||||
snapshotParams: {
|
||||
backupParams: {
|
||||
name: `core_${this.supervisor.core.version}`,
|
||||
folders: ["homeassistant"],
|
||||
homeassistant: true,
|
||||
|
@@ -113,7 +113,7 @@ class HassioHostInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
${!this.supervisor.host.features.includes("hassos")
|
||||
${!this.supervisor.host.features.includes("haos")
|
||||
? 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("hassos")
|
||||
${this.supervisor.host.features.includes("haos")
|
||||
? html`<mwc-list-item>
|
||||
${this.supervisor.localize("system.host.import_from_usb")}
|
||||
</mwc-list-item>`
|
||||
|
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
"*.{js,ts}": "eslint --fix",
|
||||
"*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
|
||||
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
|
||||
};
|
||||
|
3
netlify.toml
Normal file
3
netlify.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[build.environment]
|
||||
YARN_VERSION = "1.22.11"
|
||||
NODE_OPTIONS = "--max_old_space_size=4096"
|
128
package.json
128
package.json
@@ -17,7 +17,7 @@
|
||||
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
|
||||
"format": "yarn run format:eslint && yarn run format:prettier",
|
||||
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"",
|
||||
"test": "yarn run lint && yarn run mocha"
|
||||
"test": "yarn run mocha"
|
||||
},
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
@@ -42,72 +42,72 @@
|
||||
"@fullcalendar/daygrid": "5.1.0",
|
||||
"@fullcalendar/interaction": "5.1.0",
|
||||
"@fullcalendar/list": "5.1.0",
|
||||
"@lit-labs/virtualizer": "^0.6.0",
|
||||
"@material/chips": "=12.0.0-canary.1a8d06483.0",
|
||||
"@material/mwc-button": "canary",
|
||||
"@material/mwc-checkbox": "canary",
|
||||
"@material/mwc-circular-progress": "canary",
|
||||
"@material/mwc-dialog": "canary",
|
||||
"@material/mwc-fab": "canary",
|
||||
"@material/mwc-formfield": "canary",
|
||||
"@material/mwc-icon-button": "canary",
|
||||
"@material/mwc-list": "canary",
|
||||
"@material/mwc-menu": "canary",
|
||||
"@material/mwc-radio": "canary",
|
||||
"@material/mwc-ripple": "canary",
|
||||
"@material/mwc-switch": "canary",
|
||||
"@material/mwc-tab": "canary",
|
||||
"@material/mwc-tab-bar": "canary",
|
||||
"@material/top-app-bar": "=12.0.0-canary.1a8d06483.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",
|
||||
"@mdi/js": "5.9.55",
|
||||
"@mdi/svg": "5.9.55",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
"@polymer/app-storage": "^3.0.2",
|
||||
"@polymer/iron-autogrow-textarea": "^3.0.1",
|
||||
"@polymer/app-layout": "^3.1.0",
|
||||
"@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.2",
|
||||
"@polymer/iron-overlay-behavior": "^3.0.3",
|
||||
"@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.0.1",
|
||||
"@polymer/paper-input": "^3.0.1",
|
||||
"@polymer/paper-dropdown-menu": "^3.2.0",
|
||||
"@polymer/paper-input": "^3.2.1",
|
||||
"@polymer/paper-item": "^3.0.1",
|
||||
"@polymer/paper-listbox": "^3.0.1",
|
||||
"@polymer/paper-menu-button": "^3.0.1",
|
||||
"@polymer/paper-menu-button": "^3.1.0",
|
||||
"@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.1",
|
||||
"@polymer/paper-ripple": "^3.0.2",
|
||||
"@polymer/paper-slider": "^3.0.1",
|
||||
"@polymer/paper-styles": "^3.0.1",
|
||||
"@polymer/paper-tabs": "^3.0.1",
|
||||
"@polymer/paper-tabs": "^3.1.0",
|
||||
"@polymer/paper-toast": "^3.0.1",
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"@polymer/polymer": "3.4.1",
|
||||
"@thomasloven/round-slider": "0.5.2",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.7",
|
||||
"@vaadin/vaadin-combo-box": "^20.0.1",
|
||||
"@vaadin/vaadin-date-picker": "^20.0.1",
|
||||
"@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.7",
|
||||
"chart.js": "^2.9.4",
|
||||
"chartjs-chart-timeline": "^0.4.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"chart.js": "^3.3.2",
|
||||
"comlink": "^4.3.1",
|
||||
"core-js": "^3.6.5",
|
||||
"core-js": "^3.15.2",
|
||||
"cropperjs": "^1.5.11",
|
||||
"date-fns": "^2.22.1",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fecha": "^4.2.0",
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^1.0.5",
|
||||
"home-assistant-js-websocket": "^5.10.0",
|
||||
"hls.js": "^1.0.7",
|
||||
"home-assistant-js-websocket": "^5.11.1",
|
||||
"idb-keyval": "^5.0.5",
|
||||
"intl-messageformat": "^9.6.16",
|
||||
"js-yaml": "^4.1.0",
|
||||
@@ -122,7 +122,7 @@
|
||||
"proxy-polyfill": "^0.3.1",
|
||||
"punycode": "^2.1.1",
|
||||
"qrcode": "^1.4.4",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"regenerator-runtime": "^0.13.8",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"sortablejs": "^1.10.2",
|
||||
@@ -144,17 +144,17 @@
|
||||
"xss": "^1.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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/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/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||
"@babel/preset-env": "^7.14.2",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@babel/preset-env": "^7.14.7",
|
||||
"@babel/preset-typescript": "^7.14.5",
|
||||
"@koa/cors": "^3.1.0",
|
||||
"@open-wc/dev-server-hmr": "^0.0.2",
|
||||
"@rollup/plugin-babel": "^5.2.1",
|
||||
@@ -171,22 +171,22 @@
|
||||
"@types/mocha": "^8.2.2",
|
||||
"@types/sortablejs": "^1.10.6",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.3",
|
||||
"@typescript-eslint/parser": "^4.28.3",
|
||||
"@web/dev-server": "^0.0.24",
|
||||
"@web/dev-server-rollup": "^0.2.11",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"chai": "^4.3.4",
|
||||
"cpx": "^1.5.0",
|
||||
"del": "^4.0.0",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-config-airbnb-typescript": "^12.3.1",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-webpack": "^0.13.0",
|
||||
"eslint-import-resolver-webpack": "^0.13.1",
|
||||
"eslint-plugin-disable": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-lit": "^1.3.0",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-lit": "^1.5.1",
|
||||
"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 +198,7 @@
|
||||
"gulp-zopfli-green": "^3.0.1",
|
||||
"html-minifier": "^4.0.0",
|
||||
"husky": "^1.3.1",
|
||||
"lint-staged": "^10.5.4",
|
||||
"lint-staged": "^11.0.1",
|
||||
"lit-analyzer": "^1.2.1",
|
||||
"lodash.template": "^4.5.0",
|
||||
"magic-string": "^0.25.7",
|
||||
@@ -207,8 +207,7 @@
|
||||
"mocha": "^8.4.0",
|
||||
"object-hash": "^2.0.3",
|
||||
"open": "^7.0.4",
|
||||
"prettier": "^2.0.4",
|
||||
"raw-loader": "^2.0.0",
|
||||
"prettier": "^2.3.2",
|
||||
"require-dir": "^1.2.0",
|
||||
"rollup": "^2.8.2",
|
||||
"rollup-plugin-string": "^3.0.0",
|
||||
@@ -218,23 +217,22 @@
|
||||
"sinon": "^11.0.0",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^5.1.2",
|
||||
"terser-webpack-plugin": "^5.1.4",
|
||||
"ts-lit-plugin": "^1.2.1",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typescript": "^4.2.4",
|
||||
"typescript": "^4.3.5",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"webpack": "^5.24.1",
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack": "^5.43.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-manifest-plugin": "^3.0.0",
|
||||
"webpack-manifest-plugin": "^3.1.1",
|
||||
"workbox-build": "^6.1.5"
|
||||
},
|
||||
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",
|
||||
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
|
||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||
"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,12 +9,6 @@ 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 \
|
||||
@@ -22,9 +16,9 @@ if [ -z $(which hass) ]; then
|
||||
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||
fi
|
||||
|
||||
if [ ! -d "${WORKSPACE}/config" ]; then
|
||||
if [ ! -d "/workspaces/frontend/config" ]; then
|
||||
echo "Creating default configuration."
|
||||
mkdir -p "${WORKSPACE}/config";
|
||||
mkdir -p "/workspaces/frontend/config";
|
||||
hass --script ensure_config -c config
|
||||
echo "demo:
|
||||
|
||||
@@ -32,24 +26,24 @@ logger:
|
||||
default: info
|
||||
logs:
|
||||
homeassistant.components.frontend: debug
|
||||
" >> "${WORKSPACE}/config/configuration.yaml"
|
||||
" >> /workspaces/frontend/config/configuration.yaml
|
||||
|
||||
if [ ! -z "${HASSIO}" ]; then
|
||||
echo "
|
||||
# frontend:
|
||||
# development_repo: ${WORKSPACE}
|
||||
# development_repo: /workspaces/frontend
|
||||
|
||||
hassio:
|
||||
development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
|
||||
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
else
|
||||
echo "
|
||||
frontend:
|
||||
development_repo: ${WORKSPACE}
|
||||
development_repo: /workspaces/frontend
|
||||
|
||||
# hassio:
|
||||
# development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
|
||||
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
hass -c "${WORKSPACE}/config"
|
||||
hass -c /workspaces/frontend/config
|
||||
|
4
setup.py
4
setup.py
@@ -2,9 +2,9 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20210603.0",
|
||||
version="20210818.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
url="https://github.com/home-assistant/frontend",
|
||||
author="The Home Assistant Authors",
|
||||
author_email="hello@home-assistant.io",
|
||||
license="Apache-2.0",
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import "./ha-password-manager-polyfill";
|
||||
import { property, state } from "lit/decorators";
|
||||
import "../components/ha-form/ha-form";
|
||||
import "../components/ha-markdown";
|
||||
@@ -20,7 +21,7 @@ import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
type State = "loading" | "error" | "step";
|
||||
|
||||
class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
@property() public authProvider?: AuthProvider;
|
||||
@property({ attribute: false }) public authProvider?: AuthProvider;
|
||||
|
||||
@property() public clientId?: string;
|
||||
|
||||
@@ -37,7 +38,15 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
@state() private _errorMessage?: string;
|
||||
|
||||
protected render() {
|
||||
return html` <form>${this._renderForm()}</form> `;
|
||||
return html`
|
||||
<form>${this._renderForm()}</form>
|
||||
<ha-password-manager-polyfill
|
||||
.step=${this._step}
|
||||
.stepData=${this._stepData}
|
||||
@form-submitted=${this._handleSubmit}
|
||||
@value-changed=${this._stepDataChanged}
|
||||
></ha-password-manager-polyfill>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
@@ -231,11 +240,17 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
await this.updateComplete;
|
||||
// 100ms to give all the form elements time to initialize.
|
||||
setTimeout(() => {
|
||||
const form = this.shadowRoot!.querySelector("ha-form");
|
||||
const form = this.renderRoot.querySelector("ha-form");
|
||||
if (form) {
|
||||
(form as any).focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
this.renderRoot.querySelector(
|
||||
"ha-password-manager-polyfill"
|
||||
)!.boundingRect = this.getBoundingClientRect();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private _stepDataChanged(ev: CustomEvent) {
|
||||
@@ -329,3 +344,9 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
customElements.define("ha-auth-flow", HaAuthFlow);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-auth-flow": HaAuthFlow;
|
||||
}
|
||||
}
|
||||
|
110
src/auth/ha-password-manager-polyfill.ts
Normal file
110
src/auth/ha-password-manager-polyfill.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HaFormSchema } from "../components/ha-form/ha-form";
|
||||
import { DataEntryFlowStep } from "../data/data_entry_flow";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-password-manager-polyfill": HaPasswordManagerPolyfill;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"form-submitted": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const ENABLED_HANDLERS = [
|
||||
"homeassistant",
|
||||
"legacy_api_password",
|
||||
"command_line",
|
||||
];
|
||||
|
||||
@customElement("ha-password-manager-polyfill")
|
||||
export class HaPasswordManagerPolyfill extends LitElement {
|
||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||
|
||||
@property({ attribute: false }) public stepData: any;
|
||||
|
||||
@property({ attribute: false }) public boundingRect?: DOMRect;
|
||||
|
||||
protected createRenderRoot() {
|
||||
// Add under document body so the element isn't placed inside any shadow roots
|
||||
return document.body;
|
||||
}
|
||||
|
||||
private get styles() {
|
||||
return `
|
||||
.password-manager-polyfill {
|
||||
position: absolute;
|
||||
top: ${this.boundingRect?.y || 148}px;
|
||||
left: calc(50% - ${(this.boundingRect?.width || 360) / 2}px);
|
||||
width: ${this.boundingRect?.width || 360}px;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
.password-manager-polyfill input {
|
||||
width: 100%;
|
||||
height: 62px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.password-manager-polyfill input[type="submit"] {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (
|
||||
this.step &&
|
||||
this.step.type === "form" &&
|
||||
this.step.step_id === "init" &&
|
||||
ENABLED_HANDLERS.includes(this.step.handler[0])
|
||||
) {
|
||||
return html`
|
||||
<form
|
||||
class="password-manager-polyfill"
|
||||
aria-hidden="true"
|
||||
@submit=${this._handleSubmit}
|
||||
>
|
||||
${this.step.data_schema.map((input) => this.render_input(input))}
|
||||
<input type="submit" />
|
||||
<style>
|
||||
${this.styles}
|
||||
</style>
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
|
||||
private render_input(schema: HaFormSchema): TemplateResult | string {
|
||||
const inputType = schema.name.includes("password") ? "password" : "text";
|
||||
if (schema.type !== "string") {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<input
|
||||
tabindex="-1"
|
||||
.id=${schema.name}
|
||||
.type=${inputType}
|
||||
.value=${this.stepData[schema.name] || ""}
|
||||
@input=${this._valueChanged}
|
||||
/>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSubmit(ev: Event) {
|
||||
ev.preventDefault();
|
||||
fireEvent(this, "form-submitted");
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const target = ev.target! as HTMLInputElement;
|
||||
this.stepData = { ...this.stepData, [target.id]: target.value };
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.stepData,
|
||||
});
|
||||
}
|
||||
}
|
63
src/common/color/colors.ts
Normal file
63
src/common/color/colors.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
export const COLORS = [
|
||||
"#44739e",
|
||||
"#984ea3",
|
||||
"#00d2d5",
|
||||
"#ff7f00",
|
||||
"#af8d00",
|
||||
"#7f80cd",
|
||||
"#b3e900",
|
||||
"#c42e60",
|
||||
"#a65628",
|
||||
"#f781bf",
|
||||
"#8dd3c7",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#fccde5",
|
||||
"#bc80bd",
|
||||
"#ffed6f",
|
||||
"#c4eaff",
|
||||
"#cf8c00",
|
||||
"#1b9e77",
|
||||
"#d95f02",
|
||||
"#e7298a",
|
||||
"#e6ab02",
|
||||
"#a6761d",
|
||||
"#0097ff",
|
||||
"#00d067",
|
||||
"#f43600",
|
||||
"#4ba93b",
|
||||
"#5779bb",
|
||||
"#927acc",
|
||||
"#97ee3f",
|
||||
"#bf3947",
|
||||
"#9f5b00",
|
||||
"#f48758",
|
||||
"#8caed6",
|
||||
"#f2b94f",
|
||||
"#eff26e",
|
||||
"#e43872",
|
||||
"#d9b100",
|
||||
"#9d7a00",
|
||||
"#698cff",
|
||||
"#d9d9d9",
|
||||
"#00d27e",
|
||||
"#d06800",
|
||||
"#009f82",
|
||||
"#c49200",
|
||||
"#cbe8ff",
|
||||
"#fecddf",
|
||||
"#c27eb6",
|
||||
"#8cd2ce",
|
||||
"#c4b8d9",
|
||||
"#f883b0",
|
||||
"#a49100",
|
||||
"#f48800",
|
||||
"#27d0df",
|
||||
"#a04a9b",
|
||||
];
|
||||
|
||||
export function getColorByIndex(index: number) {
|
||||
return COLORS[index % COLORS.length];
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
const luminosity = (rgb: [number, number, number]): number => {
|
||||
export const luminosity = (rgb: [number, number, number]): number => {
|
||||
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
const lum: [number, number, number] = [0, 0, 0];
|
||||
for (let i = 0; i < rgb.length; i++) {
|
||||
|
@@ -42,6 +42,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
remote: "hass:remote",
|
||||
scene: "hass:palette",
|
||||
script: "hass:script-text",
|
||||
select: "hass:format-list-bulleted",
|
||||
sensor: "hass:eye",
|
||||
simple_alarm: "hass:bell",
|
||||
sun: "hass:white-balance-sunny",
|
||||
@@ -55,17 +56,28 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
};
|
||||
|
||||
export const FIXED_DEVICE_CLASS_ICONS = {
|
||||
aqi: "hass:air-filter",
|
||||
current: "hass:current-ac",
|
||||
carbon_dioxide: "mdi:molecule-co2",
|
||||
carbon_monoxide: "mdi:molecule-co",
|
||||
energy: "hass:flash",
|
||||
energy: "hass:lightning-bolt",
|
||||
gas: "hass:gas-cylinder",
|
||||
humidity: "hass:water-percent",
|
||||
illuminance: "hass:brightness-5",
|
||||
nitrogen_dioxide: "mdi:molecule",
|
||||
nitrogen_monoxide: "mdi:molecule",
|
||||
nitrous_oxide: "mdi:molecule",
|
||||
ozone: "mdi:molecule",
|
||||
temperature: "hass:thermometer",
|
||||
monetary: "mdi:cash",
|
||||
pm25: "mdi:molecule",
|
||||
pm1: "mdi:molecule",
|
||||
pm10: "mdi:molecule",
|
||||
pressure: "hass:gauge",
|
||||
power: "hass:flash",
|
||||
power_factor: "hass:angle-acute",
|
||||
signal_strength: "hass:wifi",
|
||||
sulphur_dioxide: "mdi:molecule",
|
||||
timestamp: "hass:clock",
|
||||
voltage: "hass:sine-wave",
|
||||
};
|
||||
@@ -83,6 +95,7 @@ export const DOMAINS_WITH_CARD = [
|
||||
"number",
|
||||
"scene",
|
||||
"script",
|
||||
"select",
|
||||
"timer",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
@@ -121,6 +134,7 @@ export const DOMAINS_HIDE_MORE_INFO = [
|
||||
"input_text",
|
||||
"number",
|
||||
"scene",
|
||||
"select",
|
||||
];
|
||||
|
||||
/** Domains that should have the history hidden in the more info dialog. */
|
||||
@@ -153,66 +167,3 @@ export const UNIT_F = "°F";
|
||||
|
||||
/** Entity ID of the default view. */
|
||||
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
|
||||
|
||||
/** HA Color Pallete. */
|
||||
export const HA_COLOR_PALETTE = [
|
||||
"ff0029",
|
||||
"66a61e",
|
||||
"377eb8",
|
||||
"984ea3",
|
||||
"00d2d5",
|
||||
"ff7f00",
|
||||
"af8d00",
|
||||
"7f80cd",
|
||||
"b3e900",
|
||||
"c42e60",
|
||||
"a65628",
|
||||
"f781bf",
|
||||
"8dd3c7",
|
||||
"bebada",
|
||||
"fb8072",
|
||||
"80b1d3",
|
||||
"fdb462",
|
||||
"fccde5",
|
||||
"bc80bd",
|
||||
"ffed6f",
|
||||
"c4eaff",
|
||||
"cf8c00",
|
||||
"1b9e77",
|
||||
"d95f02",
|
||||
"e7298a",
|
||||
"e6ab02",
|
||||
"a6761d",
|
||||
"0097ff",
|
||||
"00d067",
|
||||
"f43600",
|
||||
"4ba93b",
|
||||
"5779bb",
|
||||
"927acc",
|
||||
"97ee3f",
|
||||
"bf3947",
|
||||
"9f5b00",
|
||||
"f48758",
|
||||
"8caed6",
|
||||
"f2b94f",
|
||||
"eff26e",
|
||||
"e43872",
|
||||
"d9b100",
|
||||
"9d7a00",
|
||||
"698cff",
|
||||
"d9d9d9",
|
||||
"00d27e",
|
||||
"d06800",
|
||||
"009f82",
|
||||
"c49200",
|
||||
"cbe8ff",
|
||||
"fecddf",
|
||||
"c27eb6",
|
||||
"8cd2ce",
|
||||
"c4b8d9",
|
||||
"f883b0",
|
||||
"a49100",
|
||||
"f48800",
|
||||
"27d0df",
|
||||
"a04a9b",
|
||||
];
|
||||
|
@@ -26,6 +26,9 @@ 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();
|
||||
|
31
src/common/datetime/create_duration_data.ts
Normal file
31
src/common/datetime/create_duration_data.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { HaDurationData } from "../../components/ha-duration-input";
|
||||
import { ForDict } from "../../data/automation";
|
||||
|
||||
export const createDurationData = (
|
||||
duration: string | number | ForDict | undefined
|
||||
): HaDurationData => {
|
||||
if (duration === undefined) {
|
||||
return {};
|
||||
}
|
||||
if (typeof duration !== "object") {
|
||||
if (typeof duration === "string" || isNaN(duration)) {
|
||||
const parts = duration?.toString().split(":") || [];
|
||||
return {
|
||||
hours: Number(parts[0]) || 0,
|
||||
minutes: Number(parts[1]) || 0,
|
||||
seconds: Number(parts[2]) || 0,
|
||||
milliseconds: Number(parts[3]) || 0,
|
||||
};
|
||||
}
|
||||
return { seconds: duration };
|
||||
}
|
||||
const { days, minutes, seconds, milliseconds } = duration;
|
||||
let hours = duration.hours || 0;
|
||||
hours = (hours || 0) + (days || 0) * 24;
|
||||
return {
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
milliseconds,
|
||||
};
|
||||
};
|
@@ -3,20 +3,11 @@ import memoizeOne from "memoize-one";
|
||||
import { FrontendLocaleData } from "../../data/translation";
|
||||
import { toLocaleDateStringSupportsOptions } from "./check_options_support";
|
||||
|
||||
const formatDateMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})
|
||||
);
|
||||
|
||||
export const formatDate = toLocaleDateStringSupportsOptions
|
||||
// Tuesday, August 10
|
||||
export const formatDateWeekday = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "longDate");
|
||||
|
||||
formatDateWeekdayMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "dddd, MMMM D");
|
||||
const formatDateWeekdayMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
@@ -26,7 +17,80 @@ const formatDateWeekdayMem = memoizeOne(
|
||||
})
|
||||
);
|
||||
|
||||
export const formatDateWeekday = toLocaleDateStringSupportsOptions
|
||||
// August 10, 2021
|
||||
export const formatDate = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateWeekdayMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "dddd, MMM D");
|
||||
formatDateMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "MMMM D, YYYY");
|
||||
const formatDateMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})
|
||||
);
|
||||
|
||||
// 10/08/2021
|
||||
export const formatDateNumeric = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateNumericMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "M/D/YYYY");
|
||||
const formatDateNumericMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
})
|
||||
);
|
||||
|
||||
// Aug 10
|
||||
export const formatDateShort = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateShortMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "MMM D");
|
||||
const formatDateShortMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
})
|
||||
);
|
||||
|
||||
// August 2021
|
||||
export const formatDateMonthYear = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateMonthYearMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "MMMM YYYY");
|
||||
const formatDateMonthYearMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
})
|
||||
);
|
||||
|
||||
// August
|
||||
export const formatDateMonth = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateMonthMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "MMMM");
|
||||
const formatDateMonthMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
month: "long",
|
||||
})
|
||||
);
|
||||
|
||||
// 2021
|
||||
export const formatDateYear = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateYearMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "YYYY");
|
||||
const formatDateYearMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
})
|
||||
);
|
||||
|
@@ -4,6 +4,12 @@ import { FrontendLocaleData } from "../../data/translation";
|
||||
import { toLocaleStringSupportsOptions } from "./check_options_support";
|
||||
import { useAmPm } from "./use_am_pm";
|
||||
|
||||
// August 9, 2021, 8:23 AM
|
||||
export const formatDateTime = toLocaleStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateTimeMem(locale).format(dateObj)
|
||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||
const formatDateTimeMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
@@ -16,12 +22,12 @@ const formatDateTimeMem = memoizeOne(
|
||||
})
|
||||
);
|
||||
|
||||
export const formatDateTime = toLocaleStringSupportsOptions
|
||||
// August 9, 2021, 8:23:15 AM
|
||||
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateTimeMem(locale).format(dateObj)
|
||||
formatDateTimeWithSecondsMem(locale).format(dateObj)
|
||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
format(dateObj, "MMMM D, YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||
|
||||
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
|
||||
const formatDateTimeWithSecondsMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
@@ -35,8 +41,20 @@ const formatDateTimeWithSecondsMem = memoizeOne(
|
||||
})
|
||||
);
|
||||
|
||||
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
|
||||
// 9/8/2021, 8:23 AM
|
||||
export const formatDateTimeNumeric = toLocaleStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateTimeWithSecondsMem(locale).format(dateObj)
|
||||
formatDateTimeNumericMem(locale).format(dateObj)
|
||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
format(dateObj, "MMMM D, YYYY, HH:mm:ss" + useAmPm(locale) ? " A" : "");
|
||||
format(dateObj, "M/D/YYYY, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||
const formatDateTimeNumericMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
);
|
||||
|
@@ -4,6 +4,12 @@ import { FrontendLocaleData } from "../../data/translation";
|
||||
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
|
||||
import { useAmPm } from "./use_am_pm";
|
||||
|
||||
// 9:15 PM || 21:15
|
||||
export const formatTime = toLocaleTimeStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatTimeMem(locale).format(dateObj)
|
||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");
|
||||
const formatTimeMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
@@ -13,12 +19,12 @@ const formatTimeMem = memoizeOne(
|
||||
})
|
||||
);
|
||||
|
||||
export const formatTime = toLocaleTimeStringSupportsOptions
|
||||
// 9:15:24 PM || 21:15:24
|
||||
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatTimeMem(locale).format(dateObj)
|
||||
formatTimeWithSecondsMem(locale).format(dateObj)
|
||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
format(dateObj, "shortTime" + useAmPm(locale) ? " A" : "");
|
||||
|
||||
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
|
||||
const formatTimeWithSecondsMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
@@ -29,12 +35,12 @@ const formatTimeWithSecondsMem = memoizeOne(
|
||||
})
|
||||
);
|
||||
|
||||
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
|
||||
// Tuesday 7:00 PM || Tuesday 19:00
|
||||
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatTimeWithSecondsMem(locale).format(dateObj)
|
||||
formatTimeWeekdayMem(locale).format(dateObj)
|
||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
format(dateObj, "mediumTime" + useAmPm(locale) ? " A" : "");
|
||||
|
||||
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||
const formatTimeWeekdayMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
@@ -44,9 +50,3 @@ const formatTimeWeekdayMem = memoizeOne(
|
||||
hour12: useAmPm(locale),
|
||||
})
|
||||
);
|
||||
|
||||
export const formatTimeWeekday = toLocaleTimeStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatTimeWeekdayMem(locale).format(dateObj)
|
||||
: (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
format(dateObj, "dddd, HH:mm" + useAmPm(locale) ? " A" : "");
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import memoizeOne from "memoize-one";
|
||||
import { FrontendLocaleData, TimeFormat } from "../../data/translation";
|
||||
|
||||
export const useAmPm = (locale: FrontendLocaleData): boolean => {
|
||||
export const useAmPm = memoizeOne((locale: FrontendLocaleData): boolean => {
|
||||
if (
|
||||
locale.time_format === TimeFormat.language ||
|
||||
locale.time_format === TimeFormat.system
|
||||
@@ -12,4 +13,4 @@ export const useAmPm = (locale: FrontendLocaleData): boolean => {
|
||||
}
|
||||
|
||||
return locale.time_format === TimeFormat.am_pm;
|
||||
};
|
||||
});
|
||||
|
@@ -82,67 +82,71 @@ 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) {
|
||||
const setValue = (el: ReactiveElement, value: any) => {
|
||||
let oldValue: unknown | undefined;
|
||||
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,
|
||||
});
|
||||
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;
|
||||
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,
|
||||
},
|
||||
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;
|
||||
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];
|
||||
}
|
||||
target.scrollTop = this[element.key];
|
||||
}
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -44,6 +44,8 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
return is_off ? "hass:home-outline" : "hass:home";
|
||||
case "sound":
|
||||
return is_off ? "hass:music-note-off" : "hass:music-note";
|
||||
case "update":
|
||||
return is_off ? "mdi:package" : "mdi:package-up";
|
||||
case "vibration":
|
||||
return is_off ? "hass:crop-portrait" : "hass:vibrate";
|
||||
case "window":
|
||||
|
@@ -21,6 +21,16 @@ 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
|
||||
}`;
|
||||
@@ -29,37 +39,61 @@ export const computeStateDisplay = (
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
if (domain === "input_datetime") {
|
||||
let date: Date;
|
||||
if (!stateObj.attributes.has_time) {
|
||||
if (state) {
|
||||
// If trying to display an explicit state, need to parse the explict state to `Date` then format.
|
||||
// Attributes aren't available, we have to use `state`.
|
||||
try {
|
||||
const components = state.split(" ");
|
||||
if (components.length === 2) {
|
||||
// Date and time.
|
||||
return formatDateTime(new Date(components.join("T")), locale);
|
||||
}
|
||||
if (components.length === 1) {
|
||||
if (state.includes("-")) {
|
||||
// Date only.
|
||||
return formatDate(new Date(`${state}T00:00`), locale);
|
||||
}
|
||||
if (state.includes(":")) {
|
||||
// Time only.
|
||||
const now = new Date();
|
||||
return formatTime(
|
||||
new Date(`${now.toISOString().split("T")[0]}T${state}`),
|
||||
locale
|
||||
);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
} catch {
|
||||
// Formatting methods may throw error if date parsing doesn't go well,
|
||||
// just return the state string in that case.
|
||||
return state;
|
||||
}
|
||||
} else {
|
||||
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
||||
let date: Date;
|
||||
if (!stateObj.attributes.has_time) {
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day
|
||||
);
|
||||
return formatDate(date, locale);
|
||||
}
|
||||
if (!stateObj.attributes.has_date) {
|
||||
date = new Date();
|
||||
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
||||
return formatTime(date, locale);
|
||||
}
|
||||
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day
|
||||
);
|
||||
return formatDate(date, locale);
|
||||
}
|
||||
if (!stateObj.attributes.has_date) {
|
||||
const now = new Date();
|
||||
date = new Date(
|
||||
// Due to bugs.chromium.org/p/chromium/issues/detail?id=797548
|
||||
// don't use artificial 1970 year.
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDay(),
|
||||
stateObj.attributes.day,
|
||||
stateObj.attributes.hour,
|
||||
stateObj.attributes.minute
|
||||
);
|
||||
return formatTime(date, locale);
|
||||
return formatDateTime(date, locale);
|
||||
}
|
||||
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day,
|
||||
stateObj.attributes.hour,
|
||||
stateObj.attributes.minute
|
||||
);
|
||||
return formatDateTime(date, locale);
|
||||
}
|
||||
|
||||
if (domain === "humidifier") {
|
||||
|
@@ -43,7 +43,17 @@ export const domainIcon = (
|
||||
: "hass:air-humidifier";
|
||||
|
||||
case "lock":
|
||||
return compareState === "unlocked" ? "hass:lock-open" : "hass: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";
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
2
src/common/number/clamp.ts
Normal file
2
src/common/number/clamp.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const clamp = (value: number, min: number, max: number) =>
|
||||
Math.min(Math.max(value, min), max);
|
2
src/common/number/round.ts
Normal file
2
src/common/number/round.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const round = (value: number, precision = 2): number =>
|
||||
Math.round(value * 10 ** precision) / 10 ** precision;
|
@@ -67,9 +67,11 @@ 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,4 +1,22 @@
|
||||
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.
|
||||
@@ -9,27 +27,12 @@ import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
||||
*/
|
||||
export const formatNumber = (
|
||||
num: string | number,
|
||||
locale?: FrontendLocaleData,
|
||||
localeOptions?: FrontendLocaleData,
|
||||
options?: Intl.NumberFormatOptions
|
||||
): string => {
|
||||
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;
|
||||
}
|
||||
const locale = localeOptions
|
||||
? numberFormatToLocale(localeOptions)
|
||||
: undefined;
|
||||
|
||||
// Polyfill for Number.isNaN, which is more reliable than the global isNaN()
|
||||
Number.isNaN =
|
||||
@@ -39,13 +42,13 @@ export const formatNumber = (
|
||||
};
|
||||
|
||||
if (
|
||||
localeOptions?.number_format !== NumberFormat.none &&
|
||||
!Number.isNaN(Number(num)) &&
|
||||
Intl &&
|
||||
locale?.number_format !== NumberFormat.none
|
||||
Intl
|
||||
) {
|
||||
try {
|
||||
return new Intl.NumberFormat(
|
||||
format,
|
||||
locale,
|
||||
getDefaultFormatOptions(num, options)
|
||||
).format(Number(num));
|
||||
} catch (error) {
|
||||
@@ -58,7 +61,12 @@ export const formatNumber = (
|
||||
).format(Number(num));
|
||||
}
|
||||
}
|
||||
return num.toString();
|
||||
if (typeof num === "string") {
|
||||
return num;
|
||||
}
|
||||
return `${round(num, options?.maximumFractionDigits).toString()}${
|
||||
options?.style === "currency" ? ` ${options.currency}` : ""
|
||||
}`;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -70,7 +78,10 @@ const getDefaultFormatOptions = (
|
||||
num: string | number,
|
||||
options?: Intl.NumberFormatOptions
|
||||
): Intl.NumberFormatOptions => {
|
||||
const defaultOptions: Intl.NumberFormatOptions = options || {};
|
||||
const defaultOptions: Intl.NumberFormatOptions = {
|
||||
maximumFractionDigits: 2,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (typeof num !== "string") {
|
||||
return defaultOptions;
|
||||
|
@@ -6,6 +6,7 @@
|
||||
// 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);
|
||||
|
@@ -29,31 +29,28 @@ export const iconColorCSS = css`
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="cooling"] {
|
||||
color: var(--cool-color, #2b9af9);
|
||||
color: var(--cool-color, var(--state-climate-cool-color));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="heating"] {
|
||||
color: var(--heat-color, #ff8100);
|
||||
color: var(--heat-color, var(--state-climate-heat-color));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="drying"] {
|
||||
color: var(--dry-color, #efbd07);
|
||||
color: var(--dry-color, var(--state-climate-dry-color));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"] {
|
||||
color: var(--alarm-color-armed, var(--label-badge-red));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
|
||||
color: var(--alarm-color-disarmed, var(--label-badge-green));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="arming"] {
|
||||
color: var(--alarm-color-pending, var(--label-badge-yellow));
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
|
||||
color: var(--alarm-color-triggered, var(--label-badge-red));
|
||||
animation: pulse 1s infinite;
|
||||
@@ -73,11 +70,11 @@ export const iconColorCSS = css`
|
||||
|
||||
ha-icon[data-domain="plant"][data-state="problem"],
|
||||
ha-icon[data-domain="zwave"][data-state="dead"] {
|
||||
color: var(--error-state-color, #db4437);
|
||||
color: var(--state-icon-error-color);
|
||||
}
|
||||
|
||||
/* Color the icon if unavailable */
|
||||
ha-icon[data-state="unavailable"] {
|
||||
color: var(--state-icon-unavailable-color);
|
||||
color: var(--state-unavailable-color);
|
||||
}
|
||||
`;
|
||||
|
15
src/common/util/group-by.ts
Normal file
15
src/common/util/group-by.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const groupBy = <T>(
|
||||
list: T[],
|
||||
keySelector: (item: T) => string
|
||||
): { [key: string]: T[] } => {
|
||||
const result = {};
|
||||
for (const item of list) {
|
||||
const key = keySelector(item);
|
||||
if (key in result) {
|
||||
result[key].push(item);
|
||||
} else {
|
||||
result[key] = [item];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
@@ -5,32 +5,20 @@
|
||||
// as much as it can, without ever going more than once per `wait` duration;
|
||||
// but if you'd like to disable the execution on the leading edge, pass
|
||||
// `false for leading`. To disable execution on the trailing edge, ditto.
|
||||
export const throttle = <T extends (...args) => unknown>(
|
||||
func: T,
|
||||
export const throttle = <T extends any[]>(
|
||||
func: (...args: T) => void,
|
||||
wait: number,
|
||||
leading = true,
|
||||
trailing = true
|
||||
): T => {
|
||||
) => {
|
||||
let timeout: number | undefined;
|
||||
let previous = 0;
|
||||
let context: any;
|
||||
let args: any;
|
||||
const later = () => {
|
||||
previous = leading === false ? 0 : Date.now();
|
||||
timeout = undefined;
|
||||
func.apply(context, args);
|
||||
if (!timeout) {
|
||||
context = null;
|
||||
args = null;
|
||||
}
|
||||
};
|
||||
// @ts-ignore
|
||||
return function (...argmnts) {
|
||||
// @ts-ignore
|
||||
// @typescript-eslint/no-this-alias
|
||||
context = this;
|
||||
args = argmnts;
|
||||
|
||||
return (...args: T): void => {
|
||||
const later = () => {
|
||||
previous = leading === false ? 0 : Date.now();
|
||||
timeout = undefined;
|
||||
func(...args);
|
||||
};
|
||||
const now = Date.now();
|
||||
if (!previous && leading === false) {
|
||||
previous = now;
|
||||
@@ -42,7 +30,7 @@ export const throttle = <T extends (...args) => unknown>(
|
||||
timeout = undefined;
|
||||
}
|
||||
previous = now;
|
||||
func.apply(context, args);
|
||||
func(...args);
|
||||
} else if (!timeout && trailing !== false) {
|
||||
timeout = window.setTimeout(later, remaining);
|
||||
}
|
||||
|
@@ -7,14 +7,10 @@ interface ResultCache<T> {
|
||||
export const timeCachePromiseFunc = async <T>(
|
||||
cacheKey: string,
|
||||
cacheTime: number,
|
||||
func: (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
...args: unknown[]
|
||||
) => Promise<T>,
|
||||
func: (hass: HomeAssistant, entityId: string, ...args: any[]) => Promise<T>,
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
...args: unknown[]
|
||||
...args: any[]
|
||||
): Promise<T> => {
|
||||
let cache: ResultCache<T> | undefined = (hass as any)[cacheKey];
|
||||
|
||||
|
@@ -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(function () {
|
||||
.then(() => {
|
||||
el.fire("hass-service-called", eventData);
|
||||
});
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user