mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-10 01:57:20 +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:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.3.5
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: frontend-bundle-stats
|
||||
path: build/stats/*.json
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.3.5
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: supervisor-bundle-stats
|
||||
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
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4.3.5
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/home_assistant_frontend*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@v4.3.5
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: translations
|
||||
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
|
||||
steps:
|
||||
- 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:
|
||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||
token: ${{ github.token }}
|
||||
|
||||
@@ -1,35 +1,76 @@
|
||||
// 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 gulp from "gulp";
|
||||
import { minify } from "html-minifier-terser";
|
||||
import template from "lodash.template";
|
||||
import path from "path";
|
||||
import { dirname, extname, resolve } from "node:path";
|
||||
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
|
||||
import env from "../env.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 compiled = template(
|
||||
fs.readFileSync(templateFile, { encoding: "utf-8" })
|
||||
);
|
||||
return compiled({
|
||||
...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
|
||||
renderTemplate: (childTemplate) =>
|
||||
renderTemplate(
|
||||
path.resolve(path.dirname(templateFile), childTemplate),
|
||||
data
|
||||
),
|
||||
renderTemplate(resolve(dirname(templateFile), childTemplate), data),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -63,10 +104,12 @@ const genPagesDevTask =
|
||||
publicRoot = ""
|
||||
) =>
|
||||
async () => {
|
||||
const commonVars = getCommonTemplateVars();
|
||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||
const content = renderTemplate(
|
||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
||||
resolve(inputRoot, inputSub, `${page}.template`),
|
||||
{
|
||||
...commonVars,
|
||||
latestEntryJS: entries.map((entry) =>
|
||||
useWDS
|
||||
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
||||
@@ -81,7 +124,7 @@ const genPagesDevTask =
|
||||
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 () => {
|
||||
const latestManifest = fs.readJsonSync(
|
||||
path.resolve(outputLatest, "manifest.json")
|
||||
resolve(outputLatest, "manifest.json")
|
||||
);
|
||||
const es5Manifest = outputES5
|
||||
? fs.readJsonSync(path.resolve(outputES5, "manifest.json"))
|
||||
? fs.readJsonSync(resolve(outputES5, "manifest.json"))
|
||||
: {};
|
||||
const commonVars = getCommonTemplateVars();
|
||||
const minifiedHTML = [];
|
||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||
const content = renderTemplate(
|
||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
||||
resolve(inputRoot, inputSub, `${page}.template`),
|
||||
{
|
||||
...commonVars,
|
||||
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
|
||||
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
|
||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||
@@ -115,8 +160,8 @@ const genPagesProdTask =
|
||||
}
|
||||
);
|
||||
minifiedHTML.push(
|
||||
minifyHtml(content, path.extname(page)).then((minified) =>
|
||||
fs.outputFileSync(path.resolve(outputRoot, page), minified)
|
||||
minifyHtml(content, extname(page)).then((minified) =>
|
||||
fs.outputFileSync(resolve(outputRoot, page), minified)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -532,15 +532,6 @@ export default {
|
||||
last_changed: "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": {
|
||||
entity_id: "input_select.living_room_preset",
|
||||
state: "Visitors",
|
||||
|
||||
42
package.json
42
package.json
@@ -25,15 +25,15 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.25.0",
|
||||
"@babel/runtime": "7.25.4",
|
||||
"@braintree/sanitize-url": "7.1.0",
|
||||
"@codemirror/autocomplete": "6.18.0",
|
||||
"@codemirror/commands": "6.6.0",
|
||||
"@codemirror/language": "6.10.2",
|
||||
"@codemirror/legacy-modes": "6.4.0",
|
||||
"@codemirror/legacy-modes": "6.4.1",
|
||||
"@codemirror/search": "6.5.6",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@codemirror/view": "6.30.0",
|
||||
"@codemirror/view": "6.32.0",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||
"@formatjs/intl-displaynames": "6.6.8",
|
||||
@@ -49,7 +49,7 @@
|
||||
"@fullcalendar/list": "6.1.15",
|
||||
"@fullcalendar/luxon3": "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/motion": "1.0.7",
|
||||
"@lit-labs/observers": "2.0.2",
|
||||
@@ -80,7 +80,7 @@
|
||||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.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/svg": "7.4.47",
|
||||
"@polymer/paper-item": "3.0.1",
|
||||
@@ -88,8 +88,8 @@
|
||||
"@polymer/paper-tabs": "3.1.0",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.4.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.5",
|
||||
"@vaadin/combo-box": "24.4.6",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.6",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "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/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"chart.js": "4.4.3",
|
||||
"chart.js": "4.4.4",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.1",
|
||||
"core-js": "3.38.0",
|
||||
"core-js": "3.38.1",
|
||||
"cropperjs": "1.6.2",
|
||||
"date-fns": "3.6.0",
|
||||
"date-fns-tz": "3.1.3",
|
||||
@@ -118,7 +118,7 @@
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.8.0",
|
||||
"luxon": "3.5.0",
|
||||
"marked": "13.0.3",
|
||||
"marked": "14.0.0",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
@@ -130,7 +130,7 @@
|
||||
"sortablejs": "1.15.2",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "2.1.0",
|
||||
"tinykeys": "3.0.0",
|
||||
"tsparticles-engine": "2.12.0",
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
"ua-parser-js": "1.0.38",
|
||||
@@ -152,15 +152,15 @@
|
||||
"@babel/core": "7.25.2",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||
"@babel/plugin-proposal-decorators": "7.24.7",
|
||||
"@babel/plugin-transform-runtime": "7.24.7",
|
||||
"@babel/preset-env": "7.25.3",
|
||||
"@babel/plugin-transform-runtime": "7.25.4",
|
||||
"@babel/preset-env": "7.25.4",
|
||||
"@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",
|
||||
"@lokalise/node-api": "12.7.0",
|
||||
"@octokit/auth-oauth-device": "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",
|
||||
"@rollup/plugin-babel": "6.0.4",
|
||||
"@rollup/plugin-commonjs": "26.0.1",
|
||||
@@ -168,7 +168,7 @@
|
||||
"@rollup/plugin-node-resolve": "15.2.3",
|
||||
"@rollup/plugin-replace": "5.0.7",
|
||||
"@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/color-name": "1.1.4",
|
||||
"@types/glob": "8.1.0",
|
||||
@@ -202,8 +202,8 @@
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-lit": "1.14.0",
|
||||
"eslint-plugin-lit-a11y": "4.1.4",
|
||||
"eslint-plugin-unused-imports": "4.0.1",
|
||||
"eslint-plugin-wc": "2.1.0",
|
||||
"eslint-plugin-unused-imports": "4.1.3",
|
||||
"eslint-plugin-wc": "2.1.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "11.0.0",
|
||||
@@ -213,10 +213,10 @@
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.4",
|
||||
"husky": "9.1.5",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.2.8",
|
||||
"lint-staged": "15.2.9",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
@@ -239,7 +239,7 @@
|
||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.5.4",
|
||||
"webpack": "5.93.0",
|
||||
"webpack": "5.94.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
|
||||
@@ -40,7 +40,6 @@ import {
|
||||
mdiImageFilterFrames,
|
||||
mdiLightbulb,
|
||||
mdiLightningBolt,
|
||||
mdiMailbox,
|
||||
mdiMapMarkerRadius,
|
||||
mdiMeterGas,
|
||||
mdiMicrophoneMessage,
|
||||
@@ -119,7 +118,6 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
input_text: mdiFormTextbox,
|
||||
lawn_mower: mdiRobotMower,
|
||||
light: mdiLightbulb,
|
||||
mailbox: mdiMailbox,
|
||||
notify: mdiCommentAlert,
|
||||
number: mdiRayVertex,
|
||||
persistent_notification: mdiBell,
|
||||
|
||||
@@ -26,7 +26,7 @@ export const FIXED_DOMAIN_STATES = {
|
||||
humidifier: ["on", "off"],
|
||||
input_boolean: ["on", "off"],
|
||||
input_button: [],
|
||||
lawn_mower: ["error", "paused", "mowing", "docked"],
|
||||
lawn_mower: ["error", "paused", "mowing", "returning", "docked"],
|
||||
light: ["on", "off"],
|
||||
lock: [
|
||||
"jammed",
|
||||
|
||||
@@ -28,6 +28,11 @@ const LAWN_MOWER_ACTIONS: Partial<
|
||||
service: "start_mowing",
|
||||
feature: LawnMowerEntityFeature.START_MOWING,
|
||||
},
|
||||
returning: {
|
||||
action: "pause",
|
||||
service: "pause",
|
||||
feature: LawnMowerEntityFeature.PAUSE,
|
||||
},
|
||||
paused: {
|
||||
action: "resume_mowing",
|
||||
service: "start_mowing",
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { BooleanSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-checkbox";
|
||||
import "../ha-formfield";
|
||||
import "../ha-switch";
|
||||
import "../ha-input-helper-text";
|
||||
import "../ha-switch";
|
||||
|
||||
@customElement("ha-selector-boolean")
|
||||
export class HaBooleanSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: BooleanSelector;
|
||||
|
||||
@property({ type: Boolean }) public value = false;
|
||||
|
||||
@property() public placeholder?: any;
|
||||
@@ -21,13 +25,24 @@ export class HaBooleanSelector extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
const checkbox = this.selector.boolean?.mode === "checkbox";
|
||||
return html`
|
||||
<ha-formfield alignEnd spaceBetween .label=${this.label}>
|
||||
<ha-switch
|
||||
.checked=${this.value ?? this.placeholder === true}
|
||||
@change=${this._handleChange}
|
||||
.disabled=${this.disabled}
|
||||
></ha-switch>
|
||||
<ha-formfield .alignEnd=${!checkbox} spaceBetween .label=${this.label}>
|
||||
${checkbox
|
||||
? html`
|
||||
<ha-checkbox
|
||||
.checked=${this.value ?? this.placeholder === true}
|
||||
@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>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
|
||||
@@ -81,15 +81,16 @@ export class HaTargetSelector extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`<ha-target-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.disabled=${this.disabled}
|
||||
.createDomains=${this._createDomains}
|
||||
></ha-target-picker>`;
|
||||
return html` ${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<ha-target-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.disabled=${this.disabled}
|
||||
.createDomains=${this._createDomains}
|
||||
></ha-target-picker>`;
|
||||
}
|
||||
|
||||
private _filterEntities = (entity: HassEntity): boolean => {
|
||||
|
||||
@@ -497,6 +497,11 @@ export class HaServiceControl extends LitElement {
|
||||
dataField.name ||
|
||||
dataField.key}
|
||||
>
|
||||
${this._renderSectionDescription(
|
||||
dataField,
|
||||
domain,
|
||||
serviceName
|
||||
)}
|
||||
${Object.entries(dataField.fields).map(([key, field]) =>
|
||||
this._renderField(
|
||||
{ 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 = (
|
||||
dataField: ExtHassService["fields"][number],
|
||||
hasOptional: boolean,
|
||||
|
||||
@@ -4,7 +4,12 @@ import {
|
||||
} from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
|
||||
export type LawnMowerEntityState = "paused" | "mowing" | "docked" | "error";
|
||||
export type LawnMowerEntityState =
|
||||
| "paused"
|
||||
| "mowing"
|
||||
| "returning"
|
||||
| "docked"
|
||||
| "error";
|
||||
|
||||
export const enum LawnMowerEntityFeature {
|
||||
START_MOWING = 1,
|
||||
|
||||
@@ -101,8 +101,9 @@ export interface AttributeSelector {
|
||||
}
|
||||
|
||||
export interface BooleanSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
boolean: {} | null;
|
||||
boolean: {
|
||||
mode?: "checkbox" | "switch";
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface ColorRGBSelector {
|
||||
|
||||
@@ -140,10 +140,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
|
||||
const controlHA = !this._pipeline
|
||||
? false
|
||||
: supportsFeature(
|
||||
this.hass.states[this._pipeline?.conversation_engine],
|
||||
ConversationEntityFeature.CONTROL
|
||||
);
|
||||
: 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;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ const COMPONENTS = {
|
||||
history: () => import("../panels/history/ha-panel-history"),
|
||||
iframe: () => import("../panels/iframe/ha-panel-iframe"),
|
||||
logbook: () => import("../panels/logbook/ha-panel-logbook"),
|
||||
mailbox: () => import("../panels/mailbox/ha-panel-mailbox"),
|
||||
map: () => import("../panels/map/ha-panel-map"),
|
||||
my: () => import("../panels/my/ha-panel-my"),
|
||||
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> {
|
||||
await copyToClipboard(
|
||||
`{% set action_response = ${JSON.stringify(this._response)} %}`
|
||||
`{% set ${this._serviceData?.response_variable || "action_response"} = ${JSON.stringify(this._response)} %}`
|
||||
);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
|
||||
@@ -88,13 +88,8 @@ class HaPanelDevTemplate extends LitElement {
|
||||
: "dict"
|
||||
: type;
|
||||
return html`
|
||||
<div
|
||||
class="content ${classMap({
|
||||
layout: !this.narrow,
|
||||
horizontal: !this.narrow,
|
||||
})}"
|
||||
>
|
||||
<div class="edit-pane">
|
||||
<div class="content">
|
||||
<div class="description">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.description"
|
||||
@@ -126,123 +121,143 @@ class HaPanelDevTemplate extends LitElement {
|
||||
>
|
||||
</li>
|
||||
</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
|
||||
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">
|
||||
${this._rendering
|
||||
? html`<ha-circular-progress
|
||||
class="render-spinner"
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>`
|
||||
: ""}
|
||||
${this._error
|
||||
? html`<ha-alert
|
||||
alert-type=${this._errorLevel?.toLowerCase() || "error"}
|
||||
>${this._error}</ha-alert
|
||||
>`
|
||||
: nothing}
|
||||
${this._templateResult
|
||||
? html`${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.result_type"
|
||||
)}:
|
||||
${resultType}
|
||||
<!-- prettier-ignore -->
|
||||
<pre class="rendered ${classMap({
|
||||
[resultType]: resultType,
|
||||
})}"
|
||||
>${type === "object"
|
||||
? JSON.stringify(this._templateResult.result, null, 2)
|
||||
: this._templateResult.result}</pre>
|
||||
${this._templateResult.listeners.time
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.time"
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
${!this._templateResult.listeners
|
||||
? nothing
|
||||
: this._templateResult.listeners.all
|
||||
<ha-card
|
||||
class="render-pane"
|
||||
header=${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.result"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._rendering
|
||||
? html`<ha-circular-progress
|
||||
class="render-spinner"
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>`
|
||||
: ""}
|
||||
${this._error
|
||||
? html`<ha-alert
|
||||
alert-type=${this._errorLevel?.toLowerCase() || "error"}
|
||||
>${this._error}</ha-alert
|
||||
>`
|
||||
: nothing}
|
||||
${this._templateResult
|
||||
? html`${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.result_type"
|
||||
)}:
|
||||
${resultType}
|
||||
<!-- prettier-ignore -->
|
||||
<pre class="rendered ${classMap({
|
||||
[resultType]: resultType,
|
||||
})}"
|
||||
>${type === "object"
|
||||
? JSON.stringify(this._templateResult.result, null, 2)
|
||||
: this._templateResult.result}</pre>
|
||||
${this._templateResult.listeners.time
|
||||
? html`
|
||||
<p class="all_listeners">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.all_listeners"
|
||||
"ui.panel.developer-tools.tabs.templates.time"
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: this._templateResult.listeners.domains.length ||
|
||||
this._templateResult.listeners.entities.length
|
||||
: ""}
|
||||
${!this._templateResult.listeners
|
||||
? nothing
|
||||
: this._templateResult.listeners.all
|
||||
? html`
|
||||
<p>
|
||||
<p class="all_listeners">
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.listeners"
|
||||
"ui.panel.developer-tools.tabs.templates.all_listeners"
|
||||
)}
|
||||
</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
|
||||
? html`<span class="all_listeners">
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.no_listeners"
|
||||
)}
|
||||
</span>`
|
||||
: nothing}`
|
||||
: nothing}
|
||||
</div>
|
||||
: this._templateResult.listeners.domains.length ||
|
||||
this._templateResult.listeners.entities.length
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.listeners"
|
||||
)}
|
||||
</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
|
||||
? html`<span class="all_listeners">
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.no_listeners"
|
||||
)}
|
||||
</span>`
|
||||
: nothing}`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -258,6 +273,7 @@ class HaPanelDevTemplate extends LitElement {
|
||||
}
|
||||
|
||||
.content {
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
padding: max(16px, env(safe-area-inset-top))
|
||||
max(16px, env(safe-area-inset-right))
|
||||
@@ -265,10 +281,11 @@ class HaPanelDevTemplate extends LitElement {
|
||||
max(16px, env(safe-area-inset-left));
|
||||
}
|
||||
|
||||
ha-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.edit-pane {
|
||||
margin-right: 16px;
|
||||
margin-inline-start: initial;
|
||||
margin-inline-end: 16px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
@@ -280,12 +297,6 @@ class HaPanelDevTemplate extends LitElement {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.render-pane {
|
||||
position: relative;
|
||||
max-width: 50%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.render-spinner {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { mdiAlertCircle } from "@mdi/js";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color";
|
||||
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 "../../../components/ha-ripple";
|
||||
import "../../../components/ha-state-icon";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
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;
|
||||
|
||||
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);
|
||||
@@ -206,6 +218,9 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
--badge-color: var(--state-inactive-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.badge.error {
|
||||
--badge-color: var(--red-color);
|
||||
}
|
||||
.badge {
|
||||
position: relative;
|
||||
--ha-ripple-color: var(--badge-color);
|
||||
@@ -225,8 +240,14 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
box-sizing: border-box;
|
||||
width: auto;
|
||||
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);
|
||||
box-shadow: var(--ha-card-box-shadow, none);
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
@@ -277,7 +298,8 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
letter-spacing: 0.1px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-state-icon {
|
||||
ha-state-icon,
|
||||
ha-svg-icon {
|
||||
color: var(--badge-color);
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
@@ -96,7 +96,12 @@ class HuiAlarmModeCardFeature
|
||||
}
|
||||
|
||||
private async _setMode(mode: AlarmMode) {
|
||||
setProtectedAlarmControlPanelMode(this, this.hass!, this.stateObj!, mode);
|
||||
await setProtectedAlarmControlPanelMode(
|
||||
this,
|
||||
this.hass!,
|
||||
this.stateObj!,
|
||||
mode
|
||||
);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | null {
|
||||
|
||||
@@ -44,6 +44,7 @@ const HIDE_DOMAIN = new Set([
|
||||
"persistent_notification",
|
||||
"script",
|
||||
"sun",
|
||||
"tag",
|
||||
"todo",
|
||||
"zone",
|
||||
...ASSIST_ENTITIES,
|
||||
|
||||
@@ -226,8 +226,15 @@ export class HuiActionEditor extends LitElement {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
let action = this.config?.action;
|
||||
|
||||
if (action === "call-service") {
|
||||
action = "perform-action";
|
||||
}
|
||||
|
||||
const value = ev.target.value;
|
||||
if (this.config?.action === value) {
|
||||
|
||||
if (action === value) {
|
||||
return;
|
||||
}
|
||||
if (value === "default") {
|
||||
@@ -292,6 +299,7 @@ export class HuiActionEditor extends LitElement {
|
||||
ev.stopPropagation();
|
||||
const value = {
|
||||
...this.config!,
|
||||
action: "perform-action",
|
||||
perform_action: ev.detail.value.action || "",
|
||||
data: ev.detail.value.data,
|
||||
target: ev.detail.value.target || {},
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
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-entities-card";
|
||||
import "../cards/hui-button-card";
|
||||
import "../cards/hui-entity-button-card";
|
||||
import "../cards/hui-glance-card";
|
||||
import "../cards/hui-grid-card";
|
||||
import "../cards/hui-light-card";
|
||||
import "../cards/hui-sensor-card";
|
||||
import "../cards/hui-thermostat-card";
|
||||
import "../cards/hui-tile-card";
|
||||
import "../cards/hui-weather-forecast-card";
|
||||
import "../cards/hui-tile-card";
|
||||
import {
|
||||
createLovelaceElement,
|
||||
getLovelaceElementClass,
|
||||
|
||||
@@ -102,7 +102,7 @@ export class HaCardConditionState extends LitElement {
|
||||
const data: StateConditionData = {
|
||||
...content,
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ const actionConfigStructService = object({
|
||||
entity_id: optional(union([string(), array(string())])),
|
||||
device_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),
|
||||
|
||||
@@ -231,14 +231,15 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
if (!value) {
|
||||
showConfirmationDialog(this, {
|
||||
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(
|
||||
"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"),
|
||||
confirm: () => this._removeConfig(),
|
||||
destructive: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { HuiBadge } from "../badges/hui-badge";
|
||||
import "../badges/hui-view-badges";
|
||||
import { HuiCard } from "../cards/hui-card";
|
||||
import { HuiCardOptions } from "../components/hui-card-options";
|
||||
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 badges: HuiBadge[] = [];
|
||||
|
||||
@state() private _config?: LovelaceViewConfig;
|
||||
|
||||
private _mqlListenerRef?: () => void;
|
||||
@@ -85,6 +89,12 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hui-view-badges
|
||||
.hass=${this.hass}
|
||||
.badges=${this.badges}
|
||||
.lovelace=${this.lovelace}
|
||||
.viewIndex=${this.index}
|
||||
></hui-view-badges>
|
||||
<div
|
||||
class="container ${this.lovelace?.editMode ? "edit-mode" : ""}"
|
||||
></div>
|
||||
@@ -191,6 +201,12 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
hui-view-badges {
|
||||
display: block;
|
||||
margin: 12px 8px 20px 8px;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
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"
|
||||
),
|
||||
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) {
|
||||
// Cancel
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"map": "Map",
|
||||
"logbook": "Logbook",
|
||||
"history": "History",
|
||||
"mailbox": "Mailbox",
|
||||
"todo": "To-do lists",
|
||||
"developer_tools": "Developer tools",
|
||||
"media_browser": "Media",
|
||||
@@ -71,6 +70,11 @@
|
||||
"backup": {
|
||||
"upload_backup": "Upload backup"
|
||||
},
|
||||
"badge": {
|
||||
"entity": {
|
||||
"not_found": "[%key:ui::card::tile::not_found%]"
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"common": {
|
||||
"turn_on": "Turn on",
|
||||
@@ -171,6 +175,7 @@
|
||||
"actions": {
|
||||
"resume_mowing": "Resume mowing",
|
||||
"start_mowing": "Start mowing",
|
||||
"pause": "Pause",
|
||||
"dock": "Return to dock"
|
||||
}
|
||||
},
|
||||
@@ -5492,8 +5497,8 @@
|
||||
"saved": "Saved",
|
||||
"reload": "Reload",
|
||||
"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_remove_config_text": "We will automatically generate your dashboard views with your areas and devices if you remove your dashboard configuration.",
|
||||
"confirm_delete_config_title": "Delete 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_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}",
|
||||
@@ -6355,12 +6360,6 @@
|
||||
},
|
||||
"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": {
|
||||
"error": {
|
||||
"player_not_exist": "Media player {name} does not exist"
|
||||
@@ -6857,6 +6856,7 @@
|
||||
"title": "Template",
|
||||
"description": "Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.",
|
||||
"editor": "Template editor",
|
||||
"result": "Result",
|
||||
"reset": "Reset to 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?",
|
||||
|
||||
Reference in New Issue
Block a user