mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-12 19:17:21 +00:00
Compare commits
45 Commits
20240809.0
...
boolean_se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
932120869b | ||
|
|
061521a979 | ||
|
|
d3f73baa36 | ||
|
|
3037bf494c | ||
|
|
b4dd953128 | ||
|
|
430a28f350 | ||
|
|
0eacf3fdac | ||
|
|
7f9bf69a08 | ||
|
|
32cca9e30c | ||
|
|
d7d62307b8 | ||
|
|
12bfa5dab2 | ||
|
|
c0a728bc66 | ||
|
|
19e8667349 | ||
|
|
f3688b95d4 | ||
|
|
01f692f05c | ||
|
|
d652f6382d | ||
|
|
c0f96d9473 | ||
|
|
f1a2af24b3 | ||
|
|
8501098bd1 | ||
|
|
a235f76985 | ||
|
|
968c0de141 | ||
|
|
77d8aff1f4 | ||
|
|
5e486d9cf0 | ||
|
|
f730761b3f | ||
|
|
46fc9c1a33 | ||
|
|
8ed68bf295 | ||
|
|
5622180d42 | ||
|
|
ef1f9b371d | ||
|
|
0b79684cf1 | ||
|
|
bc68d8df11 | ||
|
|
ebbade2fb7 | ||
|
|
2b5f778f2e | ||
|
|
91fc2383cb | ||
|
|
1080a8c961 | ||
|
|
6144049f8c | ||
|
|
4ad3ad6e93 | ||
|
|
16e84296da | ||
|
|
3e1ea8d236 | ||
|
|
8c9996fc81 | ||
|
|
336b5fb547 | ||
|
|
3f0f3affb6 | ||
|
|
6754b8893b | ||
|
|
6ec4323c76 | ||
|
|
20408392d2 | ||
|
|
b030a5d1f0 |
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@@ -89,7 +89,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@v4.3.5
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: frontend-bundle-stats
|
name: frontend-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
@@ -113,7 +113,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
IS_TEST: "true"
|
IS_TEST: "true"
|
||||||
- name: Upload bundle stats
|
- name: Upload bundle stats
|
||||||
uses: actions/upload-artifact@v4.3.5
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: supervisor-bundle-stats
|
name: supervisor-bundle-stats
|
||||||
path: build/stats/*.json
|
path: build/stats/*.json
|
||||||
|
|||||||
4
.github/workflows/nightly.yaml
vendored
4
.github/workflows/nightly.yaml
vendored
@@ -57,14 +57,14 @@ jobs:
|
|||||||
run: tar -czvf translations.tar.gz translations
|
run: tar -czvf translations.tar.gz translations
|
||||||
|
|
||||||
- name: Upload build artifacts
|
- name: Upload build artifacts
|
||||||
uses: actions/upload-artifact@v4.3.5
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels
|
||||||
path: dist/home_assistant_frontend*.whl
|
path: dist/home_assistant_frontend*.whl
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload translations
|
- name: Upload translations
|
||||||
uses: actions/upload-artifact@v4.3.5
|
uses: actions/upload-artifact@v4.3.6
|
||||||
with:
|
with:
|
||||||
name: translations
|
name: translations
|
||||||
path: translations.tar.gz
|
path: translations.tar.gz
|
||||||
|
|||||||
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.11
|
uses: relative-ci/agent-action@v2.1.12
|
||||||
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 }}
|
||||||
|
|||||||
@@ -1,35 +1,76 @@
|
|||||||
// Tasks to generate entry HTML
|
// Tasks to generate entry HTML
|
||||||
|
|
||||||
import { getUserAgentRegex } from "browserslist-useragent-regexp";
|
import {
|
||||||
|
applyVersionsToRegexes,
|
||||||
|
compileRegex,
|
||||||
|
getPreUserAgentRegexes,
|
||||||
|
} from "browserslist-useragent-regexp";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import { minify } from "html-minifier-terser";
|
import { minify } from "html-minifier-terser";
|
||||||
import template from "lodash.template";
|
import template from "lodash.template";
|
||||||
import path from "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 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,
|
||||||
|
// and it is not in the default user agent string. So we add an additional regex
|
||||||
|
// to serve modern based on a minimum macOS version. We take the minimum Safari
|
||||||
|
// major version from browserslist and manually map that to a supported macOS
|
||||||
|
// version. Note this assumes the user has kept Safari updated.
|
||||||
|
const HA_MACOS_REGEX =
|
||||||
|
/Home Assistant\/[\d.]+ \(.+; macOS (\d+)\.(\d+)(?:\.(\d+))?\)/;
|
||||||
|
const SAFARI_TO_MACOS = {
|
||||||
|
15: [10, 15, 0],
|
||||||
|
16: [11, 0, 0],
|
||||||
|
17: [12, 0, 0],
|
||||||
|
18: [13, 0, 0],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCommonTemplateVars = () => {
|
||||||
|
const browserRegexes = getPreUserAgentRegexes({
|
||||||
|
env: "modern",
|
||||||
|
allowHigherVersions: true,
|
||||||
|
mobileToDesktop: true,
|
||||||
|
throwOnMissing: true,
|
||||||
|
});
|
||||||
|
const minSafariVersion = browserRegexes.find(
|
||||||
|
(regex) => regex.family === "safari"
|
||||||
|
)?.matchedVersions[0][0];
|
||||||
|
const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
|
||||||
|
if (!minMacOSVersion) {
|
||||||
|
throw Error(
|
||||||
|
`Could not find minimum MacOS version for Safari ${minSafariVersion}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const haMacOSRegex = applyVersionsToRegexes(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
family: "ha_macos",
|
||||||
|
regex: HA_MACOS_REGEX,
|
||||||
|
matchedVersions: [minMacOSVersion],
|
||||||
|
requestVersions: [minMacOSVersion],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{ ignorePatch: true, allowHigherVersions: true }
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
useRollup: env.useRollup(),
|
||||||
|
useWDS: env.useWDS(),
|
||||||
|
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const renderTemplate = (templateFile, data = {}) => {
|
const renderTemplate = (templateFile, data = {}) => {
|
||||||
const compiled = template(
|
const compiled = template(
|
||||||
fs.readFileSync(templateFile, { encoding: "utf-8" })
|
fs.readFileSync(templateFile, { encoding: "utf-8" })
|
||||||
);
|
);
|
||||||
return compiled({
|
return compiled({
|
||||||
...data,
|
...data,
|
||||||
useRollup: env.useRollup(),
|
|
||||||
useWDS: env.useWDS(),
|
|
||||||
modernRegex: getUserAgentRegex({
|
|
||||||
env: "modern",
|
|
||||||
allowHigherVersions: true,
|
|
||||||
mobileToDesktop: true,
|
|
||||||
throwOnMissing: true,
|
|
||||||
}).toString(),
|
|
||||||
// Resolve any child/nested templates relative to the parent and pass the same data
|
// Resolve any child/nested templates relative to the parent and pass the same data
|
||||||
renderTemplate: (childTemplate) =>
|
renderTemplate: (childTemplate) =>
|
||||||
renderTemplate(
|
renderTemplate(resolve(dirname(templateFile), childTemplate), data),
|
||||||
path.resolve(path.dirname(templateFile), childTemplate),
|
|
||||||
data
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,10 +104,12 @@ const genPagesDevTask =
|
|||||||
publicRoot = ""
|
publicRoot = ""
|
||||||
) =>
|
) =>
|
||||||
async () => {
|
async () => {
|
||||||
|
const commonVars = getCommonTemplateVars();
|
||||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||||
const content = renderTemplate(
|
const content = renderTemplate(
|
||||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
resolve(inputRoot, inputSub, `${page}.template`),
|
||||||
{
|
{
|
||||||
|
...commonVars,
|
||||||
latestEntryJS: entries.map((entry) =>
|
latestEntryJS: entries.map((entry) =>
|
||||||
useWDS
|
useWDS
|
||||||
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
||||||
@@ -81,7 +124,7 @@ const genPagesDevTask =
|
|||||||
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
fs.outputFileSync(path.resolve(outputRoot, page), content);
|
fs.outputFileSync(resolve(outputRoot, page), content);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,16 +141,18 @@ const genPagesProdTask =
|
|||||||
) =>
|
) =>
|
||||||
async () => {
|
async () => {
|
||||||
const latestManifest = fs.readJsonSync(
|
const latestManifest = fs.readJsonSync(
|
||||||
path.resolve(outputLatest, "manifest.json")
|
resolve(outputLatest, "manifest.json")
|
||||||
);
|
);
|
||||||
const es5Manifest = outputES5
|
const es5Manifest = outputES5
|
||||||
? fs.readJsonSync(path.resolve(outputES5, "manifest.json"))
|
? fs.readJsonSync(resolve(outputES5, "manifest.json"))
|
||||||
: {};
|
: {};
|
||||||
|
const commonVars = getCommonTemplateVars();
|
||||||
const minifiedHTML = [];
|
const minifiedHTML = [];
|
||||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||||
const content = renderTemplate(
|
const content = renderTemplate(
|
||||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
resolve(inputRoot, inputSub, `${page}.template`),
|
||||||
{
|
{
|
||||||
|
...commonVars,
|
||||||
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
|
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
|
||||||
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
|
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
|
||||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||||
@@ -115,8 +160,8 @@ const genPagesProdTask =
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
minifiedHTML.push(
|
minifiedHTML.push(
|
||||||
minifyHtml(content, path.extname(page)).then((minified) =>
|
minifyHtml(content, extname(page)).then((minified) =>
|
||||||
fs.outputFileSync(path.resolve(outputRoot, page), minified)
|
fs.outputFileSync(resolve(outputRoot, page), minified)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -532,15 +532,6 @@ export default {
|
|||||||
last_changed: "2018-07-19T10:44:46.200946+00:00",
|
last_changed: "2018-07-19T10:44:46.200946+00:00",
|
||||||
last_updated: "2018-07-19T10:44:46.200946+00:00",
|
last_updated: "2018-07-19T10:44:46.200946+00:00",
|
||||||
},
|
},
|
||||||
"mailbox.demomailbox": {
|
|
||||||
entity_id: "mailbox.demomailbox",
|
|
||||||
state: "10",
|
|
||||||
attributes: {
|
|
||||||
friendly_name: "DemoMailbox",
|
|
||||||
},
|
|
||||||
last_changed: "2018-07-19T10:45:16.555210+00:00",
|
|
||||||
last_updated: "2018-07-19T10:45:16.555210+00:00",
|
|
||||||
},
|
|
||||||
"input_select.living_room_preset": {
|
"input_select.living_room_preset": {
|
||||||
entity_id: "input_select.living_room_preset",
|
entity_id: "input_select.living_room_preset",
|
||||||
state: "Visitors",
|
state: "Visitors",
|
||||||
|
|||||||
42
package.json
42
package.json
@@ -25,15 +25,15 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.25.0",
|
"@babel/runtime": "7.25.4",
|
||||||
"@braintree/sanitize-url": "7.1.0",
|
"@braintree/sanitize-url": "7.1.0",
|
||||||
"@codemirror/autocomplete": "6.18.0",
|
"@codemirror/autocomplete": "6.18.0",
|
||||||
"@codemirror/commands": "6.6.0",
|
"@codemirror/commands": "6.6.0",
|
||||||
"@codemirror/language": "6.10.2",
|
"@codemirror/language": "6.10.2",
|
||||||
"@codemirror/legacy-modes": "6.4.0",
|
"@codemirror/legacy-modes": "6.4.1",
|
||||||
"@codemirror/search": "6.5.6",
|
"@codemirror/search": "6.5.6",
|
||||||
"@codemirror/state": "6.4.1",
|
"@codemirror/state": "6.4.1",
|
||||||
"@codemirror/view": "6.30.0",
|
"@codemirror/view": "6.32.0",
|
||||||
"@egjs/hammerjs": "2.0.17",
|
"@egjs/hammerjs": "2.0.17",
|
||||||
"@formatjs/intl-datetimeformat": "6.12.5",
|
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||||
"@formatjs/intl-displaynames": "6.6.8",
|
"@formatjs/intl-displaynames": "6.6.8",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"@fullcalendar/list": "6.1.15",
|
"@fullcalendar/list": "6.1.15",
|
||||||
"@fullcalendar/luxon3": "6.1.15",
|
"@fullcalendar/luxon3": "6.1.15",
|
||||||
"@fullcalendar/timegrid": "6.1.15",
|
"@fullcalendar/timegrid": "6.1.15",
|
||||||
"@lezer/highlight": "1.2.0",
|
"@lezer/highlight": "1.2.1",
|
||||||
"@lit-labs/context": "0.4.1",
|
"@lit-labs/context": "0.4.1",
|
||||||
"@lit-labs/motion": "1.0.7",
|
"@lit-labs/motion": "1.0.7",
|
||||||
"@lit-labs/observers": "2.0.2",
|
"@lit-labs/observers": "2.0.2",
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"@material/mwc-top-app-bar": "0.27.0",
|
"@material/mwc-top-app-bar": "0.27.0",
|
||||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/web": "2.0.0",
|
"@material/web": "2.1.0",
|
||||||
"@mdi/js": "7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@mdi/svg": "7.4.47",
|
"@mdi/svg": "7.4.47",
|
||||||
"@polymer/paper-item": "3.0.1",
|
"@polymer/paper-item": "3.0.1",
|
||||||
@@ -88,8 +88,8 @@
|
|||||||
"@polymer/paper-tabs": "3.1.0",
|
"@polymer/paper-tabs": "3.1.0",
|
||||||
"@polymer/polymer": "3.5.1",
|
"@polymer/polymer": "3.5.1",
|
||||||
"@thomasloven/round-slider": "0.6.0",
|
"@thomasloven/round-slider": "0.6.0",
|
||||||
"@vaadin/combo-box": "24.4.5",
|
"@vaadin/combo-box": "24.4.6",
|
||||||
"@vaadin/vaadin-themable-mixin": "24.4.5",
|
"@vaadin/vaadin-themable-mixin": "24.4.6",
|
||||||
"@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",
|
||||||
@@ -97,10 +97,10 @@
|
|||||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||||
"app-datepicker": "5.1.1",
|
"app-datepicker": "5.1.1",
|
||||||
"chart.js": "4.4.3",
|
"chart.js": "4.4.4",
|
||||||
"color-name": "2.0.0",
|
"color-name": "2.0.0",
|
||||||
"comlink": "4.4.1",
|
"comlink": "4.4.1",
|
||||||
"core-js": "3.38.0",
|
"core-js": "3.38.1",
|
||||||
"cropperjs": "1.6.2",
|
"cropperjs": "1.6.2",
|
||||||
"date-fns": "3.6.0",
|
"date-fns": "3.6.0",
|
||||||
"date-fns-tz": "3.1.3",
|
"date-fns-tz": "3.1.3",
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
"leaflet-draw": "1.0.4",
|
"leaflet-draw": "1.0.4",
|
||||||
"lit": "2.8.0",
|
"lit": "2.8.0",
|
||||||
"luxon": "3.5.0",
|
"luxon": "3.5.0",
|
||||||
"marked": "13.0.3",
|
"marked": "14.0.0",
|
||||||
"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",
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
"sortablejs": "1.15.2",
|
"sortablejs": "1.15.2",
|
||||||
"stacktrace-js": "2.0.2",
|
"stacktrace-js": "2.0.2",
|
||||||
"superstruct": "2.0.2",
|
"superstruct": "2.0.2",
|
||||||
"tinykeys": "2.1.0",
|
"tinykeys": "3.0.0",
|
||||||
"tsparticles-engine": "2.12.0",
|
"tsparticles-engine": "2.12.0",
|
||||||
"tsparticles-preset-links": "2.12.0",
|
"tsparticles-preset-links": "2.12.0",
|
||||||
"ua-parser-js": "1.0.38",
|
"ua-parser-js": "1.0.38",
|
||||||
@@ -152,15 +152,15 @@
|
|||||||
"@babel/core": "7.25.2",
|
"@babel/core": "7.25.2",
|
||||||
"@babel/helper-define-polyfill-provider": "0.6.2",
|
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||||
"@babel/plugin-proposal-decorators": "7.24.7",
|
"@babel/plugin-proposal-decorators": "7.24.7",
|
||||||
"@babel/plugin-transform-runtime": "7.24.7",
|
"@babel/plugin-transform-runtime": "7.25.4",
|
||||||
"@babel/preset-env": "7.25.3",
|
"@babel/preset-env": "7.25.4",
|
||||||
"@babel/preset-typescript": "7.24.7",
|
"@babel/preset-typescript": "7.24.7",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.14.0",
|
"@bundle-stats/plugin-webpack-filter": "4.14.2",
|
||||||
"@koa/cors": "5.0.0",
|
"@koa/cors": "5.0.0",
|
||||||
"@lokalise/node-api": "12.7.0",
|
"@lokalise/node-api": "12.7.0",
|
||||||
"@octokit/auth-oauth-device": "7.1.1",
|
"@octokit/auth-oauth-device": "7.1.1",
|
||||||
"@octokit/plugin-retry": "7.1.1",
|
"@octokit/plugin-retry": "7.1.1",
|
||||||
"@octokit/rest": "21.0.1",
|
"@octokit/rest": "21.0.2",
|
||||||
"@open-wc/dev-server-hmr": "0.1.4",
|
"@open-wc/dev-server-hmr": "0.1.4",
|
||||||
"@rollup/plugin-babel": "6.0.4",
|
"@rollup/plugin-babel": "6.0.4",
|
||||||
"@rollup/plugin-commonjs": "26.0.1",
|
"@rollup/plugin-commonjs": "26.0.1",
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"@rollup/plugin-node-resolve": "15.2.3",
|
"@rollup/plugin-node-resolve": "15.2.3",
|
||||||
"@rollup/plugin-replace": "5.0.7",
|
"@rollup/plugin-replace": "5.0.7",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.16",
|
"@types/chromecast-caf-receiver": "6.0.17",
|
||||||
"@types/chromecast-caf-sender": "1.0.10",
|
"@types/chromecast-caf-sender": "1.0.10",
|
||||||
"@types/color-name": "1.1.4",
|
"@types/color-name": "1.1.4",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
@@ -202,8 +202,8 @@
|
|||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-lit": "1.14.0",
|
"eslint-plugin-lit": "1.14.0",
|
||||||
"eslint-plugin-lit-a11y": "4.1.4",
|
"eslint-plugin-lit-a11y": "4.1.4",
|
||||||
"eslint-plugin-unused-imports": "4.0.1",
|
"eslint-plugin-unused-imports": "4.1.3",
|
||||||
"eslint-plugin-wc": "2.1.0",
|
"eslint-plugin-wc": "2.1.1",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
"fs-extra": "11.2.0",
|
"fs-extra": "11.2.0",
|
||||||
"glob": "11.0.0",
|
"glob": "11.0.0",
|
||||||
@@ -213,10 +213,10 @@
|
|||||||
"gulp-rename": "2.0.0",
|
"gulp-rename": "2.0.0",
|
||||||
"gulp-zopfli-green": "6.0.2",
|
"gulp-zopfli-green": "6.0.2",
|
||||||
"html-minifier-terser": "7.2.0",
|
"html-minifier-terser": "7.2.0",
|
||||||
"husky": "9.1.4",
|
"husky": "9.1.5",
|
||||||
"instant-mocha": "1.5.2",
|
"instant-mocha": "1.5.2",
|
||||||
"jszip": "3.10.1",
|
"jszip": "3.10.1",
|
||||||
"lint-staged": "15.2.8",
|
"lint-staged": "15.2.9",
|
||||||
"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",
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.5.4",
|
"typescript": "5.5.4",
|
||||||
"webpack": "5.93.0",
|
"webpack": "5.94.0",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.0.4",
|
"webpack-dev-server": "5.0.4",
|
||||||
"webpack-manifest-plugin": "5.0.0",
|
"webpack-manifest-plugin": "5.0.0",
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import {
|
|||||||
mdiImageFilterFrames,
|
mdiImageFilterFrames,
|
||||||
mdiLightbulb,
|
mdiLightbulb,
|
||||||
mdiLightningBolt,
|
mdiLightningBolt,
|
||||||
mdiMailbox,
|
|
||||||
mdiMapMarkerRadius,
|
mdiMapMarkerRadius,
|
||||||
mdiMeterGas,
|
mdiMeterGas,
|
||||||
mdiMicrophoneMessage,
|
mdiMicrophoneMessage,
|
||||||
@@ -119,7 +118,6 @@ export const FIXED_DOMAIN_ICONS = {
|
|||||||
input_text: mdiFormTextbox,
|
input_text: mdiFormTextbox,
|
||||||
lawn_mower: mdiRobotMower,
|
lawn_mower: mdiRobotMower,
|
||||||
light: mdiLightbulb,
|
light: mdiLightbulb,
|
||||||
mailbox: mdiMailbox,
|
|
||||||
notify: mdiCommentAlert,
|
notify: mdiCommentAlert,
|
||||||
number: mdiRayVertex,
|
number: mdiRayVertex,
|
||||||
persistent_notification: mdiBell,
|
persistent_notification: mdiBell,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const FIXED_DOMAIN_STATES = {
|
|||||||
humidifier: ["on", "off"],
|
humidifier: ["on", "off"],
|
||||||
input_boolean: ["on", "off"],
|
input_boolean: ["on", "off"],
|
||||||
input_button: [],
|
input_button: [],
|
||||||
lawn_mower: ["error", "paused", "mowing", "docked"],
|
lawn_mower: ["error", "paused", "mowing", "returning", "docked"],
|
||||||
light: ["on", "off"],
|
light: ["on", "off"],
|
||||||
lock: [
|
lock: [
|
||||||
"jammed",
|
"jammed",
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ const LAWN_MOWER_ACTIONS: Partial<
|
|||||||
service: "start_mowing",
|
service: "start_mowing",
|
||||||
feature: LawnMowerEntityFeature.START_MOWING,
|
feature: LawnMowerEntityFeature.START_MOWING,
|
||||||
},
|
},
|
||||||
|
returning: {
|
||||||
|
action: "pause",
|
||||||
|
service: "pause",
|
||||||
|
feature: LawnMowerEntityFeature.PAUSE,
|
||||||
|
},
|
||||||
paused: {
|
paused: {
|
||||||
action: "resume_mowing",
|
action: "resume_mowing",
|
||||||
service: "start_mowing",
|
service: "start_mowing",
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { BooleanSelector } from "../../data/selector";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-checkbox";
|
||||||
import "../ha-formfield";
|
import "../ha-formfield";
|
||||||
import "../ha-switch";
|
|
||||||
import "../ha-input-helper-text";
|
import "../ha-input-helper-text";
|
||||||
|
import "../ha-switch";
|
||||||
|
|
||||||
@customElement("ha-selector-boolean")
|
@customElement("ha-selector-boolean")
|
||||||
export class HaBooleanSelector extends LitElement {
|
export class HaBooleanSelector extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public selector!: BooleanSelector;
|
||||||
|
|
||||||
@property({ type: Boolean }) public value = false;
|
@property({ type: Boolean }) public value = false;
|
||||||
|
|
||||||
@property() public placeholder?: any;
|
@property() public placeholder?: any;
|
||||||
@@ -21,13 +25,24 @@ export class HaBooleanSelector extends LitElement {
|
|||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
|
const checkbox = this.selector.boolean?.mode === "checkbox";
|
||||||
return html`
|
return html`
|
||||||
<ha-formfield alignEnd spaceBetween .label=${this.label}>
|
<ha-formfield .alignEnd=${!checkbox} spaceBetween .label=${this.label}>
|
||||||
<ha-switch
|
${checkbox
|
||||||
.checked=${this.value ?? this.placeholder === true}
|
? html`
|
||||||
@change=${this._handleChange}
|
<ha-checkbox
|
||||||
.disabled=${this.disabled}
|
.checked=${this.value ?? this.placeholder === true}
|
||||||
></ha-switch>
|
@change=${this._handleChange}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
></ha-checkbox>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<ha-switch
|
||||||
|
.checked=${this.value ?? this.placeholder === true}
|
||||||
|
@change=${this._handleChange}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
></ha-switch>
|
||||||
|
`}
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
${this.helper
|
${this.helper
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||||
|
|||||||
@@ -81,15 +81,16 @@ export class HaTargetSelector extends LitElement {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`<ha-target-picker
|
return html` ${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||||
.hass=${this.hass}
|
<ha-target-picker
|
||||||
.value=${this.value}
|
.hass=${this.hass}
|
||||||
.helper=${this.helper}
|
.value=${this.value}
|
||||||
.deviceFilter=${this._filterDevices}
|
.helper=${this.helper}
|
||||||
.entityFilter=${this._filterEntities}
|
.deviceFilter=${this._filterDevices}
|
||||||
.disabled=${this.disabled}
|
.entityFilter=${this._filterEntities}
|
||||||
.createDomains=${this._createDomains}
|
.disabled=${this.disabled}
|
||||||
></ha-target-picker>`;
|
.createDomains=${this._createDomains}
|
||||||
|
></ha-target-picker>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _filterEntities = (entity: HassEntity): boolean => {
|
private _filterEntities = (entity: HassEntity): boolean => {
|
||||||
|
|||||||
@@ -497,6 +497,11 @@ export class HaServiceControl extends LitElement {
|
|||||||
dataField.name ||
|
dataField.name ||
|
||||||
dataField.key}
|
dataField.key}
|
||||||
>
|
>
|
||||||
|
${this._renderSectionDescription(
|
||||||
|
dataField,
|
||||||
|
domain,
|
||||||
|
serviceName
|
||||||
|
)}
|
||||||
${Object.entries(dataField.fields).map(([key, field]) =>
|
${Object.entries(dataField.fields).map(([key, field]) =>
|
||||||
this._renderField(
|
this._renderField(
|
||||||
{ key, ...field },
|
{ key, ...field },
|
||||||
@@ -517,6 +522,22 @@ export class HaServiceControl extends LitElement {
|
|||||||
)} `;
|
)} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderSectionDescription(
|
||||||
|
dataField: ExtHassService["fields"][number],
|
||||||
|
domain: string | undefined,
|
||||||
|
serviceName: string | undefined
|
||||||
|
) {
|
||||||
|
const description = this.hass!.localize(
|
||||||
|
`component.${domain}.services.${serviceName}.sections.${dataField.key}.description`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!description) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`<p>${description}</p>`;
|
||||||
|
}
|
||||||
|
|
||||||
private _renderField = (
|
private _renderField = (
|
||||||
dataField: ExtHassService["fields"][number],
|
dataField: ExtHassService["fields"][number],
|
||||||
hasOptional: boolean,
|
hasOptional: boolean,
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import {
|
|||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { UNAVAILABLE } from "./entity";
|
import { UNAVAILABLE } from "./entity";
|
||||||
|
|
||||||
export type LawnMowerEntityState = "paused" | "mowing" | "docked" | "error";
|
export type LawnMowerEntityState =
|
||||||
|
| "paused"
|
||||||
|
| "mowing"
|
||||||
|
| "returning"
|
||||||
|
| "docked"
|
||||||
|
| "error";
|
||||||
|
|
||||||
export const enum LawnMowerEntityFeature {
|
export const enum LawnMowerEntityFeature {
|
||||||
START_MOWING = 1,
|
START_MOWING = 1,
|
||||||
|
|||||||
@@ -101,8 +101,9 @@ export interface AttributeSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BooleanSelector {
|
export interface BooleanSelector {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
boolean: {
|
||||||
boolean: {} | null;
|
mode?: "checkbox" | "switch";
|
||||||
|
} | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ColorRGBSelector {
|
export interface ColorRGBSelector {
|
||||||
|
|||||||
@@ -140,10 +140,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
|||||||
|
|
||||||
const controlHA = !this._pipeline
|
const controlHA = !this._pipeline
|
||||||
? false
|
? false
|
||||||
: supportsFeature(
|
: this.hass.states[this._pipeline?.conversation_engine]
|
||||||
this.hass.states[this._pipeline?.conversation_engine],
|
? supportsFeature(
|
||||||
ConversationEntityFeature.CONTROL
|
this.hass.states[this._pipeline?.conversation_engine],
|
||||||
);
|
ConversationEntityFeature.CONTROL
|
||||||
|
)
|
||||||
|
: true;
|
||||||
const supportsMicrophone = AudioRecorder.isSupported;
|
const supportsMicrophone = AudioRecorder.isSupported;
|
||||||
const supportsSTT = this._pipeline?.stt_engine;
|
const supportsSTT = this._pipeline?.stt_engine;
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ const COMPONENTS = {
|
|||||||
history: () => import("../panels/history/ha-panel-history"),
|
history: () => import("../panels/history/ha-panel-history"),
|
||||||
iframe: () => import("../panels/iframe/ha-panel-iframe"),
|
iframe: () => import("../panels/iframe/ha-panel-iframe"),
|
||||||
logbook: () => import("../panels/logbook/ha-panel-logbook"),
|
logbook: () => import("../panels/logbook/ha-panel-logbook"),
|
||||||
mailbox: () => import("../panels/mailbox/ha-panel-mailbox"),
|
|
||||||
map: () => import("../panels/map/ha-panel-map"),
|
map: () => import("../panels/map/ha-panel-map"),
|
||||||
my: () => import("../panels/my/ha-panel-my"),
|
my: () => import("../panels/my/ha-panel-my"),
|
||||||
profile: () => import("../panels/profile/ha-panel-profile"),
|
profile: () => import("../panels/profile/ha-panel-profile"),
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
const documentContainer = document.createElement("template");
|
|
||||||
documentContainer.setAttribute("style", "display: none;");
|
|
||||||
|
|
||||||
documentContainer.innerHTML = `<dom-module id="ha-form-style">
|
|
||||||
<template>
|
|
||||||
<style>
|
|
||||||
.form-group {
|
|
||||||
@apply --layout-horizontal;
|
|
||||||
@apply --layout-center;
|
|
||||||
padding: 8px 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
@apply --layout-flex-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group .form-control {
|
|
||||||
@apply --layout-flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group.vertical {
|
|
||||||
@apply --layout-vertical;
|
|
||||||
@apply --layout-start;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</template>
|
|
||||||
</dom-module>`;
|
|
||||||
|
|
||||||
document.head.appendChild(documentContainer.content);
|
|
||||||
@@ -308,7 +308,7 @@ class HaPanelDevAction extends LitElement {
|
|||||||
|
|
||||||
private async _copyTemplate(): Promise<void> {
|
private async _copyTemplate(): Promise<void> {
|
||||||
await copyToClipboard(
|
await copyToClipboard(
|
||||||
`{% set action_response = ${JSON.stringify(this._response)} %}`
|
`{% set ${this._serviceData?.response_variable || "action_response"} = ${JSON.stringify(this._response)} %}`
|
||||||
);
|
);
|
||||||
showToast(this, {
|
showToast(this, {
|
||||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||||
|
|||||||
@@ -88,13 +88,8 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
: "dict"
|
: "dict"
|
||||||
: type;
|
: type;
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div class="content">
|
||||||
class="content ${classMap({
|
<div class="description">
|
||||||
layout: !this.narrow,
|
|
||||||
horizontal: !this.narrow,
|
|
||||||
})}"
|
|
||||||
>
|
|
||||||
<div class="edit-pane">
|
|
||||||
<p>
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.developer-tools.tabs.templates.description"
|
"ui.panel.developer-tools.tabs.templates.description"
|
||||||
@@ -126,123 +121,143 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.developer-tools.tabs.templates.editor"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<ha-code-editor
|
|
||||||
mode="jinja2"
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${this._template}
|
|
||||||
.error=${this._error}
|
|
||||||
autofocus
|
|
||||||
autocomplete-entities
|
|
||||||
autocomplete-icons
|
|
||||||
@value-changed=${this._templateChanged}
|
|
||||||
dir="ltr"
|
|
||||||
></ha-code-editor>
|
|
||||||
<mwc-button @click=${this._restoreDemo}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.developer-tools.tabs.templates.reset"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button @click=${this._clear}>
|
|
||||||
${this.hass.localize("ui.common.clear")}
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="content ${classMap({
|
||||||
|
layout: !this.narrow,
|
||||||
|
horizontal: !this.narrow,
|
||||||
|
})}"
|
||||||
|
>
|
||||||
|
<ha-card
|
||||||
|
class="edit-pane"
|
||||||
|
header=${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.templates.editor"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class="card-content">
|
||||||
|
<ha-code-editor
|
||||||
|
mode="jinja2"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this._template}
|
||||||
|
.error=${this._error}
|
||||||
|
autofocus
|
||||||
|
autocomplete-entities
|
||||||
|
autocomplete-icons
|
||||||
|
@value-changed=${this._templateChanged}
|
||||||
|
dir="ltr"
|
||||||
|
></ha-code-editor>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button @click=${this._restoreDemo}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.templates.reset"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button @click=${this._clear}>
|
||||||
|
${this.hass.localize("ui.common.clear")}
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
|
||||||
<div class="render-pane">
|
<ha-card
|
||||||
${this._rendering
|
class="render-pane"
|
||||||
? html`<ha-circular-progress
|
header=${this.hass.localize(
|
||||||
class="render-spinner"
|
"ui.panel.developer-tools.tabs.templates.result"
|
||||||
indeterminate
|
)}
|
||||||
size="small"
|
>
|
||||||
></ha-circular-progress>`
|
<div class="card-content">
|
||||||
: ""}
|
${this._rendering
|
||||||
${this._error
|
? html`<ha-circular-progress
|
||||||
? html`<ha-alert
|
class="render-spinner"
|
||||||
alert-type=${this._errorLevel?.toLowerCase() || "error"}
|
indeterminate
|
||||||
>${this._error}</ha-alert
|
size="small"
|
||||||
>`
|
></ha-circular-progress>`
|
||||||
: nothing}
|
: ""}
|
||||||
${this._templateResult
|
${this._error
|
||||||
? html`${this.hass.localize(
|
? html`<ha-alert
|
||||||
"ui.panel.developer-tools.tabs.templates.result_type"
|
alert-type=${this._errorLevel?.toLowerCase() || "error"}
|
||||||
)}:
|
>${this._error}</ha-alert
|
||||||
${resultType}
|
>`
|
||||||
<!-- prettier-ignore -->
|
: nothing}
|
||||||
<pre class="rendered ${classMap({
|
${this._templateResult
|
||||||
[resultType]: resultType,
|
? html`${this.hass.localize(
|
||||||
})}"
|
"ui.panel.developer-tools.tabs.templates.result_type"
|
||||||
>${type === "object"
|
)}:
|
||||||
? JSON.stringify(this._templateResult.result, null, 2)
|
${resultType}
|
||||||
: this._templateResult.result}</pre>
|
<!-- prettier-ignore -->
|
||||||
${this._templateResult.listeners.time
|
<pre class="rendered ${classMap({
|
||||||
? html`
|
[resultType]: resultType,
|
||||||
<p>
|
})}"
|
||||||
${this.hass.localize(
|
>${type === "object"
|
||||||
"ui.panel.developer-tools.tabs.templates.time"
|
? JSON.stringify(this._templateResult.result, null, 2)
|
||||||
)}
|
: this._templateResult.result}</pre>
|
||||||
</p>
|
${this._templateResult.listeners.time
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${!this._templateResult.listeners
|
|
||||||
? nothing
|
|
||||||
: this._templateResult.listeners.all
|
|
||||||
? html`
|
? html`
|
||||||
<p class="all_listeners">
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.developer-tools.tabs.templates.all_listeners"
|
"ui.panel.developer-tools.tabs.templates.time"
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
`
|
`
|
||||||
: this._templateResult.listeners.domains.length ||
|
: ""}
|
||||||
this._templateResult.listeners.entities.length
|
${!this._templateResult.listeners
|
||||||
|
? nothing
|
||||||
|
: this._templateResult.listeners.all
|
||||||
? html`
|
? html`
|
||||||
<p>
|
<p class="all_listeners">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.developer-tools.tabs.templates.listeners"
|
"ui.panel.developer-tools.tabs.templates.all_listeners"
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
|
||||||
${this._templateResult.listeners.domains
|
|
||||||
.sort()
|
|
||||||
.map(
|
|
||||||
(domain) => html`
|
|
||||||
<li>
|
|
||||||
<b
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.developer-tools.tabs.templates.domain"
|
|
||||||
)}</b
|
|
||||||
>: ${domain}
|
|
||||||
</li>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
${this._templateResult.listeners.entities
|
|
||||||
.sort()
|
|
||||||
.map(
|
|
||||||
(entity_id) => html`
|
|
||||||
<li>
|
|
||||||
<b
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.developer-tools.tabs.templates.entity"
|
|
||||||
)}</b
|
|
||||||
>: ${entity_id}
|
|
||||||
</li>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</ul>
|
|
||||||
`
|
`
|
||||||
: !this._templateResult.listeners.time
|
: this._templateResult.listeners.domains.length ||
|
||||||
? html`<span class="all_listeners">
|
this._templateResult.listeners.entities.length
|
||||||
${this.hass.localize(
|
? html`
|
||||||
"ui.panel.developer-tools.tabs.templates.no_listeners"
|
<p>
|
||||||
)}
|
${this.hass.localize(
|
||||||
</span>`
|
"ui.panel.developer-tools.tabs.templates.listeners"
|
||||||
: nothing}`
|
)}
|
||||||
: nothing}
|
</p>
|
||||||
</div>
|
<ul>
|
||||||
|
${this._templateResult.listeners.domains
|
||||||
|
.sort()
|
||||||
|
.map(
|
||||||
|
(domain) => html`
|
||||||
|
<li>
|
||||||
|
<b
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.templates.domain"
|
||||||
|
)}</b
|
||||||
|
>: ${domain}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
${this._templateResult.listeners.entities
|
||||||
|
.sort()
|
||||||
|
.map(
|
||||||
|
(entity_id) => html`
|
||||||
|
<li>
|
||||||
|
<b
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.templates.entity"
|
||||||
|
)}</b
|
||||||
|
>: ${entity_id}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
`
|
||||||
|
: !this._templateResult.listeners.time
|
||||||
|
? html`<span class="all_listeners">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.developer-tools.tabs.templates.no_listeners"
|
||||||
|
)}
|
||||||
|
</span>`
|
||||||
|
: nothing}`
|
||||||
|
: nothing}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -258,6 +273,7 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
gap: 16px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
padding: max(16px, env(safe-area-inset-top))
|
padding: max(16px, env(safe-area-inset-top))
|
||||||
max(16px, env(safe-area-inset-right))
|
max(16px, env(safe-area-inset-right))
|
||||||
@@ -265,10 +281,11 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
max(16px, env(safe-area-inset-left));
|
max(16px, env(safe-area-inset-left));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-card {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.edit-pane {
|
.edit-pane {
|
||||||
margin-right: 16px;
|
|
||||||
margin-inline-start: initial;
|
|
||||||
margin-inline-end: 16px;
|
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,12 +297,6 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.render-pane {
|
|
||||||
position: relative;
|
|
||||||
max-width: 50%;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.render-spinner {
|
.render-spinner {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { classMap } from "lit/directives/class-map";
|
|||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { mdiAlertCircle } from "@mdi/js";
|
||||||
import { computeCssColor } from "../../../common/color/compute-color";
|
import { computeCssColor } from "../../../common/color/compute-color";
|
||||||
import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color";
|
import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
@@ -12,6 +13,7 @@ import { stateActive } from "../../../common/entity/state_active";
|
|||||||
import { stateColorCss } from "../../../common/entity/state_color";
|
import { stateColorCss } from "../../../common/entity/state_color";
|
||||||
import "../../../components/ha-ripple";
|
import "../../../components/ha-ripple";
|
||||||
import "../../../components/ha-state-icon";
|
import "../../../components/ha-state-icon";
|
||||||
|
import "../../../components/ha-svg-icon";
|
||||||
import { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
import { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||||
@@ -129,7 +131,17 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
|||||||
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
||||||
|
|
||||||
if (!stateObj) {
|
if (!stateObj) {
|
||||||
return nothing;
|
return html`
|
||||||
|
<div class="badge error">
|
||||||
|
<ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon>
|
||||||
|
<span class="content">
|
||||||
|
<span class="name">${entityId}</span>
|
||||||
|
<span class="state">
|
||||||
|
${this.hass.localize("ui.badge.entity.not_found")}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const active = stateActive(stateObj);
|
const active = stateActive(stateObj);
|
||||||
@@ -206,6 +218,9 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
|||||||
--badge-color: var(--state-inactive-color);
|
--badge-color: var(--state-inactive-color);
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
.badge.error {
|
||||||
|
--badge-color: var(--red-color);
|
||||||
|
}
|
||||||
.badge {
|
.badge {
|
||||||
position: relative;
|
position: relative;
|
||||||
--ha-ripple-color: var(--badge-color);
|
--ha-ripple-color: var(--badge-color);
|
||||||
@@ -225,8 +240,14 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: auto;
|
width: auto;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
background-color: var(--card-background-color, white);
|
background: var(
|
||||||
|
--ha-card-background,
|
||||||
|
var(--card-background-color, white)
|
||||||
|
);
|
||||||
|
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
|
||||||
|
backdrop-filter: var(--ha-card-backdrop-filter, none);
|
||||||
border-width: var(--ha-card-border-width, 1px);
|
border-width: var(--ha-card-border-width, 1px);
|
||||||
|
box-shadow: var(--ha-card-box-shadow, none);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: var(
|
border-color: var(
|
||||||
--ha-card-border-color,
|
--ha-card-border-color,
|
||||||
@@ -277,7 +298,8 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
|||||||
letter-spacing: 0.1px;
|
letter-spacing: 0.1px;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
ha-state-icon {
|
ha-state-icon,
|
||||||
|
ha-svg-icon {
|
||||||
color: var(--badge-color);
|
color: var(--badge-color);
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,12 @@ class HuiAlarmModeCardFeature
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _setMode(mode: AlarmMode) {
|
private async _setMode(mode: AlarmMode) {
|
||||||
setProtectedAlarmControlPanelMode(this, this.hass!, this.stateObj!, mode);
|
await setProtectedAlarmControlPanelMode(
|
||||||
|
this,
|
||||||
|
this.hass!,
|
||||||
|
this.stateObj!,
|
||||||
|
mode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult | null {
|
protected render(): TemplateResult | null {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ const HIDE_DOMAIN = new Set([
|
|||||||
"persistent_notification",
|
"persistent_notification",
|
||||||
"script",
|
"script",
|
||||||
"sun",
|
"sun",
|
||||||
|
"tag",
|
||||||
"todo",
|
"todo",
|
||||||
"zone",
|
"zone",
|
||||||
...ASSIST_ENTITIES,
|
...ASSIST_ENTITIES,
|
||||||
|
|||||||
@@ -226,8 +226,15 @@ export class HuiActionEditor extends LitElement {
|
|||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let action = this.config?.action;
|
||||||
|
|
||||||
|
if (action === "call-service") {
|
||||||
|
action = "perform-action";
|
||||||
|
}
|
||||||
|
|
||||||
const value = ev.target.value;
|
const value = ev.target.value;
|
||||||
if (this.config?.action === value) {
|
|
||||||
|
if (action === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (value === "default") {
|
if (value === "default") {
|
||||||
@@ -292,6 +299,7 @@ export class HuiActionEditor extends LitElement {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const value = {
|
const value = {
|
||||||
...this.config!,
|
...this.config!,
|
||||||
|
action: "perform-action",
|
||||||
perform_action: ev.detail.value.action || "",
|
perform_action: ev.detail.value.action || "",
|
||||||
data: ev.detail.value.data,
|
data: ev.detail.value.data,
|
||||||
target: ev.detail.value.target || {},
|
target: ev.detail.value.target || {},
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
import { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||||
import "../cards/hui-button-card";
|
|
||||||
import "../cards/hui-calendar-card";
|
|
||||||
import "../cards/hui-entities-card";
|
|
||||||
import "../cards/hui-entity-button-card";
|
|
||||||
import "../cards/hui-entity-card";
|
import "../cards/hui-entity-card";
|
||||||
|
import "../cards/hui-entities-card";
|
||||||
|
import "../cards/hui-button-card";
|
||||||
|
import "../cards/hui-entity-button-card";
|
||||||
import "../cards/hui-glance-card";
|
import "../cards/hui-glance-card";
|
||||||
import "../cards/hui-grid-card";
|
import "../cards/hui-grid-card";
|
||||||
import "../cards/hui-light-card";
|
import "../cards/hui-light-card";
|
||||||
import "../cards/hui-sensor-card";
|
import "../cards/hui-sensor-card";
|
||||||
import "../cards/hui-thermostat-card";
|
import "../cards/hui-thermostat-card";
|
||||||
import "../cards/hui-tile-card";
|
|
||||||
import "../cards/hui-weather-forecast-card";
|
import "../cards/hui-weather-forecast-card";
|
||||||
|
import "../cards/hui-tile-card";
|
||||||
import {
|
import {
|
||||||
createLovelaceElement,
|
createLovelaceElement,
|
||||||
getLovelaceElementClass,
|
getLovelaceElementClass,
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export class HaCardConditionState extends LitElement {
|
|||||||
const data: StateConditionData = {
|
const data: StateConditionData = {
|
||||||
...content,
|
...content,
|
||||||
entity: this.condition.entity,
|
entity: this.condition.entity,
|
||||||
invert: this.condition.state_not ? "true" : "false",
|
invert: this.condition.state_not !== undefined ? "true" : "false",
|
||||||
state: this.condition.state_not ?? this.condition.state,
|
state: this.condition.state_not ?? this.condition.state,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ const actionConfigStructService = object({
|
|||||||
entity_id: optional(union([string(), array(string())])),
|
entity_id: optional(union([string(), array(string())])),
|
||||||
device_id: optional(union([string(), array(string())])),
|
device_id: optional(union([string(), array(string())])),
|
||||||
area_id: optional(union([string(), array(string())])),
|
area_id: optional(union([string(), array(string())])),
|
||||||
|
floor_id: optional(union([string(), array(string())])),
|
||||||
|
label_id: optional(union([string(), array(string())])),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
confirmation: optional(actionConfigStructConfirmation),
|
confirmation: optional(actionConfigStructConfirmation),
|
||||||
|
|||||||
@@ -231,14 +231,15 @@ class LovelaceFullConfigEditor extends LitElement {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
showConfirmationDialog(this, {
|
showConfirmationDialog(this, {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
"ui.panel.lovelace.editor.raw_editor.confirm_remove_config_title"
|
"ui.panel.lovelace.editor.raw_editor.confirm_delete_config_title"
|
||||||
),
|
),
|
||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.lovelace.editor.raw_editor.confirm_remove_config_text"
|
"ui.panel.lovelace.editor.raw_editor.confirm_delete_config_text"
|
||||||
),
|
),
|
||||||
confirmText: this.hass.localize("ui.common.remove"),
|
confirmText: this.hass.localize("ui.common.delete"),
|
||||||
dismissText: this.hass.localize("ui.common.cancel"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
confirm: () => this._removeConfig(),
|
confirm: () => this._removeConfig(),
|
||||||
|
destructive: true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
|||||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
import { HuiBadge } from "../badges/hui-badge";
|
||||||
|
import "../badges/hui-view-badges";
|
||||||
import { HuiCard } from "../cards/hui-card";
|
import { HuiCard } from "../cards/hui-card";
|
||||||
import { HuiCardOptions } from "../components/hui-card-options";
|
import { HuiCardOptions } from "../components/hui-card-options";
|
||||||
import { replaceCard } from "../editor/config-util";
|
import { replaceCard } from "../editor/config-util";
|
||||||
@@ -28,6 +30,8 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public cards: HuiCard[] = [];
|
@property({ attribute: false }) public cards: HuiCard[] = [];
|
||||||
|
|
||||||
|
@property({ attribute: false }) public badges: HuiBadge[] = [];
|
||||||
|
|
||||||
@state() private _config?: LovelaceViewConfig;
|
@state() private _config?: LovelaceViewConfig;
|
||||||
|
|
||||||
private _mqlListenerRef?: () => void;
|
private _mqlListenerRef?: () => void;
|
||||||
@@ -85,6 +89,12 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
|
<hui-view-badges
|
||||||
|
.hass=${this.hass}
|
||||||
|
.badges=${this.badges}
|
||||||
|
.lovelace=${this.lovelace}
|
||||||
|
.viewIndex=${this.index}
|
||||||
|
></hui-view-badges>
|
||||||
<div
|
<div
|
||||||
class="container ${this.lovelace?.editMode ? "edit-mode" : ""}"
|
class="container ${this.lovelace?.editMode ? "edit-mode" : ""}"
|
||||||
></div>
|
></div>
|
||||||
@@ -191,6 +201,12 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
|||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hui-view-badges {
|
||||||
|
display: block;
|
||||||
|
margin: 12px 8px 20px 8px;
|
||||||
|
font-size: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -1,153 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "../../components/ha-dialog";
|
|
||||||
import "../../components/ha-circular-progress";
|
|
||||||
import "../../components/ha-icon";
|
|
||||||
import "../../components/ha-icon-button";
|
|
||||||
import {
|
|
||||||
CSSResultGroup,
|
|
||||||
LitElement,
|
|
||||||
TemplateResult,
|
|
||||||
css,
|
|
||||||
html,
|
|
||||||
nothing,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
|
||||||
|
|
||||||
@customElement("ha-dialog-show-audio-message")
|
|
||||||
class HaDialogShowAudioMessage extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() private _currentMessage?: any;
|
|
||||||
|
|
||||||
@state() private _errorMsg?: string;
|
|
||||||
|
|
||||||
@state() private _loading: boolean = false;
|
|
||||||
|
|
||||||
@state() private _opened: boolean = false;
|
|
||||||
|
|
||||||
@state() private _blobUrl?: string;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<ha-dialog
|
|
||||||
.open=${this._opened}
|
|
||||||
@closed=${this._closeDialog}
|
|
||||||
heading=${this.hass.localize("ui.panel.mailbox.playback_title")}
|
|
||||||
>
|
|
||||||
${this._loading
|
|
||||||
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
|
|
||||||
: html`<div class="icon">
|
|
||||||
<ha-icon-button id="delicon" @click=${this._openDeleteDialog}>
|
|
||||||
<ha-icon icon="hass:delete"></ha-icon>
|
|
||||||
</ha-icon-button>
|
|
||||||
</div>
|
|
||||||
${
|
|
||||||
this._currentMessage
|
|
||||||
? html`<div id="transcribe">
|
|
||||||
${this._currentMessage?.message}
|
|
||||||
</div>`
|
|
||||||
: nothing
|
|
||||||
}
|
|
||||||
${
|
|
||||||
this._errorMsg
|
|
||||||
? html`<div class="error">${this._errorMsg}</div>`
|
|
||||||
: nothing
|
|
||||||
}
|
|
||||||
${
|
|
||||||
this._blobUrl
|
|
||||||
? html` <audio id="mp3" preload="none" controls autoplay>
|
|
||||||
<source
|
|
||||||
id="mp3src"
|
|
||||||
src=${this._blobUrl}
|
|
||||||
type="audio/mpeg"
|
|
||||||
/>
|
|
||||||
</audio>`
|
|
||||||
: nothing
|
|
||||||
}
|
|
||||||
</div>`}
|
|
||||||
</ha-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
showDialog({ hass, message }) {
|
|
||||||
this.hass = hass;
|
|
||||||
this._errorMsg = undefined;
|
|
||||||
this._currentMessage = message;
|
|
||||||
this._opened = true;
|
|
||||||
const platform = message.platform;
|
|
||||||
if (platform.has_media) {
|
|
||||||
this._loading = true;
|
|
||||||
const url = `/api/mailbox/media/${platform.name}/${message.sha}`;
|
|
||||||
this.hass
|
|
||||||
.fetchWithAuth(url)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.blob();
|
|
||||||
}
|
|
||||||
return Promise.reject({
|
|
||||||
status: response.status,
|
|
||||||
statusText: response.statusText,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then((blob) => {
|
|
||||||
this._loading = false;
|
|
||||||
this._blobUrl = window.URL.createObjectURL(blob);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
this._loading = false;
|
|
||||||
this._errorMsg = `Error loading audio: ${err.statusText}`;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this._loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _openDeleteDialog() {
|
|
||||||
if (confirm(this.hass.localize("ui.panel.mailbox.delete_prompt"))) {
|
|
||||||
this._deleteSelected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _deleteSelected() {
|
|
||||||
const msg = this._currentMessage;
|
|
||||||
this.hass.callApi(
|
|
||||||
"DELETE",
|
|
||||||
`mailbox/delete/${msg.platform.name}/${msg.sha}`
|
|
||||||
);
|
|
||||||
this._closeDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _closeDialog() {
|
|
||||||
const mp3 = this.shadowRoot!.querySelector("#mp3")! as any;
|
|
||||||
mp3.pause();
|
|
||||||
this._currentMessage = undefined;
|
|
||||||
this._errorMsg = undefined;
|
|
||||||
this._loading = false;
|
|
||||||
this._opened = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
.icon {
|
|
||||||
text-align: var(--float-end);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-dialog-show-audio-message": HaDialogShowAudioMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
import {
|
|
||||||
CSSResultGroup,
|
|
||||||
LitElement,
|
|
||||||
TemplateResult,
|
|
||||||
css,
|
|
||||||
html,
|
|
||||||
nothing,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import "@material/mwc-button";
|
|
||||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
|
||||||
import "../../components/ha-card";
|
|
||||||
import "../../components/ha-menu-button";
|
|
||||||
import "../../components/ha-tabs";
|
|
||||||
import "@polymer/paper-tabs/paper-tab";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
|
||||||
import { haStyle } from "../../resources/styles";
|
|
||||||
import "../../components/ha-top-app-bar-fixed";
|
|
||||||
import { formatDuration } from "../../common/datetime/format_duration";
|
|
||||||
|
|
||||||
let registeredDialog = false;
|
|
||||||
|
|
||||||
interface MailboxMessage {
|
|
||||||
info: {
|
|
||||||
origtime: number;
|
|
||||||
callerid: string;
|
|
||||||
duration: string;
|
|
||||||
};
|
|
||||||
text: string;
|
|
||||||
sha: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ha-panel-mailbox")
|
|
||||||
class HaPanelMailbox extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public narrow!: boolean;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public platforms?: any[];
|
|
||||||
|
|
||||||
@state() private _messages?: any[];
|
|
||||||
|
|
||||||
@state() private _currentPlatform: number = 0;
|
|
||||||
|
|
||||||
private _unsubEvents?;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<ha-top-app-bar-fixed>
|
|
||||||
<ha-menu-button
|
|
||||||
slot="navigationIcon"
|
|
||||||
.hass=${this.hass}
|
|
||||||
.narrow=${this.narrow}
|
|
||||||
></ha-menu-button>
|
|
||||||
<div slot="title">${this.hass.localize("panel.mailbox")}</div>
|
|
||||||
${!this._areTabsHidden(this.platforms)
|
|
||||||
? html`<div sticky>
|
|
||||||
<ha-tabs
|
|
||||||
scrollable
|
|
||||||
.selected=${this._currentPlatform}
|
|
||||||
@iron-activate=${this._handlePlatformSelected}
|
|
||||||
>
|
|
||||||
${this.platforms?.map(
|
|
||||||
(platform) =>
|
|
||||||
html` <paper-tab data-entity=${platform}>
|
|
||||||
${this._getPlatformName(platform)}
|
|
||||||
</paper-tab>`
|
|
||||||
)}
|
|
||||||
</ha-tabs>
|
|
||||||
</div>`
|
|
||||||
: ""}
|
|
||||||
</ha-top-app-bar-fixed>
|
|
||||||
<div class="content">
|
|
||||||
<ha-card>
|
|
||||||
${!this._messages?.length
|
|
||||||
? html`<div class="card-content empty">
|
|
||||||
${this.hass.localize("ui.panel.mailbox.empty")}
|
|
||||||
</div>`
|
|
||||||
: nothing}
|
|
||||||
${this._messages?.map(
|
|
||||||
(message) =>
|
|
||||||
html` <ha-list-item
|
|
||||||
.message=${message}
|
|
||||||
@click=${this._openMP3Dialog}
|
|
||||||
twoline
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<span>${message.caller}</span>
|
|
||||||
<span class="tip">
|
|
||||||
${formatDuration(this.hass.locale, {
|
|
||||||
seconds: message.duration,
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
<span slot="secondary">
|
|
||||||
<span class="date">${message.timestamp}</span> -
|
|
||||||
${message.message}
|
|
||||||
</span>
|
|
||||||
</ha-list-item>`
|
|
||||||
)}
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
if (!registeredDialog) {
|
|
||||||
registeredDialog = true;
|
|
||||||
fireEvent(this, "register-dialog", {
|
|
||||||
dialogShowEvent: "show-audio-message-dialog",
|
|
||||||
dialogTag: "ha-dialog-show-audio-message",
|
|
||||||
dialogImport: () => import("./ha-dialog-show-audio-message"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.hassChanged = this.hassChanged.bind(this);
|
|
||||||
this.hass.connection
|
|
||||||
.subscribeEvents(this.hassChanged, "mailbox_updated")
|
|
||||||
.then((unsub) => {
|
|
||||||
this._unsubEvents = unsub;
|
|
||||||
});
|
|
||||||
this._computePlatforms().then((platforms) => {
|
|
||||||
this.platforms = platforms;
|
|
||||||
this.hassChanged();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
if (this._unsubEvents) this._unsubEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
hassChanged() {
|
|
||||||
if (!this._messages) {
|
|
||||||
this._messages = [];
|
|
||||||
}
|
|
||||||
this._getMessages().then((items) => {
|
|
||||||
this._messages = items;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _openMP3Dialog(ev) {
|
|
||||||
const message: any = (ev.currentTarget! as any).message;
|
|
||||||
fireEvent(this, "show-audio-message-dialog", {
|
|
||||||
hass: this.hass,
|
|
||||||
message: message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getMessages() {
|
|
||||||
const platform = this.platforms![this._currentPlatform];
|
|
||||||
return this.hass
|
|
||||||
.callApi<MailboxMessage[]>("GET", `mailbox/messages/${platform.name}`)
|
|
||||||
.then((values) => {
|
|
||||||
const platformItems: any[] = [];
|
|
||||||
const arrayLength = values.length;
|
|
||||||
for (let i = 0; i < arrayLength; i++) {
|
|
||||||
const datetime = formatDateTime(
|
|
||||||
new Date(values[i].info.origtime * 1000),
|
|
||||||
this.hass.locale,
|
|
||||||
this.hass.config
|
|
||||||
);
|
|
||||||
platformItems.push({
|
|
||||||
timestamp: datetime,
|
|
||||||
caller: values[i].info.callerid,
|
|
||||||
message: values[i].text,
|
|
||||||
sha: values[i].sha,
|
|
||||||
duration: values[i].info.duration,
|
|
||||||
platform: platform,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return platformItems.sort((a, b) => b.timestamp - a.timestamp);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computePlatforms(): Promise<any[]> {
|
|
||||||
return this.hass.callApi<any[]>("GET", "mailbox/platforms");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handlePlatformSelected(ev) {
|
|
||||||
const newPlatform = ev.detail.selected;
|
|
||||||
if (newPlatform !== this._currentPlatform) {
|
|
||||||
this._currentPlatform = newPlatform;
|
|
||||||
this.hassChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _areTabsHidden(platforms) {
|
|
||||||
return !platforms || platforms.length < 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _getPlatformName(item) {
|
|
||||||
const entity = `mailbox.${item.name}`;
|
|
||||||
const stateObj = this.hass.states[entity.toLowerCase()];
|
|
||||||
return stateObj.attributes.friendly_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyle,
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
-ms-user-select: initial;
|
|
||||||
-webkit-user-select: initial;
|
|
||||||
-moz-user-select: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
padding: 16px;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-card {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-tabs {
|
|
||||||
margin-left: max(env(safe-area-inset-left), 24px);
|
|
||||||
margin-right: max(env(safe-area-inset-right), 24px);
|
|
||||||
margin-inline-start: max(env(safe-area-inset-left), 24px);
|
|
||||||
margin-inline-end: max(env(safe-area-inset-right), 24px);
|
|
||||||
--paper-tabs-selection-bar-color: #fff;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
@apply --paper-font-title;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media all and (max-width: 450px) {
|
|
||||||
.content {
|
|
||||||
width: auto;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tip {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.date {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-panel-mailbox": HaPanelMailbox;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
// for fire event
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"show-audio-message-dialog": {
|
|
||||||
hass: HomeAssistant;
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -364,6 +364,9 @@ class DialogTodoItemEditor extends LitElement {
|
|||||||
"ui.components.todo.item.confirm_delete.delete"
|
"ui.components.todo.item.confirm_delete.delete"
|
||||||
),
|
),
|
||||||
text: this.hass.localize("ui.components.todo.item.confirm_delete.prompt"),
|
text: this.hass.localize("ui.components.todo.item.confirm_delete.prompt"),
|
||||||
|
destructive: true,
|
||||||
|
confirmText: this.hass.localize("ui.common.delete"),
|
||||||
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
});
|
});
|
||||||
if (!confirm) {
|
if (!confirm) {
|
||||||
// Cancel
|
// Cancel
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"map": "Map",
|
"map": "Map",
|
||||||
"logbook": "Logbook",
|
"logbook": "Logbook",
|
||||||
"history": "History",
|
"history": "History",
|
||||||
"mailbox": "Mailbox",
|
|
||||||
"todo": "To-do lists",
|
"todo": "To-do lists",
|
||||||
"developer_tools": "Developer tools",
|
"developer_tools": "Developer tools",
|
||||||
"media_browser": "Media",
|
"media_browser": "Media",
|
||||||
@@ -71,6 +70,11 @@
|
|||||||
"backup": {
|
"backup": {
|
||||||
"upload_backup": "Upload backup"
|
"upload_backup": "Upload backup"
|
||||||
},
|
},
|
||||||
|
"badge": {
|
||||||
|
"entity": {
|
||||||
|
"not_found": "[%key:ui::card::tile::not_found%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
"card": {
|
"card": {
|
||||||
"common": {
|
"common": {
|
||||||
"turn_on": "Turn on",
|
"turn_on": "Turn on",
|
||||||
@@ -171,6 +175,7 @@
|
|||||||
"actions": {
|
"actions": {
|
||||||
"resume_mowing": "Resume mowing",
|
"resume_mowing": "Resume mowing",
|
||||||
"start_mowing": "Start mowing",
|
"start_mowing": "Start mowing",
|
||||||
|
"pause": "Pause",
|
||||||
"dock": "Return to dock"
|
"dock": "Return to dock"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -5492,8 +5497,8 @@
|
|||||||
"saved": "Saved",
|
"saved": "Saved",
|
||||||
"reload": "Reload",
|
"reload": "Reload",
|
||||||
"lovelace_changed": "Your dashboard was updated, do you want to load the updated config in the editor and lose your current changes?",
|
"lovelace_changed": "Your dashboard was updated, do you want to load the updated config in the editor and lose your current changes?",
|
||||||
"confirm_remove_config_title": "Are you sure you want to remove your dashboard configuration?",
|
"confirm_delete_config_title": "Delete dashboard configuration?",
|
||||||
"confirm_remove_config_text": "We will automatically generate your dashboard views with your areas and devices if you remove your dashboard configuration.",
|
"confirm_delete_config_text": "This dashboard will be permanently deleted. The dashboard will be automatically regenerated to display your areas, devices and entities.",
|
||||||
"confirm_unsaved_changes": "You have unsaved changes, are you sure you want to exit?",
|
"confirm_unsaved_changes": "You have unsaved changes, are you sure you want to exit?",
|
||||||
"confirm_unsaved_comments": "Your configuration might contains comment(s), these will not be saved. Do you want to continue?",
|
"confirm_unsaved_comments": "Your configuration might contains comment(s), these will not be saved. Do you want to continue?",
|
||||||
"error_parse_yaml": "Unable to parse YAML: {error}",
|
"error_parse_yaml": "Unable to parse YAML: {error}",
|
||||||
@@ -6355,12 +6360,6 @@
|
|||||||
},
|
},
|
||||||
"reload_lovelace": "Reload UI"
|
"reload_lovelace": "Reload UI"
|
||||||
},
|
},
|
||||||
"mailbox": {
|
|
||||||
"empty": "You do not have any messages",
|
|
||||||
"playback_title": "Message playback",
|
|
||||||
"delete_prompt": "Delete this message?",
|
|
||||||
"delete_button": "Delete"
|
|
||||||
},
|
|
||||||
"media-browser": {
|
"media-browser": {
|
||||||
"error": {
|
"error": {
|
||||||
"player_not_exist": "Media player {name} does not exist"
|
"player_not_exist": "Media player {name} does not exist"
|
||||||
@@ -6857,6 +6856,7 @@
|
|||||||
"title": "Template",
|
"title": "Template",
|
||||||
"description": "Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.",
|
"description": "Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.",
|
||||||
"editor": "Template editor",
|
"editor": "Template editor",
|
||||||
|
"result": "Result",
|
||||||
"reset": "Reset to demo template",
|
"reset": "Reset to demo template",
|
||||||
"confirm_reset": "Do you want to reset your current template back to the demo template?",
|
"confirm_reset": "Do you want to reset your current template back to the demo template?",
|
||||||
"confirm_clear": "Do you want to clear your current template?",
|
"confirm_clear": "Do you want to clear your current template?",
|
||||||
|
|||||||
Reference in New Issue
Block a user