mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-17 17:09:43 +00:00
Compare commits
106 Commits
20241127.5
...
fix_new_sc
Author | SHA1 | Date | |
---|---|---|---|
![]() |
637eb6e894 | ||
![]() |
f688780677 | ||
![]() |
0ce98a86e6 | ||
![]() |
bf624f5ca7 | ||
![]() |
ce5ce37de7 | ||
![]() |
da727d3a3a | ||
![]() |
9d83cc6988 | ||
![]() |
ed625d4e0b | ||
![]() |
84157c8ea5 | ||
![]() |
8f19c0abb0 | ||
![]() |
008647aa7a | ||
![]() |
b86d6021da | ||
![]() |
98af479fd3 | ||
![]() |
d1c981bc19 | ||
![]() |
1b0e53d3d9 | ||
![]() |
0d49927541 | ||
![]() |
9e8d452438 | ||
![]() |
ddd646007e | ||
![]() |
787fba82bd | ||
![]() |
1393a3ade8 | ||
![]() |
bf24d67066 | ||
![]() |
9774deef6d | ||
![]() |
8390c6e29b | ||
![]() |
c78d371a9c | ||
![]() |
7750299a66 | ||
![]() |
43f31dd455 | ||
![]() |
df21900341 | ||
![]() |
6934f0626c | ||
![]() |
ea5bf17780 | ||
![]() |
0b7af715a8 | ||
![]() |
8579bee053 | ||
![]() |
400ddbf625 | ||
![]() |
5fdefc0c20 | ||
![]() |
c72c74828e | ||
![]() |
e0b157d280 | ||
![]() |
e02736b4e2 | ||
![]() |
af049274d9 | ||
![]() |
7a12fd2853 | ||
![]() |
f724d8e7a9 | ||
![]() |
0d7e0df194 | ||
![]() |
7d567bc386 | ||
![]() |
ad52386ae0 | ||
![]() |
512fee47b6 | ||
![]() |
4092f56ea5 | ||
![]() |
926972cce6 | ||
![]() |
d51eea7bb6 | ||
![]() |
a3ca889ca3 | ||
![]() |
a7406b3201 | ||
![]() |
b54057cc4b | ||
![]() |
d77dd5300e | ||
![]() |
9396d5cf8c | ||
![]() |
a78ddb50fd | ||
![]() |
64e8b636b9 | ||
![]() |
2c604ff946 | ||
![]() |
f8ce7c2ce1 | ||
![]() |
af1622e306 | ||
![]() |
3c03dfb322 | ||
![]() |
697a99f913 | ||
![]() |
06925f0716 | ||
![]() |
afcfdb5140 | ||
![]() |
6126280f2d | ||
![]() |
8e6f4886e8 | ||
![]() |
480de9ef03 | ||
![]() |
614c4ec404 | ||
![]() |
d43291ae52 | ||
![]() |
7c486ec969 | ||
![]() |
19f54b6ba2 | ||
![]() |
97cc9f9ab9 | ||
![]() |
a104d38fc9 | ||
![]() |
2582023ab2 | ||
![]() |
e731f060f1 | ||
![]() |
c3942d244d | ||
![]() |
f4ef4c628a | ||
![]() |
a0f3e4f785 | ||
![]() |
2c13c5a18c | ||
![]() |
22cfd40ccc | ||
![]() |
5c7d9b3fa3 | ||
![]() |
55b6aa09d9 | ||
![]() |
96395dd5e1 | ||
![]() |
7191edd3c6 | ||
![]() |
6169379f4c | ||
![]() |
5500dd1332 | ||
![]() |
5c681896f3 | ||
![]() |
d826113319 | ||
![]() |
f72b298f97 | ||
![]() |
90b7cad7ac | ||
![]() |
e0239486bc | ||
![]() |
e10fb3c168 | ||
![]() |
5ee8bee6dc | ||
![]() |
4b678ffb41 | ||
![]() |
82c23026b3 | ||
![]() |
94b7b60fe0 | ||
![]() |
e1553a7ccf | ||
![]() |
fb0ca49742 | ||
![]() |
97015788a2 | ||
![]() |
0cf05ea2cc | ||
![]() |
acbe77c0d6 | ||
![]() |
bede8c66c5 | ||
![]() |
de87aee15b | ||
![]() |
36312cc273 | ||
![]() |
3120184d63 | ||
![]() |
a1be9d923e | ||
![]() |
7dee34ca75 | ||
![]() |
f7c8c6e3e8 | ||
![]() |
3411967fd9 | ||
![]() |
125805d6d1 |
@@ -4,13 +4,12 @@
|
|||||||
# - released in the last year + current alpha/beta versions
|
# - released in the last year + current alpha/beta versions
|
||||||
# - Firefox extended support release (ESR)
|
# - Firefox extended support release (ESR)
|
||||||
# - with global utilization at or above 0.5%
|
# - with global utilization at or above 0.5%
|
||||||
# - must support dynamic import of ES modules
|
# - exclude dead browsers (no security maintenance for 2+ years)
|
||||||
# - exclude browsers no longer being maintained
|
|
||||||
# - exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data
|
# - exclude KaiOS, QQ, and UC browsers due to lack of sufficient feature support data
|
||||||
unreleased versions
|
unreleased versions
|
||||||
last 1 year
|
last 1 year
|
||||||
Firefox ESR
|
Firefox ESR
|
||||||
>= 0.5% and supports es6-module-dynamic-import
|
>= 0.5%
|
||||||
not dead
|
not dead
|
||||||
not KaiOS > 0
|
not KaiOS > 0
|
||||||
not QQAndroid > 0
|
not QQAndroid > 0
|
||||||
@@ -20,23 +19,18 @@ not UCAndroid > 0
|
|||||||
# Legacy builds are served when modern requirements are not met and support browsers:
|
# Legacy builds are served when modern requirements are not met and support browsers:
|
||||||
# - released in the last 7 years + current alpha/beta versionss
|
# - released in the last 7 years + current alpha/beta versionss
|
||||||
# - with global utilization at or above 0.05%
|
# - with global utilization at or above 0.05%
|
||||||
# The lattermost query ensures that support for popular old browsers is not dropped too early
|
# - exclude dead browsers (no security maintenance for 2+ years)
|
||||||
# (e.g. IE 11, Android 4.4, or Samsung 4).
|
# - exclude Opera Mini which does not support web sockets
|
||||||
#
|
|
||||||
# In addition, legacy browsers must support some minimum features that cannot be polyfilled:
|
|
||||||
# - ES5 (strict mode)
|
|
||||||
# - web sockets to communicate with backend
|
|
||||||
# - inline SVG used widely in buttons, widgets, etc.
|
|
||||||
# - custom events used for most user interactions
|
|
||||||
# - CSS flexbox used in the majority of the layout
|
|
||||||
# Nearly all of these are redundant with the above rules.
|
|
||||||
# As of May 2023, only web sockets must be added to the query.
|
|
||||||
unreleased versions
|
unreleased versions
|
||||||
last 7 years
|
last 7 years
|
||||||
>= 0.05% and supports websockets
|
>= 0.05%
|
||||||
|
not dead
|
||||||
|
not op_mini all
|
||||||
|
|
||||||
[legacy-sw]
|
[legacy-sw]
|
||||||
# Same as legacy plus supports service workers
|
# Same as legacy plus supports service workers
|
||||||
unreleased versions
|
unreleased versions
|
||||||
last 7 years
|
last 7 years
|
||||||
>= 0.05% and supports websockets and supports serviceworkers
|
>= 0.05% and supports serviceworkers
|
||||||
|
not dead
|
||||||
|
not op_mini all
|
||||||
|
10
.github/workflows/ci.yaml
vendored
10
.github/workflows/ci.yaml
vendored
@@ -31,7 +31,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- uses: actions/cache@v4.1.2
|
- uses: actions/cache@v4.2.0
|
||||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
with:
|
with:
|
||||||
path: "node_modules"
|
path: "node_modules"
|
||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
- name: Build resources
|
- name: Build resources
|
||||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-pages
|
||||||
- name: Setup lint cache
|
- name: Setup lint cache
|
||||||
uses: actions/cache@v4.1.2
|
uses: actions/cache@v4.2.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
node_modules/.cache/prettier
|
node_modules/.cache/prettier
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- uses: actions/cache@v4.1.2
|
- uses: actions/cache@v4.2.0
|
||||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
with:
|
with:
|
||||||
path: "node_modules"
|
path: "node_modules"
|
||||||
@@ -97,7 +97,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- uses: actions/cache@v4.1.2
|
- uses: actions/cache@v4.2.0
|
||||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
with:
|
with:
|
||||||
path: "node_modules"
|
path: "node_modules"
|
||||||
@@ -129,7 +129,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version-file: ".nvmrc"
|
node-version-file: ".nvmrc"
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- uses: actions/cache@v4.1.2
|
- uses: actions/cache@v4.2.0
|
||||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||||
with:
|
with:
|
||||||
path: "node_modules"
|
path: "node_modules"
|
||||||
|
2
.github/workflows/relative-ci.yaml
vendored
2
.github/workflows/relative-ci.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Send bundle stats and build information to RelativeCI
|
- name: Send bundle stats and build information to RelativeCI
|
||||||
uses: relative-ci/agent-action@v2.1.13
|
uses: relative-ci/agent-action@v2.1.14
|
||||||
with:
|
with:
|
||||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||||
token: ${{ github.token }}
|
token: ${{ github.token }}
|
||||||
|
59
.github/workflows/release.yaml
vendored
59
.github/workflows/release.yaml
vendored
@@ -4,7 +4,6 @@ on:
|
|||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- published
|
- published
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PYTHON_VERSION: "3.12"
|
PYTHON_VERSION: "3.12"
|
||||||
@@ -82,3 +81,61 @@ jobs:
|
|||||||
arch: amd64
|
arch: amd64
|
||||||
wheels-key: ${{ secrets.WHEELS_KEY }}
|
wheels-key: ${{ secrets.WHEELS_KEY }}
|
||||||
requirements: "requirements.txt"
|
requirements: "requirements.txt"
|
||||||
|
|
||||||
|
release-landing-page:
|
||||||
|
name: Release landing-page frontend
|
||||||
|
if: github.event.release.prerelease == false
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # Required to upload release assets
|
||||||
|
steps:
|
||||||
|
- name: Checkout the repository
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4.1.0
|
||||||
|
with:
|
||||||
|
node-version-file: ".nvmrc"
|
||||||
|
cache: yarn
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install
|
||||||
|
- name: Download Translations
|
||||||
|
run: ./script/translations_download
|
||||||
|
env:
|
||||||
|
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||||
|
- name: Build landing-page
|
||||||
|
run: landing-page/script/build_landing_page
|
||||||
|
- name: Tar folder
|
||||||
|
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
||||||
|
- name: Upload release asset
|
||||||
|
uses: softprops/action-gh-release@v2.1.0
|
||||||
|
with:
|
||||||
|
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
|
||||||
|
release-supervisor:
|
||||||
|
name: Release supervisor frontend
|
||||||
|
if: github.event.release.prerelease == false
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # Required to upload release assets
|
||||||
|
steps:
|
||||||
|
- name: Checkout the repository
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4.1.0
|
||||||
|
with:
|
||||||
|
node-version-file: ".nvmrc"
|
||||||
|
cache: yarn
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install
|
||||||
|
- name: Download Translations
|
||||||
|
run: ./script/translations_download
|
||||||
|
env:
|
||||||
|
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||||
|
- name: Build supervisor
|
||||||
|
run: hassio/script/build_hassio
|
||||||
|
- name: Tar folder
|
||||||
|
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
||||||
|
- name: Upload release asset
|
||||||
|
uses: softprops/action-gh-release@v2.1.0
|
||||||
|
with:
|
||||||
|
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
||||||
|
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ enableGlobalCache: false
|
|||||||
|
|
||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-4.5.2.cjs
|
yarnPath: .yarn/releases/yarn-4.5.3.cjs
|
||||||
|
@@ -53,6 +53,11 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
|
|||||||
__SUPERVISOR__: false,
|
__SUPERVISOR__: false,
|
||||||
__BACKWARDS_COMPAT__: false,
|
__BACKWARDS_COMPAT__: false,
|
||||||
__STATIC_PATH__: "/static/",
|
__STATIC_PATH__: "/static/",
|
||||||
|
__HASS_URL__: `\`${
|
||||||
|
"HASS_URL" in process.env
|
||||||
|
? process.env["HASS_URL"]
|
||||||
|
: "${location.protocol}//${location.host}"
|
||||||
|
}\``,
|
||||||
"process.env.NODE_ENV": JSON.stringify(
|
"process.env.NODE_ENV": JSON.stringify(
|
||||||
isProdBuild ? "production" : "development"
|
isProdBuild ? "production" : "development"
|
||||||
),
|
),
|
||||||
|
@@ -5,9 +5,6 @@ const paths = require("./paths.cjs");
|
|||||||
const isTrue = (value) => value === "1" || value?.toLowerCase() === "true";
|
const isTrue = (value) => value === "1" || value?.toLowerCase() === "true";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
useWDS() {
|
|
||||||
return isTrue(process.env.WDS);
|
|
||||||
},
|
|
||||||
isProdBuild() {
|
isProdBuild() {
|
||||||
return (
|
return (
|
||||||
process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
|
process.env.NODE_ENV === "production" || module.exports.isStatsBuild()
|
||||||
|
@@ -8,7 +8,6 @@ import "./gen-icons-json.js";
|
|||||||
import "./locale-data.js";
|
import "./locale-data.js";
|
||||||
import "./service-worker.js";
|
import "./service-worker.js";
|
||||||
import "./translations.js";
|
import "./translations.js";
|
||||||
import "./wds.js";
|
|
||||||
import "./rspack.js";
|
import "./rspack.js";
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
@@ -26,7 +25,7 @@ gulp.task(
|
|||||||
"build-locale-data"
|
"build-locale-data"
|
||||||
),
|
),
|
||||||
"copy-static-app",
|
"copy-static-app",
|
||||||
env.useWDS() ? "wds-watch-app" : "rspack-watch-app"
|
"rspack-watch-app"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
import { constants } from "node:zlib";
|
import { constants } from "node:zlib";
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import brotli from "gulp-brotli";
|
import brotli from "gulp-brotli";
|
||||||
import zopfli from "gulp-zopfli-green";
|
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
const filesGlob = "*.{js,json,css,svg,xml}";
|
const filesGlob = "*.{js,json,css,svg,xml}";
|
||||||
@@ -13,7 +12,6 @@ const brotliOptions = {
|
|||||||
[constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,
|
[constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const zopfliOptions = { threshold: 150 };
|
|
||||||
|
|
||||||
const compressDistBrotli = (rootDir, modernDir, compressServiceWorker = true) =>
|
const compressDistBrotli = (rootDir, modernDir, compressServiceWorker = true) =>
|
||||||
gulp
|
gulp
|
||||||
@@ -29,20 +27,6 @@ const compressDistBrotli = (rootDir, modernDir, compressServiceWorker = true) =>
|
|||||||
.pipe(brotli(brotliOptions))
|
.pipe(brotli(brotliOptions))
|
||||||
.pipe(gulp.dest(rootDir));
|
.pipe(gulp.dest(rootDir));
|
||||||
|
|
||||||
const compressDistZopfli = (rootDir, modernDir, compressModern = false) =>
|
|
||||||
gulp
|
|
||||||
.src(
|
|
||||||
[
|
|
||||||
`${rootDir}/**/${filesGlob}`,
|
|
||||||
compressModern ? undefined : `!${modernDir}/**/${filesGlob}`,
|
|
||||||
`!${rootDir}/{sw-modern,service_worker}.js`,
|
|
||||||
`${rootDir}/{authorize,onboarding}.html`,
|
|
||||||
].filter(Boolean),
|
|
||||||
{ base: rootDir }
|
|
||||||
)
|
|
||||||
.pipe(zopfli(zopfliOptions))
|
|
||||||
.pipe(gulp.dest(rootDir));
|
|
||||||
|
|
||||||
const compressAppBrotli = () =>
|
const compressAppBrotli = () =>
|
||||||
compressDistBrotli(paths.app_output_root, paths.app_output_latest);
|
compressDistBrotli(paths.app_output_root, paths.app_output_latest);
|
||||||
const compressHassioBrotli = () =>
|
const compressHassioBrotli = () =>
|
||||||
@@ -52,17 +36,5 @@ const compressHassioBrotli = () =>
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
const compressAppZopfli = () =>
|
gulp.task("compress-app", compressAppBrotli);
|
||||||
compressDistZopfli(paths.app_output_root, paths.app_output_latest);
|
gulp.task("compress-hassio", compressHassioBrotli);
|
||||||
const compressHassioZopfli = () =>
|
|
||||||
compressDistZopfli(
|
|
||||||
paths.hassio_output_root,
|
|
||||||
paths.hassio_output_latest,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli));
|
|
||||||
gulp.task(
|
|
||||||
"compress-hassio",
|
|
||||||
gulp.parallel(compressHassioBrotli, compressHassioZopfli)
|
|
||||||
);
|
|
||||||
|
@@ -11,7 +11,6 @@ import { minify } from "html-minifier-terser";
|
|||||||
import template from "lodash.template";
|
import template from "lodash.template";
|
||||||
import { dirname, extname, resolve } from "node:path";
|
import { dirname, extname, resolve } from "node:path";
|
||||||
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
|
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
|
||||||
import env from "../env.cjs";
|
|
||||||
import paths from "../paths.cjs";
|
import paths from "../paths.cjs";
|
||||||
|
|
||||||
// macOS companion app has no way to obtain the Safari version used by WKWebView,
|
// macOS companion app has no way to obtain the Safari version used by WKWebView,
|
||||||
@@ -56,7 +55,6 @@ const getCommonTemplateVars = () => {
|
|||||||
{ ignorePatch: true, allowHigherVersions: true }
|
{ ignorePatch: true, allowHigherVersions: true }
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
useWDS: env.useWDS(),
|
|
||||||
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
|
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -92,13 +90,11 @@ const minifyHtml = (content, ext) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Function to generate a dev task for each project's configuration
|
// Function to generate a dev task for each project's configuration
|
||||||
// Note Currently WDS paths are hard-coded to only work for app
|
|
||||||
const genPagesDevTask =
|
const genPagesDevTask =
|
||||||
(
|
(
|
||||||
pageEntries,
|
pageEntries,
|
||||||
inputRoot,
|
inputRoot,
|
||||||
outputRoot,
|
outputRoot,
|
||||||
useWDS = false,
|
|
||||||
inputSub = "src/html",
|
inputSub = "src/html",
|
||||||
publicRoot = ""
|
publicRoot = ""
|
||||||
) =>
|
) =>
|
||||||
@@ -109,17 +105,13 @@ const genPagesDevTask =
|
|||||||
resolve(inputRoot, inputSub, `${page}.template`),
|
resolve(inputRoot, inputSub, `${page}.template`),
|
||||||
{
|
{
|
||||||
...commonVars,
|
...commonVars,
|
||||||
latestEntryJS: entries.map((entry) =>
|
latestEntryJS: entries.map(
|
||||||
useWDS
|
(entry) => `${publicRoot}/frontend_latest/${entry}.js`
|
||||||
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
|
||||||
: `${publicRoot}/frontend_latest/${entry}.js`
|
|
||||||
),
|
),
|
||||||
es5EntryJS: entries.map(
|
es5EntryJS: entries.map(
|
||||||
(entry) => `${publicRoot}/frontend_es5/${entry}.js`
|
(entry) => `${publicRoot}/frontend_es5/${entry}.js`
|
||||||
),
|
),
|
||||||
latestCustomPanelJS: useWDS
|
latestCustomPanelJS: `${publicRoot}/frontend_latest/custom-panel.js`,
|
||||||
? "http://localhost:8000/src/entrypoints/custom-panel.ts"
|
|
||||||
: `${publicRoot}/frontend_latest/custom-panel.js`,
|
|
||||||
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -176,12 +168,7 @@ const APP_PAGE_ENTRIES = {
|
|||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
"gen-pages-app-dev",
|
"gen-pages-app-dev",
|
||||||
genPagesDevTask(
|
genPagesDevTask(APP_PAGE_ENTRIES, paths.polymer_dir, paths.app_output_root)
|
||||||
APP_PAGE_ENTRIES,
|
|
||||||
paths.polymer_dir,
|
|
||||||
paths.app_output_root,
|
|
||||||
env.useWDS()
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
gulp.task(
|
gulp.task(
|
||||||
@@ -287,7 +274,6 @@ gulp.task(
|
|||||||
HASSIO_PAGE_ENTRIES,
|
HASSIO_PAGE_ENTRIES,
|
||||||
paths.hassio_dir,
|
paths.hassio_dir,
|
||||||
paths.hassio_output_root,
|
paths.hassio_output_root,
|
||||||
undefined,
|
|
||||||
"src",
|
"src",
|
||||||
paths.hassio_publicPath
|
paths.hassio_publicPath
|
||||||
)
|
)
|
||||||
|
@@ -66,7 +66,7 @@ gulp.task("fetch-nightly-translations", async function () {
|
|||||||
tokenAuth = JSON.parse(await readFile(TOKEN_FILE, "utf-8"));
|
tokenAuth = JSON.parse(await readFile(TOKEN_FILE, "utf-8"));
|
||||||
} catch {
|
} catch {
|
||||||
if (!allowTokenSetup) {
|
if (!allowTokenSetup) {
|
||||||
console.log("No token found so build wil continue with English only");
|
console.log("No token found so build will continue with English only");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auth = createOAuthDeviceAuth({
|
const auth = createOAuthDeviceAuth({
|
||||||
|
@@ -9,7 +9,6 @@ const outDir = join(paths.build_dir, "locale-data");
|
|||||||
|
|
||||||
const INTL_POLYFILLS = {
|
const INTL_POLYFILLS = {
|
||||||
"intl-datetimeformat": "DateTimeFormat",
|
"intl-datetimeformat": "DateTimeFormat",
|
||||||
"intl-durationFormat": "DurationFormat",
|
|
||||||
"intl-displaynames": "DisplayNames",
|
"intl-displaynames": "DisplayNames",
|
||||||
"intl-listformat": "ListFormat",
|
"intl-listformat": "ListFormat",
|
||||||
"intl-numberformat": "NumberFormat",
|
"intl-numberformat": "NumberFormat",
|
||||||
|
@@ -1,10 +0,0 @@
|
|||||||
import gulp from "gulp";
|
|
||||||
import { startDevServer } from "@web/dev-server";
|
|
||||||
|
|
||||||
gulp.task("wds-watch-app", async () => {
|
|
||||||
startDevServer({
|
|
||||||
config: {
|
|
||||||
watch: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
@@ -25,9 +25,9 @@ class HcLovelace extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public lovelaceConfig!: LovelaceConfig;
|
public lovelaceConfig!: LovelaceConfig;
|
||||||
|
|
||||||
@property() public viewPath?: string | number | null;
|
@property({ attribute: false }) public viewPath?: string | number | null;
|
||||||
|
|
||||||
@property() public urlPath: string | null = null;
|
@property({ attribute: false }) public urlPath: string | null = null;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const index = this._viewIndex;
|
const index = this._viewIndex;
|
||||||
|
@@ -144,10 +144,10 @@ export class HcMain extends HassElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (senderId) {
|
if (senderId) {
|
||||||
this.sendMessage(senderId, status);
|
this._sendMessage(senderId, status);
|
||||||
} else {
|
} else {
|
||||||
for (const sender of castContext.getSenders()) {
|
for (const sender of castContext.getSenders()) {
|
||||||
this.sendMessage(sender.id, status);
|
this._sendMessage(sender.id, status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,10 +164,10 @@ export class HcMain extends HassElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (senderId) {
|
if (senderId) {
|
||||||
this.sendMessage(senderId, error);
|
this._sendMessage(senderId, error);
|
||||||
} else {
|
} else {
|
||||||
for (const sender of castContext.getSenders()) {
|
for (const sender of castContext.getSenders()) {
|
||||||
this.sendMessage(sender.id, error);
|
this._sendMessage(sender.id, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -394,7 +394,7 @@ export class HcMain extends HassElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendMessage(senderId: string, response: any) {
|
private _sendMessage(senderId: string, response: any) {
|
||||||
castContext.sendCustomMessage(CAST_NS, senderId, response);
|
castContext.sendCustomMessage(CAST_NS, senderId, response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -46,7 +46,6 @@ class CastDemoRow extends LitElement implements LovelaceRow {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
});
|
});
|
||||||
mgr.castContext.addEventListener(
|
mgr.castContext.addEventListener(
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
|
||||||
(ev) => {
|
(ev) => {
|
||||||
// On Android, opening a new session always results in SESSION_RESUMED.
|
// On Android, opening a new session always results in SESSION_RESUMED.
|
||||||
|
@@ -26,7 +26,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
@state() private _switching = false;
|
@state() private _switching = false;
|
||||||
|
|
||||||
private _hidden = localStorage.hide_demo_card;
|
private _hidden = window.localStorage.getItem("hide_demo_card");
|
||||||
|
|
||||||
public getCardSize() {
|
public getCardSize() {
|
||||||
return this._hidden ? 0 : 2;
|
return this._hidden ? 0 : 2;
|
||||||
|
@@ -7,10 +7,10 @@ import { fileURLToPath } from "node:url";
|
|||||||
import js from "@eslint/js";
|
import js from "@eslint/js";
|
||||||
import { FlatCompat } from "@eslint/eslintrc";
|
import { FlatCompat } from "@eslint/eslintrc";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const _dirname = path.dirname(_filename);
|
||||||
const compat = new FlatCompat({
|
const compat = new FlatCompat({
|
||||||
baseDirectory: __dirname,
|
baseDirectory: _dirname,
|
||||||
recommendedConfig: js.configs.recommended,
|
recommendedConfig: js.configs.recommended,
|
||||||
allConfig: js.configs.all,
|
allConfig: js.configs.all,
|
||||||
});
|
});
|
||||||
@@ -114,12 +114,10 @@ export default [
|
|||||||
"@typescript-eslint/no-shadow": ["error"],
|
"@typescript-eslint/no-shadow": ["error"],
|
||||||
|
|
||||||
"@typescript-eslint/naming-convention": [
|
"@typescript-eslint/naming-convention": [
|
||||||
"off",
|
"error",
|
||||||
{
|
{
|
||||||
selector: "default",
|
selector: ["objectLiteralProperty", "objectLiteralMethod"],
|
||||||
format: ["camelCase", "snake_case"],
|
format: null,
|
||||||
leadingUnderscore: "allow",
|
|
||||||
trailingUnderscore: "allow",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: ["variable"],
|
selector: ["variable"],
|
||||||
@@ -127,10 +125,27 @@ export default [
|
|||||||
leadingUnderscore: "allow",
|
leadingUnderscore: "allow",
|
||||||
trailingUnderscore: "allow",
|
trailingUnderscore: "allow",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
selector: ["variable"],
|
||||||
|
modifiers: ["exported"],
|
||||||
|
format: ["camelCase", "PascalCase", "UPPER_CASE"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
selector: "typeLike",
|
selector: "typeLike",
|
||||||
format: ["PascalCase"],
|
format: ["PascalCase"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
selector: "method",
|
||||||
|
modifiers: ["public"],
|
||||||
|
format: ["camelCase"],
|
||||||
|
leadingUnderscore: "forbid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: "method",
|
||||||
|
modifiers: ["private"],
|
||||||
|
format: ["camelCase"],
|
||||||
|
leadingUnderscore: "require",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
@@ -147,16 +162,16 @@ export default [
|
|||||||
],
|
],
|
||||||
|
|
||||||
"unused-imports/no-unused-imports": "error",
|
"unused-imports/no-unused-imports": "error",
|
||||||
"lit/attribute-names": "warn",
|
"lit/attribute-names": "error",
|
||||||
"lit/attribute-value-entities": "off",
|
"lit/attribute-value-entities": "off",
|
||||||
"lit/no-template-map": "off",
|
"lit/no-template-map": "off",
|
||||||
"lit/no-native-attributes": "warn",
|
"lit/no-native-attributes": "error",
|
||||||
"lit/no-this-assign-in-render": "warn",
|
"lit/no-this-assign-in-render": "error",
|
||||||
"lit-a11y/click-events-have-key-events": ["off"],
|
"lit-a11y/click-events-have-key-events": ["off"],
|
||||||
"lit-a11y/no-autofocus": "off",
|
"lit-a11y/no-autofocus": "off",
|
||||||
"lit-a11y/alt-text": "warn",
|
"lit-a11y/alt-text": "error",
|
||||||
"lit-a11y/anchor-is-valid": "warn",
|
"lit-a11y/anchor-is-valid": "error",
|
||||||
"lit-a11y/role-has-required-aria-attrs": "warn",
|
"lit-a11y/role-has-required-aria-attrs": "error",
|
||||||
"@typescript-eslint/consistent-type-imports": "error",
|
"@typescript-eslint/consistent-type-imports": "error",
|
||||||
"@typescript-eslint/no-import-type-side-effects": "error",
|
"@typescript-eslint/no-import-type-side-effects": "error",
|
||||||
},
|
},
|
||||||
|
@@ -9,6 +9,7 @@ import "../../../src/components/ha-card";
|
|||||||
|
|
||||||
@customElement("demo-black-white-row")
|
@customElement("demo-black-white-row")
|
||||||
class DemoBlackWhiteRow extends LitElement {
|
class DemoBlackWhiteRow extends LitElement {
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property() title!: string;
|
@property() title!: string;
|
||||||
|
|
||||||
@property() value?: any;
|
@property() value?: any;
|
||||||
|
@@ -18,7 +18,8 @@ class DemoCard extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public config!: DemoCardConfig;
|
@property({ attribute: false }) public config!: DemoCardConfig;
|
||||||
|
|
||||||
@property({ type: Boolean }) public showConfig = false;
|
@property({ attribute: "show-config", type: Boolean })
|
||||||
|
public showConfig = false;
|
||||||
|
|
||||||
@state() private _size?: number;
|
@state() private _size?: number;
|
||||||
|
|
||||||
|
@@ -44,11 +44,11 @@ class DemoCards extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_showConfigToggled(ev) {
|
private _showConfigToggled(ev) {
|
||||||
this._showConfig = ev.target.checked;
|
this._showConfig = ev.target.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
_darkThemeToggled(ev) {
|
private _darkThemeToggled(ev) {
|
||||||
applyThemesOnElement(this._container, { themes: {} } as any, "default", {
|
applyThemesOnElement(this._container, { themes: {} } as any, "default", {
|
||||||
dark: ev.target.checked,
|
dark: ev.target.checked,
|
||||||
});
|
});
|
||||||
|
@@ -10,9 +10,10 @@ import type { HomeAssistant } from "../../../src/types";
|
|||||||
class DemoMoreInfo extends LitElement {
|
class DemoMoreInfo extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public entityId!: string;
|
@property({ attribute: false }) public entityId!: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public showConfig = false;
|
@property({ attribute: "show-config", type: Boolean })
|
||||||
|
public showConfig = false;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const state = this._getState(this.entityId, this.hass.states);
|
const state = this._getState(this.entityId, this.hass.states);
|
||||||
@@ -23,7 +24,7 @@ class DemoMoreInfo extends LitElement {
|
|||||||
<state-card-content
|
<state-card-content
|
||||||
.stateObj=${state}
|
.stateObj=${state}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
inDialog
|
in-dialog
|
||||||
></state-card-content>
|
></state-card-content>
|
||||||
|
|
||||||
<more-info-content
|
<more-info-content
|
||||||
|
@@ -58,11 +58,11 @@ class DemoMoreInfos extends LitElement {
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
_showConfigToggled(ev) {
|
private _showConfigToggled(ev) {
|
||||||
this._showConfig = ev.target.checked;
|
this._showConfig = ev.target.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
_darkThemeToggled(ev) {
|
private _darkThemeToggled(ev) {
|
||||||
applyThemesOnElement(
|
applyThemesOnElement(
|
||||||
this.shadowRoot!.querySelector("#container"),
|
this.shadowRoot!.querySelector("#container"),
|
||||||
{
|
{
|
||||||
|
@@ -182,7 +182,7 @@ class HaGallery extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_menuTapped() {
|
private _menuTapped() {
|
||||||
this._drawer.open = !this._drawer.open;
|
this._drawer.open = !this._drawer.open;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -63,11 +63,6 @@ class DemoHaAutomationEditorAction extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const valueChanged = (ev) => {
|
|
||||||
const sampleIdx = ev.target.sampleIdx;
|
|
||||||
this.data[sampleIdx] = ev.detail.value;
|
|
||||||
this.requestUpdate();
|
|
||||||
};
|
|
||||||
return html`
|
return html`
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<ha-formfield label="Disabled">
|
<ha-formfield label="Disabled">
|
||||||
@@ -92,7 +87,7 @@ class DemoHaAutomationEditorAction extends LitElement {
|
|||||||
.actions=${this.data[sampleIdx]}
|
.actions=${this.data[sampleIdx]}
|
||||||
.sampleIdx=${sampleIdx}
|
.sampleIdx=${sampleIdx}
|
||||||
.disabled=${this._disabled}
|
.disabled=${this._disabled}
|
||||||
@value-changed=${valueChanged}
|
@value-changed=${this._handleValueChange}
|
||||||
></ha-automation-action>
|
></ha-automation-action>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
@@ -102,6 +97,12 @@ class DemoHaAutomationEditorAction extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleValueChange(ev) {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this.data[sampleIdx] = ev.detail.value;
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
private _handleOptionChange(ev) {
|
private _handleOptionChange(ev) {
|
||||||
this[`_${ev.target.name}`] = ev.target.checked;
|
this[`_${ev.target.name}`] = ev.target.checked;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
|
||||||
import type { TemplateResult } from "lit";
|
import type { TemplateResult } from "lit";
|
||||||
import { LitElement, html, css } from "lit";
|
import { LitElement, html, css } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
@@ -104,11 +103,6 @@ export class DemoAutomationEditorCondition extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const valueChanged = (ev) => {
|
|
||||||
const sampleIdx = ev.target.sampleIdx;
|
|
||||||
this.data[sampleIdx] = ev.detail.value;
|
|
||||||
this.requestUpdate();
|
|
||||||
};
|
|
||||||
return html`
|
return html`
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<ha-formfield label="Disabled">
|
<ha-formfield label="Disabled">
|
||||||
@@ -133,7 +127,7 @@ export class DemoAutomationEditorCondition extends LitElement {
|
|||||||
.conditions=${this.data[sampleIdx]}
|
.conditions=${this.data[sampleIdx]}
|
||||||
.sampleIdx=${sampleIdx}
|
.sampleIdx=${sampleIdx}
|
||||||
.disabled=${this._disabled}
|
.disabled=${this._disabled}
|
||||||
@value-changed=${valueChanged}
|
@value-changed=${this._handleValueChange}
|
||||||
></ha-automation-condition>
|
></ha-automation-condition>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
@@ -143,6 +137,12 @@ export class DemoAutomationEditorCondition extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleValueChange(ev) {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this.data[sampleIdx] = ev.detail.value;
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
private _handleOptionChange(ev) {
|
private _handleOptionChange(ev) {
|
||||||
this[`_${ev.target.name}`] = ev.target.checked;
|
this[`_${ev.target.name}`] = ev.target.checked;
|
||||||
}
|
}
|
||||||
|
@@ -149,11 +149,6 @@ export class DemoAutomationEditorTrigger extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const valueChanged = (ev) => {
|
|
||||||
const sampleIdx = ev.target.sampleIdx;
|
|
||||||
this.data[sampleIdx] = ev.detail.value;
|
|
||||||
this.requestUpdate();
|
|
||||||
};
|
|
||||||
return html`
|
return html`
|
||||||
<div class="options">
|
<div class="options">
|
||||||
<ha-formfield label="Disabled">
|
<ha-formfield label="Disabled">
|
||||||
@@ -178,7 +173,7 @@ export class DemoAutomationEditorTrigger extends LitElement {
|
|||||||
.triggers=${this.data[sampleIdx]}
|
.triggers=${this.data[sampleIdx]}
|
||||||
.sampleIdx=${sampleIdx}
|
.sampleIdx=${sampleIdx}
|
||||||
.disabled=${this._disabled}
|
.disabled=${this._disabled}
|
||||||
@value-changed=${valueChanged}
|
@value-changed=${this._handleValueChange}
|
||||||
></ha-automation-trigger>
|
></ha-automation-trigger>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
@@ -188,6 +183,12 @@ export class DemoAutomationEditorTrigger extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleValueChange(ev) {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this.data[sampleIdx] = ev.detail.value;
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
private _handleOptionChange(ev) {
|
private _handleOptionChange(ev) {
|
||||||
this[`_${ev.target.name}`] = ev.target.checked;
|
this[`_${ev.target.name}`] = ev.target.checked;
|
||||||
}
|
}
|
||||||
|
@@ -31,22 +31,17 @@ export class DemoAutomationTrace extends LitElement {
|
|||||||
<hat-script-graph
|
<hat-script-graph
|
||||||
.trace=${trace.trace}
|
.trace=${trace.trace}
|
||||||
.selected=${this._selected[idx]}
|
.selected=${this._selected[idx]}
|
||||||
@graph-node-selected=${(ev) => {
|
@graph-node-selected=${this._handleGraphNodeSelected}
|
||||||
this._selected = { ...this._selected, [idx]: ev.detail.path };
|
.sampleIdx=${idx}
|
||||||
}}
|
|
||||||
></hat-script-graph>
|
></hat-script-graph>
|
||||||
<hat-trace-timeline
|
<hat-trace-timeline
|
||||||
allowPick
|
allow-pick
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.trace=${trace.trace}
|
.trace=${trace.trace}
|
||||||
.logbookEntries=${trace.logbookEntries}
|
.logbookEntries=${trace.logbookEntries}
|
||||||
.selectedPath=${this._selected[idx]}
|
.selectedPath=${this._selected[idx]}
|
||||||
@value-changed=${(ev) => {
|
@value-changed=${this._handleTimelineValueChanged}
|
||||||
this._selected = {
|
.sampleIdx=${idx}
|
||||||
...this._selected,
|
|
||||||
[idx]: ev.detail.value,
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
></hat-trace-timeline>
|
></hat-trace-timeline>
|
||||||
<button @click=${() => console.log(trace)}>Log trace</button>
|
<button @click=${() => console.log(trace)}>Log trace</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,6 +58,16 @@ export class DemoAutomationTrace extends LitElement {
|
|||||||
hass.updateTranslations("config", "en");
|
hass.updateTranslations("config", "en");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleTimelineValueChanged(ev) {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this._selected = { ...this._selected, [sampleIdx]: ev.detail.value };
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleGraphNodeSelected(ev) {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this._selected = { ...this._selected, [sampleIdx]: ev.detail.path };
|
||||||
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
|
@@ -489,14 +489,8 @@ class DemoHaForm extends LitElement {
|
|||||||
.title=${info.title}
|
.title=${info.title}
|
||||||
.value=${this.data[idx]}
|
.value=${this.data[idx]}
|
||||||
.disabled=${this.disabled[idx]}
|
.disabled=${this.disabled[idx]}
|
||||||
@submitted=${() => {
|
@submitted=${this._handleSubmit}
|
||||||
this.disabled[idx] = true;
|
.sampleIdx=${idx}
|
||||||
this.requestUpdate();
|
|
||||||
setTimeout(() => {
|
|
||||||
this.disabled[idx] = false;
|
|
||||||
this.requestUpdate();
|
|
||||||
}, 2000);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
${["light", "dark"].map(
|
${["light", "dark"].map(
|
||||||
(slot) => html`
|
(slot) => html`
|
||||||
@@ -511,10 +505,8 @@ class DemoHaForm extends LitElement {
|
|||||||
.computeLabel=${(schema) =>
|
.computeLabel=${(schema) =>
|
||||||
translations[schema.name] || schema.name}
|
translations[schema.name] || schema.name}
|
||||||
.computeHelper=${() => "Helper text"}
|
.computeHelper=${() => "Helper text"}
|
||||||
@value-changed=${(e) => {
|
@value-changed=${this._handleValueChanged}
|
||||||
this.data[idx] = e.detail.value;
|
.sampleIdx=${idx}
|
||||||
this.requestUpdate();
|
|
||||||
}}
|
|
||||||
></ha-form>
|
></ha-form>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
@@ -523,6 +515,22 @@ class DemoHaForm extends LitElement {
|
|||||||
})}
|
})}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleValueChanged(ev) {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this.data[sampleIdx] = ev.detail.value;
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSubmit(ev) {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this.disabled[sampleIdx] = true;
|
||||||
|
this.requestUpdate();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.disabled[sampleIdx] = false;
|
||||||
|
this.requestUpdate();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import type { TemplateResult } from "lit";
|
import type { TemplateResult } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
@@ -591,13 +590,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
</div>
|
</div>
|
||||||
${SCHEMAS.map((info, idx) => {
|
${SCHEMAS.map((info, idx) => {
|
||||||
const data = this.data[idx];
|
const data = this.data[idx];
|
||||||
const valueChanged = (ev) => {
|
|
||||||
this.data[idx] = {
|
|
||||||
...data,
|
|
||||||
[ev.target.key]: ev.detail.value,
|
|
||||||
};
|
|
||||||
this.requestUpdate();
|
|
||||||
};
|
|
||||||
return html`
|
return html`
|
||||||
<demo-black-white-row .title=${info.name}>
|
<demo-black-white-row .title=${info.name}>
|
||||||
${["light", "dark"].map((slot) =>
|
${["light", "dark"].map((slot) =>
|
||||||
@@ -614,7 +606,8 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
.value=${data[key] ?? value!.default}
|
.value=${data[key] ?? value!.default}
|
||||||
.disabled=${this._disabled}
|
.disabled=${this._disabled}
|
||||||
.required=${this._required}
|
.required=${this._required}
|
||||||
@value-changed=${valueChanged}
|
@value-changed=${this._handleValueChanged}
|
||||||
|
.sampleIdx=${idx}
|
||||||
.helper=${this._helper ? "Helper text" : undefined}
|
.helper=${this._helper ? "Helper text" : undefined}
|
||||||
></ha-selector>
|
></ha-selector>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
@@ -627,6 +620,15 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleValueChanged(ev) {
|
||||||
|
const idx = ev.target.sampleIdx;
|
||||||
|
this.data[idx] = {
|
||||||
|
...this.data[idx],
|
||||||
|
[ev.target.key]: ev.detail.value,
|
||||||
|
};
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
private _handleOptionChange(ev) {
|
private _handleOptionChange(ev) {
|
||||||
this[`_${ev.target.name}`] = ev.target.checked;
|
this[`_${ev.target.name}`] = ev.target.checked;
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import { customElement, query } from "lit/decorators";
|
|||||||
import { CoverEntityFeature } from "../../../../src/data/cover";
|
import { CoverEntityFeature } from "../../../../src/data/cover";
|
||||||
import { LightColorMode } from "../../../../src/data/light";
|
import { LightColorMode } from "../../../../src/data/light";
|
||||||
import { LockEntityFeature } from "../../../../src/data/lock";
|
import { LockEntityFeature } from "../../../../src/data/lock";
|
||||||
|
import { MediaPlayerEntityFeature } from "../../../../src/data/media-player";
|
||||||
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
|
import { VacuumEntityFeature } from "../../../../src/data/vacuum";
|
||||||
import { getEntity } from "../../../../src/fake_data/entity";
|
import { getEntity } from "../../../../src/fake_data/entity";
|
||||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||||
@@ -28,6 +29,10 @@ const ENTITIES = [
|
|||||||
device_class: "lock",
|
device_class: "lock",
|
||||||
supported_features: LockEntityFeature.OPEN,
|
supported_features: LockEntityFeature.OPEN,
|
||||||
}),
|
}),
|
||||||
|
getEntity("media_player", "living_room", "playing", {
|
||||||
|
friendly_name: "Living room speaker",
|
||||||
|
supported_features: MediaPlayerEntityFeature.VOLUME_SET,
|
||||||
|
}),
|
||||||
getEntity("climate", "thermostat", "heat", {
|
getEntity("climate", "thermostat", "heat", {
|
||||||
current_temperature: 73,
|
current_temperature: 73,
|
||||||
min_temp: 45,
|
min_temp: 45,
|
||||||
@@ -197,6 +202,15 @@ const CONFIGS = [
|
|||||||
- type: "lock-open-door"
|
- type: "lock-open-door"
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Media player volume slider feature",
|
||||||
|
config: `
|
||||||
|
- type: tile
|
||||||
|
entity: media_player.living_room
|
||||||
|
features:
|
||||||
|
- type: "media-player-volume-slider"
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Vacuum commands feature",
|
heading: "Vacuum commands feature",
|
||||||
config: `
|
config: `
|
||||||
|
@@ -136,7 +136,7 @@ export class HassioAddonStore extends LitElement {
|
|||||||
this._manageRepositories(repositoryUrl);
|
this._manageRepositories(repositoryUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addEventListener("hass-api-called", (ev) => this.apiCalled(ev));
|
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
|
||||||
this._loadData();
|
this._loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ export class HassioAddonStore extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private apiCalled(ev) {
|
private _apiCalled(ev) {
|
||||||
if (ev.detail.success) {
|
if (ev.detail.success) {
|
||||||
this._loadData();
|
this._loadData();
|
||||||
}
|
}
|
||||||
|
@@ -58,7 +58,7 @@ export class HassioBackups extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public isWide = false;
|
@property({ attribute: "is-wide", type: Boolean }) public isWide = false;
|
||||||
|
|
||||||
@state() private _selectedBackups: string[] = [];
|
@state() private _selectedBackups: string[] = [];
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ export class HassioBackups extends LitElement {
|
|||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (this.hass && this._firstUpdatedCalled) {
|
if (this.hass && this._firstUpdatedCalled) {
|
||||||
this.fetchBackups();
|
this._fetchBackups();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ export class HassioBackups extends LitElement {
|
|||||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProperties);
|
||||||
if (this.hass && this.isConnected) {
|
if (this.hass && this.isConnected) {
|
||||||
this.fetchBackups();
|
this._fetchBackups();
|
||||||
}
|
}
|
||||||
this._firstUpdatedCalled = true;
|
this._firstUpdatedCalled = true;
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ export class HassioBackups extends LitElement {
|
|||||||
@selection-changed=${this._handleSelectionChanged}
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
clickable
|
clickable
|
||||||
selectable
|
selectable
|
||||||
hasFab
|
has-fab
|
||||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
||||||
back-path=${atLeastVersion(this.hass.config.version, 2022, 5)
|
back-path=${atLeastVersion(this.hass.config.version, 2022, 5)
|
||||||
? "/config/system"
|
? "/config/system"
|
||||||
@@ -280,7 +280,7 @@ export class HassioBackups extends LitElement {
|
|||||||
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
private _handleAction(ev: CustomEvent<ActionDetail>) {
|
||||||
switch (ev.detail.index) {
|
switch (ev.detail.index) {
|
||||||
case 0:
|
case 0:
|
||||||
this.fetchBackups();
|
this._fetchBackups();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
showHassioBackupLocationDialog(this, { supervisor: this.supervisor });
|
showHassioBackupLocationDialog(this, { supervisor: this.supervisor });
|
||||||
@@ -303,13 +303,13 @@ export class HassioBackups extends LitElement {
|
|||||||
showHassioBackupDialog(this, {
|
showHassioBackupDialog(this, {
|
||||||
slug,
|
slug,
|
||||||
supervisor: this.supervisor,
|
supervisor: this.supervisor,
|
||||||
onDelete: () => this.fetchBackups(),
|
onDelete: () => this._fetchBackups(),
|
||||||
}),
|
}),
|
||||||
reloadBackup: () => this.fetchBackups(),
|
reloadBackup: () => this._fetchBackups(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchBackups() {
|
private async _fetchBackups() {
|
||||||
this._isLoading = true;
|
this._isLoading = true;
|
||||||
await reloadHassioBackups(this.hass);
|
await reloadHassioBackups(this.hass);
|
||||||
this._backups = await fetchHassioBackups(this.hass);
|
this._backups = await fetchHassioBackups(this.hass);
|
||||||
@@ -341,7 +341,7 @@ export class HassioBackups extends LitElement {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.fetchBackups();
|
await this._fetchBackups();
|
||||||
this._dataTable.clearSelection();
|
this._dataTable.clearSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,7 +350,7 @@ export class HassioBackups extends LitElement {
|
|||||||
showHassioBackupDialog(this, {
|
showHassioBackupDialog(this, {
|
||||||
slug,
|
slug,
|
||||||
supervisor: this.supervisor,
|
supervisor: this.supervisor,
|
||||||
onDelete: () => this.fetchBackups(),
|
onDelete: () => this._fetchBackups(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +366,7 @@ export class HassioBackups extends LitElement {
|
|||||||
}
|
}
|
||||||
showHassioCreateBackupDialog(this, {
|
showHassioCreateBackupDialog(this, {
|
||||||
supervisor: this.supervisor!,
|
supervisor: this.supervisor!,
|
||||||
onCreate: () => this.fetchBackups(),
|
onCreate: () => this._fetchBackups(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,23 +9,24 @@ import type { HomeAssistant } from "../../../src/types";
|
|||||||
class HassioCardContent extends LitElement {
|
class HassioCardContent extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property() public title!: string;
|
@property() public title!: string;
|
||||||
|
|
||||||
@property() public description?: string;
|
@property() public description?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public available = true;
|
@property({ type: Boolean }) public available = true;
|
||||||
|
|
||||||
@property({ type: Boolean }) public showTopbar = false;
|
@property({ attribute: false }) public showTopbar = false;
|
||||||
|
|
||||||
@property() public topbarClass?: string;
|
@property({ attribute: false }) public topbarClass?: string;
|
||||||
|
|
||||||
@property() public iconTitle?: string;
|
@property({ attribute: false }) public iconTitle?: string;
|
||||||
|
|
||||||
@property() public iconClass?: string;
|
@property({ attribute: false }) public iconClass?: string;
|
||||||
|
|
||||||
@property() public icon = mdiHelpCircle;
|
@property() public icon = mdiHelpCircle;
|
||||||
|
|
||||||
@property() public iconImage?: string;
|
@property({ attribute: false }) public iconImage?: string;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
@@ -35,7 +36,11 @@ class HassioCardContent extends LitElement {
|
|||||||
${this.iconImage
|
${this.iconImage
|
||||||
? html`
|
? html`
|
||||||
<div class="icon_image ${this.iconClass}">
|
<div class="icon_image ${this.iconClass}">
|
||||||
<img src=${this.iconImage} .title=${this.iconTitle} />
|
<img
|
||||||
|
src=${this.iconImage}
|
||||||
|
.title=${this.iconTitle}
|
||||||
|
alt=${this.iconTitle ?? ""}
|
||||||
|
/>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
|
@@ -73,23 +73,24 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public backup?: HassioBackupDetail;
|
@property({ attribute: false }) public backup?: HassioBackupDetail;
|
||||||
|
|
||||||
@property() public backupType: HassioBackupDetail["type"] = "full";
|
@property({ attribute: false })
|
||||||
|
public backupType: HassioBackupDetail["type"] = "full";
|
||||||
|
|
||||||
@property({ attribute: false }) public folders?: CheckboxItem[];
|
@property({ attribute: false }) public folders?: CheckboxItem[];
|
||||||
|
|
||||||
@property({ attribute: false }) public addons?: AddonCheckboxItem[];
|
@property({ attribute: false }) public addons?: AddonCheckboxItem[];
|
||||||
|
|
||||||
@property({ type: Boolean }) public homeAssistant = false;
|
@property({ attribute: false }) public homeAssistant = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public backupHasPassword = false;
|
@property({ attribute: false }) public backupHasPassword = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public onboarding = false;
|
@property({ type: Boolean }) public onboarding = false;
|
||||||
|
|
||||||
@property() public backupName = "";
|
@property({ attribute: false }) public backupName = "";
|
||||||
|
|
||||||
@property() public backupPassword = "";
|
@property({ attribute: false }) public backupPassword = "";
|
||||||
|
|
||||||
@property() public confirmBackupPassword = "";
|
@property({ attribute: false }) public confirmBackupPassword = "";
|
||||||
|
|
||||||
@query("ha-textfield, ha-radio, ha-checkbox", true) private _focusTarget;
|
@query("ha-textfield, ha-radio, ha-checkbox", true) private _focusTarget;
|
||||||
|
|
||||||
@@ -191,7 +192,7 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this.homeAssistant}
|
.checked=${this.homeAssistant}
|
||||||
@change=${this.toggleHomeAssistant}
|
@change=${this._toggleHomeAssistant}
|
||||||
>
|
>
|
||||||
</ha-checkbox>
|
</ha-checkbox>
|
||||||
</ha-formfield>`
|
</ha-formfield>`
|
||||||
@@ -277,7 +278,7 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggleHomeAssistant() {
|
private _toggleHomeAssistant() {
|
||||||
this.homeAssistant = !this.homeAssistant;
|
this.homeAssistant = !this.homeAssistant;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,9 +7,9 @@ import "../../../src/components/ha-svg-icon";
|
|||||||
class SupervisorFormfieldLabel extends LitElement {
|
class SupervisorFormfieldLabel extends LitElement {
|
||||||
@property({ type: String }) public label!: string;
|
@property({ type: String }) public label!: string;
|
||||||
|
|
||||||
@property({ type: String }) public imageUrl?: string;
|
@property({ attribute: false }) public imageUrl?: string;
|
||||||
|
|
||||||
@property({ type: String }) public iconPath?: string;
|
@property({ attribute: false }) public iconPath?: string;
|
||||||
|
|
||||||
@property({ type: String }) public version?: string;
|
@property({ type: String }) public version?: string;
|
||||||
|
|
||||||
|
@@ -76,7 +76,7 @@ class HassioDashboard extends LitElement {
|
|||||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
||||||
back-path="/config"
|
back-path="/config"
|
||||||
supervisor
|
supervisor
|
||||||
hasFab
|
has-fab
|
||||||
>
|
>
|
||||||
<span slot="header">
|
<span slot="header">
|
||||||
${this.supervisor.localize(
|
${this.supervisor.localize(
|
||||||
|
@@ -95,7 +95,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
.label=${this.dialogParams.supervisor.localize(
|
.label=${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.select_device"
|
"dialog.datadisk_move.select_device"
|
||||||
)}
|
)}
|
||||||
@selected=${this._select_device}
|
@selected=${this._selectDevice}
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
${this.devices.map(
|
${this.devices.map(
|
||||||
@@ -137,7 +137,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _select_device(ev) {
|
private _selectDevice(ev) {
|
||||||
this.selectedDevice = ev.target.value;
|
this.selectedDevice = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ import type { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown";
|
|||||||
class HassioMarkdownDialog extends LitElement {
|
class HassioMarkdownDialog extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property() public title!: string;
|
@property() public title!: string;
|
||||||
|
|
||||||
@property() public content!: string;
|
@property() public content!: string;
|
||||||
|
@@ -394,7 +394,7 @@ export class DialogHassioNetwork
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_toArray(data: string | string[]): string[] {
|
private _toArray(data: string | string[]): string[] {
|
||||||
if (Array.isArray(data)) {
|
if (Array.isArray(data)) {
|
||||||
if (data && typeof data[0] === "string") {
|
if (data && typeof data[0] === "string") {
|
||||||
data = data[0];
|
data = data[0];
|
||||||
@@ -409,7 +409,7 @@ export class DialogHassioNetwork
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
_toString(data: string | string[]): string {
|
private _toString(data: string | string[]): string {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,7 @@ class HassioIngressView extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
@property({ type: Boolean }) public ingressPanel = false;
|
@property({ attribute: false }) public ingressPanel = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@@ -58,10 +58,10 @@ const SUPERVISOR_UPDATE_NAMES = {
|
|||||||
supervisor: "Home Assistant Supervisor",
|
supervisor: "Home Assistant Supervisor",
|
||||||
};
|
};
|
||||||
|
|
||||||
type updateType = "os" | "supervisor" | "core" | "addon";
|
type UpdateType = "os" | "supervisor" | "core" | "addon";
|
||||||
|
|
||||||
const changelogUrl = (
|
const changelogUrl = (
|
||||||
entry: updateType,
|
entry: UpdateType,
|
||||||
version: string
|
version: string
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
if (entry === "addon") {
|
if (entry === "addon") {
|
||||||
@@ -99,7 +99,7 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public addonSlug?: string;
|
@property({ attribute: false }) public addonSlug?: string;
|
||||||
|
|
||||||
@state() private _updateType?: updateType;
|
@state() private _updateType?: UpdateType;
|
||||||
|
|
||||||
@state() private _changelogContent?: string;
|
@state() private _changelogContent?: string;
|
||||||
|
|
||||||
@@ -222,7 +222,7 @@ class UpdateAvailableCard extends LitElement {
|
|||||||
const updateType = ["core", "os", "supervisor"].includes(pathPart)
|
const updateType = ["core", "os", "supervisor"].includes(pathPart)
|
||||||
? pathPart
|
? pathPart
|
||||||
: "addon";
|
: "addon";
|
||||||
this._updateType = updateType as updateType;
|
this._updateType = updateType as UpdateType;
|
||||||
|
|
||||||
switch (updateType) {
|
switch (updateType) {
|
||||||
case "addon":
|
case "addon":
|
||||||
|
@@ -64,9 +64,9 @@ class HaLandingPage extends LandingPageBaseElement {
|
|||||||
<ha-language-picker
|
<ha-language-picker
|
||||||
.value=${this.language}
|
.value=${this.language}
|
||||||
.label=${""}
|
.label=${""}
|
||||||
nativeName
|
native-name
|
||||||
@value-changed=${this._languageChanged}
|
@value-changed=${this._languageChanged}
|
||||||
inlineArrow
|
inline-arrow
|
||||||
></ha-language-picker>
|
></ha-language-picker>
|
||||||
<a
|
<a
|
||||||
href="https://www.home-assistant.io/getting-started/onboarding/"
|
href="https://www.home-assistant.io/getting-started/onboarding/"
|
||||||
@@ -122,7 +122,10 @@ class HaLandingPage extends LandingPageBaseElement {
|
|||||||
if (language !== this.language && language) {
|
if (language !== this.language && language) {
|
||||||
this.language = language;
|
this.language = language;
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
window.localStorage.setItem(
|
||||||
|
"selectedLanguage",
|
||||||
|
JSON.stringify(language)
|
||||||
|
);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
64
package.json
64
package.json
@@ -8,7 +8,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "script/build_frontend",
|
"build": "script/build_frontend",
|
||||||
"lint:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore",
|
"lint:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --max-warnings=0",
|
||||||
"format:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --fix",
|
"format:eslint": "eslint --flag unstable_config_lookup_from_file \"**/src/**/*.{js,ts,html}\" --cache --cache-strategy=content --cache-location=node_modules/.cache/eslint/.eslintcache --ignore-pattern=.gitignore --fix",
|
||||||
"lint:prettier": "prettier . --cache --check",
|
"lint:prettier": "prettier . --cache --check",
|
||||||
"format:prettier": "prettier . --cache --write",
|
"format:prettier": "prettier . --cache --write",
|
||||||
@@ -30,21 +30,21 @@
|
|||||||
"@braintree/sanitize-url": "7.1.0",
|
"@braintree/sanitize-url": "7.1.0",
|
||||||
"@codemirror/autocomplete": "6.18.3",
|
"@codemirror/autocomplete": "6.18.3",
|
||||||
"@codemirror/commands": "6.7.1",
|
"@codemirror/commands": "6.7.1",
|
||||||
"@codemirror/language": "6.10.3",
|
"@codemirror/language": "6.10.6",
|
||||||
"@codemirror/legacy-modes": "6.4.2",
|
"@codemirror/legacy-modes": "6.4.2",
|
||||||
"@codemirror/search": "6.5.8",
|
"@codemirror/search": "6.5.8",
|
||||||
"@codemirror/state": "6.4.1",
|
"@codemirror/state": "6.4.1",
|
||||||
"@codemirror/view": "6.35.0",
|
"@codemirror/view": "6.35.2",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.16.5",
|
"@formatjs/intl-datetimeformat": "6.16.6",
|
||||||
"@formatjs/intl-displaynames": "6.8.5",
|
"@formatjs/intl-displaynames": "6.8.6",
|
||||||
"@formatjs/intl-durationformat": "0.6.4",
|
"@formatjs/intl-durationformat": "0.6.5",
|
||||||
"@formatjs/intl-getcanonicallocales": "2.5.3",
|
"@formatjs/intl-getcanonicallocales": "2.5.3",
|
||||||
"@formatjs/intl-listformat": "7.7.5",
|
"@formatjs/intl-listformat": "7.7.6",
|
||||||
"@formatjs/intl-locale": "4.2.5",
|
"@formatjs/intl-locale": "4.2.6",
|
||||||
"@formatjs/intl-numberformat": "8.14.5",
|
"@formatjs/intl-numberformat": "8.14.6",
|
||||||
"@formatjs/intl-pluralrules": "5.3.5",
|
"@formatjs/intl-pluralrules": "5.3.6",
|
||||||
"@formatjs/intl-relativetimeformat": "11.4.5",
|
"@formatjs/intl-relativetimeformat": "11.4.6",
|
||||||
"@fullcalendar/core": "6.1.15",
|
"@fullcalendar/core": "6.1.15",
|
||||||
"@fullcalendar/daygrid": "6.1.15",
|
"@fullcalendar/daygrid": "6.1.15",
|
||||||
"@fullcalendar/interaction": "6.1.15",
|
"@fullcalendar/interaction": "6.1.15",
|
||||||
@@ -91,8 +91,8 @@
|
|||||||
"@polymer/polymer": "3.5.2",
|
"@polymer/polymer": "3.5.2",
|
||||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@vaadin/combo-box": "24.5.4",
|
"@vaadin/combo-box": "24.5.5",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.5.4",
|
"@vaadin/vaadin-themable-mixin": "24.5.5",
|
||||||
"@vibrant/color": "3.2.1-alpha.1",
|
"@vibrant/color": "3.2.1-alpha.1",
|
||||||
"@vibrant/core": "3.2.1-alpha.1",
|
"@vibrant/core": "3.2.1-alpha.1",
|
||||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||||
@@ -101,7 +101,7 @@
|
|||||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"barcode-detector": "2.3.1",
|
"barcode-detector": "2.3.1",
|
||||||
"chart.js": "4.4.6",
|
"chart.js": "4.4.7",
|
||||||
"color-name": "2.0.0",
|
"color-name": "2.0.0",
|
||||||
"comlink": "4.4.2",
|
"comlink": "4.4.2",
|
||||||
"core-js": "3.39.0",
|
"core-js": "3.39.0",
|
||||||
@@ -111,20 +111,20 @@
|
|||||||
"deep-clone-simple": "1.1.1",
|
"deep-clone-simple": "1.1.1",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"dialog-polyfill": "0.5.6",
|
"dialog-polyfill": "0.5.6",
|
||||||
"element-internals-polyfill": "1.3.11",
|
"element-internals-polyfill": "1.3.12",
|
||||||
"fuse.js": "7.0.0",
|
"fuse.js": "7.0.0",
|
||||||
"google-timezones-json": "1.2.0",
|
"google-timezones-json": "1.2.0",
|
||||||
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
"hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch",
|
||||||
"home-assistant-js-websocket": "9.4.0",
|
"home-assistant-js-websocket": "9.4.0",
|
||||||
"idb-keyval": "6.2.1",
|
"idb-keyval": "6.2.1",
|
||||||
"intl-messageformat": "10.7.7",
|
"intl-messageformat": "10.7.8",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"leaflet": "1.9.4",
|
"leaflet": "1.9.4",
|
||||||
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
"leaflet-draw": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch",
|
||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"lit-html": "2.8.0",
|
"lit-html": "2.8.0",
|
||||||
"luxon": "3.5.0",
|
"luxon": "3.5.0",
|
||||||
"marked": "15.0.2",
|
"marked": "15.0.3",
|
||||||
"memoize-one": "6.0.0",
|
"memoize-one": "6.0.0",
|
||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
"proxy-polyfill": "0.3.2",
|
"proxy-polyfill": "0.3.2",
|
||||||
@@ -162,23 +162,21 @@
|
|||||||
"@babel/preset-env": "7.26.0",
|
"@babel/preset-env": "7.26.0",
|
||||||
"@babel/preset-typescript": "7.26.0",
|
"@babel/preset-typescript": "7.26.0",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.17.0",
|
"@bundle-stats/plugin-webpack-filter": "4.17.0",
|
||||||
"@koa/cors": "5.0.0",
|
|
||||||
"@lokalise/node-api": "12.8.0",
|
"@lokalise/node-api": "12.8.0",
|
||||||
"@octokit/auth-oauth-device": "7.1.1",
|
"@octokit/auth-oauth-device": "7.1.1",
|
||||||
"@octokit/plugin-retry": "7.1.2",
|
"@octokit/plugin-retry": "7.1.2",
|
||||||
"@octokit/rest": "21.0.2",
|
"@octokit/rest": "21.0.2",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@rsdoctor/rspack-plugin": "0.4.11",
|
||||||
"@rsdoctor/rspack-plugin": "0.4.8",
|
"@rspack/cli": "1.1.5",
|
||||||
"@rspack/cli": "1.1.4",
|
"@rspack/core": "1.1.5",
|
||||||
"@rspack/core": "1.1.4",
|
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.19",
|
"@types/chromecast-caf-receiver": "6.0.20",
|
||||||
"@types/chromecast-caf-sender": "1.0.11",
|
"@types/chromecast-caf-sender": "1.0.11",
|
||||||
"@types/color-name": "2.0.0",
|
"@types/color-name": "2.0.0",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
"@types/html-minifier-terser": "7.0.2",
|
"@types/html-minifier-terser": "7.0.2",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/leaflet": "1.9.14",
|
"@types/leaflet": "1.9.15",
|
||||||
"@types/leaflet-draw": "1.0.11",
|
"@types/leaflet-draw": "1.0.11",
|
||||||
"@types/lodash.merge": "4.6.9",
|
"@types/lodash.merge": "4.6.9",
|
||||||
"@types/luxon": "3.4.2",
|
"@types/luxon": "3.4.2",
|
||||||
@@ -191,13 +189,12 @@
|
|||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "7.18.0",
|
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||||
"@typescript-eslint/parser": "7.18.0",
|
"@typescript-eslint/parser": "7.18.0",
|
||||||
"@vitest/coverage-v8": "2.1.5",
|
"@vitest/coverage-v8": "2.1.8",
|
||||||
"@web/dev-server": "0.1.38",
|
|
||||||
"babel-loader": "9.2.1",
|
"babel-loader": "9.2.1",
|
||||||
"babel-plugin-template-html-minifier": "4.1.0",
|
"babel-plugin-template-html-minifier": "4.1.0",
|
||||||
"browserslist-useragent-regexp": "4.1.3",
|
"browserslist-useragent-regexp": "4.1.3",
|
||||||
"del": "8.0.0",
|
"del": "8.0.0",
|
||||||
"eslint": "9.15.0",
|
"eslint": "9.16.0",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
"eslint-config-airbnb-typescript": "18.0.0",
|
"eslint-config-airbnb-typescript": "18.0.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
@@ -214,30 +211,29 @@
|
|||||||
"gulp-brotli": "3.0.0",
|
"gulp-brotli": "3.0.0",
|
||||||
"gulp-json-transform": "0.5.0",
|
"gulp-json-transform": "0.5.0",
|
||||||
"gulp-rename": "2.0.0",
|
"gulp-rename": "2.0.0",
|
||||||
"gulp-zopfli-green": "6.0.2",
|
|
||||||
"html-minifier-terser": "7.2.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "9.1.7",
|
"husky": "9.1.7",
|
||||||
|
"jsdom": "25.0.1",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "15.2.10",
|
"lint-staged": "15.2.10",
|
||||||
"lit-analyzer": "2.0.3",
|
"lit-analyzer": "2.0.3",
|
||||||
"lodash.merge": "4.6.2",
|
"lodash.merge": "4.6.2",
|
||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"magic-string": "0.30.13",
|
"magic-string": "0.30.14",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
"open": "10.1.0",
|
"open": "10.1.0",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
"prettier": "3.3.3",
|
"prettier": "3.4.2",
|
||||||
"rspack-manifest-plugin": "5.0.2",
|
"rspack-manifest-plugin": "5.0.2",
|
||||||
"serve-handler": "6.1.6",
|
"serve-handler": "6.1.6",
|
||||||
"sinon": "19.0.2",
|
"sinon": "19.0.2",
|
||||||
"systemjs": "6.15.1",
|
"systemjs": "6.15.1",
|
||||||
"tar": "7.4.3",
|
"tar": "7.4.3",
|
||||||
"terser-webpack-plugin": "5.3.10",
|
"terser-webpack-plugin": "5.3.10",
|
||||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.7.2",
|
"typescript": "5.7.2",
|
||||||
"vitest": "2.1.5",
|
"vitest": "2.1.8",
|
||||||
"webpack-stats-plugin": "1.1.3",
|
"webpack-stats-plugin": "1.1.3",
|
||||||
"webpackbar": "7.0.0",
|
"webpackbar": "7.0.0",
|
||||||
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
||||||
@@ -251,7 +247,7 @@
|
|||||||
"clean-css": "5.3.3",
|
"clean-css": "5.3.3",
|
||||||
"@lit/reactive-element": "1.6.3",
|
"@lit/reactive-element": "1.6.3",
|
||||||
"@fullcalendar/daygrid": "6.1.15",
|
"@fullcalendar/daygrid": "6.1.15",
|
||||||
"globals": "15.12.0"
|
"globals": "15.13.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.5.2"
|
"packageManager": "yarn@4.5.3"
|
||||||
}
|
}
|
||||||
|
@@ -43,12 +43,6 @@
|
|||||||
"description": "Group date-fns with dependent timezone package",
|
"description": "Group date-fns with dependent timezone package",
|
||||||
"groupName": "date-fns",
|
"groupName": "date-fns",
|
||||||
"matchPackageNames": ["date-fns", "date-fns-tz"]
|
"matchPackageNames": ["date-fns", "date-fns-tz"]
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Group and temporarily disable WDS packages",
|
|
||||||
"groupName": "Web Dev Server",
|
|
||||||
"enabled": false,
|
|
||||||
"matchPackageNames": ["@web/dev-server{/,}**"]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
70
script/develop_and_serve
Executable file
70
script/develop_and_serve
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# This script can be used to develop and test the frontend without having to
|
||||||
|
# link the build in a running core instance through the frontend/development_repo setting.
|
||||||
|
#
|
||||||
|
# WARNING:
|
||||||
|
# If you have an active login session in the frontend. The core that was used
|
||||||
|
# as a backend during the time of the login remains used until you logout again.
|
||||||
|
# So if you reuse the url hosting the frontend, you will need to logout before
|
||||||
|
# it will actually start using the core backend configured by this script.
|
||||||
|
#
|
||||||
|
# If you run this script without parameters, the frontend will be accessible under http://localhost:8124.
|
||||||
|
# And it will use the core instance running under http://localhost:8123 as a backend.
|
||||||
|
# Note that from a devcontainer, the frontend will be accessible under port 8124 on the host container.
|
||||||
|
# Inside the devcontainer it will be accessible under port 8123 instead.
|
||||||
|
# The core instance endpoint remains the same in both cases, as this is resolved from the browser.
|
||||||
|
#
|
||||||
|
# You can change the core instance the frontend connects to by passing the -c option.
|
||||||
|
# For example: script/develop_and_serve -c https://myhost.duckdns.org:8123
|
||||||
|
# This will also work for existing production core instances.
|
||||||
|
# It does not need to be a development version hosted locally.
|
||||||
|
#
|
||||||
|
# You can change the port the frontend is served on by passing the -p option.
|
||||||
|
# For example: script/develop_and_serve -p 8654
|
||||||
|
# Note that if you are running from a devcontainer, you will need to setup
|
||||||
|
# port forwarding as well if you want to access it from the container host.
|
||||||
|
|
||||||
|
# Stop on errors
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
# parse input paramters
|
||||||
|
if [ -n "$DEVCONTAINER" ]; then
|
||||||
|
frontendPort=8123
|
||||||
|
else
|
||||||
|
frontendPort=8124
|
||||||
|
fi
|
||||||
|
|
||||||
|
coreUrl=http://localhost:8123
|
||||||
|
|
||||||
|
while getopts p:c:h flag
|
||||||
|
do
|
||||||
|
case "${flag}" in
|
||||||
|
p) frontendPort=${OPTARG};;
|
||||||
|
c) coreUrl="${OPTARG}";;
|
||||||
|
h) echo Documentation can be found inside "$0" && exit 0;;
|
||||||
|
*) echo Documentation can be found inside "$0" && exit 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# display used settings
|
||||||
|
if [ -n "$DEVCONTAINER" ]; then
|
||||||
|
echo Frontend is available inside container as http://localhost:${frontendPort}
|
||||||
|
if [ 8123 -eq $frontendPort ]; then
|
||||||
|
echo Frontend is available on container host as http://localhost:8124
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo Frontend is hosted on http://localhost:${frontendPort}
|
||||||
|
fi
|
||||||
|
echo Core is used from ${coreUrl}
|
||||||
|
|
||||||
|
# build the frontend so it connects to the passed core
|
||||||
|
HASS_URL="$coreUrl" ./script/develop &
|
||||||
|
|
||||||
|
# serve the frontend
|
||||||
|
yarn dlx serve -l $frontendPort ./hass_frontend -s &
|
||||||
|
|
||||||
|
# keep the script running while serving
|
||||||
|
wait
|
@@ -30,17 +30,17 @@ type State = "loading" | "error" | "step";
|
|||||||
export class HaAuthFlow extends LitElement {
|
export class HaAuthFlow extends LitElement {
|
||||||
@property({ attribute: false }) public authProvider?: AuthProvider;
|
@property({ attribute: false }) public authProvider?: AuthProvider;
|
||||||
|
|
||||||
@property() public clientId?: string;
|
@property({ attribute: false }) public clientId?: string;
|
||||||
|
|
||||||
@property() public redirectUri?: string;
|
@property({ attribute: false }) public redirectUri?: string;
|
||||||
|
|
||||||
@property() public oauth2State?: string;
|
@property({ attribute: false }) public oauth2State?: string;
|
||||||
|
|
||||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||||
|
|
||||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||||
|
|
||||||
@property({ type: Boolean }) public initStoreToken = false;
|
@property({ attribute: false }) public initStoreToken = false;
|
||||||
|
|
||||||
@state() private _storeToken = false;
|
@state() private _storeToken = false;
|
||||||
|
|
||||||
|
@@ -21,13 +21,13 @@ const appNames = {
|
|||||||
|
|
||||||
@customElement("ha-authorize")
|
@customElement("ha-authorize")
|
||||||
export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||||
@property() public clientId?: string;
|
@property({ attribute: false }) public clientId?: string;
|
||||||
|
|
||||||
@property() public redirectUri?: string;
|
@property({ attribute: false }) public redirectUri?: string;
|
||||||
|
|
||||||
@property() public oauth2State?: string;
|
@property({ attribute: false }) public oauth2State?: string;
|
||||||
|
|
||||||
@property() public translationFragment = "page-authorize";
|
@property({ attribute: false }) public translationFragment = "page-authorize";
|
||||||
|
|
||||||
@state() private _authProvider?: AuthProvider;
|
@state() private _authProvider?: AuthProvider;
|
||||||
|
|
||||||
@@ -202,9 +202,9 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
<ha-language-picker
|
<ha-language-picker
|
||||||
.value=${this.language}
|
.value=${this.language}
|
||||||
.label=${""}
|
.label=${""}
|
||||||
nativeName
|
native-name
|
||||||
@value-changed=${this._languageChanged}
|
@value-changed=${this._languageChanged}
|
||||||
inlineArrow
|
inline-arrow
|
||||||
></ha-language-picker>
|
></ha-language-picker>
|
||||||
<a
|
<a
|
||||||
href="https://www.home-assistant.io/docs/authentication/"
|
href="https://www.home-assistant.io/docs/authentication/"
|
||||||
@@ -327,7 +327,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
this.language = language;
|
this.language = language;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
window.localStorage.setItem("selectedLanguage", JSON.stringify(language));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
import type { AuthData } from "home-assistant-js-websocket";
|
import type { AuthData } from "home-assistant-js-websocket";
|
||||||
import { extractSearchParam } from "../url/search-params";
|
import { extractSearchParam } from "../url/search-params";
|
||||||
|
|
||||||
const storage = window.localStorage || {};
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__tokenCache: {
|
__tokenCache: {
|
||||||
@@ -38,9 +36,15 @@ export function saveTokens(tokens: AuthData | null) {
|
|||||||
|
|
||||||
if (tokenCache.writeEnabled) {
|
if (tokenCache.writeEnabled) {
|
||||||
try {
|
try {
|
||||||
storage.hassTokens = JSON.stringify(tokens);
|
window.localStorage.setItem("hassTokens", JSON.stringify(tokens));
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// write failed, ignore it. Happens if storage is full or private mode.
|
// write failed, ignore it. Happens if storage is full or private mode.
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn(
|
||||||
|
"Failed to store tokens; Are you in private mode or is your storage full?"
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Error storing tokens:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,12 +55,11 @@ export function enableWrite() {
|
|||||||
saveTokens(tokenCache.tokens);
|
saveTokens(tokenCache.tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadTokens() {
|
export function loadTokens() {
|
||||||
if (tokenCache.tokens === undefined) {
|
if (tokenCache.tokens === undefined) {
|
||||||
try {
|
try {
|
||||||
// Delete the old token cache.
|
const tokens = window.localStorage.getItem("hassTokens");
|
||||||
delete storage.tokens;
|
|
||||||
const tokens = storage.hassTokens;
|
|
||||||
if (tokens) {
|
if (tokens) {
|
||||||
tokenCache.tokens = JSON.parse(tokens);
|
tokenCache.tokens = JSON.parse(tokens);
|
||||||
tokenCache.writeEnabled = true;
|
tokenCache.writeEnabled = true;
|
||||||
|
@@ -25,9 +25,11 @@ export const rgb2hex = (rgb: [number, number, number]): string =>
|
|||||||
// Copyright (c) 2011-2019, Gregor Aisch
|
// Copyright (c) 2011-2019, Gregor Aisch
|
||||||
|
|
||||||
// Constants for XYZ and LAB conversion
|
// Constants for XYZ and LAB conversion
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
const Xn = 0.95047;
|
const Xn = 0.95047;
|
||||||
const Yn = 1;
|
const Yn = 1;
|
||||||
const Zn = 1.08883;
|
const Zn = 1.08883;
|
||||||
|
/* eslint-enable @typescript-eslint/naming-convention */
|
||||||
|
|
||||||
const t0 = 0.137931034; // 4 / 29
|
const t0 = 0.137931034; // 4 / 29
|
||||||
const t1 = 0.206896552; // 6 / 29
|
const t1 = 0.206896552; // 6 / 29
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { DurationFormat } from "@formatjs/intl-durationformat";
|
|
||||||
import type { DurationInput } from "@formatjs/intl-durationformat/src/types";
|
import type { DurationInput } from "@formatjs/intl-durationformat/src/types";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import type { HaDurationData } from "../../components/ha-duration-input";
|
import type { HaDurationData } from "../../components/ha-duration-input";
|
||||||
@@ -49,7 +48,7 @@ export const formatNumericDuration = (
|
|||||||
|
|
||||||
const formatDurationLongMem = memoizeOne(
|
const formatDurationLongMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new DurationFormat(locale.language, {
|
new Intl.DurationFormat(locale.language, {
|
||||||
style: "long",
|
style: "long",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -61,7 +60,7 @@ export const formatDurationLong = (
|
|||||||
|
|
||||||
const formatDigitalDurationMem = memoizeOne(
|
const formatDigitalDurationMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new DurationFormat(locale.language, {
|
new Intl.DurationFormat(locale.language, {
|
||||||
style: "digital",
|
style: "digital",
|
||||||
hoursDisplay: "auto",
|
hoursDisplay: "auto",
|
||||||
})
|
})
|
||||||
@@ -72,13 +71,13 @@ export const formatDurationDigital = (
|
|||||||
duration: HaDurationData
|
duration: HaDurationData
|
||||||
) => formatDigitalDurationMem(locale).format(duration);
|
) => formatDigitalDurationMem(locale).format(duration);
|
||||||
|
|
||||||
export const DURATION_UNITS = ["ms", "s", "min", "h", "d"] as const;
|
export const DURATION_UNITS = ["min", "h", "d"] as const;
|
||||||
|
|
||||||
type DurationUnit = (typeof DURATION_UNITS)[number];
|
type DurationUnit = (typeof DURATION_UNITS)[number];
|
||||||
|
|
||||||
const formatDurationDayMem = memoizeOne(
|
const formatDurationDayMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new DurationFormat(locale.language, {
|
new Intl.DurationFormat(locale.language, {
|
||||||
style: "narrow",
|
style: "narrow",
|
||||||
daysDisplay: "always",
|
daysDisplay: "always",
|
||||||
})
|
})
|
||||||
@@ -86,7 +85,7 @@ const formatDurationDayMem = memoizeOne(
|
|||||||
|
|
||||||
const formatDurationHourMem = memoizeOne(
|
const formatDurationHourMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new DurationFormat(locale.language, {
|
new Intl.DurationFormat(locale.language, {
|
||||||
style: "narrow",
|
style: "narrow",
|
||||||
hoursDisplay: "always",
|
hoursDisplay: "always",
|
||||||
})
|
})
|
||||||
@@ -94,28 +93,12 @@ const formatDurationHourMem = memoizeOne(
|
|||||||
|
|
||||||
const formatDurationMinuteMem = memoizeOne(
|
const formatDurationMinuteMem = memoizeOne(
|
||||||
(locale: FrontendLocaleData) =>
|
(locale: FrontendLocaleData) =>
|
||||||
new DurationFormat(locale.language, {
|
new Intl.DurationFormat(locale.language, {
|
||||||
style: "narrow",
|
style: "narrow",
|
||||||
minutesDisplay: "always",
|
minutesDisplay: "always",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const formatDurationSecondMem = memoizeOne(
|
|
||||||
(locale: FrontendLocaleData) =>
|
|
||||||
new DurationFormat(locale.language, {
|
|
||||||
style: "narrow",
|
|
||||||
secondsDisplay: "always",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const formatDurationMillisecondMem = memoizeOne(
|
|
||||||
(locale: FrontendLocaleData) =>
|
|
||||||
new DurationFormat(locale.language, {
|
|
||||||
style: "narrow",
|
|
||||||
millisecondsDisplay: "always",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
export const formatDuration = (
|
export const formatDuration = (
|
||||||
locale: FrontendLocaleData,
|
locale: FrontendLocaleData,
|
||||||
duration: string,
|
duration: string,
|
||||||
@@ -155,22 +138,6 @@ export const formatDuration = (
|
|||||||
};
|
};
|
||||||
return formatDurationMinuteMem(locale).format(input);
|
return formatDurationMinuteMem(locale).format(input);
|
||||||
}
|
}
|
||||||
case "s": {
|
|
||||||
const seconds = Math.floor(value);
|
|
||||||
const milliseconds = Math.floor((value - seconds) * 1000);
|
|
||||||
const input: DurationInput = {
|
|
||||||
seconds,
|
|
||||||
milliseconds,
|
|
||||||
};
|
|
||||||
return formatDurationSecondMem(locale).format(input);
|
|
||||||
}
|
|
||||||
case "ms": {
|
|
||||||
const milliseconds = Math.floor(value);
|
|
||||||
const input: DurationInput = {
|
|
||||||
milliseconds,
|
|
||||||
};
|
|
||||||
return formatDurationMillisecondMem(locale).format(input);
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid duration unit");
|
throw new Error("Invalid duration unit");
|
||||||
}
|
}
|
||||||
|
@@ -29,7 +29,6 @@
|
|||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line
|
|
||||||
interface HASSDomEvents {}
|
interface HASSDomEvents {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,10 +14,8 @@ export default function scrollToTarget(element, target) {
|
|||||||
const top = 0;
|
const top = 0;
|
||||||
const scroller = target;
|
const scroller = target;
|
||||||
const easingFn = function easeOutQuad(t, b, c, d) {
|
const easingFn = function easeOutQuad(t, b, c, d) {
|
||||||
/* eslint-disable no-param-reassign, space-infix-ops, no-mixed-operators */
|
|
||||||
t /= d;
|
t /= d;
|
||||||
return -c * t * (t - 2) + b;
|
return -c * t * (t - 2) + b;
|
||||||
/* eslint-enable no-param-reassign, space-infix-ops, no-mixed-operators */
|
|
||||||
};
|
};
|
||||||
const animationId = Math.random();
|
const animationId = Math.random();
|
||||||
const duration = 200;
|
const duration = 200;
|
||||||
|
@@ -56,13 +56,15 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const domain = computeDomain(entityId);
|
const domain = computeDomain(entityId);
|
||||||
|
const is_number_domain =
|
||||||
|
domain === "counter" || domain === "number" || domain === "input_number";
|
||||||
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
||||||
if (
|
if (
|
||||||
isNumericFromAttributes(
|
isNumericFromAttributes(
|
||||||
attributes,
|
attributes,
|
||||||
domain === "sensor" ? sensorNumericDeviceClasses : []
|
domain === "sensor" ? sensorNumericDeviceClasses : []
|
||||||
)
|
) ||
|
||||||
|
is_number_domain
|
||||||
) {
|
) {
|
||||||
// state is duration
|
// state is duration
|
||||||
if (
|
if (
|
||||||
@@ -165,20 +167,6 @@ export const computeStateDisplayFromEntityAttributes = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `counter` `number` and `input_number` domains do not have a unit of measurement but should still use `formatNumber`
|
|
||||||
if (
|
|
||||||
domain === "counter" ||
|
|
||||||
domain === "number" ||
|
|
||||||
domain === "input_number"
|
|
||||||
) {
|
|
||||||
// Format as an integer if the value and step are integers
|
|
||||||
return formatNumber(
|
|
||||||
state,
|
|
||||||
locale,
|
|
||||||
getNumberFormatOptions({ state, attributes } as HassEntity, entity)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// state is a timestamp
|
// state is a timestamp
|
||||||
if (
|
if (
|
||||||
[
|
[
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { historyPromise } from "../state/url-sync-mixin";
|
|
||||||
import { fireEvent } from "./dom/fire_event";
|
import { fireEvent } from "./dom/fire_event";
|
||||||
import { mainWindow } from "./dom/get_main_window";
|
import { mainWindow } from "./dom/get_main_window";
|
||||||
|
|
||||||
@@ -17,11 +16,6 @@ export interface NavigateOptions {
|
|||||||
export const navigate = (path: string, options?: NavigateOptions) => {
|
export const navigate = (path: string, options?: NavigateOptions) => {
|
||||||
const replace = options?.replace || false;
|
const replace = options?.replace || false;
|
||||||
|
|
||||||
if (historyPromise) {
|
|
||||||
historyPromise.then(() => navigate(path, options));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (__DEMO__) {
|
if (__DEMO__) {
|
||||||
if (replace) {
|
if (replace) {
|
||||||
mainWindow.history.replaceState(
|
mainWindow.history.replaceState(
|
||||||
|
@@ -12,7 +12,7 @@ export type FormatEntityAttributeValueFunc = (
|
|||||||
attribute: string,
|
attribute: string,
|
||||||
value?: any
|
value?: any
|
||||||
) => string;
|
) => string;
|
||||||
export type formatEntityAttributeNameFunc = (
|
export type FormatEntityAttributeNameFunc = (
|
||||||
stateObj: HassEntity,
|
stateObj: HassEntity,
|
||||||
attribute: string
|
attribute: string
|
||||||
) => string;
|
) => string;
|
||||||
@@ -26,7 +26,7 @@ export const computeFormatFunctions = async (
|
|||||||
): Promise<{
|
): Promise<{
|
||||||
formatEntityState: FormatEntityStateFunc;
|
formatEntityState: FormatEntityStateFunc;
|
||||||
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
|
formatEntityAttributeValue: FormatEntityAttributeValueFunc;
|
||||||
formatEntityAttributeName: formatEntityAttributeNameFunc;
|
formatEntityAttributeName: FormatEntityAttributeNameFunc;
|
||||||
}> => {
|
}> => {
|
||||||
const { computeStateDisplay } = await import(
|
const { computeStateDisplay } = await import(
|
||||||
"../entity/compute_state_display"
|
"../entity/compute_state_display"
|
||||||
|
@@ -94,6 +94,7 @@ export const computeLocalize = async <Keys extends string = LocalizeKeys>(
|
|||||||
resources: Resources,
|
resources: Resources,
|
||||||
formats?: FormatsType
|
formats?: FormatsType
|
||||||
): Promise<LocalizeFunc<Keys>> => {
|
): Promise<LocalizeFunc<Keys>> => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const { IntlMessageFormat } = await import("intl-messageformat");
|
const { IntlMessageFormat } = await import("intl-messageformat");
|
||||||
await polyfillLocaleData(language);
|
await polyfillLocaleData(language);
|
||||||
|
|
||||||
|
@@ -53,9 +53,10 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Number }) public height?: number;
|
@property({ type: Number }) public height?: number;
|
||||||
|
|
||||||
@property({ type: Number }) public paddingYAxis = 0;
|
@property({ attribute: false, type: Number }) public paddingYAxis = 0;
|
||||||
|
|
||||||
@property({ type: Boolean }) public externalHidden = false;
|
@property({ attribute: "external-hidden", type: Boolean })
|
||||||
|
public externalHidden = false;
|
||||||
|
|
||||||
@state() private _chartHeight?: number;
|
@state() private _chartHeight?: number;
|
||||||
|
|
||||||
@@ -316,6 +317,7 @@ export class HaChartBase extends LitElement {
|
|||||||
.getContext("2d")!;
|
.getContext("2d")!;
|
||||||
this._loading = true;
|
this._loading = true;
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
|
const ChartConstructor = (await import("../../resources/chartjs")).Chart;
|
||||||
|
|
||||||
const computedStyles = getComputedStyle(this);
|
const computedStyles = getComputedStyle(this);
|
||||||
|
@@ -32,25 +32,28 @@ export class StateHistoryChartLine extends LitElement {
|
|||||||
|
|
||||||
@property() public identifier?: string;
|
@property() public identifier?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public showNames = true;
|
@property({ attribute: "show-names", type: Boolean })
|
||||||
|
public showNames = true;
|
||||||
|
|
||||||
@property({ type: Boolean }) public clickForMoreInfo = true;
|
@property({ attribute: "click-for-more-info", type: Boolean })
|
||||||
|
public clickForMoreInfo = true;
|
||||||
|
|
||||||
@property({ attribute: false }) public startTime!: Date;
|
@property({ attribute: false }) public startTime!: Date;
|
||||||
|
|
||||||
@property({ attribute: false }) public endTime!: Date;
|
@property({ attribute: false }) public endTime!: Date;
|
||||||
|
|
||||||
@property({ type: Number }) public paddingYAxis = 0;
|
@property({ attribute: false, type: Number }) public paddingYAxis = 0;
|
||||||
|
|
||||||
@property({ type: Number }) public chartIndex?;
|
@property({ attribute: false, type: Number }) public chartIndex?;
|
||||||
|
|
||||||
@property({ type: Boolean }) public logarithmicScale = false;
|
@property({ attribute: "logarithmic-scale", type: Boolean })
|
||||||
|
public logarithmicScale = false;
|
||||||
|
|
||||||
@property({ type: Number }) public minYAxis?: number;
|
@property({ attribute: false, type: Number }) public minYAxis?: number;
|
||||||
|
|
||||||
@property({ type: Number }) public maxYAxis?: number;
|
@property({ attribute: false, type: Number }) public maxYAxis?: number;
|
||||||
|
|
||||||
@property({ type: Boolean }) public fitYData = false;
|
@property({ attribute: "fit-y-data", type: Boolean }) public fitYData = false;
|
||||||
|
|
||||||
@state() private _chartData?: ChartData<"line">;
|
@state() private _chartData?: ChartData<"line">;
|
||||||
|
|
||||||
|
@@ -30,9 +30,10 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
|
|
||||||
@property() public identifier?: string;
|
@property() public identifier?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public showNames = true;
|
@property({ attribute: "show-names", type: Boolean }) public showNames = true;
|
||||||
|
|
||||||
@property({ type: Boolean }) public clickForMoreInfo = true;
|
@property({ attribute: "click-for-more-info", type: Boolean })
|
||||||
|
public clickForMoreInfo = true;
|
||||||
|
|
||||||
@property({ type: Boolean }) public chunked = false;
|
@property({ type: Boolean }) public chunked = false;
|
||||||
|
|
||||||
@@ -40,9 +41,9 @@ export class StateHistoryChartTimeline extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public endTime!: Date;
|
@property({ attribute: false }) public endTime!: Date;
|
||||||
|
|
||||||
@property({ type: Number }) public paddingYAxis = 0;
|
@property({ attribute: false, type: Number }) public paddingYAxis = 0;
|
||||||
|
|
||||||
@property({ type: Number }) public chartIndex?;
|
@property({ attribute: false, type: Number }) public chartIndex?;
|
||||||
|
|
||||||
@state() private _chartData?: ChartData<"timeline">;
|
@state() private _chartData?: ChartData<"timeline">;
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import {
|
import {
|
||||||
customElement,
|
customElement,
|
||||||
eventOptions,
|
eventOptions,
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
queryAll,
|
queryAll,
|
||||||
state,
|
state,
|
||||||
} from "lit/decorators";
|
} from "lit/decorators";
|
||||||
|
import type { RenderItemFunction } from "@lit-labs/virtualizer/virtualize";
|
||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||||
import type {
|
import type {
|
||||||
@@ -58,21 +59,24 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false;
|
@property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false;
|
||||||
|
|
||||||
@property({ type: Number }) public hoursToShow?: number;
|
@property({ attribute: false, type: Number }) public hoursToShow?: number;
|
||||||
|
|
||||||
@property({ type: Boolean }) public showNames = true;
|
@property({ attribute: "show-names", type: Boolean }) public showNames = true;
|
||||||
|
|
||||||
@property({ type: Boolean }) public clickForMoreInfo = true;
|
@property({ attribute: "click-for-more-info", type: Boolean })
|
||||||
|
public clickForMoreInfo = true;
|
||||||
|
|
||||||
@property({ type: Boolean }) public isLoadingData = false;
|
@property({ attribute: "is-loading-data", type: Boolean })
|
||||||
|
public isLoadingData = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public logarithmicScale = false;
|
@property({ attribute: "logarithmic-scale", type: Boolean })
|
||||||
|
public logarithmicScale = false;
|
||||||
|
|
||||||
@property({ type: Number }) public minYAxis?: number;
|
@property({ attribute: false, type: Number }) public minYAxis?: number;
|
||||||
|
|
||||||
@property({ type: Number }) public maxYAxis?: number;
|
@property({ attribute: false, type: Number }) public maxYAxis?: number;
|
||||||
|
|
||||||
@property({ type: Boolean }) public fitYData = false;
|
@property({ attribute: "fit-y-data", type: Boolean }) public fitYData = false;
|
||||||
|
|
||||||
private _computedStartTime!: Date;
|
private _computedStartTime!: Date;
|
||||||
|
|
||||||
@@ -122,6 +126,7 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
).concat(this.historyData.line)
|
).concat(this.historyData.line)
|
||||||
: this.historyData.line;
|
: this.historyData.line;
|
||||||
|
|
||||||
|
// eslint-disable-next-line lit/no-this-assign-in-render
|
||||||
this._chartCount = combinedItems.length;
|
this._chartCount = combinedItems.length;
|
||||||
|
|
||||||
return this.virtualize
|
return this.virtualize
|
||||||
@@ -139,12 +144,12 @@ export class StateHistoryCharts extends LitElement {
|
|||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderHistoryItem = (
|
private _renderHistoryItem: RenderItemFunction<
|
||||||
item: TimelineEntity[] | LineChartUnit,
|
TimelineEntity[] | LineChartUnit
|
||||||
index: number
|
> = (item, index) => {
|
||||||
) => {
|
|
||||||
if (!item || index === undefined) {
|
if (!item || index === undefined) {
|
||||||
return nothing;
|
// eslint-disable-next-line lit/prefer-nothing
|
||||||
|
return html``;
|
||||||
}
|
}
|
||||||
if (!Array.isArray(item)) {
|
if (!Array.isArray(item)) {
|
||||||
return html`<div class="entry-container">
|
return html`<div class="entry-container">
|
||||||
|
@@ -63,28 +63,28 @@ export class StatisticsChart extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public endTime?: Date;
|
@property({ attribute: false }) public endTime?: Date;
|
||||||
|
|
||||||
@property({ type: Array }) public statTypes: Array<StatisticType> = [
|
@property({ attribute: false, type: Array })
|
||||||
"sum",
|
public statTypes: Array<StatisticType> = ["sum", "min", "mean", "max"];
|
||||||
"min",
|
|
||||||
"mean",
|
|
||||||
"max",
|
|
||||||
];
|
|
||||||
|
|
||||||
@property() public chartType: ChartType = "line";
|
@property({ attribute: false }) public chartType: ChartType = "line";
|
||||||
|
|
||||||
@property({ type: Number }) public minYAxis?: number;
|
@property({ attribute: false, type: Number }) public minYAxis?: number;
|
||||||
|
|
||||||
@property({ type: Number }) public maxYAxis?: number;
|
@property({ attribute: false, type: Number }) public maxYAxis?: number;
|
||||||
|
|
||||||
@property({ type: Boolean }) public fitYData = false;
|
@property({ attribute: "fit-y-data", type: Boolean }) public fitYData = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public hideLegend = false;
|
@property({ attribute: "hide-legend", type: Boolean }) public hideLegend =
|
||||||
|
false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public logarithmicScale = false;
|
@property({ attribute: "logarithmic-scale", type: Boolean })
|
||||||
|
public logarithmicScale = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public isLoadingData = false;
|
@property({ attribute: "is-loading-data", type: Boolean })
|
||||||
|
public isLoadingData = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public clickForMoreInfo = true;
|
@property({ attribute: "click-for-more-info", type: Boolean })
|
||||||
|
public clickForMoreInfo = true;
|
||||||
|
|
||||||
@property() public period?: string;
|
@property() public period?: string;
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ export class StatisticsChart extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-chart-base
|
<ha-chart-base
|
||||||
externalHidden
|
external-hidden
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.data=${this._chartData}
|
.data=${this._chartData}
|
||||||
.extraData=${this._chartDatasetExtra}
|
.extraData=${this._chartDatasetExtra}
|
||||||
|
@@ -185,7 +185,7 @@ export class DialogDataTableSettings extends LitElement {
|
|||||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||||
}
|
}
|
||||||
|
|
||||||
_toggle(ev) {
|
private _toggle(ev) {
|
||||||
if (!this._params) {
|
if (!this._params) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -266,7 +266,7 @@ export class DialogDataTableSettings extends LitElement {
|
|||||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
||||||
}
|
}
|
||||||
|
|
||||||
_reset() {
|
private _reset() {
|
||||||
this._columnOrder = undefined;
|
this._columnOrder = undefined;
|
||||||
this._hiddenColumns = undefined;
|
this._hiddenColumns = undefined;
|
||||||
|
|
||||||
|
@@ -116,7 +116,7 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public clickable = false;
|
@property({ type: Boolean }) public clickable = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public hasFab = false;
|
@property({ attribute: "has-fab", type: Boolean }) public hasFab = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an extra row at the bottom of the data table
|
* Add an extra row at the bottom of the data table
|
||||||
@@ -127,24 +127,25 @@ export class HaDataTable extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "auto-height" })
|
@property({ type: Boolean, attribute: "auto-height" })
|
||||||
public autoHeight = false;
|
public autoHeight = false;
|
||||||
|
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property({ type: String }) public id = "id";
|
@property({ type: String }) public id = "id";
|
||||||
|
|
||||||
@property({ type: String }) public noDataText?: string;
|
@property({ attribute: false, type: String }) public noDataText?: string;
|
||||||
|
|
||||||
@property({ type: String }) public searchLabel?: string;
|
@property({ attribute: false, type: String }) public searchLabel?: string;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-label-float" })
|
@property({ type: Boolean, attribute: "no-label-float" })
|
||||||
public noLabelFloat? = false;
|
public noLabelFloat? = false;
|
||||||
|
|
||||||
@property({ type: String }) public filter = "";
|
@property({ type: String }) public filter = "";
|
||||||
|
|
||||||
@property() public groupColumn?: string;
|
@property({ attribute: false }) public groupColumn?: string;
|
||||||
|
|
||||||
@property({ attribute: false }) public groupOrder?: string[];
|
@property({ attribute: false }) public groupOrder?: string[];
|
||||||
|
|
||||||
@property() public sortColumn?: string;
|
@property({ attribute: false }) public sortColumn?: string;
|
||||||
|
|
||||||
@property() public sortDirection: SortingDirection = null;
|
@property({ attribute: false }) public sortDirection: SortingDirection = null;
|
||||||
|
|
||||||
@property({ attribute: false }) public initialCollapsedGroups?: string[];
|
@property({ attribute: false }) public initialCollapsedGroups?: string[];
|
||||||
|
|
||||||
|
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "../common/datetime/localize_date";
|
} from "../common/datetime/localize_date";
|
||||||
import { mainWindow } from "../common/dom/get_main_window";
|
import { mainWindow } from "../common/dom/get_main_window";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const CustomDateRangePicker = Vue.extend({
|
const CustomDateRangePicker = Vue.extend({
|
||||||
mixins: [DateRangePicker],
|
mixins: [DateRangePicker],
|
||||||
methods: {
|
methods: {
|
||||||
@@ -53,6 +54,7 @@ const CustomDateRangePicker = Vue.extend({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const Component = Vue.extend({
|
const Component = Vue.extend({
|
||||||
props: {
|
props: {
|
||||||
timePicker: {
|
timePicker: {
|
||||||
@@ -154,6 +156,7 @@ const Component = Vue.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Assertion corrects HTMLElement type from package
|
// Assertion corrects HTMLElement type from package
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
const WrappedElement = wrap(
|
const WrappedElement = wrap(
|
||||||
Vue,
|
Vue,
|
||||||
Component
|
Component
|
||||||
|
@@ -24,7 +24,7 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public deviceId?: string;
|
@property({ attribute: false }) public deviceId?: string;
|
||||||
|
|
||||||
@property({ type: Object }) public value?: T;
|
@property({ type: Object }) public value?: T;
|
||||||
|
|
||||||
|
@@ -75,7 +75,7 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public entityFilter?: HaEntityPickerEntityFilterFunc;
|
public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||||
|
|
||||||
@property({ type: Array }) public createDomains?: string[];
|
@property({ attribute: false, type: Array }) public createDomains?: string[];
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
|
@@ -13,7 +13,7 @@ export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
|||||||
class HaEntityAttributePicker extends LitElement {
|
class HaEntityAttributePicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public entityId?: string;
|
@property({ attribute: false }) public entityId?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of attributes to be hidden.
|
* List of attributes to be hidden.
|
||||||
@@ -23,6 +23,7 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
@property({ type: Array, attribute: "hide-attributes" })
|
@property({ type: Array, attribute: "hide-attributes" })
|
||||||
public hideAttributes?: string[];
|
public hideAttributes?: string[];
|
||||||
|
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property({ type: Boolean }) public autofocus = false;
|
@property({ type: Boolean }) public autofocus = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
@@ -34,6 +34,7 @@ const CREATE_ID = "___create-new-entity___";
|
|||||||
export class HaEntityPicker extends LitElement {
|
export class HaEntityPicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property({ type: Boolean }) public autofocus = false;
|
@property({ type: Boolean }) public autofocus = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
@@ -49,7 +50,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
|
|
||||||
@property() public helper?: string;
|
@property() public helper?: string;
|
||||||
|
|
||||||
@property({ type: Array }) public createDomains?: string[];
|
@property({ attribute: false, type: Array }) public createDomains?: string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show entities from specific domains.
|
* Show entities from specific domains.
|
||||||
@@ -102,7 +103,8 @@ export class HaEntityPicker extends LitElement {
|
|||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public entityFilter?: HaEntityPickerEntityFilterFunc;
|
public entityFilter?: HaEntityPickerEntityFilterFunc;
|
||||||
|
|
||||||
@property({ type: Boolean }) public hideClearIcon = false;
|
@property({ attribute: "hide-clear-icon", type: Boolean })
|
||||||
|
public hideClearIcon = false;
|
||||||
|
|
||||||
@property({ attribute: "item-label-path" }) public itemLabelPath =
|
@property({ attribute: "item-label-path" }) public itemLabelPath =
|
||||||
"friendly_name";
|
"friendly_name";
|
||||||
|
@@ -79,6 +79,7 @@ class HaEntityStatePicker extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public entityId?: string;
|
@property({ attribute: false }) public entityId?: string;
|
||||||
|
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property({ type: Boolean }) public autofocus = false;
|
@property({ type: Boolean }) public autofocus = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
@@ -14,12 +14,13 @@ export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
|||||||
class HaEntityStatePicker extends LitElement {
|
class HaEntityStatePicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public entityId?: string;
|
@property({ attribute: false }) public entityId?: string;
|
||||||
|
|
||||||
@property() public attribute?: string;
|
@property() public attribute?: string;
|
||||||
|
|
||||||
@property({ attribute: false }) public extraOptions?: any[];
|
@property({ attribute: false }) public extraOptions?: any[];
|
||||||
|
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property({ type: Boolean }) public autofocus = false;
|
@property({ type: Boolean }) public autofocus = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
@@ -55,7 +55,7 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
|
|
||||||
@property() public image?: string;
|
@property() public image?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public showName = false;
|
@property({ attribute: "show-name", type: Boolean }) public showName = false;
|
||||||
|
|
||||||
@state() private _timerTimeRemaining?: number;
|
@state() private _timerTimeRemaining?: number;
|
||||||
|
|
||||||
@@ -66,13 +66,13 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._connected = true;
|
this._connected = true;
|
||||||
this.startInterval(this.state);
|
this._startInterval(this.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._connected = false;
|
this._connected = false;
|
||||||
this.clearInterval();
|
this._clearInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@@ -151,7 +151,7 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
|
|
||||||
if (this._connected && changedProperties.has("state")) {
|
if (this._connected && changedProperties.has("state")) {
|
||||||
this.startInterval(this.state);
|
this._startInterval(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,28 +237,28 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
return entityState.attributes.unit_of_measurement || null;
|
return entityState.attributes.unit_of_measurement || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearInterval() {
|
private _clearInterval() {
|
||||||
if (this._updateRemaining) {
|
if (this._updateRemaining) {
|
||||||
clearInterval(this._updateRemaining);
|
clearInterval(this._updateRemaining);
|
||||||
this._updateRemaining = undefined;
|
this._updateRemaining = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private startInterval(stateObj) {
|
private _startInterval(stateObj) {
|
||||||
this.clearInterval();
|
this._clearInterval();
|
||||||
if (stateObj && computeStateDomain(stateObj) === "timer") {
|
if (stateObj && computeStateDomain(stateObj) === "timer") {
|
||||||
this.calculateTimerRemaining(stateObj);
|
this._calculateTimerRemaining(stateObj);
|
||||||
|
|
||||||
if (stateObj.state === "active") {
|
if (stateObj.state === "active") {
|
||||||
this._updateRemaining = window.setInterval(
|
this._updateRemaining = window.setInterval(
|
||||||
() => this.calculateTimerRemaining(this.state),
|
() => this._calculateTimerRemaining(this.state),
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateTimerRemaining(stateObj) {
|
private _calculateTimerRemaining(stateObj) {
|
||||||
this._timerTimeRemaining = timerTimeRemaining(stateObj);
|
this._timerTimeRemaining = timerTimeRemaining(stateObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -39,7 +39,8 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "allow-custom-entity" })
|
@property({ type: Boolean, attribute: "allow-custom-entity" })
|
||||||
public allowCustomEntity;
|
public allowCustomEntity;
|
||||||
|
|
||||||
@property({ type: Array }) public statisticIds?: StatisticsMetaData[];
|
@property({ attribute: false, type: Array })
|
||||||
|
public statisticIds?: StatisticsMetaData[];
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@@ -84,7 +85,8 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
@property({ type: Array, attribute: "exclude-statistics" })
|
@property({ type: Array, attribute: "exclude-statistics" })
|
||||||
public excludeStatistics?: string[];
|
public excludeStatistics?: string[];
|
||||||
|
|
||||||
@property() public helpMissingEntityUrl = "/more-info/statistics/";
|
@property({ attribute: false }) public helpMissingEntityUrl =
|
||||||
|
"/more-info/statistics/";
|
||||||
|
|
||||||
@state() private _opened?: boolean;
|
@state() private _opened?: boolean;
|
||||||
|
|
||||||
|
@@ -12,7 +12,7 @@ class HaStatisticsPicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Array }) public value?: string[];
|
@property({ type: Array }) public value?: string[];
|
||||||
|
|
||||||
@property({ type: Array }) public statisticIds?: string[];
|
@property({ attribute: false, type: Array }) public statisticIds?: string[];
|
||||||
|
|
||||||
@property({ attribute: "statistic-types" })
|
@property({ attribute: "statistic-types" })
|
||||||
public statisticTypes?: "mean" | "sum";
|
public statisticTypes?: "mean" | "sum";
|
||||||
|
@@ -22,9 +22,9 @@ export class StateBadge extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
@property() public overrideIcon?: string;
|
@property({ attribute: false }) public overrideIcon?: string;
|
||||||
|
|
||||||
@property() public overrideImage?: string;
|
@property({ attribute: false }) public overrideImage?: string;
|
||||||
|
|
||||||
// Cannot be a boolean attribute because undefined is treated different than
|
// Cannot be a boolean attribute because undefined is treated different than
|
||||||
// false. When it is undefined, state is still colored for light entities.
|
// false. When it is undefined, state is still colored for light entities.
|
||||||
|
@@ -14,7 +14,7 @@ class StateInfo extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
@property({ type: Boolean }) public inDialog = false;
|
@property({ attribute: "in-dialog", type: Boolean }) public inDialog = false;
|
||||||
|
|
||||||
@property() public color?: string;
|
@property() public color?: string;
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class StateInfo extends LitElement {
|
|||||||
.color=${this.color}
|
.color=${this.color}
|
||||||
></state-badge>
|
></state-badge>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="name" .title=${name} .inDialog=${this.inDialog}>
|
<div class="name ${this.inDialog ? "in-dialog" : ""}" .title=${name}>
|
||||||
${name}
|
${name}
|
||||||
</div>
|
</div>
|
||||||
${this.inDialog
|
${this.inDialog
|
||||||
@@ -108,7 +108,7 @@ class StateInfo extends LitElement {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name[inDialog],
|
.name.in-dialog,
|
||||||
:host([secondary-line]) .name {
|
:host([secondary-line]) .name {
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ declare global {
|
|||||||
|
|
||||||
@customElement("ha-alert")
|
@customElement("ha-alert")
|
||||||
class HaAlert extends LitElement {
|
class HaAlert extends LitElement {
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property() public title = "";
|
@property() public title = "";
|
||||||
|
|
||||||
@property({ attribute: "alert-type" }) public alertType:
|
@property({ attribute: "alert-type" }) public alertType:
|
||||||
@@ -63,7 +64,7 @@ class HaAlert extends LitElement {
|
|||||||
<slot name="action">
|
<slot name="action">
|
||||||
${this.dismissable
|
${this.dismissable
|
||||||
? html`<ha-icon-button
|
? html`<ha-icon-button
|
||||||
@click=${this._dismiss_clicked}
|
@click=${this._dismissClicked}
|
||||||
label="Dismiss alert"
|
label="Dismiss alert"
|
||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
></ha-icon-button>`
|
></ha-icon-button>`
|
||||||
@@ -75,7 +76,7 @@ class HaAlert extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dismiss_clicked() {
|
private _dismissClicked() {
|
||||||
fireEvent(this, "alert-dismissed-clicked");
|
fireEvent(this, "alert-dismissed-clicked");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -183,7 +183,7 @@ export class HaAnsiToHtml extends LitElement {
|
|||||||
|
|
||||||
/* eslint-disable no-cond-assign */
|
/* eslint-disable no-cond-assign */
|
||||||
let match;
|
let match;
|
||||||
// eslint-disable-next-line
|
|
||||||
while ((match = re.exec(line)) !== null) {
|
while ((match = re.exec(line)) !== null) {
|
||||||
const j = match!.index;
|
const j = match!.index;
|
||||||
const substring = line.substring(i, j);
|
const substring = line.substring(i, j);
|
||||||
|
639
src/components/ha-assist-chat.ts
Normal file
639
src/components/ha-assist-chat.ts
Normal file
@@ -0,0 +1,639 @@
|
|||||||
|
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||||
|
import { css, LitElement, html, nothing } from "lit";
|
||||||
|
import { mdiAlertCircle, mdiMicrophone, mdiSend } from "@mdi/js";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import {
|
||||||
|
runAssistPipeline,
|
||||||
|
type AssistPipeline,
|
||||||
|
} from "../data/assist_pipeline";
|
||||||
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
|
import { ConversationEntityFeature } from "../data/conversation";
|
||||||
|
import { AudioRecorder } from "../util/audio-recorder";
|
||||||
|
import "./ha-alert";
|
||||||
|
import "./ha-textfield";
|
||||||
|
import type { HaTextField } from "./ha-textfield";
|
||||||
|
import { documentationUrl } from "../util/documentation-url";
|
||||||
|
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
||||||
|
|
||||||
|
interface AssistMessage {
|
||||||
|
who: string;
|
||||||
|
text?: string | TemplateResult;
|
||||||
|
error?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-assist-chat")
|
||||||
|
export class HaAssistChat extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public pipeline?: AssistPipeline;
|
||||||
|
|
||||||
|
@property({ type: Boolean, attribute: false })
|
||||||
|
public startListening?: boolean;
|
||||||
|
|
||||||
|
@query("#message-input") private _messageInput!: HaTextField;
|
||||||
|
|
||||||
|
@query("#scroll-container") private _scrollContainer!: HTMLDivElement;
|
||||||
|
|
||||||
|
@state() private _conversation: AssistMessage[] = [];
|
||||||
|
|
||||||
|
@state() private _showSendButton = false;
|
||||||
|
|
||||||
|
@state() private _processing = false;
|
||||||
|
|
||||||
|
private _conversationId: string | null = null;
|
||||||
|
|
||||||
|
private _audioRecorder?: AudioRecorder;
|
||||||
|
|
||||||
|
private _audioBuffer?: Int16Array[];
|
||||||
|
|
||||||
|
private _audio?: HTMLAudioElement;
|
||||||
|
|
||||||
|
private _stt_binary_handler_id?: number | null;
|
||||||
|
|
||||||
|
protected willUpdate(changedProperties: PropertyValues): void {
|
||||||
|
if (!this.hasUpdated || changedProperties.has("pipeline")) {
|
||||||
|
this._conversation = [
|
||||||
|
{
|
||||||
|
who: "hass",
|
||||||
|
text: this.hass.localize("ui.dialogs.voice_command.how_can_i_help"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||||
|
super.firstUpdated(changedProperties);
|
||||||
|
if (
|
||||||
|
this.startListening &&
|
||||||
|
this.pipeline &&
|
||||||
|
this.pipeline.stt_engine &&
|
||||||
|
AudioRecorder.isSupported
|
||||||
|
) {
|
||||||
|
this._toggleListening();
|
||||||
|
}
|
||||||
|
setTimeout(() => this._messageInput.focus(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
if (changedProps.has("_conversation")) {
|
||||||
|
this._scrollMessagesBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
this._audioRecorder?.close();
|
||||||
|
this._audioRecorder = undefined;
|
||||||
|
this._audio?.pause();
|
||||||
|
this._conversation = [];
|
||||||
|
this._conversationId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const controlHA = !this.pipeline
|
||||||
|
? false
|
||||||
|
: this.pipeline.prefer_local_intents ||
|
||||||
|
(this.hass.states[this.pipeline.conversation_engine]
|
||||||
|
? supportsFeature(
|
||||||
|
this.hass.states[this.pipeline.conversation_engine],
|
||||||
|
ConversationEntityFeature.CONTROL
|
||||||
|
)
|
||||||
|
: true);
|
||||||
|
const supportsMicrophone = AudioRecorder.isSupported;
|
||||||
|
const supportsSTT = this.pipeline?.stt_engine;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${controlHA
|
||||||
|
? nothing
|
||||||
|
: html`
|
||||||
|
<ha-alert>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.dialogs.voice_command.conversation_no_control"
|
||||||
|
)}
|
||||||
|
</ha-alert>
|
||||||
|
`}
|
||||||
|
<div class="messages">
|
||||||
|
<div class="messages-container" id="scroll-container">
|
||||||
|
${this._conversation!.map(
|
||||||
|
// New lines matter for messages
|
||||||
|
// prettier-ignore
|
||||||
|
(message) => html`
|
||||||
|
<div class="message ${classMap({ error: !!message.error, [message.who]: true })}">${message.text}</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="input" slot="primaryAction">
|
||||||
|
<ha-textfield
|
||||||
|
id="message-input"
|
||||||
|
@keyup=${this._handleKeyUp}
|
||||||
|
@input=${this._handleInput}
|
||||||
|
.label=${this.hass.localize(`ui.dialogs.voice_command.input_label`)}
|
||||||
|
.iconTrailing=${true}
|
||||||
|
>
|
||||||
|
<div slot="trailingIcon">
|
||||||
|
${this._showSendButton || !supportsSTT
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
class="listening-icon"
|
||||||
|
.path=${mdiSend}
|
||||||
|
@click=${this._handleSendMessage}
|
||||||
|
.disabled=${this._processing}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.voice_command.send_text"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
${this._audioRecorder?.active
|
||||||
|
? html`
|
||||||
|
<div class="bouncer">
|
||||||
|
<div class="double-bounce1"></div>
|
||||||
|
<div class="double-bounce2"></div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
|
<div class="listening-icon">
|
||||||
|
<ha-icon-button
|
||||||
|
.path=${mdiMicrophone}
|
||||||
|
@click=${this._handleListeningButton}
|
||||||
|
.disabled=${this._processing}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.dialogs.voice_command.start_listening"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
|
${!supportsMicrophone
|
||||||
|
? html`
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${mdiAlertCircle}
|
||||||
|
class="unsupported"
|
||||||
|
></ha-svg-icon>
|
||||||
|
`
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</ha-textfield>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _scrollMessagesBottom() {
|
||||||
|
const scrollContainer = this._scrollContainer;
|
||||||
|
if (!scrollContainer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scrollContainer.scrollTo(0, scrollContainer.scrollHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleKeyUp(ev: KeyboardEvent) {
|
||||||
|
const input = ev.target as HaTextField;
|
||||||
|
if (!this._processing && ev.key === "Enter" && input.value) {
|
||||||
|
this._processText(input.value);
|
||||||
|
input.value = "";
|
||||||
|
this._showSendButton = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleInput(ev: InputEvent) {
|
||||||
|
const value = (ev.target as HaTextField).value;
|
||||||
|
if (value && !this._showSendButton) {
|
||||||
|
this._showSendButton = true;
|
||||||
|
} else if (!value && this._showSendButton) {
|
||||||
|
this._showSendButton = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSendMessage() {
|
||||||
|
if (this._messageInput.value) {
|
||||||
|
this._processText(this._messageInput.value.trim());
|
||||||
|
this._messageInput.value = "";
|
||||||
|
this._showSendButton = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleListeningButton(ev) {
|
||||||
|
ev.stopPropagation();
|
||||||
|
ev.preventDefault();
|
||||||
|
this._toggleListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _toggleListening() {
|
||||||
|
const supportsMicrophone = AudioRecorder.isSupported;
|
||||||
|
if (!supportsMicrophone) {
|
||||||
|
this._showNotSupportedMessage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._audioRecorder?.active) {
|
||||||
|
this._startListening();
|
||||||
|
} else {
|
||||||
|
this._stopListening();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addMessage(message: AssistMessage) {
|
||||||
|
this._conversation = [...this._conversation!, message];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _showNotSupportedMessage() {
|
||||||
|
this._addMessage({
|
||||||
|
who: "hass",
|
||||||
|
text:
|
||||||
|
// New lines matter for messages
|
||||||
|
// prettier-ignore
|
||||||
|
html`${this.hass.localize(
|
||||||
|
"ui.dialogs.voice_command.not_supported_microphone_browser"
|
||||||
|
)}
|
||||||
|
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.dialogs.voice_command.not_supported_microphone_documentation",
|
||||||
|
{
|
||||||
|
documentation_link: html`<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href=${documentationUrl(
|
||||||
|
this.hass,
|
||||||
|
"/docs/configuration/securing/#remote-access"
|
||||||
|
)}
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.dialogs.voice_command.not_supported_microphone_documentation_link"
|
||||||
|
)}</a>`,
|
||||||
|
}
|
||||||
|
)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _startListening() {
|
||||||
|
this._processing = true;
|
||||||
|
this._audio?.pause();
|
||||||
|
if (!this._audioRecorder) {
|
||||||
|
this._audioRecorder = new AudioRecorder((audio) => {
|
||||||
|
if (this._audioBuffer) {
|
||||||
|
this._audioBuffer.push(audio);
|
||||||
|
} else {
|
||||||
|
this._sendAudioChunk(audio);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._stt_binary_handler_id = undefined;
|
||||||
|
this._audioBuffer = [];
|
||||||
|
const userMessage: AssistMessage = {
|
||||||
|
who: "user",
|
||||||
|
text: "…",
|
||||||
|
};
|
||||||
|
await this._audioRecorder.start();
|
||||||
|
|
||||||
|
this._addMessage(userMessage);
|
||||||
|
this.requestUpdate("_audioRecorder");
|
||||||
|
|
||||||
|
const hassMessage: AssistMessage = {
|
||||||
|
who: "hass",
|
||||||
|
text: "…",
|
||||||
|
};
|
||||||
|
// To make sure the answer is placed at the right user text, we add it before we process it
|
||||||
|
try {
|
||||||
|
const unsub = await runAssistPipeline(
|
||||||
|
this.hass,
|
||||||
|
(event) => {
|
||||||
|
if (event.type === "run-start") {
|
||||||
|
this._stt_binary_handler_id =
|
||||||
|
event.data.runner_data.stt_binary_handler_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we start STT stage, the WS has a binary handler
|
||||||
|
if (event.type === "stt-start" && this._audioBuffer) {
|
||||||
|
// Send the buffer over the WS to the STT engine.
|
||||||
|
for (const buffer of this._audioBuffer) {
|
||||||
|
this._sendAudioChunk(buffer);
|
||||||
|
}
|
||||||
|
this._audioBuffer = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop recording if the server is done with STT stage
|
||||||
|
if (event.type === "stt-end") {
|
||||||
|
this._stt_binary_handler_id = undefined;
|
||||||
|
this._stopListening();
|
||||||
|
userMessage.text = event.data.stt_output.text;
|
||||||
|
this.requestUpdate("_conversation");
|
||||||
|
// To make sure the answer is placed at the right user text, we add it before we process it
|
||||||
|
this._addMessage(hassMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "intent-end") {
|
||||||
|
this._conversationId = event.data.intent_output.conversation_id;
|
||||||
|
const plain = event.data.intent_output.response.speech?.plain;
|
||||||
|
if (plain) {
|
||||||
|
hassMessage.text = plain.speech;
|
||||||
|
}
|
||||||
|
this.requestUpdate("_conversation");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "tts-end") {
|
||||||
|
const url = event.data.tts_output.url;
|
||||||
|
this._audio = new Audio(url);
|
||||||
|
this._audio.play();
|
||||||
|
this._audio.addEventListener("ended", this._unloadAudio);
|
||||||
|
this._audio.addEventListener("pause", this._unloadAudio);
|
||||||
|
this._audio.addEventListener("canplaythrough", this._playAudio);
|
||||||
|
this._audio.addEventListener("error", this._audioError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "run-end") {
|
||||||
|
this._stt_binary_handler_id = undefined;
|
||||||
|
unsub();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "error") {
|
||||||
|
this._stt_binary_handler_id = undefined;
|
||||||
|
if (userMessage.text === "…") {
|
||||||
|
userMessage.text = event.data.message;
|
||||||
|
userMessage.error = true;
|
||||||
|
} else {
|
||||||
|
hassMessage.text = event.data.message;
|
||||||
|
hassMessage.error = true;
|
||||||
|
}
|
||||||
|
this._stopListening();
|
||||||
|
this.requestUpdate("_conversation");
|
||||||
|
unsub();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start_stage: "stt",
|
||||||
|
end_stage: this.pipeline?.tts_engine ? "tts" : "intent",
|
||||||
|
input: { sample_rate: this._audioRecorder.sampleRate! },
|
||||||
|
pipeline: this.pipeline?.id,
|
||||||
|
conversation_id: this._conversationId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (err: any) {
|
||||||
|
await showAlertDialog(this, {
|
||||||
|
title: "Error starting pipeline",
|
||||||
|
text: err.message || err,
|
||||||
|
});
|
||||||
|
this._stopListening();
|
||||||
|
} finally {
|
||||||
|
this._processing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _stopListening() {
|
||||||
|
this._audioRecorder?.stop();
|
||||||
|
this.requestUpdate("_audioRecorder");
|
||||||
|
// We're currently STTing, so finish audio
|
||||||
|
if (this._stt_binary_handler_id) {
|
||||||
|
if (this._audioBuffer) {
|
||||||
|
for (const chunk of this._audioBuffer) {
|
||||||
|
this._sendAudioChunk(chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Send empty message to indicate we're done streaming.
|
||||||
|
this._sendAudioChunk(new Int16Array());
|
||||||
|
this._stt_binary_handler_id = undefined;
|
||||||
|
}
|
||||||
|
this._audioBuffer = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _sendAudioChunk(chunk: Int16Array) {
|
||||||
|
this.hass.connection.socket!.binaryType = "arraybuffer";
|
||||||
|
|
||||||
|
// eslint-disable-next-line eqeqeq
|
||||||
|
if (this._stt_binary_handler_id == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Turn into 8 bit so we can prefix our handler ID.
|
||||||
|
const data = new Uint8Array(1 + chunk.length * 2);
|
||||||
|
data[0] = this._stt_binary_handler_id;
|
||||||
|
data.set(new Uint8Array(chunk.buffer), 1);
|
||||||
|
|
||||||
|
this.hass.connection.socket!.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _playAudio = () => {
|
||||||
|
this._audio?.play();
|
||||||
|
};
|
||||||
|
|
||||||
|
private _audioError = () => {
|
||||||
|
showAlertDialog(this, { title: "Error playing audio." });
|
||||||
|
this._audio?.removeAttribute("src");
|
||||||
|
};
|
||||||
|
|
||||||
|
private _unloadAudio = () => {
|
||||||
|
this._audio?.removeAttribute("src");
|
||||||
|
this._audio = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
private async _processText(text: string) {
|
||||||
|
this._processing = true;
|
||||||
|
this._audio?.pause();
|
||||||
|
this._addMessage({ who: "user", text });
|
||||||
|
const message: AssistMessage = {
|
||||||
|
who: "hass",
|
||||||
|
text: "…",
|
||||||
|
};
|
||||||
|
// To make sure the answer is placed at the right user text, we add it before we process it
|
||||||
|
this._addMessage(message);
|
||||||
|
try {
|
||||||
|
const unsub = await runAssistPipeline(
|
||||||
|
this.hass,
|
||||||
|
(event) => {
|
||||||
|
if (event.type === "intent-end") {
|
||||||
|
this._conversationId = event.data.intent_output.conversation_id;
|
||||||
|
const plain = event.data.intent_output.response.speech?.plain;
|
||||||
|
if (plain) {
|
||||||
|
message.text = plain.speech;
|
||||||
|
}
|
||||||
|
this.requestUpdate("_conversation");
|
||||||
|
unsub();
|
||||||
|
}
|
||||||
|
if (event.type === "error") {
|
||||||
|
message.text = event.data.message;
|
||||||
|
message.error = true;
|
||||||
|
this.requestUpdate("_conversation");
|
||||||
|
unsub();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start_stage: "intent",
|
||||||
|
input: { text },
|
||||||
|
end_stage: "intent",
|
||||||
|
pipeline: this.pipeline?.id,
|
||||||
|
conversation_id: this._conversationId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
message.text = this.hass.localize("ui.dialogs.voice_command.error");
|
||||||
|
message.error = true;
|
||||||
|
this.requestUpdate("_conversation");
|
||||||
|
} finally {
|
||||||
|
this._processing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: var(--ha-assist-chat-min-height, 415px);
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
margin: 0 24px 16px;
|
||||||
|
}
|
||||||
|
.messages {
|
||||||
|
flex: 1;
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.messages-container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0px;
|
||||||
|
left: 0px;
|
||||||
|
padding: 24px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
white-space: pre-line;
|
||||||
|
font-size: 18px;
|
||||||
|
clear: both;
|
||||||
|
margin: 8px 0;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
|
.message {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.message p:not(:last-child) {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.user {
|
||||||
|
margin-left: 24px;
|
||||||
|
margin-inline-start: 24px;
|
||||||
|
margin-inline-end: initial;
|
||||||
|
float: var(--float-end);
|
||||||
|
text-align: right;
|
||||||
|
border-bottom-right-radius: 0px;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.hass {
|
||||||
|
margin-right: 24px;
|
||||||
|
margin-inline-end: 24px;
|
||||||
|
margin-inline-start: initial;
|
||||||
|
float: var(--float-start);
|
||||||
|
border-bottom-left-radius: 0px;
|
||||||
|
background-color: var(--secondary-background-color);
|
||||||
|
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.user a {
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.hass a {
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message.error {
|
||||||
|
background-color: var(--error-color);
|
||||||
|
color: var(--text-primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bouncer {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.double-bounce1,
|
||||||
|
.double-bounce2 {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
opacity: 0.2;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
-webkit-animation: sk-bounce 2s infinite ease-in-out;
|
||||||
|
animation: sk-bounce 2s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
.double-bounce2 {
|
||||||
|
-webkit-animation-delay: -1s;
|
||||||
|
animation-delay: -1s;
|
||||||
|
}
|
||||||
|
@-webkit-keyframes sk-bounce {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
-webkit-transform: scale(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes sk-bounce {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(0);
|
||||||
|
-webkit-transform: scale(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1);
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.listening-icon {
|
||||||
|
position: relative;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
margin-right: -24px;
|
||||||
|
margin-inline-end: -24px;
|
||||||
|
margin-inline-start: initial;
|
||||||
|
direction: var(--direction);
|
||||||
|
transform: scaleX(var(--scale-direction));
|
||||||
|
}
|
||||||
|
|
||||||
|
.listening-icon[active] {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.unsupported {
|
||||||
|
color: var(--error-color);
|
||||||
|
position: absolute;
|
||||||
|
--mdc-icon-size: 16px;
|
||||||
|
right: 5px;
|
||||||
|
inset-inline-end: 5px;
|
||||||
|
inset-inline-start: initial;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-assist-chat": HaAssistChat;
|
||||||
|
}
|
||||||
|
}
|
@@ -26,7 +26,7 @@ export class HaAssistPipelinePicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public required = false;
|
@property({ type: Boolean }) public required = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public includeLastUsed = false;
|
@property({ attribute: false }) public includeLastUsed = false;
|
||||||
|
|
||||||
@state() _pipelines?: AssistPipeline[];
|
@state() _pipelines?: AssistPipeline[];
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ export class HaAttributeIcon extends LitElement {
|
|||||||
|
|
||||||
@property() public attribute?: string;
|
@property() public attribute?: string;
|
||||||
|
|
||||||
@property() public attributeValue?: string;
|
@property({ attribute: false }) public attributeValue?: string;
|
||||||
|
|
||||||
@property() public icon?: string;
|
@property() public icon?: string;
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ class HaAttributes extends LitElement {
|
|||||||
@state() private _expanded = false;
|
@state() private _expanded = false;
|
||||||
|
|
||||||
private get _filteredAttributes() {
|
private get _filteredAttributes() {
|
||||||
return this.computeDisplayAttributes(
|
return this._computeDisplayAttributes(
|
||||||
STATE_ATTRIBUTES.concat(
|
STATE_ATTRIBUTES.concat(
|
||||||
this.extraFilters ? this.extraFilters.split(",") : []
|
this.extraFilters ? this.extraFilters.split(",") : []
|
||||||
)
|
)
|
||||||
@@ -53,7 +53,7 @@ class HaAttributes extends LitElement {
|
|||||||
"ui.components.attributes.expansion_header"
|
"ui.components.attributes.expansion_header"
|
||||||
)}
|
)}
|
||||||
outlined
|
outlined
|
||||||
@expanded-will-change=${this.expandedChanged}
|
@expanded-will-change=${this._expandedChanged}
|
||||||
>
|
>
|
||||||
<div class="attribute-container">
|
<div class="attribute-container">
|
||||||
${this._expanded
|
${this._expanded
|
||||||
@@ -128,7 +128,7 @@ class HaAttributes extends LitElement {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeDisplayAttributes(filtersArray: string[]): string[] {
|
private _computeDisplayAttributes(filtersArray: string[]): string[] {
|
||||||
if (!this.stateObj) {
|
if (!this.stateObj) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ class HaAttributes extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandedChanged(ev) {
|
private _expandedChanged(ev) {
|
||||||
this._expanded = ev.detail.expanded;
|
this._expanded = ev.detail.expanded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -98,7 +98,6 @@ export class HaBadge extends LitElement {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding-inline-start: initial;
|
padding-inline-start: initial;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: Roboto;
|
|
||||||
}
|
}
|
||||||
.label {
|
.label {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
@@ -36,7 +36,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
/**
|
/**
|
||||||
* auto validate time inputs
|
* auto validate time inputs
|
||||||
*/
|
*/
|
||||||
@property({ type: Boolean }) autoValidate = false;
|
@property({ attribute: "auto-validate", type: Boolean }) autoValidate = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* determines if inputs are required
|
* determines if inputs are required
|
||||||
@@ -81,52 +81,56 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
/**
|
/**
|
||||||
* Label for the day input
|
* Label for the day input
|
||||||
*/
|
*/
|
||||||
@property() dayLabel = "";
|
@property({ attribute: false }) dayLabel = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for the hour input
|
* Label for the hour input
|
||||||
*/
|
*/
|
||||||
@property() hourLabel = "";
|
@property({ attribute: false }) hourLabel = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for the min input
|
* Label for the min input
|
||||||
*/
|
*/
|
||||||
@property() minLabel = "";
|
@property({ attribute: false }) minLabel = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for the sec input
|
* Label for the sec input
|
||||||
*/
|
*/
|
||||||
@property() secLabel = "";
|
@property({ attribute: false }) secLabel = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Label for the milli sec input
|
* Label for the milli sec input
|
||||||
*/
|
*/
|
||||||
@property() millisecLabel = "";
|
@property({ attribute: false }) millisecLabel = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show the sec field
|
* show the sec field
|
||||||
*/
|
*/
|
||||||
@property({ type: Boolean }) enableSecond = false;
|
@property({ attribute: "enable-second", type: Boolean })
|
||||||
|
public enableSecond = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show the milli sec field
|
* show the milli sec field
|
||||||
*/
|
*/
|
||||||
@property({ type: Boolean }) enableMillisecond = false;
|
@property({ attribute: "enable-millisecond", type: Boolean })
|
||||||
|
public enableMillisecond = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* show the day field
|
* show the day field
|
||||||
*/
|
*/
|
||||||
@property({ type: Boolean }) enableDay = false;
|
@property({ attribute: "enable-day", type: Boolean })
|
||||||
|
public enableDay = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* limit hours input
|
* limit hours input
|
||||||
*/
|
*/
|
||||||
@property({ type: Boolean }) noHoursLimit = false;
|
@property({ attribute: "no-hours-limit", type: Boolean })
|
||||||
|
public noHoursLimit = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AM or PM
|
* AM or PM
|
||||||
*/
|
*/
|
||||||
@property() amPm: "AM" | "PM" = "AM";
|
@property({ attribute: false }) amPm: "AM" | "PM" = "AM";
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public clearable?: boolean;
|
@property({ type: Boolean, reflect: true }) public clearable?: boolean;
|
||||||
|
|
||||||
@@ -134,7 +138,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
${this.label
|
${this.label
|
||||||
? html`<label>${this.label}${this.required ? " *" : ""}</label>`
|
? html`<label>${this.label}${this.required ? " *" : ""}</label>`
|
||||||
: ""}
|
: nothing}
|
||||||
<div class="time-input-wrap-wrap">
|
<div class="time-input-wrap-wrap">
|
||||||
<div class="time-input-wrap">
|
<div class="time-input-wrap">
|
||||||
${this.enableDay
|
${this.enableDay
|
||||||
@@ -158,7 +162,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
>
|
>
|
||||||
</ha-textfield>
|
</ha-textfield>
|
||||||
`
|
`
|
||||||
: ""}
|
: nothing}
|
||||||
|
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
id="hour"
|
id="hour"
|
||||||
@@ -221,7 +225,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
class=${this.enableMillisecond ? "has-suffix" : ""}
|
class=${this.enableMillisecond ? "has-suffix" : ""}
|
||||||
>
|
>
|
||||||
</ha-textfield>`
|
</ha-textfield>`
|
||||||
: ""}
|
: nothing}
|
||||||
${this.enableMillisecond
|
${this.enableMillisecond
|
||||||
? html`<ha-textfield
|
? html`<ha-textfield
|
||||||
id="millisec"
|
id="millisec"
|
||||||
@@ -240,7 +244,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
>
|
>
|
||||||
</ha-textfield>`
|
</ha-textfield>`
|
||||||
: ""}
|
: nothing}
|
||||||
${this.clearable && !this.required && !this.disabled
|
${this.clearable && !this.required && !this.disabled
|
||||||
? html`<ha-icon-button
|
? html`<ha-icon-button
|
||||||
label="clear"
|
label="clear"
|
||||||
@@ -251,7 +255,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
${this.format === 24
|
${this.format === 24
|
||||||
? ""
|
? nothing
|
||||||
: html`<ha-select
|
: html`<ha-select
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.value=${this.amPm}
|
.value=${this.amPm}
|
||||||
@@ -265,10 +269,10 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
<mwc-list-item value="AM">AM</mwc-list-item>
|
<mwc-list-item value="AM">AM</mwc-list-item>
|
||||||
<mwc-list-item value="PM">PM</mwc-list-item>
|
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||||
</ha-select>`}
|
</ha-select>`}
|
||||||
${this.helper
|
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
|
${this.helper
|
||||||
|
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||||
|
: nothing}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,17 +367,17 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
width: 85px;
|
width: 85px;
|
||||||
}
|
}
|
||||||
:host([clearable]) .mdc-select__anchor {
|
:host([clearable]) .mdc-select__anchor {
|
||||||
padding-inline-end: var(--select-selected-text-padding-end, 12px);
|
padding-inline-end: var(--select-selected-text-padding-end, 12px);
|
||||||
}
|
}
|
||||||
ha-icon-button {
|
ha-icon-button {
|
||||||
position: relative
|
position: relative;
|
||||||
--mdc-icon-button-size: 36px;
|
--mdc-icon-button-size: 36px;
|
||||||
--mdc-icon-size: 20px;
|
--mdc-icon-size: 20px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color:var(--mdc-text-field-fill-color, whitesmoke);
|
background-color: var(--mdc-text-field-fill-color, whitesmoke);
|
||||||
border-bottom-style: solid;
|
border-bottom-style: solid;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
@@ -398,6 +402,10 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
padding-inline-start: 4px;
|
padding-inline-start: 4px;
|
||||||
padding-inline-end: initial;
|
padding-inline-end: initial;
|
||||||
}
|
}
|
||||||
|
ha-input-helper-text {
|
||||||
|
padding-top: 8px;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ export class HaButtonMenu extends LitElement {
|
|||||||
|
|
||||||
@property() public corner: Corner = "BOTTOM_START";
|
@property() public corner: Corner = "BOTTOM_START";
|
||||||
|
|
||||||
@property() public menuCorner: MenuCorner = "START";
|
@property({ attribute: false }) public menuCorner: MenuCorner = "START";
|
||||||
|
|
||||||
@property({ type: Number }) public x: number | null = null;
|
@property({ type: Number }) public x: number | null = null;
|
||||||
|
|
||||||
|
@@ -14,7 +14,8 @@ export class HaButtonToggleGroup extends LitElement {
|
|||||||
|
|
||||||
@property() public active?: string;
|
@property() public active?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public fullWidth = false;
|
@property({ attribute: "full-width", type: Boolean })
|
||||||
|
public fullWidth = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public dense = false;
|
@property({ type: Boolean }) public dense = false;
|
||||||
|
|
||||||
|
@@ -7,9 +7,11 @@ import { HaListItem } from "./ha-list-item";
|
|||||||
export class HaClickableListItem extends HaListItem {
|
export class HaClickableListItem extends HaListItem {
|
||||||
@property() public href?: string;
|
@property() public href?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disableHref = false;
|
@property({ attribute: "disable-href", type: Boolean })
|
||||||
|
public disableHref = false;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public openNewTab = false;
|
@property({ attribute: "open-new-tab", type: Boolean, reflect: true })
|
||||||
|
public openNewTab = false;
|
||||||
|
|
||||||
@query("a") private _anchor!: HTMLAnchorElement;
|
@query("a") private _anchor!: HTMLAnchorElement;
|
||||||
|
|
||||||
@@ -18,7 +20,7 @@ export class HaClickableListItem extends HaListItem {
|
|||||||
const href = this.href || "";
|
const href = this.href || "";
|
||||||
|
|
||||||
return html`${this.disableHref
|
return html`${this.disableHref
|
||||||
? html`<a>${r}</a>`
|
? html`<a href="#" class="disabled">${r}</a>`
|
||||||
: html`<a target=${this.openNewTab ? "_blank" : ""} href=${href}
|
: html`<a target=${this.openNewTab ? "_blank" : ""} href=${href}
|
||||||
>${r}</a
|
>${r}</a
|
||||||
>`}`;
|
>`}`;
|
||||||
@@ -44,6 +46,9 @@ export class HaClickableListItem extends HaListItem {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -44,9 +44,10 @@ export class HaCodeEditor extends ReactiveElement {
|
|||||||
|
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
|
|
||||||
|
// eslint-disable-next-line lit/no-native-attributes
|
||||||
@property({ type: Boolean }) public autofocus = false;
|
@property({ type: Boolean }) public autofocus = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public readOnly = false;
|
@property({ attribute: "read-only", type: Boolean }) public readOnly = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public linewrap = false;
|
@property({ type: Boolean }) public linewrap = false;
|
||||||
|
|
||||||
|
@@ -82,7 +82,7 @@ export class HaColorPicker extends LitElement {
|
|||||||
`
|
`
|
||||||
: value === "state"
|
: value === "state"
|
||||||
? html`<ha-svg-icon path=${mdiPalette}></ha-svg-icon>`
|
? html`<ha-svg-icon path=${mdiPalette}></ha-svg-icon>`
|
||||||
: this.renderColorCircle(value || "grey")}
|
: this._renderColorCircle(value || "grey")}
|
||||||
</span>
|
</span>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
@@ -123,7 +123,7 @@ export class HaColorPicker extends LitElement {
|
|||||||
${this.defaultColor === color
|
${this.defaultColor === color
|
||||||
? ` (${this.hass.localize("ui.components.color-picker.default")})`
|
? ` (${this.hass.localize("ui.components.color-picker.default")})`
|
||||||
: nothing}
|
: nothing}
|
||||||
<span slot="graphic">${this.renderColorCircle(color)}</span>
|
<span slot="graphic">${this._renderColorCircle(color)}</span>
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
@@ -131,7 +131,7 @@ export class HaColorPicker extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<ha-list-item .value=${value} graphic="icon">
|
<ha-list-item .value=${value} graphic="icon">
|
||||||
${value}
|
${value}
|
||||||
<span slot="graphic">${this.renderColorCircle(value)}</span>
|
<span slot="graphic">${this._renderColorCircle(value)}</span>
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
@@ -139,7 +139,7 @@ export class HaColorPicker extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderColorCircle(color: string) {
|
private _renderColorCircle(color: string) {
|
||||||
return html`
|
return html`
|
||||||
<span
|
<span
|
||||||
class="circle-color"
|
class="circle-color"
|
||||||
|
@@ -71,7 +71,7 @@ export class HaComboBox extends LitElement {
|
|||||||
|
|
||||||
@property() public placeholder?: string;
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
@property() public validationMessage?: string;
|
@property({ attribute: false }) public validationMessage?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
@property() public helper?: string;
|
||||||
|
|
||||||
|
@@ -435,7 +435,7 @@ export class HaControlCircularSlider extends LitElement {
|
|||||||
this._activeSlider = undefined;
|
this._activeSlider = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleKeyUp(e: KeyboardEvent) {
|
private _handleKeyUp(e: KeyboardEvent) {
|
||||||
if (!A11Y_KEY_CODES.has(e.code)) return;
|
if (!A11Y_KEY_CODES.has(e.code)) return;
|
||||||
this._activeSlider = (e.currentTarget as any).id as ActiveSlider;
|
this._activeSlider = (e.currentTarget as any).id as ActiveSlider;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@@ -52,7 +52,7 @@ export class HaControlNumberButton extends LitElement {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
private boundedValue(value: number) {
|
private _boundedValue(value: number) {
|
||||||
const clamped = conditionalClamp(value, this.min, this.max);
|
const clamped = conditionalClamp(value, this.min, this.max);
|
||||||
return Math.round(clamped / this._step) * this._step;
|
return Math.round(clamped / this._step) * this._step;
|
||||||
}
|
}
|
||||||
@@ -86,14 +86,14 @@ export class HaControlNumberButton extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _increment() {
|
private _increment() {
|
||||||
this.value = this.boundedValue(this._value + this._step);
|
this.value = this._boundedValue(this._value + this._step);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _decrement() {
|
private _decrement() {
|
||||||
this.value = this.boundedValue(this._value - this._step);
|
this.value = this._boundedValue(this._value - this._step);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleKeyDown(e: KeyboardEvent) {
|
private _handleKeyDown(e: KeyboardEvent) {
|
||||||
if (this.disabled) return;
|
if (this.disabled) return;
|
||||||
if (!A11Y_KEY_CODES.has(e.code)) return;
|
if (!A11Y_KEY_CODES.has(e.code)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -107,10 +107,10 @@ export class HaControlNumberButton extends LitElement {
|
|||||||
this._decrement();
|
this._decrement();
|
||||||
break;
|
break;
|
||||||
case "PageUp":
|
case "PageUp":
|
||||||
this.value = this.boundedValue(this._value + this._tenPercentStep);
|
this.value = this._boundedValue(this._value + this._tenPercentStep);
|
||||||
break;
|
break;
|
||||||
case "PageDown":
|
case "PageDown":
|
||||||
this.value = this.boundedValue(this._value - this._tenPercentStep);
|
this.value = this._boundedValue(this._value - this._tenPercentStep);
|
||||||
break;
|
break;
|
||||||
case "Home":
|
case "Home":
|
||||||
if (this.min != null) {
|
if (this.min != null) {
|
||||||
|
@@ -57,12 +57,13 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
aria-labelledby=${ifDefined(labelledby)}
|
aria-labelledby=${ifDefined(labelledby)}
|
||||||
aria-label=${ifDefined(labelAttribute)}
|
aria-label=${ifDefined(labelAttribute)}
|
||||||
aria-required=${this.required}
|
aria-required=${this.required}
|
||||||
|
aria-controls="listbox"
|
||||||
@focus=${this.onFocus}
|
@focus=${this.onFocus}
|
||||||
@blur=${this.onBlur}
|
@blur=${this.onBlur}
|
||||||
@click=${this.onClick}
|
@click=${this.onClick}
|
||||||
@keydown=${this.onKeydown}
|
@keydown=${this.onKeydown}
|
||||||
>
|
>
|
||||||
${this.renderIcon()}
|
${this._renderIcon()}
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${this.hideLabel
|
${this.hideLabel
|
||||||
? nothing
|
? nothing
|
||||||
@@ -71,7 +72,7 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
? html`<p class="value">${this.selectedText}</p>`
|
? html`<p class="value">${this.selectedText}</p>`
|
||||||
: nothing}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
${this.renderArrow()}
|
${this._renderArrow()}
|
||||||
<ha-ripple .disabled=${this.disabled}></ha-ripple>
|
<ha-ripple .disabled=${this.disabled}></ha-ripple>
|
||||||
</div>
|
</div>
|
||||||
${this.renderMenu()}
|
${this.renderMenu()}
|
||||||
@@ -79,7 +80,7 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderArrow() {
|
private _renderArrow() {
|
||||||
if (!this.showArrow) return nothing;
|
if (!this.showArrow) return nothing;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
@@ -89,7 +90,7 @@ export class HaControlSelectMenu extends SelectBase {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderIcon() {
|
private _renderIcon() {
|
||||||
const index = this.mdcFoundation?.getSelectedIndex();
|
const index = this.mdcFoundation?.getSelectedIndex();
|
||||||
const items = this.menuElement?.items ?? [];
|
const items = this.menuElement?.items ?? [];
|
||||||
const item = index != null ? items[index] : undefined;
|
const item = index != null ? items[index] : undefined;
|
||||||
|
@@ -215,12 +215,12 @@ export class HaControlSlider extends LitElement {
|
|||||||
return Math.max(this.step, (this.max - this.min) / 10);
|
return Math.max(this.step, (this.max - this.min) / 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
_showTooltip() {
|
private _showTooltip() {
|
||||||
if (this._tooltipTimeout != null) window.clearTimeout(this._tooltipTimeout);
|
if (this._tooltipTimeout != null) window.clearTimeout(this._tooltipTimeout);
|
||||||
this.tooltipVisible = true;
|
this.tooltipVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_hideTooltip(delay?: number) {
|
private _hideTooltip(delay?: number) {
|
||||||
if (!delay) {
|
if (!delay) {
|
||||||
this.tooltipVisible = false;
|
this.tooltipVisible = false;
|
||||||
return;
|
return;
|
||||||
@@ -230,7 +230,7 @@ export class HaControlSlider extends LitElement {
|
|||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleKeyDown(e: KeyboardEvent) {
|
private _handleKeyDown(e: KeyboardEvent) {
|
||||||
if (!A11Y_KEY_CODES.has(e.code)) return;
|
if (!A11Y_KEY_CODES.has(e.code)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
switch (e.code) {
|
switch (e.code) {
|
||||||
@@ -265,7 +265,7 @@ export class HaControlSlider extends LitElement {
|
|||||||
|
|
||||||
private _tooltipTimeout?: number;
|
private _tooltipTimeout?: number;
|
||||||
|
|
||||||
_handleKeyUp(e: KeyboardEvent) {
|
private _handleKeyUp(e: KeyboardEvent) {
|
||||||
if (!A11Y_KEY_CODES.has(e.code)) return;
|
if (!A11Y_KEY_CODES.has(e.code)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._hideTooltip(500);
|
this._hideTooltip(500);
|
||||||
|
@@ -22,10 +22,10 @@ export class HaControlSwitch extends LitElement {
|
|||||||
@property({ type: Boolean, reflect: true }) public checked = false;
|
@property({ type: Boolean, reflect: true }) public checked = false;
|
||||||
|
|
||||||
// SVG icon path (if you need a non SVG icon instead, use the provided on icon slot to pass an <ha-icon slot="icon-on"> in)
|
// SVG icon path (if you need a non SVG icon instead, use the provided on icon slot to pass an <ha-icon slot="icon-on"> in)
|
||||||
@property({ type: String }) pathOn?: string;
|
@property({ attribute: false, type: String }) pathOn?: string;
|
||||||
|
|
||||||
// SVG icon path (if you need a non SVG icon instead, use the provided off icon slot to pass an <ha-icon slot="icon-off"> in)
|
// SVG icon path (if you need a non SVG icon instead, use the provided off icon slot to pass an <ha-icon slot="icon-off"> in)
|
||||||
@property({ type: String }) pathOff?: string;
|
@property({ attribute: false, type: String }) pathOff?: string;
|
||||||
|
|
||||||
@property({ attribute: "touch-action" })
|
@property({ attribute: "touch-action" })
|
||||||
public touchAction?: string;
|
public touchAction?: string;
|
||||||
|
@@ -277,7 +277,7 @@ export class HaCountryPicker extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public noSort = false;
|
@property({ attribute: "no-sort", type: Boolean }) public noSort = false;
|
||||||
|
|
||||||
private _getOptions = memoizeOne(
|
private _getOptions = memoizeOne(
|
||||||
(language?: string, countries?: string[]) => {
|
(language?: string, countries?: string[]) => {
|
||||||
|
@@ -13,7 +13,7 @@ import "./ha-textfield";
|
|||||||
|
|
||||||
const loadDatePickerDialog = () => import("./ha-dialog-date-picker");
|
const loadDatePickerDialog = () => import("./ha-dialog-date-picker");
|
||||||
|
|
||||||
export interface datePickerDialogParams {
|
export interface DatePickerDialogParams {
|
||||||
value?: string;
|
value?: string;
|
||||||
min?: string;
|
min?: string;
|
||||||
max?: string;
|
max?: string;
|
||||||
@@ -25,7 +25,7 @@ export interface datePickerDialogParams {
|
|||||||
|
|
||||||
const showDatePickerDialog = (
|
const showDatePickerDialog = (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
dialogParams: datePickerDialogParams
|
dialogParams: DatePickerDialogParams
|
||||||
): void => {
|
): void => {
|
||||||
fireEvent(element, "show-dialog", {
|
fireEvent(element, "show-dialog", {
|
||||||
dialogTag: "ha-dialog-date-picker",
|
dialogTag: "ha-dialog-date-picker",
|
||||||
@@ -51,7 +51,7 @@ export class HaDateInput extends LitElement {
|
|||||||
|
|
||||||
@property() public helper?: string;
|
@property() public helper?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public canClear = false;
|
@property({ attribute: "can-clear", type: Boolean }) public canClear = false;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<ha-textfield
|
return html`<ha-textfield
|
||||||
|
@@ -53,9 +53,11 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
|
|
||||||
@state() private _ranges?: DateRangePickerRanges;
|
@state() private _ranges?: DateRangePickerRanges;
|
||||||
|
|
||||||
@property({ type: Boolean }) public autoApply = false;
|
@property({ attribute: "auto-apply", type: Boolean })
|
||||||
|
public autoApply = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public timePicker = true;
|
@property({ attribute: "time-picker", type: Boolean })
|
||||||
|
public timePicker = true;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@@ -63,9 +65,14 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
|
|
||||||
@state() private _hour24format = false;
|
@state() private _hour24format = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public extendedPresets = false;
|
@property({ attribute: "extended-presets", type: Boolean })
|
||||||
|
public extendedPresets = false;
|
||||||
|
|
||||||
@property() public openingDirection?: "right" | "left" | "center" | "inline";
|
@property({ attribute: false }) public openingDirection?:
|
||||||
|
| "right"
|
||||||
|
| "left"
|
||||||
|
| "center"
|
||||||
|
| "inline";
|
||||||
|
|
||||||
@state() private _calcedOpeningDirection?:
|
@state() private _calcedOpeningDirection?:
|
||||||
| "right"
|
| "right"
|
||||||
|
@@ -7,7 +7,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { nextRender } from "../common/util/render-status";
|
import { nextRender } from "../common/util/render-status";
|
||||||
import { haStyleDialog } from "../resources/styles";
|
import { haStyleDialog } from "../resources/styles";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import type { datePickerDialogParams } from "./ha-date-input";
|
import type { DatePickerDialogParams } from "./ha-date-input";
|
||||||
import "./ha-dialog";
|
import "./ha-dialog";
|
||||||
|
|
||||||
@customElement("ha-dialog-date-picker")
|
@customElement("ha-dialog-date-picker")
|
||||||
@@ -20,11 +20,11 @@ export class HaDialogDatePicker extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@state() private _params?: datePickerDialogParams;
|
@state() private _params?: DatePickerDialogParams;
|
||||||
|
|
||||||
@state() private _value?: string;
|
@state() private _value?: string;
|
||||||
|
|
||||||
public async showDialog(params: datePickerDialogParams): Promise<void> {
|
public async showDialog(params: DatePickerDialogParams): Promise<void> {
|
||||||
// app-datepicker has a bug, that it removes its handlers when disconnected, but doesn't add them back when reconnected.
|
// app-datepicker has a bug, that it removes its handlers when disconnected, but doesn't add them back when reconnected.
|
||||||
// So we need to wait for the next render to make sure the element is removed and re-created so the handlers are added.
|
// So we need to wait for the next render to make sure the element is removed and re-created so the handlers are added.
|
||||||
await nextRender();
|
await nextRender();
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user