mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-12 10:49:25 +00:00
Compare commits
148 Commits
context-fo
...
20220301.2
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f9ccfa00a2 | ||
![]() |
070e11a2db | ||
![]() |
fc1c6cea24 | ||
![]() |
c6a103bd30 | ||
![]() |
9d1618024e | ||
![]() |
307aa161a6 | ||
![]() |
affa6a92e7 | ||
![]() |
1e5ec241d5 | ||
![]() |
27a98a32fc | ||
![]() |
2c8ac58f97 | ||
![]() |
6b995969b1 | ||
![]() |
72c107484a | ||
![]() |
d1085b6657 | ||
![]() |
e9003ac35e | ||
![]() |
1dd5214b42 | ||
![]() |
96738350bb | ||
![]() |
5bdecf57cf | ||
![]() |
ec12282f8c | ||
![]() |
552dbca201 | ||
![]() |
0bbc0ebb3c | ||
![]() |
ac7acc5802 | ||
![]() |
64e1d160d1 | ||
![]() |
8e51878b6d | ||
![]() |
7c94ced303 | ||
![]() |
a040e1d5e0 | ||
![]() |
87c7407857 | ||
![]() |
d0d0c44ec7 | ||
![]() |
7f086c0900 | ||
![]() |
17018c0f26 | ||
![]() |
cd6a478130 | ||
![]() |
4f6d7ca5c9 | ||
![]() |
c2994343b4 | ||
![]() |
e5f77c35d4 | ||
![]() |
a9e5a5dd44 | ||
![]() |
1159798b8d | ||
![]() |
437de42c55 | ||
![]() |
89e0bb3f16 | ||
![]() |
28c9631b6c | ||
![]() |
8882624618 | ||
![]() |
a769f84755 | ||
![]() |
7abf9c2473 | ||
![]() |
298296a81f | ||
![]() |
6907fa5c8e | ||
![]() |
546461b70f | ||
![]() |
4031009c26 | ||
![]() |
91e4557625 | ||
![]() |
f0c4b92dbb | ||
![]() |
ffac3d055e | ||
![]() |
04ae8c9d14 | ||
![]() |
0158610d42 | ||
![]() |
5ab6121581 | ||
![]() |
3d9c31aef9 | ||
![]() |
acfeea5c92 | ||
![]() |
75e8e17073 | ||
![]() |
976fd4b32d | ||
![]() |
49beafbe5f | ||
![]() |
151f8d5524 | ||
![]() |
48355aa98e | ||
![]() |
fc31929f41 | ||
![]() |
b7c149fcc1 | ||
![]() |
02d058561b | ||
![]() |
4e57fb1ec1 | ||
![]() |
30f79c5a46 | ||
![]() |
30f7252d84 | ||
![]() |
8af795a7ce | ||
![]() |
8576eeae41 | ||
![]() |
cd740ed135 | ||
![]() |
892f774792 | ||
![]() |
aa504fe1f8 | ||
![]() |
be491451d5 | ||
![]() |
bad184210d | ||
![]() |
a43b3b64b3 | ||
![]() |
aa831a9adf | ||
![]() |
43d4f55392 | ||
![]() |
130c66fb24 | ||
![]() |
684c232c8c | ||
![]() |
09f8f816d1 | ||
![]() |
1719d062b3 | ||
![]() |
87290c4330 | ||
![]() |
fec0dc0032 | ||
![]() |
70ca27c8c9 | ||
![]() |
9ae1f01ad6 | ||
![]() |
0113cc3cf6 | ||
![]() |
2a98ace0b3 | ||
![]() |
5f69a4c165 | ||
![]() |
8db22d4f88 | ||
![]() |
3204dbfc4d | ||
![]() |
430b47fc4a | ||
![]() |
5d8b3227f3 | ||
![]() |
b341ee9d38 | ||
![]() |
e6dbbc31a8 | ||
![]() |
0010bf5a8f | ||
![]() |
6e2e80a297 | ||
![]() |
aa9ff01030 | ||
![]() |
7f8ecf57d7 | ||
![]() |
6be6755f6f | ||
![]() |
64459a06c6 | ||
![]() |
df35496c6e | ||
![]() |
aa988c758d | ||
![]() |
1dd1095d19 | ||
![]() |
7e68393c84 | ||
![]() |
540c06c9f7 | ||
![]() |
f633cc2b0d | ||
![]() |
1baaf76471 | ||
![]() |
8263e299a8 | ||
![]() |
ebd6a26554 | ||
![]() |
5335772a7a | ||
![]() |
f5b5414461 | ||
![]() |
1e6f402d0f | ||
![]() |
ed9d886009 | ||
![]() |
940f5c0002 | ||
![]() |
15d1b8b2ac | ||
![]() |
73855e6f99 | ||
![]() |
d230541256 | ||
![]() |
b1f369a355 | ||
![]() |
e6d1e86c64 | ||
![]() |
eb1f94c370 | ||
![]() |
27750b8b5d | ||
![]() |
564a725284 | ||
![]() |
a5ee610af5 | ||
![]() |
eaf97ee7f5 | ||
![]() |
a14d75deec | ||
![]() |
72b5721c88 | ||
![]() |
94b4b818aa | ||
![]() |
98699b640a | ||
![]() |
decc0d3e0d | ||
![]() |
2281f5bafa | ||
![]() |
6cac7eeff0 | ||
![]() |
794bc161c8 | ||
![]() |
28cd9b6408 | ||
![]() |
9b4c6eea63 | ||
![]() |
41ec65ef3d | ||
![]() |
79e1e195a0 | ||
![]() |
dfbf7fb436 | ||
![]() |
f37a5fa021 | ||
![]() |
5e2fcf928c | ||
![]() |
bc6ef7780c | ||
![]() |
b29563a254 | ||
![]() |
fe8a1152c4 | ||
![]() |
26689a0a85 | ||
![]() |
4f6a241817 | ||
![]() |
246724c59e | ||
![]() |
8f5c9295d3 | ||
![]() |
0abafff4c9 | ||
![]() |
f88ce269a7 | ||
![]() |
0dc56d7983 | ||
![]() |
51938fb51f | ||
![]() |
c85236e251 |
631
.yarn/releases/yarn-3.0.2.cjs
vendored
631
.yarn/releases/yarn-3.0.2.cjs
vendored
File diff suppressed because one or more lines are too long
785
.yarn/releases/yarn-3.2.0.cjs
vendored
Executable file
785
.yarn/releases/yarn-3.2.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@@ -6,4 +6,4 @@ plugins:
|
|||||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||||
spec: "@yarnpkg/plugin-interactive-tools"
|
spec: "@yarnpkg/plugin-interactive-tools"
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-3.0.2.cjs
|
yarnPath: .yarn/releases/yarn-3.2.0.cjs
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
This is the repository for the official [Home Assistant](https://home-assistant.io) frontend.
|
||||||
|
|
||||||
[](https://demo.home-assistant.io/)
|
[](https://demo.home-assistant.io/)
|
||||||
|
|
||||||
- [View demo of Home Assistant](https://demo.home-assistant.io/)
|
- [View demo of Home Assistant](https://demo.home-assistant.io/)
|
||||||
- [More information about Home Assistant](https://home-assistant.io)
|
- [More information about Home Assistant](https://home-assistant.io)
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const marked = require("marked");
|
const { marked } = require("marked");
|
||||||
const glob = require("glob");
|
const glob = require("glob");
|
||||||
const yaml = require("js-yaml");
|
const yaml = require("js-yaml");
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ const source = require("vinyl-source-stream");
|
|||||||
const vinylBuffer = require("vinyl-buffer");
|
const vinylBuffer = require("vinyl-buffer");
|
||||||
const gulp = require("gulp");
|
const gulp = require("gulp");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const foreach = require("gulp-foreach");
|
const flatmap = require("gulp-flatmap");
|
||||||
const merge = require("gulp-merge-json");
|
const merge = require("gulp-merge-json");
|
||||||
const rename = require("gulp-rename");
|
const rename = require("gulp-rename");
|
||||||
const transform = require("gulp-json-transform");
|
const transform = require("gulp-json-transform");
|
||||||
@@ -183,7 +183,7 @@ gulp.task("build-merged-translations", () =>
|
|||||||
})
|
})
|
||||||
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
|
||||||
.pipe(
|
.pipe(
|
||||||
foreach((stream, file) => {
|
flatmap((stream, file) => {
|
||||||
// For each language generate a merged json file. It begins with the master
|
// For each language generate a merged json file. It begins with the master
|
||||||
// translation as a failsafe for untranslated strings, and merges all parent
|
// translation as a failsafe for untranslated strings, and merges all parent
|
||||||
// tags into one file for each specific subtag
|
// tags into one file for each specific subtag
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
import { LovelaceConfig } from "../../../../src/data/lovelace";
|
||||||
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
import { Lovelace } from "../../../../src/panels/lovelace/types";
|
||||||
@@ -20,6 +20,8 @@ class HcLovelace extends LitElement {
|
|||||||
|
|
||||||
@property() public urlPath: string | null = null;
|
@property() public urlPath: string | null = null;
|
||||||
|
|
||||||
|
@query("hui-view") private _huiView?: HTMLElement;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const index = this._viewIndex;
|
const index = this._viewIndex;
|
||||||
if (index === undefined) {
|
if (index === undefined) {
|
||||||
@@ -78,12 +80,12 @@ class HcLovelace extends LitElement {
|
|||||||
this.lovelaceConfig.background;
|
this.lovelaceConfig.background;
|
||||||
|
|
||||||
if (configBackground) {
|
if (configBackground) {
|
||||||
(this.shadowRoot!.querySelector(
|
this._huiView!.style.setProperty(
|
||||||
"hui-view"
|
|
||||||
) as HTMLElement)!.style.setProperty(
|
|
||||||
"--lovelace-background",
|
"--lovelace-background",
|
||||||
configBackground
|
configBackground
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
this._huiView!.style.removeProperty("--lovelace-background");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,6 +118,9 @@ class HcLovelace extends LitElement {
|
|||||||
:host > * {
|
:host > * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
hui-view {
|
||||||
|
background: var(--lovelace-background, var(--primary-background-color));
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -78,6 +78,9 @@ class DemoCards extends LitElement {
|
|||||||
ha-formfield {
|
ha-formfield {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
#container {
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -38,6 +38,7 @@ const SCHEMAS: {
|
|||||||
select: "Select",
|
select: "Select",
|
||||||
icon: "Icon",
|
icon: "Icon",
|
||||||
media: "Media",
|
media: "Media",
|
||||||
|
location: "Location",
|
||||||
},
|
},
|
||||||
schema: [
|
schema: [
|
||||||
{ name: "addon", selector: { addon: {} } },
|
{ name: "addon", selector: { addon: {} } },
|
||||||
@@ -75,6 +76,10 @@ const SCHEMAS: {
|
|||||||
media: {},
|
media: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "location",
|
||||||
|
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -168,6 +168,11 @@ const SCHEMAS: {
|
|||||||
},
|
},
|
||||||
icon: { name: "Icon", selector: { icon: {} } },
|
icon: { name: "Icon", selector: { icon: {} } },
|
||||||
media: { name: "Media", selector: { media: {} } },
|
media: { name: "Media", selector: { media: {} } },
|
||||||
|
location: { name: "Location", selector: { location: {} } },
|
||||||
|
location_radius: {
|
||||||
|
name: "Location with radius",
|
||||||
|
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -279,7 +284,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
can_play: true,
|
can_play: true,
|
||||||
can_expand: false,
|
can_expand: false,
|
||||||
children_media_class: null,
|
children_media_class: null,
|
||||||
thumbnail: null,
|
thumbnail: "https://brands.home-assistant.io/_/image/logo.png",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "movie.mp4",
|
title: "movie.mp4",
|
||||||
|
@@ -29,6 +29,7 @@ const createConfigEntry = (
|
|||||||
source: "zeroconf",
|
source: "zeroconf",
|
||||||
state: "loaded",
|
state: "loaded",
|
||||||
supports_options: false,
|
supports_options: false,
|
||||||
|
supports_remove_device: false,
|
||||||
supports_unload: true,
|
supports_unload: true,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
pref_disable_new_entities: false,
|
pref_disable_new_entities: false,
|
||||||
|
@@ -14,7 +14,7 @@ import memoizeOne from "memoize-one";
|
|||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import { navigate } from "../../../src/common/navigate";
|
import { navigate } from "../../../src/common/navigate";
|
||||||
import "../../../src/common/search/search-input";
|
import "../../../src/components/search-input";
|
||||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
import "../../../src/components/ha-icon-button";
|
import "../../../src/components/ha-icon-button";
|
||||||
@@ -110,8 +110,6 @@ class HassioAddonStore extends LitElement {
|
|||||||
<div class="search">
|
<div class="search">
|
||||||
<search-input
|
<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
no-label-float
|
|
||||||
no-underline
|
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._filterChanged}
|
@value-changed=${this._filterChanged}
|
||||||
></search-input>
|
></search-input>
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@material/mwc-select";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -14,6 +13,7 @@ import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
|
|||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
|
import "../../../../src/components/ha-select";
|
||||||
import {
|
import {
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
HassioAddonSetOptionParams,
|
HassioAddonSetOptionParams,
|
||||||
@@ -57,7 +57,7 @@ class HassioAddonAudio extends LitElement {
|
|||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${this._inputDevices &&
|
${this._inputDevices &&
|
||||||
html`<mwc-select
|
html`<ha-select
|
||||||
.label=${this.supervisor.localize(
|
.label=${this.supervisor.localize(
|
||||||
"addon.configuration.audio.input"
|
"addon.configuration.audio.input"
|
||||||
)}
|
)}
|
||||||
@@ -74,9 +74,9 @@ class HassioAddonAudio extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>`}
|
</ha-select>`}
|
||||||
${this._outputDevices &&
|
${this._outputDevices &&
|
||||||
html`<mwc-select
|
html`<ha-select
|
||||||
.label=${this.supervisor.localize(
|
.label=${this.supervisor.localize(
|
||||||
"addon.configuration.audio.output"
|
"addon.configuration.audio.output"
|
||||||
)}
|
)}
|
||||||
@@ -93,7 +93,7 @@ class HassioAddonAudio extends LitElement {
|
|||||||
>
|
>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>`}
|
</ha-select>`}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-progress-button @click=${this._saveSettings}>
|
<ha-progress-button @click=${this._saveSettings}>
|
||||||
@@ -119,10 +119,10 @@ class HassioAddonAudio extends LitElement {
|
|||||||
.card-actions {
|
.card-actions {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
mwc-select {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
mwc-select:last-child {
|
ha-select:last-child {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import { mdiFolderUpload } from "@mdi/js";
|
import { mdiFolderUpload } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input-container";
|
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { atLeastVersion } from "../../../src/common/config/version";
|
import { atLeastVersion } from "../../../src/common/config/version";
|
||||||
import { formatDate } from "../../../src/common/datetime/format_date";
|
import { formatDate } from "../../../src/common/datetime/format_date";
|
||||||
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
||||||
@@ -92,6 +92,8 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
|
|
||||||
@property() public confirmBackupPassword = "";
|
@property() public confirmBackupPassword = "";
|
||||||
|
|
||||||
|
@query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget;
|
||||||
|
|
||||||
public willUpdate(changedProps) {
|
public willUpdate(changedProps) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
@@ -109,6 +111,10 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override focus() {
|
||||||
|
this._focusTarget?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
private _localize = (string: string) =>
|
private _localize = (string: string) =>
|
||||||
this.supervisor?.localize(`backup.${string}`) ||
|
this.supervisor?.localize(`backup.${string}`) ||
|
||||||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
||||||
@@ -169,24 +175,23 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.backupType === "partial"
|
${this.backupType === "partial"
|
||||||
? html`<div class="partial-picker">
|
? html`<div class="partial-picker">
|
||||||
${this.backup && this.backup.homeassistant
|
<ha-formfield
|
||||||
? html`
|
.label=${html`<supervisor-formfield-label
|
||||||
<ha-formfield
|
label="Home Assistant"
|
||||||
.label=${html`<supervisor-formfield-label
|
.iconPath=${mdiHomeAssistant}
|
||||||
label="Home Assistant"
|
.version=${this.backup
|
||||||
.iconPath=${mdiHomeAssistant}
|
? this.backup.homeassistant
|
||||||
.version=${this.backup.homeassistant}
|
: this.hass.config.version}
|
||||||
>
|
>
|
||||||
</supervisor-formfield-label>`}
|
</supervisor-formfield-label>`}
|
||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this.homeAssistant}
|
.checked=${this.homeAssistant}
|
||||||
@click=${this.toggleHomeAssistant}
|
@click=${this.toggleHomeAssistant}
|
||||||
>
|
>
|
||||||
</ha-checkbox>
|
</ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${foldersSection?.templates.length
|
${foldersSection?.templates.length
|
||||||
? html`
|
? html`
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
|
@@ -64,6 +64,7 @@ export class DialogHassioBackupUpload
|
|||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
slot="actionItems"
|
slot="actionItems"
|
||||||
dialogAction="cancel"
|
dialogAction="cancel"
|
||||||
|
dialogInitialFocus
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -92,6 +92,7 @@ class HassioBackupDialog
|
|||||||
.backup=${this._backup}
|
.backup=${this._backup}
|
||||||
.onboarding=${this._dialogParams.onboarding || false}
|
.onboarding=${this._dialogParams.onboarding || false}
|
||||||
.localize=${this._dialogParams.localize}
|
.localize=${this._dialogParams.localize}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</supervisor-backup-content>`}
|
</supervisor-backup-content>`}
|
||||||
${this._error
|
${this._error
|
||||||
|
@@ -61,6 +61,7 @@ class HassioCreateBackupDialog extends LitElement {
|
|||||||
: html`<supervisor-backup-content
|
: html`<supervisor-backup-content
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.supervisor=${this._dialogParams.supervisor}
|
.supervisor=${this._dialogParams.supervisor}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</supervisor-backup-content>`}
|
</supervisor-backup-content>`}
|
||||||
${this._error
|
${this._error
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-select";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import "../../../../src/components/ha-circular-progress";
|
import "../../../../src/components/ha-circular-progress";
|
||||||
import "../../../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
|
import "../../../../src/components/ha-select";
|
||||||
import {
|
import {
|
||||||
extractApiErrorMessage,
|
extractApiErrorMessage,
|
||||||
ignoreSupervisorError,
|
ignoreSupervisorError,
|
||||||
@@ -89,11 +89,12 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
)}
|
)}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${this.dialogParams.supervisor.localize(
|
.label=${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.select_device"
|
"dialog.datadisk_move.select_device"
|
||||||
)}
|
)}
|
||||||
@selected=${this._select_device}
|
@selected=${this._select_device}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
${this.devices.map(
|
${this.devices.map(
|
||||||
(device) =>
|
(device) =>
|
||||||
@@ -101,7 +102,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
>${device}</mwc-list-item
|
>${device}</mwc-list-item
|
||||||
>`
|
>`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
`
|
`
|
||||||
: this.devices === undefined
|
: this.devices === undefined
|
||||||
? this.dialogParams.supervisor.localize(
|
? this.dialogParams.supervisor.localize(
|
||||||
@@ -111,7 +112,11 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
"dialog.datadisk_move.no_devices"
|
"dialog.datadisk_move.no_devices"
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
<mwc-button
|
||||||
|
slot="secondaryAction"
|
||||||
|
@click=${this.closeDialog}
|
||||||
|
dialogInitialFocus
|
||||||
|
>
|
||||||
${this.dialogParams.supervisor.localize(
|
${this.dialogParams.supervisor.localize(
|
||||||
"dialog.datadisk_move.cancel"
|
"dialog.datadisk_move.cancel"
|
||||||
)}
|
)}
|
||||||
@@ -156,7 +161,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
haStyle,
|
haStyle,
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
mwc-select {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
ha-circular-progress {
|
ha-circular-progress {
|
||||||
|
@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import "../../../../src/common/search/search-input";
|
import "../../../../src/components/search-input";
|
||||||
import { stringCompare } from "../../../../src/common/string/compare";
|
import { stringCompare } from "../../../../src/common/string/compare";
|
||||||
import "../../../../src/components/ha-dialog";
|
import "../../../../src/components/ha-dialog";
|
||||||
import "../../../../src/components/ha-expansion-panel";
|
import "../../../../src/components/ha-expansion-panel";
|
||||||
@@ -80,8 +80,6 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<search-input
|
<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
autofocus
|
|
||||||
no-label-float
|
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._handleSearchChange}
|
@value-changed=${this._handleSearchChange}
|
||||||
.label=${this._dialogParams.supervisor.localize(
|
.label=${this._dialogParams.supervisor.localize(
|
||||||
|
@@ -37,7 +37,10 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
@closed=${this.closeDialog}
|
@closed=${this.closeDialog}
|
||||||
.heading=${createCloseHeading(this.hass, this.title)}
|
.heading=${createCloseHeading(this.hass, this.title)}
|
||||||
>
|
>
|
||||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
<ha-markdown
|
||||||
|
.content=${this.content || ""}
|
||||||
|
dialogInitialFocus
|
||||||
|
></ha-markdown>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -119,6 +119,7 @@ export class DialogHassioNetwork
|
|||||||
html`<mwc-tab
|
html`<mwc-tab
|
||||||
.id=${device.interface}
|
.id=${device.interface}
|
||||||
.label=${device.interface}
|
.label=${device.interface}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</mwc-tab>`
|
</mwc-tab>`
|
||||||
)}
|
)}
|
||||||
@@ -315,6 +316,7 @@ export class DialogHassioNetwork
|
|||||||
value="auto"
|
value="auto"
|
||||||
name="${version}method"
|
name="${version}method"
|
||||||
.checked=${this._interface![version]?.method === "auto"}
|
.checked=${this._interface![version]?.method === "auto"}
|
||||||
|
dialogInitialFocus
|
||||||
>
|
>
|
||||||
</ha-radio>
|
</ha-radio>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
|
@@ -80,6 +80,7 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
.schema=${SCHEMA}
|
.schema=${SCHEMA}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.computeLabel=${this._computeLabel}
|
.computeLabel=${this._computeLabel}
|
||||||
|
dialogInitialFocus
|
||||||
></ha-form>
|
></ha-form>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button
|
<mwc-button
|
||||||
@@ -124,7 +125,7 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
</ha-alert>
|
</ha-alert>
|
||||||
`}
|
`}
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button @click=${this._addRegistry}>
|
<mwc-button @click=${this._addRegistry} dialogInitialFocus>
|
||||||
${this.supervisor.localize(
|
${this.supervisor.localize(
|
||||||
"dialog.registries.add_new_registry"
|
"dialog.registries.add_new_registry"
|
||||||
)}
|
)}
|
||||||
|
@@ -106,6 +106,9 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
</paper-item-body>
|
</paper-item-body>
|
||||||
<div class="delete">
|
<div class="delete">
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
|
.label=${this._dialogParams!.supervisor.localize(
|
||||||
|
"dialog.repositories.remove"
|
||||||
|
)}
|
||||||
.disabled=${usedRepositories.includes(repo.slug)}
|
.disabled=${usedRepositories.includes(repo.slug)}
|
||||||
.slug=${repo.slug}
|
.slug=${repo.slug}
|
||||||
.path=${usedRepositories.includes(repo.slug)
|
.path=${usedRepositories.includes(repo.slug)
|
||||||
@@ -139,6 +142,7 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
"dialog.repositories.add"
|
"dialog.repositories.add"
|
||||||
)}
|
)}
|
||||||
@keydown=${this._handleKeyAdd}
|
@keydown=${this._handleKeyAdd}
|
||||||
|
dialogInitialFocus
|
||||||
></paper-input>
|
></paper-input>
|
||||||
<mwc-button @click=${this._addRepository}>
|
<mwc-button @click=${this._addRepository}>
|
||||||
${this._processing
|
${this._processing
|
||||||
|
@@ -121,7 +121,8 @@ export class HassioMain extends SupervisorBaseElement {
|
|||||||
this.parentElement,
|
this.parentElement,
|
||||||
this.hass.themes,
|
this.hass.themes,
|
||||||
themeName,
|
themeName,
|
||||||
themeSettings
|
themeSettings,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import { customElement, property, state } from "lit/decorators";
|
|||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../src/components/ha-alert";
|
import "../../../src/components/ha-alert";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
|
import "../../../src/components/ha-select";
|
||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
@@ -70,7 +71,7 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.hass.userData?.showAdvanced
|
${this.hass.userData?.showAdvanced
|
||||||
? html`
|
? html`
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${this.supervisor.localize("system.log.log_provider")}
|
.label=${this.supervisor.localize("system.log.log_provider")}
|
||||||
@selected=${this._setLogProvider}
|
@selected=${this._setLogProvider}
|
||||||
.value=${this._selectedLogProvider}
|
.value=${this._selectedLogProvider}
|
||||||
@@ -82,7 +83,7 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
@@ -145,7 +146,7 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
mwc-select {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
29
package.json
29
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"description": "A frontend for Home Assistant using the Polymer framework",
|
"description": "A frontend for Home Assistant",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/home-assistant/home-assistant-polymer"
|
"url": "https://github.com/home-assistant/frontend"
|
||||||
},
|
},
|
||||||
"name": "home-assistant-frontend",
|
"name": "home-assistant-frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
"@fullcalendar/daygrid": "5.9.0",
|
"@fullcalendar/daygrid": "5.9.0",
|
||||||
"@fullcalendar/interaction": "5.9.0",
|
"@fullcalendar/interaction": "5.9.0",
|
||||||
"@fullcalendar/list": "5.9.0",
|
"@fullcalendar/list": "5.9.0",
|
||||||
|
"@lit-labs/motion": "^1.0.2",
|
||||||
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
|
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.7.0-pre.2#./.yarn/patches/@lit-labs/virtualizer/event-target-shim.patch",
|
||||||
"@material/chips": "14.0.0-canary.261f2db59.0",
|
"@material/chips": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
||||||
@@ -95,6 +96,7 @@
|
|||||||
"@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",
|
||||||
"@vue/web-component-wrapper": "^1.2.0",
|
"@vue/web-component-wrapper": "^1.2.0",
|
||||||
|
"@webcomponents/scoped-custom-element-registry": "^0.0.5",
|
||||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||||
"app-datepicker": "^5.0.1",
|
"app-datepicker": "^5.0.1",
|
||||||
"chart.js": "^3.3.2",
|
"chart.js": "^3.3.2",
|
||||||
@@ -115,7 +117,7 @@
|
|||||||
"leaflet-draw": "^1.0.4",
|
"leaflet-draw": "^1.0.4",
|
||||||
"lit": "^2.1.2",
|
"lit": "^2.1.2",
|
||||||
"lit-vaadin-helpers": "^0.3.0",
|
"lit-vaadin-helpers": "^0.3.0",
|
||||||
"marked": "^3.0.2",
|
"marked": "^4.0.12",
|
||||||
"memoize-one": "^5.2.1",
|
"memoize-one": "^5.2.1",
|
||||||
"node-vibrant": "3.2.1-alpha.1",
|
"node-vibrant": "3.2.1-alpha.1",
|
||||||
"proxy-polyfill": "^0.3.2",
|
"proxy-polyfill": "^0.3.2",
|
||||||
@@ -135,12 +137,12 @@
|
|||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
"vue2-daterange-picker": "^0.5.1",
|
"vue2-daterange-picker": "^0.5.1",
|
||||||
"web-animations-js": "^2.3.2",
|
"web-animations-js": "^2.3.2",
|
||||||
"workbox-cacheable-response": "^6.1.5",
|
"workbox-cacheable-response": "^6.4.2",
|
||||||
"workbox-core": "^6.1.5",
|
"workbox-core": "^6.4.2",
|
||||||
"workbox-expiration": "^6.1.5",
|
"workbox-expiration": "^6.4.2",
|
||||||
"workbox-precaching": "^6.1.5",
|
"workbox-precaching": "^6.4.2",
|
||||||
"workbox-routing": "^6.1.5",
|
"workbox-routing": "^6.4.2",
|
||||||
"workbox-strategies": "^6.1.5",
|
"workbox-strategies": "^6.4.2",
|
||||||
"xss": "^1.0.9"
|
"xss": "^1.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -169,7 +171,7 @@
|
|||||||
"@types/js-yaml": "^4",
|
"@types/js-yaml": "^4",
|
||||||
"@types/leaflet": "^1",
|
"@types/leaflet": "^1",
|
||||||
"@types/leaflet-draw": "^1",
|
"@types/leaflet-draw": "^1",
|
||||||
"@types/marked": "^2",
|
"@types/marked": "^4",
|
||||||
"@types/mocha": "^8",
|
"@types/mocha": "^8",
|
||||||
"@types/qrcode": "^1.4.2",
|
"@types/qrcode": "^1.4.2",
|
||||||
"@types/sortablejs": "^1",
|
"@types/sortablejs": "^1",
|
||||||
@@ -196,7 +198,7 @@
|
|||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
"glob": "^7.2.0",
|
"glob": "^7.2.0",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-foreach": "^0.1.0",
|
"gulp-flatmap": "^1.0.2",
|
||||||
"gulp-json-transform": "^0.4.6",
|
"gulp-json-transform": "^0.4.6",
|
||||||
"gulp-merge-json": "^1.3.1",
|
"gulp-merge-json": "^1.3.1",
|
||||||
"gulp-rename": "^2.0.0",
|
"gulp-rename": "^2.0.0",
|
||||||
@@ -233,7 +235,7 @@
|
|||||||
"webpack-dev-server": "^4.3.0",
|
"webpack-dev-server": "^4.3.0",
|
||||||
"webpack-manifest-plugin": "^4.0.2",
|
"webpack-manifest-plugin": "^4.0.2",
|
||||||
"webpackbar": "^5.0.0-3",
|
"webpackbar": "^5.0.0-3",
|
||||||
"workbox-build": "^6.1.5"
|
"workbox-build": "^6.4.2"
|
||||||
},
|
},
|
||||||
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
@@ -253,5 +255,6 @@
|
|||||||
"prettier": {
|
"prettier": {
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"arrowParens": "always"
|
"arrowParens": "always"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@3.2.0"
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,6 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def where():
|
def where() -> Path:
|
||||||
"""Return path to the frontend."""
|
"""Return path to the frontend."""
|
||||||
return Path(__file__).parent
|
return Path(__file__).parent
|
||||||
|
0
public/py.typed
Normal file
0
public/py.typed
Normal file
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = home-assistant-frontend
|
name = home-assistant-frontend
|
||||||
version = 20220220.0
|
version = 20220301.2
|
||||||
author = The Home Assistant Authors
|
author = The Home Assistant Authors
|
||||||
author_email = hello@home-assistant.io
|
author_email = hello@home-assistant.io
|
||||||
license = Apache-2.0
|
license = Apache-2.0
|
||||||
@@ -19,3 +19,8 @@ python_requires = >= 3.4.0
|
|||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
include =
|
include =
|
||||||
hass_frontend*
|
hass_frontend*
|
||||||
|
|
||||||
|
[mypy]
|
||||||
|
python_version = 3.4
|
||||||
|
show_error_codes = True
|
||||||
|
strict = True
|
||||||
|
@@ -101,13 +101,19 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
this._fetchAuthProviders();
|
this._fetchAuthProviders();
|
||||||
|
|
||||||
if (matchMedia("(prefers-color-scheme: dark)").matches) {
|
if (matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||||
applyThemesOnElement(document.documentElement, {
|
applyThemesOnElement(
|
||||||
default_theme: "default",
|
document.documentElement,
|
||||||
default_dark_theme: null,
|
{
|
||||||
themes: {},
|
default_theme: "default",
|
||||||
darkMode: true,
|
default_dark_theme: null,
|
||||||
theme: "default",
|
themes: {},
|
||||||
});
|
darkMode: true,
|
||||||
|
theme: "default",
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.redirectUri) {
|
if (!this.redirectUri) {
|
||||||
|
@@ -3,9 +3,9 @@ import type { ForDict } from "../../data/automation";
|
|||||||
|
|
||||||
export const createDurationData = (
|
export const createDurationData = (
|
||||||
duration: string | number | ForDict | undefined
|
duration: string | number | ForDict | undefined
|
||||||
): HaDurationData => {
|
): HaDurationData | undefined => {
|
||||||
if (duration === undefined) {
|
if (duration === undefined) {
|
||||||
return {};
|
return undefined;
|
||||||
}
|
}
|
||||||
if (typeof duration !== "object") {
|
if (typeof duration !== "object") {
|
||||||
if (typeof duration === "string" || isNaN(duration)) {
|
if (typeof duration === "string" || isNaN(duration)) {
|
||||||
|
@@ -31,11 +31,12 @@ export const applyThemesOnElement = (
|
|||||||
element,
|
element,
|
||||||
themes: HomeAssistant["themes"],
|
themes: HomeAssistant["themes"],
|
||||||
selectedTheme?: string,
|
selectedTheme?: string,
|
||||||
themeSettings?: Partial<HomeAssistant["selectedTheme"]>
|
themeSettings?: Partial<HomeAssistant["selectedTheme"]>,
|
||||||
|
main?: boolean
|
||||||
) => {
|
) => {
|
||||||
// If there is no explicitly desired theme provided, we automatically
|
// If there is no explicitly desired theme provided, and the element is the main element we automatically
|
||||||
// use the active one from `themes`.
|
// use the active one from `themes`.
|
||||||
const themeToApply = selectedTheme || themes.theme;
|
const themeToApply = selectedTheme || (main ? themes.theme : undefined);
|
||||||
|
|
||||||
// If there is no explicitly desired dark mode provided, we automatically
|
// If there is no explicitly desired dark mode provided, we automatically
|
||||||
// use the active one from `themes`.
|
// use the active one from `themes`.
|
||||||
@@ -47,7 +48,7 @@ export const applyThemesOnElement = (
|
|||||||
let cacheKey = themeToApply;
|
let cacheKey = themeToApply;
|
||||||
let themeRules: Partial<ThemeVars> = {};
|
let themeRules: Partial<ThemeVars> = {};
|
||||||
|
|
||||||
if (darkMode) {
|
if (themeToApply && darkMode) {
|
||||||
cacheKey = `${cacheKey}__dark`;
|
cacheKey = `${cacheKey}__dark`;
|
||||||
themeRules = { ...darkStyles };
|
themeRules = { ...darkStyles };
|
||||||
}
|
}
|
||||||
|
@@ -120,6 +120,7 @@ export const computeOpenIcon = (stateObj: HassEntity): string => {
|
|||||||
case "awning":
|
case "awning":
|
||||||
case "door":
|
case "door":
|
||||||
case "gate":
|
case "gate":
|
||||||
|
case "curtain":
|
||||||
return mdiArrowExpandHorizontal;
|
return mdiArrowExpandHorizontal;
|
||||||
default:
|
default:
|
||||||
return mdiArrowUp;
|
return mdiArrowUp;
|
||||||
@@ -131,6 +132,7 @@ export const computeCloseIcon = (stateObj: HassEntity): string => {
|
|||||||
case "awning":
|
case "awning":
|
||||||
case "door":
|
case "door":
|
||||||
case "gate":
|
case "gate":
|
||||||
|
case "curtain":
|
||||||
return mdiArrowCollapseHorizontal;
|
return mdiArrowCollapseHorizontal;
|
||||||
default:
|
default:
|
||||||
return mdiArrowDown;
|
return mdiArrowDown;
|
||||||
|
@@ -11,7 +11,7 @@ export const debounce = <T extends any[]>(
|
|||||||
immediate = false
|
immediate = false
|
||||||
) => {
|
) => {
|
||||||
let timeout: number | undefined;
|
let timeout: number | undefined;
|
||||||
return (...args: T): void => {
|
const debouncedFunc = (...args: T): void => {
|
||||||
const later = () => {
|
const later = () => {
|
||||||
timeout = undefined;
|
timeout = undefined;
|
||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
@@ -25,4 +25,8 @@ export const debounce = <T extends any[]>(
|
|||||||
func(...args);
|
func(...args);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
debouncedFunc.cancel = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
return debouncedFunc;
|
||||||
};
|
};
|
||||||
|
@@ -21,7 +21,7 @@ import { styleMap } from "lit/directives/style-map";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../common/search/search-input";
|
import "../search-input";
|
||||||
import { debounce } from "../../common/util/debounce";
|
import { debounce } from "../../common/util/debounce";
|
||||||
import { nextRender } from "../../common/util/render-status";
|
import { nextRender } from "../../common/util/render-status";
|
||||||
import { haStyleScrollbar } from "../../resources/styles";
|
import { haStyleScrollbar } from "../../resources/styles";
|
||||||
|
@@ -115,6 +115,9 @@ class DateRangePickerElement extends WrappedElement {
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
min-width: initial !important;
|
min-width: initial !important;
|
||||||
}
|
}
|
||||||
|
.daterangepicker:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
.daterangepicker:after {
|
.daterangepicker:after {
|
||||||
border-bottom: 6px solid var(--card-background-color);
|
border-bottom: 6px solid var(--card-background-color);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
@@ -8,6 +7,7 @@ import {
|
|||||||
deviceAutomationsEqual,
|
deviceAutomationsEqual,
|
||||||
} from "../../data/device_automation";
|
} from "../../data/device_automation";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-select";
|
||||||
|
|
||||||
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
const NO_AUTOMATION_KEY = "NO_AUTOMATION";
|
||||||
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
const UNKNOWN_AUTOMATION_KEY = "UNKNOWN_AUTOMATION";
|
||||||
@@ -90,7 +90,7 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
}
|
}
|
||||||
const value = this._value;
|
const value = this._value;
|
||||||
return html`
|
return html`
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${value}
|
.value=${value}
|
||||||
@selected=${this._automationChanged}
|
@selected=${this._automationChanged}
|
||||||
@@ -113,7 +113,7 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ export abstract class HaDeviceAutomationPicker<
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
mwc-select {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
@@ -114,7 +114,7 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
const newValue = event.detail.value;
|
const newValue = event.detail.value;
|
||||||
if (
|
if (
|
||||||
newValue === curValue ||
|
newValue === curValue ||
|
||||||
(newValue !== "" && !isValidEntityId(newValue))
|
(newValue !== undefined && !isValidEntityId(newValue))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -147,7 +147,7 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static override styles = css`
|
static override styles = css`
|
||||||
ha-entity-picker {
|
div {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { LitElement, html, TemplateResult, css } from "lit";
|
import { LitElement, html, TemplateResult, css } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "@material/mwc-select/mwc-select";
|
import "./ha-select";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "./ha-textfield";
|
import "./ha-textfield";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
@@ -193,7 +193,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${this.format === 24
|
${this.format === 24
|
||||||
? ""
|
? ""
|
||||||
: html`<mwc-select
|
: html`<ha-select
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.value=${this.amPm}
|
.value=${this.amPm}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@@ -205,7 +205,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
>
|
>
|
||||||
<mwc-list-item value="AM">AM</mwc-list-item>
|
<mwc-list-item value="AM">AM</mwc-list-item>
|
||||||
<mwc-list-item value="PM">PM</mwc-list-item>
|
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||||
</mwc-select>`}
|
</ha-select>`}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -280,7 +280,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
ha-textfield:last-child {
|
ha-textfield:last-child {
|
||||||
--text-field-border-top-right-radius: var(--mdc-shape-medium);
|
--text-field-border-top-right-radius: var(--mdc-shape-medium);
|
||||||
}
|
}
|
||||||
mwc-select {
|
ha-select {
|
||||||
--mdc-shape-small: 0;
|
--mdc-shape-small: 0;
|
||||||
width: 85px;
|
width: 85px;
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-select/mwc-select";
|
import "./ha-select";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -24,7 +24,7 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
public open() {
|
public open() {
|
||||||
const select = this.shadowRoot?.querySelector("mwc-select");
|
const select = this.shadowRoot?.querySelector("ha-select");
|
||||||
if (select) {
|
if (select) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
select.menuOpen = true;
|
select.menuOpen = true;
|
||||||
@@ -49,7 +49,7 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${this.label ||
|
.label=${this.label ||
|
||||||
this.hass.localize("ui.components.blueprint-picker.label")}
|
this.hass.localize("ui.components.blueprint-picker.label")}
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
@@ -71,7 +71,7 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ class HaBluePrintPicker extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
mwc-select {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
display: block;
|
display: block;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import "@material/mwc-menu";
|
import "@material/mwc-menu";
|
||||||
import type { Corner, Menu } from "@material/mwc-menu";
|
import type { Corner, Menu, MenuCorner } from "@material/mwc-menu";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
|
||||||
@@ -7,6 +7,12 @@ import { customElement, property, query } from "lit/decorators";
|
|||||||
export class HaButtonMenu extends LitElement {
|
export class HaButtonMenu extends LitElement {
|
||||||
@property() public corner: Corner = "TOP_START";
|
@property() public corner: Corner = "TOP_START";
|
||||||
|
|
||||||
|
@property() public menuCorner: MenuCorner = "START";
|
||||||
|
|
||||||
|
@property({ type: Number }) public x?: number;
|
||||||
|
|
||||||
|
@property({ type: Number }) public y?: number;
|
||||||
|
|
||||||
@property({ type: Boolean }) public multi = false;
|
@property({ type: Boolean }) public multi = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public activatable = false;
|
@property({ type: Boolean }) public activatable = false;
|
||||||
@@ -32,9 +38,12 @@ export class HaButtonMenu extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
<mwc-menu
|
<mwc-menu
|
||||||
.corner=${this.corner}
|
.corner=${this.corner}
|
||||||
|
.menuCorner=${this.menuCorner}
|
||||||
.fixed=${this.fixed}
|
.fixed=${this.fixed}
|
||||||
.multi=${this.multi}
|
.multi=${this.multi}
|
||||||
.activatable=${this.activatable}
|
.activatable=${this.activatable}
|
||||||
|
.y=${this.y}
|
||||||
|
.x=${this.x}
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</mwc-menu>
|
</mwc-menu>
|
||||||
|
@@ -41,7 +41,7 @@ export class HaDateInput extends LitElement {
|
|||||||
return html`<ha-textfield
|
return html`<ha-textfield
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
iconTrailing="calendar"
|
iconTrailing
|
||||||
@click=${this._openDialog}
|
@click=${this._openDialog}
|
||||||
.value=${this.value
|
.value=${this.value
|
||||||
? formatDateNumeric(new Date(this.value), this.locale)
|
? formatDateNumeric(new Date(this.value), this.locale)
|
||||||
|
@@ -3,7 +3,6 @@ import "@material/mwc-list/mwc-list";
|
|||||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiCalendar } from "@mdi/js";
|
import { mdiCalendar } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -19,6 +18,7 @@ import { computeRTLDirection } from "../common/util/compute_rtl";
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./date-range-picker";
|
import "./date-range-picker";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
import "./ha-textfield";
|
||||||
|
|
||||||
export interface DateRangePickerRanges {
|
export interface DateRangePickerRanges {
|
||||||
[key: string]: [Date, Date];
|
[key: string]: [Date, Date];
|
||||||
@@ -61,7 +61,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
>
|
>
|
||||||
<div slot="input" class="date-range-inputs">
|
<div slot="input" class="date-range-inputs">
|
||||||
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.value=${formatDateTime(this.startDate, this.hass.locale)}
|
.value=${formatDateTime(this.startDate, this.hass.locale)}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.date-range-picker.start_date"
|
"ui.components.date-range-picker.start_date"
|
||||||
@@ -69,16 +69,16 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@click=${this._handleInputClick}
|
@click=${this._handleInputClick}
|
||||||
readonly
|
readonly
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.value=${formatDateTime(this.endDate, this.hass.locale)}
|
.value=${formatDateTime(this.endDate, this.hass.locale)}
|
||||||
label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.date-range-picker.end_date"
|
"ui.components.date-range-picker.end_date"
|
||||||
)}
|
)}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
@click=${this._handleInputClick}
|
@click=${this._handleInputClick}
|
||||||
readonly
|
readonly
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
</div>
|
</div>
|
||||||
${this.ranges
|
${this.ranges
|
||||||
? html`<div
|
? html`<div
|
||||||
@@ -158,13 +158,13 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
border-top: 1px solid var(--divider-color);
|
border-top: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-input {
|
ha-textfield {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-input:last-child {
|
ha-textfield:last-child {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ export class HaDateRangePicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: 500px) {
|
@media only screen and (max-width: 500px) {
|
||||||
paper-input {
|
ha-textfield {
|
||||||
min-width: inherit;
|
min-width: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,13 @@
|
|||||||
import { mdiChevronDown } from "@mdi/js";
|
import { mdiChevronDown } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import {
|
||||||
import { customElement, property, query } from "lit/decorators";
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { nextRender } from "../common/util/render-status";
|
import { nextRender } from "../common/util/render-status";
|
||||||
@@ -16,11 +23,21 @@ class HaExpansionPanel extends LitElement {
|
|||||||
|
|
||||||
@property() secondary?: string;
|
@property() secondary?: string;
|
||||||
|
|
||||||
|
@state() _showContent = this.expanded;
|
||||||
|
|
||||||
@query(".container") private _container!: HTMLDivElement;
|
@query(".container") private _container!: HTMLDivElement;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="summary" @click=${this._toggleContainer}>
|
<div
|
||||||
|
id="summary"
|
||||||
|
@click=${this._toggleContainer}
|
||||||
|
@keydown=${this._toggleContainer}
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
aria-expanded=${this.expanded}
|
||||||
|
aria-controls="sect1"
|
||||||
|
>
|
||||||
<slot class="header" name="header">
|
<slot class="header" name="header">
|
||||||
${this.header}
|
${this.header}
|
||||||
<slot class="secondary" name="secondary">${this.secondary}</slot>
|
<slot class="secondary" name="secondary">${this.secondary}</slot>
|
||||||
@@ -33,21 +50,37 @@ class HaExpansionPanel extends LitElement {
|
|||||||
<div
|
<div
|
||||||
class="container ${classMap({ expanded: this.expanded })}"
|
class="container ${classMap({ expanded: this.expanded })}"
|
||||||
@transitionend=${this._handleTransitionEnd}
|
@transitionend=${this._handleTransitionEnd}
|
||||||
|
role="region"
|
||||||
|
aria-labelledby="summary"
|
||||||
|
aria-hidden=${!this.expanded}
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<slot></slot>
|
${this._showContent ? html`<slot></slot>` : ""}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleTransitionEnd() {
|
protected willUpdate(changedProps: PropertyValues) {
|
||||||
this._container.style.removeProperty("height");
|
if (changedProps.has("expanded") && this.expanded) {
|
||||||
|
this._showContent = this.expanded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _toggleContainer(): Promise<void> {
|
private _handleTransitionEnd() {
|
||||||
|
this._container.style.removeProperty("height");
|
||||||
|
this._showContent = this.expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _toggleContainer(ev): Promise<void> {
|
||||||
|
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
const newExpanded = !this.expanded;
|
const newExpanded = !this.expanded;
|
||||||
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
|
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
|
||||||
|
|
||||||
if (newExpanded) {
|
if (newExpanded) {
|
||||||
|
this._showContent = true;
|
||||||
// allow for dynamic content to be rendered
|
// allow for dynamic content to be rendered
|
||||||
await nextRender();
|
await nextRender();
|
||||||
}
|
}
|
||||||
@@ -80,17 +113,21 @@ class HaExpansionPanel extends LitElement {
|
|||||||
var(--divider-color, #e0e0e0)
|
var(--divider-color, #e0e0e0)
|
||||||
);
|
);
|
||||||
border-radius: var(--ha-card-border-radius, 4px);
|
border-radius: var(--ha-card-border-radius, 4px);
|
||||||
padding: 0 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary {
|
#summary {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: var(--expansion-panel-summary-padding, 0);
|
padding: var(--expansion-panel-summary-padding, 0 8px);
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#summary:focus {
|
||||||
|
background: var(--input-fill-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-icon {
|
.summary-icon {
|
||||||
@@ -103,6 +140,7 @@ class HaExpansionPanel extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
padding: var(--expansion-panel-content-padding, 0 8px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
height: 0px;
|
height: 0px;
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
|
import { styles } from "@material/mwc-textfield/mwc-textfield.css";
|
||||||
import { mdiClose } from "@mdi/js";
|
import { mdiClose } from "@mdi/js";
|
||||||
import "@polymer/iron-input/iron-input";
|
|
||||||
import "@polymer/paper-input/paper-input-container";
|
|
||||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
@@ -21,7 +20,7 @@ export class HaFileUpload extends LitElement {
|
|||||||
|
|
||||||
@property() public accept!: string;
|
@property() public accept!: string;
|
||||||
|
|
||||||
@property() public icon!: string;
|
@property() public icon?: string;
|
||||||
|
|
||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
@@ -39,15 +38,7 @@ export class HaFileUpload extends LitElement {
|
|||||||
protected firstUpdated(changedProperties: PropertyValues) {
|
protected firstUpdated(changedProperties: PropertyValues) {
|
||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProperties);
|
||||||
if (this.autoOpenFileDialog) {
|
if (this.autoOpenFileDialog) {
|
||||||
this._input?.click();
|
this._openFilePicker();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues) {
|
|
||||||
if (changedProperties.has("_drag") && !this.uploading) {
|
|
||||||
(
|
|
||||||
this.shadowRoot!.querySelector("paper-input-container") as any
|
|
||||||
)._setFocused(this._drag);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,51 +51,75 @@ export class HaFileUpload extends LitElement {
|
|||||||
active
|
active
|
||||||
></ha-circular-progress>`
|
></ha-circular-progress>`
|
||||||
: html`
|
: html`
|
||||||
<label for="input">
|
<label
|
||||||
<paper-input-container
|
for="input"
|
||||||
.alwaysFloatLabel=${Boolean(this.value)}
|
class="mdc-text-field mdc-text-field--filled ${classMap({
|
||||||
@drop=${this._handleDrop}
|
"mdc-text-field--focused": this._drag,
|
||||||
@dragenter=${this._handleDragStart}
|
"mdc-text-field--with-leading-icon": Boolean(this.icon),
|
||||||
@dragover=${this._handleDragStart}
|
"mdc-text-field--with-trailing-icon": Boolean(this.value),
|
||||||
@dragleave=${this._handleDragEnd}
|
})}"
|
||||||
@dragend=${this._handleDragEnd}
|
@drop=${this._handleDrop}
|
||||||
class=${classMap({
|
@dragenter=${this._handleDragStart}
|
||||||
dragged: this._drag,
|
@dragover=${this._handleDragStart}
|
||||||
})}
|
@dragleave=${this._handleDragEnd}
|
||||||
|
@dragend=${this._handleDragEnd}
|
||||||
|
>
|
||||||
|
<span class="mdc-text-field__ripple"></span>
|
||||||
|
<span
|
||||||
|
class="mdc-floating-label ${this.value || this._drag
|
||||||
|
? "mdc-floating-label--float-above"
|
||||||
|
: ""}"
|
||||||
|
id="label"
|
||||||
|
>${this.label}</span
|
||||||
>
|
>
|
||||||
<label for="input" slot="label"> ${this.label} </label>
|
${this.icon
|
||||||
<iron-input slot="input">
|
? html`<span
|
||||||
<input
|
class="mdc-text-field__icon mdc-text-field__icon--leading"
|
||||||
id="input"
|
tabindex="-1"
|
||||||
type="file"
|
>
|
||||||
class="file"
|
<ha-icon-button
|
||||||
accept=${this.accept}
|
@click=${this._openFilePicker}
|
||||||
@change=${this._handleFilePicked}
|
.path=${this.icon}
|
||||||
/>
|
></ha-icon-button>
|
||||||
${this.value}
|
</span>`
|
||||||
</iron-input>
|
: ""}
|
||||||
${this.value
|
<div class="value">${this.value}</div>
|
||||||
? html`
|
<input
|
||||||
<ha-icon-button
|
id="input"
|
||||||
slot="suffix"
|
type="file"
|
||||||
@click=${this._clearValue}
|
class="mdc-text-field__input file"
|
||||||
.label=${this.hass?.localize("ui.common.close") ||
|
accept=${this.accept}
|
||||||
"close"}
|
@change=${this._handleFilePicked}
|
||||||
.path=${mdiClose}
|
aria-labelledby="label"
|
||||||
></ha-icon-button>
|
/>
|
||||||
`
|
${this.value
|
||||||
: html`
|
? html`<span
|
||||||
<ha-icon-button
|
class="mdc-text-field__icon mdc-text-field__icon--trailing"
|
||||||
slot="suffix"
|
tabindex="1"
|
||||||
.path=${this.icon}
|
>
|
||||||
></ha-icon-button>
|
<ha-icon-button
|
||||||
`}
|
slot="suffix"
|
||||||
</paper-input-container>
|
@click=${this._clearValue}
|
||||||
|
.label=${this.hass?.localize("ui.common.close") ||
|
||||||
|
"close"}
|
||||||
|
.path=${mdiClose}
|
||||||
|
></ha-icon-button>
|
||||||
|
</span>`
|
||||||
|
: ""}
|
||||||
|
<span
|
||||||
|
class="mdc-line-ripple ${this._drag
|
||||||
|
? "mdc-line-ripple--active"
|
||||||
|
: ""}"
|
||||||
|
></span>
|
||||||
</label>
|
</label>
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _openFilePicker() {
|
||||||
|
this._input?.click();
|
||||||
|
}
|
||||||
|
|
||||||
private _handleDrop(ev: DragEvent) {
|
private _handleDrop(ev: DragEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
@@ -137,40 +152,66 @@ export class HaFileUpload extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
return css`
|
return [
|
||||||
paper-input-container {
|
styles,
|
||||||
position: relative;
|
css`
|
||||||
padding: 8px;
|
:host {
|
||||||
margin: 0 -8px;
|
display: block;
|
||||||
}
|
}
|
||||||
paper-input-container.dragged:before {
|
.mdc-text-field--filled {
|
||||||
position: var(--layout-fit_-_position);
|
height: auto;
|
||||||
top: var(--layout-fit_-_top);
|
padding-top: 16px;
|
||||||
right: var(--layout-fit_-_right);
|
cursor: pointer;
|
||||||
bottom: var(--layout-fit_-_bottom);
|
}
|
||||||
left: var(--layout-fit_-_left);
|
.mdc-text-field--filled.mdc-text-field--with-trailing-icon {
|
||||||
background: currentColor;
|
padding-top: 28px;
|
||||||
content: "";
|
}
|
||||||
opacity: var(--dark-divider-opacity);
|
.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon {
|
||||||
pointer-events: none;
|
color: var(--secondary-text-color);
|
||||||
border-radius: 4px;
|
}
|
||||||
}
|
.mdc-text-field--filled.mdc-text-field--with-trailing-icon
|
||||||
input.file {
|
.mdc-text-field__icon {
|
||||||
display: none;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
img {
|
.mdc-text-field__icon--leading {
|
||||||
max-width: 125px;
|
margin-bottom: 12px;
|
||||||
max-height: 125px;
|
}
|
||||||
}
|
.mdc-text-field--filled .mdc-floating-label--float-above {
|
||||||
ha-icon-button {
|
transform: scale(0.75);
|
||||||
--mdc-icon-button-size: 24px;
|
top: 8px;
|
||||||
--mdc-icon-size: 20px;
|
}
|
||||||
}
|
.dragged:before {
|
||||||
ha-circular-progress {
|
position: var(--layout-fit_-_position);
|
||||||
display: block;
|
top: var(--layout-fit_-_top);
|
||||||
text-align-last: center;
|
right: var(--layout-fit_-_right);
|
||||||
}
|
bottom: var(--layout-fit_-_bottom);
|
||||||
`;
|
left: var(--layout-fit_-_left);
|
||||||
|
background: currentColor;
|
||||||
|
content: "";
|
||||||
|
opacity: var(--dark-divider-opacity);
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input.file {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 125px;
|
||||||
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
--mdc-icon-button-size: 24px;
|
||||||
|
--mdc-icon-size: 20px;
|
||||||
|
}
|
||||||
|
ha-circular-progress {
|
||||||
|
display: block;
|
||||||
|
text-align-last: center;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,7 +9,9 @@ export class HaFormConstant extends LitElement implements HaFormElement {
|
|||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`<span class="label">${this.label}</span>: ${this.schema.value}`;
|
return html`<span class="label">${this.label}</span>${this.schema.value
|
||||||
|
? `: ${this.schema.value}`
|
||||||
|
: ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
95
src/components/ha-form/ha-form-grid.ts
Normal file
95
src/components/ha-form/ha-form-grid.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import "./ha-form";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import type {
|
||||||
|
HaFormGridSchema,
|
||||||
|
HaFormDataContainer,
|
||||||
|
HaFormElement,
|
||||||
|
HaFormSchema,
|
||||||
|
} from "./types";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
@customElement("ha-form-grid")
|
||||||
|
export class HaFormGrid extends LitElement implements HaFormElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public data!: HaFormDataContainer;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public schema!: HaFormGridSchema;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property() public computeLabel?: (
|
||||||
|
schema: HaFormSchema,
|
||||||
|
data?: HaFormDataContainer
|
||||||
|
) => string;
|
||||||
|
|
||||||
|
@property() public computeHelper?: (schema: HaFormSchema) => string;
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.setAttribute("own-margin", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues): void {
|
||||||
|
super.updated(changedProps);
|
||||||
|
if (changedProps.has("schema")) {
|
||||||
|
if (this.schema.column_min_width) {
|
||||||
|
this.style.setProperty(
|
||||||
|
"--form-grid-min-width",
|
||||||
|
this.schema.column_min_width
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.style.setProperty("--form-grid-min-width", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${this.schema.schema.map(
|
||||||
|
(item) =>
|
||||||
|
html`
|
||||||
|
<ha-form
|
||||||
|
.hass=${this.hass}
|
||||||
|
.data=${this.data}
|
||||||
|
.schema=${[item]}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.computeLabel=${this.computeLabel}
|
||||||
|
.computeHelper=${this.computeHelper}
|
||||||
|
></ha-form>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: repeat(
|
||||||
|
var(--form-grid-column-count, auto-fit),
|
||||||
|
minmax(var(--form-grid-min-width, 200px), 1fr)
|
||||||
|
);
|
||||||
|
grid-gap: 8px;
|
||||||
|
}
|
||||||
|
:host > ha-form {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-form-grid": HaFormGrid;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import { mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import { mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
import "@material/mwc-select";
|
|
||||||
import type { Select } from "@material/mwc-select";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-radio";
|
|
||||||
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./types";
|
|
||||||
|
|
||||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
|
import "../ha-radio";
|
||||||
import type { HaRadio } from "../ha-radio";
|
import type { HaRadio } from "../ha-radio";
|
||||||
|
import "../ha-select";
|
||||||
|
import type { HaSelect } from "../ha-select";
|
||||||
|
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./types";
|
||||||
|
|
||||||
@customElement("ha-form-select")
|
@customElement("ha-form-select")
|
||||||
export class HaFormSelect extends LitElement implements HaFormElement {
|
export class HaFormSelect extends LitElement implements HaFormElement {
|
||||||
@@ -20,7 +19,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("mwc-select", true) private _input?: HTMLElement;
|
@query("ha-select", true) private _input?: HTMLElement;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
@@ -50,7 +49,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<mwc-select
|
<ha-select
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
@@ -67,13 +66,13 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
<mwc-list-item .value=${value}>${label}</mwc-list-item>
|
<mwc-list-item .value=${value}>${label}</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
private _valueChanged(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
let value: string | undefined = (ev.target as Select | HaRadio).value;
|
let value: string | undefined = (ev.target as HaSelect | HaRadio).value;
|
||||||
|
|
||||||
if (value === this.data) {
|
if (value === this.data) {
|
||||||
return;
|
return;
|
||||||
@@ -90,7 +89,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
mwc-select,
|
ha-select,
|
||||||
mwc-formfield {
|
mwc-formfield {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,18 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
PropertyValues,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-alert";
|
import "../ha-alert";
|
||||||
import "./ha-form-boolean";
|
import "./ha-form-boolean";
|
||||||
import "./ha-form-constant";
|
import "./ha-form-constant";
|
||||||
|
import "./ha-form-grid";
|
||||||
import "./ha-form-float";
|
import "./ha-form-float";
|
||||||
import "./ha-form-integer";
|
import "./ha-form-integer";
|
||||||
import "./ha-form-multi_select";
|
import "./ha-form-multi_select";
|
||||||
@@ -14,17 +22,20 @@ import "./ha-form-string";
|
|||||||
import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types";
|
import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
const getValue = (obj, item) => (obj ? obj[item.name] : null);
|
const getValue = (obj, item) =>
|
||||||
|
obj ? (!item.name ? obj : obj[item.name]) : null;
|
||||||
|
|
||||||
|
const getError = (obj, item) => (obj && item.name ? obj[item.name] : null);
|
||||||
|
|
||||||
let selectorImported = false;
|
let selectorImported = false;
|
||||||
|
|
||||||
@customElement("ha-form")
|
@customElement("ha-form")
|
||||||
export class HaForm extends LitElement implements HaFormElement {
|
export class HaForm extends LitElement implements HaFormElement {
|
||||||
@property() public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public data!: HaFormDataContainer;
|
@property({ attribute: false }) public data!: HaFormDataContainer;
|
||||||
|
|
||||||
@property() public schema!: HaFormSchema[];
|
@property({ attribute: false }) public schema!: HaFormSchema[];
|
||||||
|
|
||||||
@property() public error?: Record<string, string>;
|
@property() public error?: Record<string, string>;
|
||||||
|
|
||||||
@@ -64,7 +75,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="root">
|
<div class="root">
|
||||||
${this.error && this.error.base
|
${this.error && this.error.base
|
||||||
@@ -75,7 +86,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this.schema.map((item) => {
|
${this.schema.map((item) => {
|
||||||
const error = getValue(this.error, item);
|
const error = getError(this.error, item);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${error
|
${error
|
||||||
@@ -101,6 +112,9 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
data: getValue(this.data, item),
|
data: getValue(this.data, item),
|
||||||
label: this._computeLabel(item, this.data),
|
label: this._computeLabel(item, this.data),
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
|
hass: this.hass,
|
||||||
|
computeLabel: this.computeLabel,
|
||||||
|
computeHelper: this.computeHelper,
|
||||||
})}
|
})}
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
@@ -115,8 +129,12 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
||||||
|
|
||||||
|
const newValue = !schema.name
|
||||||
|
? ev.detail.value
|
||||||
|
: { [schema.name]: ev.detail.value };
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { ...this.data, [schema.name]: ev.detail.value },
|
value: { ...this.data, ...newValue },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return root;
|
return root;
|
||||||
|
@@ -11,7 +11,8 @@ export type HaFormSchema =
|
|||||||
| HaFormSelectSchema
|
| HaFormSelectSchema
|
||||||
| HaFormMultiSelectSchema
|
| HaFormMultiSelectSchema
|
||||||
| HaFormTimeSchema
|
| HaFormTimeSchema
|
||||||
| HaFormSelector;
|
| HaFormSelector
|
||||||
|
| HaFormGridSchema;
|
||||||
|
|
||||||
export interface HaFormBaseSchema {
|
export interface HaFormBaseSchema {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -25,6 +26,13 @@ export interface HaFormBaseSchema {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HaFormGridSchema extends HaFormBaseSchema {
|
||||||
|
type: "grid";
|
||||||
|
name: "";
|
||||||
|
column_min_width?: string;
|
||||||
|
schema: HaFormSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface HaFormSelector extends HaFormBaseSchema {
|
export interface HaFormSelector extends HaFormBaseSchema {
|
||||||
type?: never;
|
type?: never;
|
||||||
selector: Selector;
|
selector: Selector;
|
||||||
@@ -32,7 +40,7 @@ export interface HaFormSelector extends HaFormBaseSchema {
|
|||||||
|
|
||||||
export interface HaFormConstantSchema extends HaFormBaseSchema {
|
export interface HaFormConstantSchema extends HaFormBaseSchema {
|
||||||
type: "constant";
|
type: "constant";
|
||||||
value: string;
|
value?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import { mdiCamera } from "@mdi/js";
|
import { mdiCamera } from "@mdi/js";
|
||||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
|
57
src/components/ha-select.ts
Normal file
57
src/components/ha-select.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { SelectBase } from "@material/mwc-select/mwc-select-base";
|
||||||
|
import { styles } from "@material/mwc-select/mwc-select.css";
|
||||||
|
import { css, html, nothing } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { debounce } from "../common/util/debounce";
|
||||||
|
import { nextRender } from "../common/util/render-status";
|
||||||
|
|
||||||
|
@customElement("ha-select")
|
||||||
|
export class HaSelect extends SelectBase {
|
||||||
|
// @ts-ignore
|
||||||
|
@property({ type: Boolean }) public icon?: boolean;
|
||||||
|
|
||||||
|
protected override renderLeadingIcon() {
|
||||||
|
if (!this.icon) {
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`<span class="mdc-select__icon"
|
||||||
|
><slot name="icon"></slot
|
||||||
|
></span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
window.addEventListener("translations-updated", this._translationsUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
window.removeEventListener(
|
||||||
|
"translations-updated",
|
||||||
|
this._translationsUpdated
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _translationsUpdated = debounce(async () => {
|
||||||
|
await nextRender();
|
||||||
|
this.layoutOptions();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
static override styles = [
|
||||||
|
styles,
|
||||||
|
css`
|
||||||
|
.mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.mdc-select__anchor {
|
||||||
|
width: var(--ha-select-min-width, 200px);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-select": HaSelect;
|
||||||
|
}
|
||||||
|
}
|
@@ -35,9 +35,12 @@ export class HaBooleanSelector extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
|
:host {
|
||||||
|
height: 56px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
ha-formfield {
|
ha-formfield {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 16px 0;
|
|
||||||
--mdc-typography-body2-font-size: 1em;
|
--mdc-typography-body2-font-size: 1em;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -50,7 +50,13 @@ export class HaEntitySelector extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
private _filterEntities = (entity: HassEntity): boolean => {
|
private _filterEntities = (entity: HassEntity): boolean => {
|
||||||
if (this.selector.entity?.domain) {
|
if (this.selector.entity?.domain) {
|
||||||
if (computeStateDomain(entity) !== this.selector.entity.domain) {
|
const filterDomain = this.selector.entity.domain;
|
||||||
|
const filterDomainIsArray = Array.isArray(filterDomain);
|
||||||
|
const entityDomain = computeStateDomain(entity);
|
||||||
|
if (
|
||||||
|
(filterDomainIsArray && !filterDomain.includes(entityDomain)) ||
|
||||||
|
(!filterDomainIsArray && entityDomain !== filterDomain)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,8 @@ export class HaIconSelector extends LitElement {
|
|||||||
<ha-icon-picker
|
<ha-icon-picker
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
|
.fallbackPath=${this.selector.icon.fallbackPath}
|
||||||
|
.placeholder=${this.selector.icon.placeholder}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-icon-picker>
|
></ha-icon-picker>
|
||||||
`;
|
`;
|
||||||
|
80
src/components/ha-selector/ha-selector-location.ts
Normal file
80
src/components/ha-selector/ha-selector-location.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import type {
|
||||||
|
LocationSelector,
|
||||||
|
LocationSelectorValue,
|
||||||
|
} from "../../data/selector";
|
||||||
|
import "../../panels/lovelace/components/hui-theme-select-editor";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import type { MarkerLocation } from "../map/ha-locations-editor";
|
||||||
|
import "../map/ha-locations-editor";
|
||||||
|
|
||||||
|
@customElement("ha-selector-location")
|
||||||
|
export class HaLocationSelector extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public selector!: LocationSelector;
|
||||||
|
|
||||||
|
@property() public value?: LocationSelectorValue;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<ha-locations-editor
|
||||||
|
class="flex"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.locations=${this._location(this.selector, this.value)}
|
||||||
|
@location-updated=${this._locationChanged}
|
||||||
|
@radius-updated=${this._radiusChanged}
|
||||||
|
></ha-locations-editor>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _location = memoizeOne(
|
||||||
|
(
|
||||||
|
selector: LocationSelector,
|
||||||
|
value?: LocationSelectorValue
|
||||||
|
): MarkerLocation[] => {
|
||||||
|
const computedStyles = getComputedStyle(this);
|
||||||
|
const zoneRadiusColor = selector.location.radius
|
||||||
|
? computedStyles.getPropertyValue("--zone-radius-color") ||
|
||||||
|
computedStyles.getPropertyValue("--accent-color")
|
||||||
|
: undefined;
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: "location",
|
||||||
|
latitude: value?.latitude || this.hass.config.latitude,
|
||||||
|
longitude: value?.longitude || this.hass.config.longitude,
|
||||||
|
radius: selector.location.radius ? value?.radius || 1000 : undefined,
|
||||||
|
radius_color: zoneRadiusColor,
|
||||||
|
icon: selector.location.icon,
|
||||||
|
location_editable: true,
|
||||||
|
radius_editable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private _locationChanged(ev: CustomEvent) {
|
||||||
|
const [latitude, longitude] = ev.detail.location;
|
||||||
|
fireEvent(this, "value-changed", {
|
||||||
|
value: { ...this.value, latitude, longitude },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _radiusChanged(ev: CustomEvent) {
|
||||||
|
const radius = ev.detail.radius;
|
||||||
|
fireEvent(this, "value-changed", { value: { ...this.value, radius } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-location": HaLocationSelector;
|
||||||
|
}
|
||||||
|
}
|
@@ -12,6 +12,7 @@ import {
|
|||||||
} from "../../data/media-player";
|
} from "../../data/media-player";
|
||||||
import type { MediaSelector, MediaSelectorValue } from "../../data/selector";
|
import type { MediaSelector, MediaSelectorValue } from "../../data/selector";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
|
||||||
import "../ha-alert";
|
import "../ha-alert";
|
||||||
import "../ha-form/ha-form";
|
import "../ha-form/ha-form";
|
||||||
import type { HaFormSchema } from "../ha-form/types";
|
import type { HaFormSchema } from "../ha-form/types";
|
||||||
@@ -50,6 +51,18 @@ export class HaMediaSelector extends LitElement {
|
|||||||
getSignedPath(this.hass, thumbnail).then((signedPath) => {
|
getSignedPath(this.hass, thumbnail).then((signedPath) => {
|
||||||
this._thumbnailUrl = signedPath.path;
|
this._thumbnailUrl = signedPath.path;
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
thumbnail &&
|
||||||
|
thumbnail.startsWith("https://brands.home-assistant.io")
|
||||||
|
) {
|
||||||
|
// The backend is not aware of the theme used by the users,
|
||||||
|
// so we rewrite the URL to show a proper icon
|
||||||
|
this._thumbnailUrl = brandsUrl({
|
||||||
|
domain: extractDomainFromBrandUrl(thumbnail),
|
||||||
|
type: "icon",
|
||||||
|
useFallback: true,
|
||||||
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this._thumbnailUrl = thumbnail;
|
this._thumbnailUrl = thumbnail;
|
||||||
}
|
}
|
||||||
|
@@ -19,22 +19,24 @@ export class HaNumberSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public required = true;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`${this.label}
|
return html`${this.selector.number.mode !== "box"
|
||||||
${this.selector.number.mode !== "box"
|
? html`${this.label}<ha-slider
|
||||||
? html`<ha-slider
|
.min=${this.selector.number.min}
|
||||||
.min=${this.selector.number.min}
|
.max=${this.selector.number.max}
|
||||||
.max=${this.selector.number.max}
|
.value=${this._value}
|
||||||
.value=${this._value}
|
.step=${this.selector.number.step ?? 1}
|
||||||
.step=${this.selector.number.step ?? 1}
|
.disabled=${this.disabled}
|
||||||
.disabled=${this.disabled}
|
.required=${this.required}
|
||||||
pin
|
pin
|
||||||
ignore-bar-touch
|
ignore-bar-touch
|
||||||
@change=${this._handleSliderChange}
|
@change=${this._handleSliderChange}
|
||||||
>
|
>
|
||||||
</ha-slider>`
|
</ha-slider>`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
@@ -44,9 +46,10 @@ export class HaNumberSelector extends LitElement {
|
|||||||
class=${classMap({ single: this.selector.number.mode === "box" })}
|
class=${classMap({ single: this.selector.number.mode === "box" })}
|
||||||
.min=${this.selector.number.min}
|
.min=${this.selector.number.min}
|
||||||
.max=${this.selector.number.max}
|
.max=${this.selector.number.max}
|
||||||
.value=${this.value}
|
.value=${this.value || ""}
|
||||||
.step=${this.selector.number.step ?? 1}
|
.step=${this.selector.number.step ?? 1}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.required}
|
||||||
.suffix=${this.selector.number.unit_of_measurement}
|
.suffix=${this.selector.number.unit_of_measurement}
|
||||||
type="number"
|
type="number"
|
||||||
autoValidate
|
autoValidate
|
||||||
@@ -57,14 +60,16 @@ export class HaNumberSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
return this.value ?? 0;
|
return this.value ?? (this.selector.number.min || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleInputChange(ev) {
|
private _handleInputChange(ev) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const value =
|
const value =
|
||||||
ev.target.value === "" || isNaN(ev.target.value)
|
ev.target.value === "" || isNaN(ev.target.value)
|
||||||
? undefined
|
? this.required
|
||||||
|
? this.selector.number.min || 0
|
||||||
|
: undefined
|
||||||
: Number(ev.target.value);
|
: Number(ev.target.value);
|
||||||
if (this.value === value) {
|
if (this.value === value) {
|
||||||
return;
|
return;
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
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 { stopPropagation } from "../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
import { SelectOption, SelectSelector } from "../../data/selector";
|
import { SelectOption, SelectSelector } from "../../data/selector";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "@material/mwc-select/mwc-select";
|
import "../ha-select";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
|
|
||||||
@customElement("ha-selector-select")
|
@customElement("ha-selector-select")
|
||||||
export class HaSelectSelector extends LitElement {
|
export class HaSelectSelector extends LitElement {
|
||||||
@@ -22,7 +22,7 @@ export class HaSelectSelector extends LitElement {
|
|||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`<mwc-select
|
return html`<ha-select
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
@@ -38,7 +38,7 @@ export class HaSelectSelector extends LitElement {
|
|||||||
|
|
||||||
return html`<mwc-list-item .value=${value}>${label}</mwc-list-item>`;
|
return html`<mwc-list-item .value=${value}>${label}</mwc-list-item>`;
|
||||||
})}
|
})}
|
||||||
</mwc-select>`;
|
</ha-select>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev) {
|
private _valueChanged(ev) {
|
||||||
@@ -53,7 +53,7 @@ export class HaSelectSelector extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
mwc-select {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -69,10 +69,14 @@ export class HaTextSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleChange(ev) {
|
private _handleChange(ev) {
|
||||||
const value = ev.target.value;
|
let value = ev.target.value;
|
||||||
if (this.value === value) {
|
if (this.value === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (value === "" && !this.required) {
|
||||||
|
value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
fireEvent(this, "value-changed", { value });
|
fireEvent(this, "value-changed", { value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
34
src/components/ha-selector/ha-selector-theme.ts
Normal file
34
src/components/ha-selector/ha-selector-theme.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import "../../panels/lovelace/components/hui-theme-select-editor";
|
||||||
|
import { html, LitElement } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import type { ThemeSelector } from "../../data/selector";
|
||||||
|
|
||||||
|
@customElement("ha-selector-theme")
|
||||||
|
export class HaThemeSelector extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public selector!: ThemeSelector;
|
||||||
|
|
||||||
|
@property() public value?: string;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
|
protected render() {
|
||||||
|
return html`
|
||||||
|
<hui-theme-select-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this.value}
|
||||||
|
.label=${this.label}
|
||||||
|
></hui-theme-select-editor>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-selector-theme": HaThemeSelector;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||||
import { Selector } from "../../data/selector";
|
import type { Selector } from "../../data/selector";
|
||||||
import { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "./ha-selector-action";
|
import "./ha-selector-action";
|
||||||
import "./ha-selector-addon";
|
import "./ha-selector-addon";
|
||||||
import "./ha-selector-area";
|
import "./ha-selector-area";
|
||||||
@@ -19,6 +19,8 @@ import "./ha-selector-text";
|
|||||||
import "./ha-selector-time";
|
import "./ha-selector-time";
|
||||||
import "./ha-selector-icon";
|
import "./ha-selector-icon";
|
||||||
import "./ha-selector-media";
|
import "./ha-selector-media";
|
||||||
|
import "./ha-selector-theme";
|
||||||
|
import "./ha-selector-location";
|
||||||
|
|
||||||
@customElement("ha-selector")
|
@customElement("ha-selector")
|
||||||
export class HaSelector extends LitElement {
|
export class HaSelector extends LitElement {
|
||||||
|
@@ -9,6 +9,12 @@ export class HaTextField extends TextFieldBase {
|
|||||||
|
|
||||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
@property({ type: Boolean }) public icon?: boolean;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
@property({ type: Boolean }) public iconTrailing?: boolean;
|
||||||
|
|
||||||
override updated(changedProperties: PropertyValues) {
|
override updated(changedProperties: PropertyValues) {
|
||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (
|
if (
|
||||||
@@ -53,6 +59,15 @@ export class HaTextField extends TextFieldBase {
|
|||||||
padding-right: var(--text-field-suffix-padding-right, 0px);
|
padding-right: var(--text-field-suffix-padding-right, 0px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdc-text-field:not(.mdc-text-field--disabled)
|
||||||
|
.mdc-text-field__affix--suffix {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-text-field__icon {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
text-align: var(--text-field-text-align);
|
text-align: var(--text-field-text-align);
|
||||||
}
|
}
|
||||||
|
@@ -43,7 +43,7 @@ class HaWebRtcPlayer extends LitElement {
|
|||||||
|
|
||||||
private _remoteStream?: MediaStream;
|
private _remoteStream?: MediaStream;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected override render(): TemplateResult {
|
||||||
if (this._error) {
|
if (this._error) {
|
||||||
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
return html`<ha-alert alert-type="error">${this._error}</ha-alert>`;
|
||||||
}
|
}
|
||||||
@@ -58,12 +58,19 @@ class HaWebRtcPlayer extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public override connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
if (this.hasUpdated) {
|
||||||
|
this._startWebRtc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._cleanUp();
|
this._cleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProperties: PropertyValues<this>) {
|
protected override updated(changedProperties: PropertyValues<this>) {
|
||||||
if (!changedProperties.has("entityid")) {
|
if (!changedProperties.has("entityid")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
337
src/components/media-player/dialog-media-manage.ts
Normal file
337
src/components/media-player/dialog-media-manage.ts
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
import { animate } from "@lit-labs/motion";
|
||||||
|
import "@material/mwc-list/mwc-list";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import { mdiClose, mdiDelete } from "@mdi/js";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { repeat } from "lit/directives/repeat";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||||
|
import {
|
||||||
|
MediaClassBrowserSettings,
|
||||||
|
MediaPlayerItem,
|
||||||
|
} from "../../data/media-player";
|
||||||
|
import {
|
||||||
|
browseLocalMediaPlayer,
|
||||||
|
removeLocalMedia,
|
||||||
|
} from "../../data/media_source";
|
||||||
|
import { showConfirmationDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-circular-progress";
|
||||||
|
import "../ha-dialog";
|
||||||
|
import "../ha-header-bar";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
import "../ha-check-list-item";
|
||||||
|
import "./ha-media-player-browse";
|
||||||
|
import "./ha-media-upload-button";
|
||||||
|
import type { MediaManageDialogParams } from "./show-media-manage-dialog";
|
||||||
|
|
||||||
|
@customElement("dialog-media-manage")
|
||||||
|
class DialogMediaManage extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _currentItem?: MediaPlayerItem;
|
||||||
|
|
||||||
|
@state() private _params?: MediaManageDialogParams;
|
||||||
|
|
||||||
|
@state() private _uploading = false;
|
||||||
|
|
||||||
|
@state() private _deleting = false;
|
||||||
|
|
||||||
|
@state() private _selected = new Set<number>();
|
||||||
|
|
||||||
|
private _filesChanged = false;
|
||||||
|
|
||||||
|
public showDialog(params: MediaManageDialogParams): void {
|
||||||
|
this._params = params;
|
||||||
|
this._refreshMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog() {
|
||||||
|
if (this._filesChanged && this._params!.onClose) {
|
||||||
|
this._params!.onClose();
|
||||||
|
}
|
||||||
|
this._params = undefined;
|
||||||
|
this._currentItem = undefined;
|
||||||
|
this._uploading = false;
|
||||||
|
this._deleting = false;
|
||||||
|
this._filesChanged = false;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._params) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
const children =
|
||||||
|
this._currentItem?.children?.filter((child) => !child.can_expand) || [];
|
||||||
|
|
||||||
|
let fileIndex = 0;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
scrimClickAction
|
||||||
|
escapeKeyAction
|
||||||
|
hideActions
|
||||||
|
flexContent
|
||||||
|
.heading=${this._params.currentItem.title}
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
>
|
||||||
|
<ha-header-bar slot="heading">
|
||||||
|
${this._selected.size === 0
|
||||||
|
? html`
|
||||||
|
<span slot="title">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.title"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<ha-media-upload-button
|
||||||
|
.disabled=${this._deleting}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.currentItem=${this._params.currentItem}
|
||||||
|
@uploading=${this._startUploading}
|
||||||
|
@media-refresh=${this._doneUploading}
|
||||||
|
slot="actionItems"
|
||||||
|
></ha-media-upload-button>
|
||||||
|
${this._uploading
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<ha-icon-button
|
||||||
|
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
||||||
|
.path=${mdiClose}
|
||||||
|
dialogAction="close"
|
||||||
|
slot="actionItems"
|
||||||
|
class="header_button"
|
||||||
|
dir=${computeRTLDirection(this.hass)}
|
||||||
|
></ha-icon-button>
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<mwc-button
|
||||||
|
class="danger"
|
||||||
|
slot="title"
|
||||||
|
.disabled=${this._deleting}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.components.media-browser.file_management.${
|
||||||
|
this._deleting ? "deleting" : "delete"
|
||||||
|
}`,
|
||||||
|
{ count: this._selected.size }
|
||||||
|
)}
|
||||||
|
@click=${this._handleDelete}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiDelete} slot="icon"></ha-svg-icon>
|
||||||
|
</mwc-button>
|
||||||
|
|
||||||
|
${this._deleting
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<mwc-button
|
||||||
|
slot="actionItems"
|
||||||
|
.label=${`Deselect all`}
|
||||||
|
@click=${this._handleDeselectAll}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${mdiClose}
|
||||||
|
slot="icon"
|
||||||
|
></ha-svg-icon>
|
||||||
|
</mwc-button>
|
||||||
|
`}
|
||||||
|
`}
|
||||||
|
</ha-header-bar>
|
||||||
|
${!this._currentItem
|
||||||
|
? html`
|
||||||
|
<div class="refresh">
|
||||||
|
<ha-circular-progress active></ha-circular-progress>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: !children.length
|
||||||
|
? html`<div class="no-items">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.no_items"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
${this._currentItem?.children?.length
|
||||||
|
? html`<span class="folders"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.folders_not_supported"
|
||||||
|
)}</span
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
|
</div>`
|
||||||
|
: html`
|
||||||
|
<mwc-list multi @selected=${this._handleSelected}>
|
||||||
|
${repeat(
|
||||||
|
children,
|
||||||
|
(item) => item.media_content_id,
|
||||||
|
(item) => {
|
||||||
|
const icon = html`
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="graphic"
|
||||||
|
.path=${MediaClassBrowserSettings[
|
||||||
|
item.media_class === "directory"
|
||||||
|
? item.children_media_class || item.media_class
|
||||||
|
: item.media_class
|
||||||
|
].icon}
|
||||||
|
></ha-svg-icon>
|
||||||
|
`;
|
||||||
|
return html`
|
||||||
|
<ha-check-list-item
|
||||||
|
${animate({
|
||||||
|
id: item.media_content_id,
|
||||||
|
skipInitial: true,
|
||||||
|
})}
|
||||||
|
graphic="icon"
|
||||||
|
.disabled=${this._uploading || this._deleting}
|
||||||
|
.selected=${this._selected.has(fileIndex++)}
|
||||||
|
.item=${item}
|
||||||
|
>
|
||||||
|
${icon} ${item.title}
|
||||||
|
</ha-check-list-item>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</mwc-list>
|
||||||
|
`}
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleSelected(ev) {
|
||||||
|
this._selected = ev.detail.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _startUploading() {
|
||||||
|
this._uploading = true;
|
||||||
|
this._filesChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _doneUploading() {
|
||||||
|
this._uploading = false;
|
||||||
|
this._refreshMedia();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleDeselectAll() {
|
||||||
|
if (this._selected.size) {
|
||||||
|
this._selected = new Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _handleDelete() {
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.confirm_delete",
|
||||||
|
{ count: this._selected.size }
|
||||||
|
),
|
||||||
|
warning: true,
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._filesChanged = true;
|
||||||
|
this._deleting = true;
|
||||||
|
|
||||||
|
const toDelete: MediaPlayerItem[] = [];
|
||||||
|
let fileIndex = 0;
|
||||||
|
this._currentItem!.children!.forEach((item) => {
|
||||||
|
if (item.can_expand) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._selected.has(fileIndex++)) {
|
||||||
|
toDelete.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
toDelete.map(async (item) => {
|
||||||
|
await removeLocalMedia(this.hass, item.media_content_id);
|
||||||
|
this._currentItem = {
|
||||||
|
...this._currentItem!,
|
||||||
|
children: this._currentItem!.children!.filter((i) => i !== item),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this._deleting = false;
|
||||||
|
this._selected = new Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _refreshMedia() {
|
||||||
|
this._selected = new Set();
|
||||||
|
this._currentItem = undefined;
|
||||||
|
this._currentItem = await browseLocalMediaPlayer(
|
||||||
|
this.hass,
|
||||||
|
this._params!.currentItem.media_content_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
--dialog-z-index: 8;
|
||||||
|
--dialog-content-padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 800px) {
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-max-width: 800px;
|
||||||
|
--dialog-surface-position: fixed;
|
||||||
|
--dialog-surface-top: 40px;
|
||||||
|
--mdc-dialog-max-height: calc(100vh - 72px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-header-bar {
|
||||||
|
--mdc-theme-on-primary: var(--primary-text-color);
|
||||||
|
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-media-upload-button,
|
||||||
|
mwc-button {
|
||||||
|
--mdc-theme-primary: var(--mdc-theme-on-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
--mdc-theme-primary: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-svg-icon[slot="icon"] {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh {
|
||||||
|
display: flex;
|
||||||
|
height: 200px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-items {
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.folders {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-media-manage": DialogMediaManage;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import "../ha-header-bar";
|
import "../ha-header-bar";
|
||||||
import { mdiArrowLeft, mdiClose } from "@mdi/js";
|
import { mdiArrowLeft, mdiClose } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
|
||||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||||
import type {
|
import type {
|
||||||
@@ -13,7 +13,11 @@ import { haStyleDialog } from "../../resources/styles";
|
|||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-dialog";
|
import "../ha-dialog";
|
||||||
import "./ha-media-player-browse";
|
import "./ha-media-player-browse";
|
||||||
import type { MediaPlayerItemId } from "./ha-media-player-browse";
|
import "./ha-media-manage-button";
|
||||||
|
import type {
|
||||||
|
HaMediaPlayerBrowse,
|
||||||
|
MediaPlayerItemId,
|
||||||
|
} from "./ha-media-player-browse";
|
||||||
import { MediaPlayerBrowseDialogParams } from "./show-media-browser-dialog";
|
import { MediaPlayerBrowseDialogParams } from "./show-media-browser-dialog";
|
||||||
|
|
||||||
@customElement("dialog-media-player-browse")
|
@customElement("dialog-media-player-browse")
|
||||||
@@ -26,6 +30,8 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
|
|
||||||
@state() private _params?: MediaPlayerBrowseDialogParams;
|
@state() private _params?: MediaPlayerBrowseDialogParams;
|
||||||
|
|
||||||
|
@query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse;
|
||||||
|
|
||||||
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
||||||
this._params = params;
|
this._params = params;
|
||||||
this._navigateIds = params.navigateIds || [
|
this._navigateIds = params.navigateIds || [
|
||||||
@@ -80,6 +86,12 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
: this._currentItem.title}
|
: this._currentItem.title}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<ha-media-manage-button
|
||||||
|
slot="actionItems"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.currentItem=${this._currentItem}
|
||||||
|
@media-refresh=${this._refreshMedia}
|
||||||
|
></ha-media-manage-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
.label=${this.hass.localize("ui.dialogs.generic.close")}
|
||||||
.path=${mdiClose}
|
.path=${mdiClose}
|
||||||
@@ -124,6 +136,10 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
return this._params!.action || "play";
|
return this._params!.action || "play";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _refreshMedia() {
|
||||||
|
this._browser.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
@@ -157,6 +173,10 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12));
|
border-bottom: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-media-manage-button {
|
||||||
|
--mdc-theme-primary: var(--mdc-theme-on-primary);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import "@material/mwc-select";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { css, html, LitElement, PropertyValues } from "lit";
|
import { css, html, LitElement, PropertyValues } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { LocalStorage } from "../../common/decorators/local-storage";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
import { fetchCloudStatus, updateCloudPref } from "../../data/cloud";
|
import { fetchCloudStatus, updateCloudPref } from "../../data/cloud";
|
||||||
import {
|
import {
|
||||||
CloudTTSInfo,
|
CloudTTSInfo,
|
||||||
@@ -15,12 +16,11 @@ import {
|
|||||||
MediaPlayerBrowseAction,
|
MediaPlayerBrowseAction,
|
||||||
MediaPlayerItem,
|
MediaPlayerItem,
|
||||||
} from "../../data/media-player";
|
} from "../../data/media-player";
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import "../ha-textarea";
|
|
||||||
import { buttonLinkStyle } from "../../resources/styles";
|
|
||||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
import { LocalStorage } from "../../common/decorators/local-storage";
|
import { buttonLinkStyle } from "../../resources/styles";
|
||||||
import { stopPropagation } from "../../common/dom/stop_propagation";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-select";
|
||||||
|
import "../ha-textarea";
|
||||||
|
|
||||||
export interface TtsMediaPickedEvent {
|
export interface TtsMediaPickedEvent {
|
||||||
item: MediaPlayerItem;
|
item: MediaPlayerItem;
|
||||||
@@ -103,7 +103,7 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="cloud-options">
|
<div class="cloud-options">
|
||||||
<mwc-select
|
<ha-select
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -117,9 +117,9 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
([key, label]) =>
|
([key, label]) =>
|
||||||
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
|
|
||||||
<mwc-select
|
<ha-select
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
.label=${this.hass.localize("ui.components.media-browser.tts.gender")}
|
.label=${this.hass.localize("ui.components.media-browser.tts.gender")}
|
||||||
@@ -131,7 +131,7 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
([key, label]) =>
|
([key, label]) =>
|
||||||
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
html`<mwc-list-item .value=${key}>${label}</mwc-list-item>`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ class BrowseMediaTTS extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.cloud-options mwc-select {
|
.cloud-options ha-select {
|
||||||
width: 48%;
|
width: 48%;
|
||||||
}
|
}
|
||||||
ha-textarea {
|
ha-textarea {
|
||||||
|
69
src/components/media-player/ha-media-manage-button.ts
Normal file
69
src/components/media-player/ha-media-manage-button.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { mdiFolderEdit } from "@mdi/js";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { MediaPlayerItem } from "../../data/media-player";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
import { isLocalMediaSourceContentId } from "../../data/media_source";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { showMediaManageDialog } from "./show-media-manage-dialog";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
"media-refresh": unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-media-manage-button")
|
||||||
|
class MediaManageButton extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() currentItem?: MediaPlayerItem;
|
||||||
|
|
||||||
|
@state() _uploading = 0;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (
|
||||||
|
!this.currentItem ||
|
||||||
|
!isLocalMediaSourceContentId(this.currentItem.media_content_id || "")
|
||||||
|
) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<mwc-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.manage"
|
||||||
|
)}
|
||||||
|
@click=${this._manage}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${mdiFolderEdit} slot="icon"></ha-svg-icon>
|
||||||
|
</mwc-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _manage() {
|
||||||
|
showMediaManageDialog(this, {
|
||||||
|
currentItem: this.currentItem!,
|
||||||
|
onClose: () => fireEvent(this, "media-refresh"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
mwc-button {
|
||||||
|
/* We use icon + text to show disabled state */
|
||||||
|
--mdc-button-disabled-ink-color: --mdc-theme-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-svg-icon[slot="icon"],
|
||||||
|
ha-circular-progress[slot="icon"] {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-media-manage-button": MediaManageButton;
|
||||||
|
}
|
||||||
|
}
|
@@ -34,23 +34,24 @@ import {
|
|||||||
MediaPickedEvent,
|
MediaPickedEvent,
|
||||||
MediaPlayerBrowseAction,
|
MediaPlayerBrowseAction,
|
||||||
} from "../../data/media-player";
|
} from "../../data/media-player";
|
||||||
|
import { browseLocalMediaPlayer } from "../../data/media_source";
|
||||||
|
import { isTTSMediaSource } from "../../data/tts";
|
||||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
|
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
|
||||||
import { documentationUrl } from "../../util/documentation-url";
|
import { documentationUrl } from "../../util/documentation-url";
|
||||||
import "../entity/ha-entity-picker";
|
import "../entity/ha-entity-picker";
|
||||||
import "../ha-button-menu";
|
import "../ha-button-menu";
|
||||||
import "../ha-card";
|
import "../ha-card";
|
||||||
import type { HaCard } from "../ha-card";
|
import type { HaCard } from "../ha-card";
|
||||||
import "../ha-circular-progress";
|
import "../ha-circular-progress";
|
||||||
|
import "../ha-fab";
|
||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "../ha-fab";
|
|
||||||
import { browseLocalMediaPlayer } from "../../data/media_source";
|
|
||||||
import { isTTSMediaSource } from "../../data/tts";
|
|
||||||
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
|
||||||
import "./ha-browse-media-tts";
|
import "./ha-browse-media-tts";
|
||||||
|
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@@ -131,6 +132,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
currentId.media_content_id,
|
currentId.media_content_id,
|
||||||
currentId.media_content_type
|
currentId.media_content_type
|
||||||
);
|
);
|
||||||
|
// Update the parent with latest item.
|
||||||
|
fireEvent(this, "media-browsed", {
|
||||||
|
ids: this.navigateIds,
|
||||||
|
current: this._currentItem,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._setError(err);
|
this._setError(err);
|
||||||
}
|
}
|
||||||
@@ -158,10 +164,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
const subtitle = this.hass.localize(
|
const subtitle = this.hass.localize(
|
||||||
`ui.components.media-browser.class.${currentItem.media_class}`
|
`ui.components.media-browser.class.${currentItem.media_class}`
|
||||||
);
|
);
|
||||||
|
const children = currentItem.children || [];
|
||||||
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
|
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
|
||||||
const childrenMediaClass =
|
const childrenMediaClass = currentItem.children_media_class
|
||||||
MediaClassBrowserSettings[currentItem.children_media_class];
|
? MediaClassBrowserSettings[currentItem.children_media_class]
|
||||||
|
: MediaClassBrowserSettings.directory;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${
|
${
|
||||||
@@ -264,7 +271,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
@tts-picked=${this._ttsPicked}
|
@tts-picked=${this._ttsPicked}
|
||||||
></ha-browse-media-tts>
|
></ha-browse-media-tts>
|
||||||
`
|
`
|
||||||
: !currentItem.children?.length
|
: !children.length && !currentItem.not_shown
|
||||||
? html`
|
? html`
|
||||||
<div class="container no-items">
|
<div class="container no-items">
|
||||||
${currentItem.media_content_id ===
|
${currentItem.media_content_id ===
|
||||||
@@ -296,7 +303,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
childrenMediaClass.thumbnail_ratio === "portrait",
|
childrenMediaClass.thumbnail_ratio === "portrait",
|
||||||
})}"
|
})}"
|
||||||
>
|
>
|
||||||
${currentItem.children.map(
|
${children.map(
|
||||||
(child) => html`
|
(child) => html`
|
||||||
<div
|
<div
|
||||||
class="child"
|
class="child"
|
||||||
@@ -360,11 +367,23 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
${currentItem.not_shown
|
||||||
|
? html`
|
||||||
|
<div class="grid not-shown">
|
||||||
|
<div class="title">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.not_shown",
|
||||||
|
{ count: currentItem.not_shown }
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<mwc-list>
|
<mwc-list>
|
||||||
${currentItem.children.map(
|
${children.map(
|
||||||
(child) => html`
|
(child) => html`
|
||||||
<mwc-list-item
|
<mwc-list-item
|
||||||
@click=${this._childClicked}
|
@click=${this._childClicked}
|
||||||
@@ -408,6 +427,25 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
<li divider role="separator"></li>
|
<li divider role="separator"></li>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
${currentItem.not_shown
|
||||||
|
? html`
|
||||||
|
<mwc-list-item
|
||||||
|
noninteractive
|
||||||
|
class="not-shown"
|
||||||
|
.graphic=${mediaClass.show_list_images
|
||||||
|
? "medium"
|
||||||
|
: "avatar"}
|
||||||
|
dir=${computeRTLDirection(this.hass)}
|
||||||
|
>
|
||||||
|
<span class="title">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.not_shown",
|
||||||
|
{ count: currentItem.not_shown }
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</mwc-list-item>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
</mwc-list>
|
</mwc-list>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
@@ -644,6 +682,17 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
// Thumbnails served by local API require authentication
|
// Thumbnails served by local API require authentication
|
||||||
const signedPath = await getSignedPath(this.hass, thumbnailUrl);
|
const signedPath = await getSignedPath(this.hass, thumbnailUrl);
|
||||||
thumbnailUrl = signedPath.path;
|
thumbnailUrl = signedPath.path;
|
||||||
|
} else if (
|
||||||
|
thumbnailUrl.startsWith("https://brands.home-assistant.io")
|
||||||
|
) {
|
||||||
|
// The backend is not aware of the theme used by the users,
|
||||||
|
// so we rewrite the URL to show a proper icon
|
||||||
|
thumbnailUrl = brandsUrl({
|
||||||
|
domain: extractDomainFromBrandUrl(thumbnailUrl),
|
||||||
|
type: "icon",
|
||||||
|
useFallback: true,
|
||||||
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
thumbnailCard.style.backgroundImage = `url(${thumbnailUrl})`;
|
thumbnailCard.style.backgroundImage = `url(${thumbnailUrl})`;
|
||||||
observer.unobserve(thumbnailCard); // loaded, so no need to observe anymore
|
observer.unobserve(thumbnailCard); // loaded, so no need to observe anymore
|
||||||
@@ -874,6 +923,17 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
transition: height 0.5s, margin 0.5s;
|
transition: height 0.5s, margin 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.not-shown {
|
||||||
|
font-style: italic;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid.not-shown {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============= CHILDREN ============= */
|
/* ============= CHILDREN ============= */
|
||||||
|
|
||||||
mwc-list {
|
mwc-list {
|
||||||
|
129
src/components/media-player/ha-media-upload-button.ts
Normal file
129
src/components/media-player/ha-media-upload-button.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { mdiUpload } from "@mdi/js";
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { MediaPlayerItem } from "../../data/media-player";
|
||||||
|
import "../ha-circular-progress";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
import {
|
||||||
|
isLocalMediaSourceContentId,
|
||||||
|
uploadLocalMedia,
|
||||||
|
} from "../../data/media_source";
|
||||||
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HASSDomEvents {
|
||||||
|
uploading: unknown;
|
||||||
|
"media-refresh": unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("ha-media-upload-button")
|
||||||
|
class MediaUploadButton extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property() currentItem?: MediaPlayerItem;
|
||||||
|
|
||||||
|
@state() _uploading = 0;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (
|
||||||
|
!this.currentItem ||
|
||||||
|
!isLocalMediaSourceContentId(this.currentItem.media_content_id || "")
|
||||||
|
) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<mwc-button
|
||||||
|
.label=${this._uploading > 0
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.uploading",
|
||||||
|
{
|
||||||
|
count: this._uploading,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.add_media"
|
||||||
|
)}
|
||||||
|
.disabled=${this._uploading > 0}
|
||||||
|
@click=${this._startUpload}
|
||||||
|
>
|
||||||
|
${this._uploading > 0
|
||||||
|
? html`
|
||||||
|
<ha-circular-progress
|
||||||
|
size="tiny"
|
||||||
|
active
|
||||||
|
alt=""
|
||||||
|
slot="icon"
|
||||||
|
></ha-circular-progress>
|
||||||
|
`
|
||||||
|
: html` <ha-svg-icon .path=${mdiUpload} slot="icon"></ha-svg-icon> `}
|
||||||
|
</mwc-button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _startUpload() {
|
||||||
|
if (this._uploading > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.accept = "audio/*,video/*,image/*";
|
||||||
|
input.multiple = true;
|
||||||
|
input.addEventListener(
|
||||||
|
"change",
|
||||||
|
async () => {
|
||||||
|
fireEvent(this, "uploading");
|
||||||
|
const files = input.files!;
|
||||||
|
document.body.removeChild(input);
|
||||||
|
const target = this.currentItem!.media_content_id!;
|
||||||
|
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
this._uploading = files.length - i;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await uploadLocalMedia(this.hass, target, files[i]);
|
||||||
|
} catch (err: any) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.upload_failed",
|
||||||
|
{
|
||||||
|
reason: err.message || err,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._uploading = 0;
|
||||||
|
fireEvent(this, "media-refresh");
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
// https://stackoverflow.com/questions/47664777/javascript-file-input-onchange-not-working-ios-safari-only
|
||||||
|
input.style.display = "none";
|
||||||
|
document.body.append(input);
|
||||||
|
input.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
mwc-button {
|
||||||
|
/* We use icon + text to show disabled state */
|
||||||
|
--mdc-button-disabled-ink-color: --mdc-theme-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-svg-icon[slot="icon"],
|
||||||
|
ha-circular-progress[slot="icon"] {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-media-upload-button": MediaUploadButton;
|
||||||
|
}
|
||||||
|
}
|
18
src/components/media-player/show-media-manage-dialog.ts
Normal file
18
src/components/media-player/show-media-manage-dialog.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { MediaPlayerItem } from "../../data/media-player";
|
||||||
|
|
||||||
|
export interface MediaManageDialogParams {
|
||||||
|
currentItem: MediaPlayerItem;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const showMediaManageDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: MediaManageDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-media-manage",
|
||||||
|
dialogImport: () => import("./dialog-media-manage"),
|
||||||
|
dialogParams,
|
||||||
|
});
|
||||||
|
};
|
@@ -1,12 +1,12 @@
|
|||||||
import { mdiClose, mdiMagnify } from "@mdi/js";
|
import { mdiClose, mdiMagnify } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import "../../components/ha-icon-button";
|
import "./ha-icon-button";
|
||||||
import "../../components/ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
import "../../components/ha-textfield";
|
import "./ha-textfield";
|
||||||
import type { HaTextField } from "../../components/ha-textfield";
|
import type { HaTextField } from "./ha-textfield";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { fireEvent } from "../dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
|
||||||
@customElement("search-input")
|
@customElement("search-input")
|
||||||
class SearchInput extends LitElement {
|
class SearchInput extends LitElement {
|
||||||
@@ -35,7 +35,7 @@ class SearchInput extends LitElement {
|
|||||||
.autofocus=${this.autofocus}
|
.autofocus=${this.autofocus}
|
||||||
.label=${this.label || "Search"}
|
.label=${this.label || "Search"}
|
||||||
.value=${this.filter || ""}
|
.value=${this.filter || ""}
|
||||||
.icon=${true}
|
icon
|
||||||
.iconTrailing=${this.filter || this.suffix}
|
.iconTrailing=${this.filter || this.suffix}
|
||||||
@input=${this._filterInputChanged}
|
@input=${this._filterInputChanged}
|
||||||
>
|
>
|
@@ -1,3 +1,4 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { property } from "lit/decorators";
|
import { property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
@@ -5,9 +6,8 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { stringCompare } from "../../common/string/compare";
|
import { stringCompare } from "../../common/string/compare";
|
||||||
import { fetchUsers, User } from "../../data/user";
|
import { fetchUsers, User } from "../../data/user";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-select";
|
||||||
import "./ha-user-badge";
|
import "./ha-user-badge";
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
|
|
||||||
class HaUserPicker extends LitElement {
|
class HaUserPicker extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
@@ -34,7 +34,7 @@ class HaUserPicker extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
@@ -58,7 +58,7 @@ class HaUserPicker extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
src/data/config.ts
Normal file
24
src/data/config.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
interface ValidConfig {
|
||||||
|
valid: true;
|
||||||
|
error: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InvalidConfig {
|
||||||
|
valid: false;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidKeys = "trigger" | "action" | "condition";
|
||||||
|
|
||||||
|
export const validateConfig = <
|
||||||
|
T extends Partial<{ [key in ValidKeys]: unknown }>
|
||||||
|
>(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: T
|
||||||
|
): Promise<Record<keyof T, ValidConfig | InvalidConfig>> =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "validate_config",
|
||||||
|
...config,
|
||||||
|
});
|
@@ -13,6 +13,7 @@ export interface ConfigEntry {
|
|||||||
| "not_loaded"
|
| "not_loaded"
|
||||||
| "failed_unload";
|
| "failed_unload";
|
||||||
supports_options: boolean;
|
supports_options: boolean;
|
||||||
|
supports_remove_device: boolean;
|
||||||
supports_unload: boolean;
|
supports_unload: boolean;
|
||||||
pref_disable_new_entities: boolean;
|
pref_disable_new_entities: boolean;
|
||||||
pref_disable_polling: boolean;
|
pref_disable_polling: boolean;
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Connection, createCollection } from "home-assistant-js-websocket";
|
import { Connection, createCollection } from "home-assistant-js-websocket";
|
||||||
|
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||||
import { computeStateName } from "../common/entity/compute_state_name";
|
import { computeStateName } from "../common/entity/compute_state_name";
|
||||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||||
import { debounce } from "../common/util/debounce";
|
import { debounce } from "../common/util/debounce";
|
||||||
@@ -77,12 +78,26 @@ export const updateDeviceRegistryEntry = (
|
|||||||
...updates,
|
...updates,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchDeviceRegistry = (conn) =>
|
export const removeConfigEntryFromDevice = (
|
||||||
conn.sendMessagePromise({
|
hass: HomeAssistant,
|
||||||
|
deviceId: string,
|
||||||
|
configEntryId: string
|
||||||
|
) =>
|
||||||
|
hass.callWS<DeviceRegistryEntry>({
|
||||||
|
type: "config/device_registry/remove_config_entry",
|
||||||
|
device_id: deviceId,
|
||||||
|
config_entry_id: configEntryId,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchDeviceRegistry = (conn: Connection) =>
|
||||||
|
conn.sendMessagePromise<DeviceRegistryEntry[]>({
|
||||||
type: "config/device_registry/list",
|
type: "config/device_registry/list",
|
||||||
});
|
});
|
||||||
|
|
||||||
const subscribeDeviceRegistryUpdates = (conn, store) =>
|
const subscribeDeviceRegistryUpdates = (
|
||||||
|
conn: Connection,
|
||||||
|
store: Store<DeviceRegistryEntry[]>
|
||||||
|
) =>
|
||||||
conn.subscribeEvents(
|
conn.subscribeEvents(
|
||||||
debounce(
|
debounce(
|
||||||
() =>
|
() =>
|
||||||
|
@@ -29,7 +29,7 @@ export const createImage = async (
|
|||||||
body: fd,
|
body: fd,
|
||||||
});
|
});
|
||||||
if (resp.status === 413) {
|
if (resp.status === 413) {
|
||||||
throw new Error("Uploaded image is too large");
|
throw new Error(`Uploaded image is too large (${file.name})`);
|
||||||
} else if (resp.status !== 200) {
|
} else if (resp.status !== 200) {
|
||||||
throw new Error("Unknown error");
|
throw new Error("Unknown error");
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
export interface InputDateTime {
|
export interface InputDateTime {
|
||||||
@@ -17,6 +18,19 @@ export interface InputDateTimeMutableParams {
|
|||||||
has_date: boolean;
|
has_date: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const stateToIsoDateString = (entityState: HassEntity) =>
|
||||||
|
`${entityState.attributes.year || "1970"}-${String(
|
||||||
|
entityState.attributes.month || "01"
|
||||||
|
).padStart(2, "0")}-${String(entityState.attributes.day || "01").padStart(
|
||||||
|
2,
|
||||||
|
"0"
|
||||||
|
)}T${String(entityState.attributes.hour || "00").padStart(2, "0")}:${String(
|
||||||
|
entityState.attributes.minute || "00"
|
||||||
|
).padStart(2, "0")}:${String(entityState.attributes.second || "00").padStart(
|
||||||
|
2,
|
||||||
|
"0"
|
||||||
|
)}`;
|
||||||
|
|
||||||
export const setInputDateTimeValue = (
|
export const setInputDateTimeValue = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityId: string,
|
entityId: string,
|
||||||
|
@@ -33,7 +33,8 @@ import type { HomeAssistant } from "../types";
|
|||||||
import { UNAVAILABLE_STATES } from "./entity";
|
import { UNAVAILABLE_STATES } from "./entity";
|
||||||
|
|
||||||
interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
|
interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
|
||||||
media_content_type?: any;
|
media_content_id?: string;
|
||||||
|
media_content_type?: string;
|
||||||
media_artist?: string;
|
media_artist?: string;
|
||||||
media_playlist?: string;
|
media_playlist?: string;
|
||||||
media_series_title?: string;
|
media_series_title?: string;
|
||||||
@@ -167,11 +168,12 @@ export interface MediaPlayerItem {
|
|||||||
media_content_type: string;
|
media_content_type: string;
|
||||||
media_content_id: string;
|
media_content_id: string;
|
||||||
media_class: string;
|
media_class: string;
|
||||||
children_media_class: string;
|
children_media_class?: string;
|
||||||
can_play: boolean;
|
can_play: boolean;
|
||||||
can_expand: boolean;
|
can_expand: boolean;
|
||||||
thumbnail?: string;
|
thumbnail?: string;
|
||||||
children?: MediaPlayerItem[];
|
children?: MediaPlayerItem[];
|
||||||
|
not_shown?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const browseMediaPlayer = (
|
export const browseMediaPlayer = (
|
||||||
@@ -339,7 +341,7 @@ export const computeMediaControls = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const formatMediaTime = (seconds: number | undefined): string => {
|
export const formatMediaTime = (seconds: number | undefined): string => {
|
||||||
if (seconds === undefined) {
|
if (seconds === undefined || seconds === Infinity) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,3 +361,17 @@ export const cleanupMediaTitle = (title?: string): string | undefined => {
|
|||||||
const index = title.indexOf("?authSig=");
|
const index = title.indexOf("?authSig=");
|
||||||
return index > 0 ? title.slice(0, index) : title;
|
return index > 0 ? title.slice(0, index) : title;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set volume of a media player entity.
|
||||||
|
* @param hass Home Assistant object
|
||||||
|
* @param entity_id entity ID of media player
|
||||||
|
* @param volume_level number between 0..1
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const setMediaPlayerVolume = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_id: string,
|
||||||
|
volume_level: number
|
||||||
|
) =>
|
||||||
|
hass.callService("media_player", "volume_set", { entity_id, volume_level });
|
||||||
|
@@ -43,9 +43,18 @@ export const uploadLocalMedia = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (resp.status === 413) {
|
if (resp.status === 413) {
|
||||||
throw new Error("Uploaded image is too large");
|
throw new Error(`Uploaded file is too large (${file.name})`);
|
||||||
} else if (resp.status !== 200) {
|
} else if (resp.status !== 200) {
|
||||||
throw new Error("Unknown error");
|
throw new Error("Unknown error");
|
||||||
}
|
}
|
||||||
return resp.json();
|
return resp.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeLocalMedia = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
media_content_id: string
|
||||||
|
) =>
|
||||||
|
hass.callWS({
|
||||||
|
type: "media_source/local_source/remove",
|
||||||
|
media_content_id,
|
||||||
|
});
|
||||||
|
@@ -14,12 +14,14 @@ export type Selector =
|
|||||||
| ObjectSelector
|
| ObjectSelector
|
||||||
| SelectSelector
|
| SelectSelector
|
||||||
| IconSelector
|
| IconSelector
|
||||||
| MediaSelector;
|
| MediaSelector
|
||||||
|
| ThemeSelector
|
||||||
|
| LocationSelector;
|
||||||
|
|
||||||
export interface EntitySelector {
|
export interface EntitySelector {
|
||||||
entity: {
|
entity: {
|
||||||
integration?: string;
|
integration?: string;
|
||||||
domain?: string;
|
domain?: string | string[];
|
||||||
device_class?: string;
|
device_class?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -86,8 +88,8 @@ export interface TargetSelector {
|
|||||||
|
|
||||||
export interface NumberSelector {
|
export interface NumberSelector {
|
||||||
number: {
|
number: {
|
||||||
min: number;
|
min?: number;
|
||||||
max: number;
|
max?: number;
|
||||||
step?: number;
|
step?: number;
|
||||||
mode?: "box" | "slider";
|
mode?: "box" | "slider";
|
||||||
unit_of_measurement?: string;
|
unit_of_measurement?: string;
|
||||||
@@ -147,8 +149,15 @@ export interface SelectSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IconSelector {
|
export interface IconSelector {
|
||||||
|
icon: {
|
||||||
|
placeholder?: string;
|
||||||
|
fallbackPath?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThemeSelector {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
icon: {};
|
theme: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaSelector {
|
export interface MediaSelector {
|
||||||
@@ -156,6 +165,16 @@ export interface MediaSelector {
|
|||||||
media: {};
|
media: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LocationSelector {
|
||||||
|
location: { radius?: boolean; icon?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocationSelectorValue {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
radius?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MediaSelectorValue {
|
export interface MediaSelectorValue {
|
||||||
entity_id?: string;
|
entity_id?: string;
|
||||||
media_content_id?: string;
|
media_content_id?: string;
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { Action } from "./script";
|
import { Action } from "./script";
|
||||||
|
|
||||||
export const callExecuteScript = (hass: HomeAssistant, sequence: Action[]) =>
|
export const callExecuteScript = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
sequence: Action | Action[]
|
||||||
|
) =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "execute_script",
|
type: "execute_script",
|
||||||
sequence,
|
sequence,
|
||||||
|
@@ -12,12 +12,12 @@ export interface Zone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ZoneMutableParams {
|
export interface ZoneMutableParams {
|
||||||
|
name: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
name: string;
|
passive?: boolean;
|
||||||
passive: boolean;
|
radius?: number;
|
||||||
radius: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchZones = (hass: HomeAssistant) =>
|
export const fetchZones = (hass: HomeAssistant) =>
|
||||||
|
@@ -117,13 +117,17 @@ class DataEntryFlowDialog extends LitElement {
|
|||||||
);
|
);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this.closeDialog();
|
this.closeDialog();
|
||||||
|
let message = err.message || err.body || "Unknown error";
|
||||||
|
if (typeof message !== "string") {
|
||||||
|
message = JSON.stringify(message);
|
||||||
|
}
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
"ui.panel.config.integrations.config_flow.error"
|
"ui.panel.config.integrations.config_flow.error"
|
||||||
),
|
),
|
||||||
text: `${this.hass.localize(
|
text: `${this.hass.localize(
|
||||||
"ui.panel.config.integrations.config_flow.could_not_load"
|
"ui.panel.config.integrations.config_flow.could_not_load"
|
||||||
)}: ${err.message || err.body}`,
|
)}: ${message}`,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -373,13 +377,20 @@ class DataEntryFlowDialog extends LitElement {
|
|||||||
step = await this._params!.flowConfig.createFlow(this.hass, handler);
|
step = await this._params!.flowConfig.createFlow(this.hass, handler);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this.closeDialog();
|
this.closeDialog();
|
||||||
|
const message =
|
||||||
|
err?.status_code === 404
|
||||||
|
? this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_flow.no_config_flow"
|
||||||
|
)
|
||||||
|
: `${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_flow.could_not_load"
|
||||||
|
)}: ${err?.body?.message || err?.message}`;
|
||||||
|
|
||||||
showAlertDialog(this, {
|
showAlertDialog(this, {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
"ui.panel.config.integrations.config_flow.error"
|
"ui.panel.config.integrations.config_flow.error"
|
||||||
),
|
),
|
||||||
text: `${this.hass.localize(
|
text: message,
|
||||||
"ui.panel.config.integrations.config_flow.could_not_load"
|
|
||||||
)}: ${err.message || err.body}`,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
|
@@ -15,7 +15,7 @@ import memoizeOne from "memoize-one";
|
|||||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { navigate } from "../../common/navigate";
|
import { navigate } from "../../common/navigate";
|
||||||
import "../../common/search/search-input";
|
import "../../components/search-input";
|
||||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||||
import { LocalizeFunc } from "../../common/translations/localize";
|
import { LocalizeFunc } from "../../common/translations/localize";
|
||||||
import "../../components/ha-icon-next";
|
import "../../components/ha-icon-next";
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@polymer/paper-input/paper-input";
|
import { mdiAlertOutline } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../../components/ha-dialog";
|
import "../../components/ha-dialog";
|
||||||
|
import "../../components/ha-svg-icon";
|
||||||
import "../../components/ha-switch";
|
import "../../components/ha-switch";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import "../../components/ha-textfield";
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { DialogBoxParams } from "./show-dialog-box";
|
import { DialogBoxParams } from "./show-dialog-box";
|
||||||
@@ -51,38 +51,40 @@ class DialogBox extends LitElement {
|
|||||||
?escapeKeyAction=${confirmPrompt}
|
?escapeKeyAction=${confirmPrompt}
|
||||||
@closed=${this._dialogClosed}
|
@closed=${this._dialogClosed}
|
||||||
defaultAction="ignore"
|
defaultAction="ignore"
|
||||||
.heading=${this._params.title
|
.heading=${html`${this._params.warning
|
||||||
|
? html`<ha-svg-icon
|
||||||
|
.path=${mdiAlertOutline}
|
||||||
|
style="color: var(--warning-color)"
|
||||||
|
></ha-svg-icon> `
|
||||||
|
: ""}${this._params.title
|
||||||
? this._params.title
|
? this._params.title
|
||||||
: this._params.confirmation &&
|
: this._params.confirmation &&
|
||||||
this.hass.localize("ui.dialogs.generic.default_confirmation_title")}
|
this.hass.localize(
|
||||||
|
"ui.dialogs.generic.default_confirmation_title"
|
||||||
|
)}`}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
${this._params.text
|
${this._params.text
|
||||||
? html`
|
? html`
|
||||||
<p
|
<p class=${this._params.prompt ? "no-bottom-padding" : ""}>
|
||||||
class=${classMap({
|
|
||||||
"no-bottom-padding": Boolean(this._params.prompt),
|
|
||||||
warning: Boolean(this._params.warning),
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
${this._params.text}
|
${this._params.text}
|
||||||
</p>
|
</p>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this._params.prompt
|
${this._params.prompt
|
||||||
? html`
|
? html`
|
||||||
<paper-input
|
<ha-textfield
|
||||||
dialogInitialFocus
|
dialogInitialFocus
|
||||||
.value=${this._value}
|
.value=${this._value || ""}
|
||||||
@keyup=${this._handleKeyUp}
|
@keyup=${this._handleKeyUp}
|
||||||
@value-changed=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
.label=${this._params.inputLabel
|
.label=${this._params.inputLabel
|
||||||
? this._params.inputLabel
|
? this._params.inputLabel
|
||||||
: ""}
|
: ""}
|
||||||
.type=${this._params.inputType
|
.type=${this._params.inputType
|
||||||
? this._params.inputType
|
? this._params.inputType
|
||||||
: "text"}
|
: "text"}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
@@ -107,8 +109,8 @@ class DialogBox extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
private _valueChanged(ev) {
|
||||||
this._value = ev.detail.value;
|
this._value = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _dismiss(): void {
|
private _dismiss(): void {
|
||||||
@@ -173,9 +175,6 @@ class DialogBox extends LitElement {
|
|||||||
/* Place above other dialogs */
|
/* Place above other dialogs */
|
||||||
--dialog-z-index: 104;
|
--dialog-z-index: 104;
|
||||||
}
|
}
|
||||||
.warning {
|
|
||||||
color: var(--warning-color);
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,281 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
|
||||||
import { FORMAT_NUMBER } from "../../../data/alarm_control_panel";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
|
|
||||||
class MoreInfoAlarmControlPanel extends LocalizeMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex"></style>
|
|
||||||
<style>
|
|
||||||
paper-input {
|
|
||||||
margin: auto;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
.pad {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
.pad div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.pad mwc-button {
|
|
||||||
padding: 8px;
|
|
||||||
width: 80px;
|
|
||||||
}
|
|
||||||
.actions mwc-button {
|
|
||||||
flex: 1 0 50%;
|
|
||||||
margin: 0 4px 16px;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
mwc-button.disarm {
|
|
||||||
color: var(--error-color);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[_codeFormat]]">
|
|
||||||
<paper-input
|
|
||||||
label="[[localize('ui.card.alarm_control_panel.code')]]"
|
|
||||||
value="{{_enteredCode}}"
|
|
||||||
type="password"
|
|
||||||
inputmode="[[_inputMode]]"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
></paper-input>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[_isNumber(_codeFormat)]]">
|
|
||||||
<div class="pad">
|
|
||||||
<div>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="1"
|
|
||||||
outlined
|
|
||||||
>1</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="4"
|
|
||||||
outlined
|
|
||||||
>4</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="7"
|
|
||||||
outlined
|
|
||||||
>7</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="2"
|
|
||||||
outlined
|
|
||||||
>2</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="5"
|
|
||||||
outlined
|
|
||||||
>5</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="8"
|
|
||||||
outlined
|
|
||||||
>8</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="0"
|
|
||||||
outlined
|
|
||||||
>0</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="3"
|
|
||||||
outlined
|
|
||||||
>3</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="6"
|
|
||||||
outlined
|
|
||||||
>6</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_digitClicked"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
data-digit="9"
|
|
||||||
outlined
|
|
||||||
>9</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
on-click="_clearEnteredCode"
|
|
||||||
disabled="[[!_inputEnabled]]"
|
|
||||||
outlined
|
|
||||||
>
|
|
||||||
[[localize('ui.card.alarm_control_panel.clear_code')]]
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="layout horizontal center-justified actions">
|
|
||||||
<template is="dom-if" if="[[_disarmVisible]]">
|
|
||||||
<mwc-button
|
|
||||||
outlined
|
|
||||||
class="disarm"
|
|
||||||
on-click="_callService"
|
|
||||||
data-service="alarm_disarm"
|
|
||||||
disabled="[[!_codeValid]]"
|
|
||||||
>
|
|
||||||
[[localize('ui.card.alarm_control_panel.disarm')]]
|
|
||||||
</mwc-button>
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[_armVisible]]">
|
|
||||||
<mwc-button
|
|
||||||
outlined
|
|
||||||
on-click="_callService"
|
|
||||||
data-service="alarm_arm_home"
|
|
||||||
disabled="[[!_codeValid]]"
|
|
||||||
>
|
|
||||||
[[localize('ui.card.alarm_control_panel.arm_home')]]
|
|
||||||
</mwc-button>
|
|
||||||
<mwc-button
|
|
||||||
outlined
|
|
||||||
on-click="_callService"
|
|
||||||
data-service="alarm_arm_away"
|
|
||||||
disabled="[[!_codeValid]]"
|
|
||||||
>
|
|
||||||
[[localize('ui.card.alarm_control_panel.arm_away')]]
|
|
||||||
</mwc-button>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
observer: "_stateObjChanged",
|
|
||||||
},
|
|
||||||
_enteredCode: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
_codeFormat: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
_codeValid: {
|
|
||||||
type: Boolean,
|
|
||||||
computed:
|
|
||||||
"_validateCode(_enteredCode, _codeFormat, _armVisible, _codeArmRequired)",
|
|
||||||
},
|
|
||||||
_disarmVisible: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
_armVisible: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
_inputEnabled: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
_inputMode: {
|
|
||||||
type: String,
|
|
||||||
computed: "_getInputMode(_codeFormat)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this._armedStates = [
|
|
||||||
"armed_home",
|
|
||||||
"armed_away",
|
|
||||||
"armed_night",
|
|
||||||
"armed_custom_bypass",
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
_stateObjChanged(newVal, oldVal) {
|
|
||||||
if (newVal) {
|
|
||||||
const state = newVal.state;
|
|
||||||
const props = {
|
|
||||||
_codeFormat: newVal.attributes.code_format,
|
|
||||||
_armVisible: state === "disarmed",
|
|
||||||
_codeArmRequired: newVal.attributes.code_arm_required,
|
|
||||||
_disarmVisible:
|
|
||||||
this._armedStates.includes(state) ||
|
|
||||||
state === "pending" ||
|
|
||||||
state === "triggered" ||
|
|
||||||
state === "arming",
|
|
||||||
};
|
|
||||||
props._inputEnabled = props._disarmVisible || props._armVisible;
|
|
||||||
this.setProperties(props);
|
|
||||||
}
|
|
||||||
if (oldVal) {
|
|
||||||
setTimeout(() => {
|
|
||||||
fireEvent(this, "iron-resize");
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getInputMode(format) {
|
|
||||||
return this._isNumber(format) ? "numeric" : "text";
|
|
||||||
}
|
|
||||||
|
|
||||||
_isNumber(format) {
|
|
||||||
return format === FORMAT_NUMBER;
|
|
||||||
}
|
|
||||||
|
|
||||||
_validateCode(code, format, armVisible, codeArmRequired) {
|
|
||||||
return !format || code.length > 0 || (armVisible && !codeArmRequired);
|
|
||||||
}
|
|
||||||
|
|
||||||
_digitClicked(ev) {
|
|
||||||
this._enteredCode += ev.target.getAttribute("data-digit");
|
|
||||||
}
|
|
||||||
|
|
||||||
_clearEnteredCode() {
|
|
||||||
this._enteredCode = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
_callService(ev) {
|
|
||||||
const service = ev.target.getAttribute("data-service");
|
|
||||||
const data = {
|
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
code: this._enteredCode,
|
|
||||||
};
|
|
||||||
this.hass.callService("alarm_control_panel", service, data).then(() => {
|
|
||||||
this._enteredCode = "";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
customElements.define(
|
|
||||||
"more-info-alarm_control_panel",
|
|
||||||
MoreInfoAlarmControlPanel
|
|
||||||
);
|
|
165
src/dialogs/more-info/controls/more-info-alarm_control_panel.ts
Normal file
165
src/dialogs/more-info/controls/more-info-alarm_control_panel.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import "../../../components/ha-textfield";
|
||||||
|
import type { HaTextField } from "../../../components/ha-textfield";
|
||||||
|
import {
|
||||||
|
callAlarmAction,
|
||||||
|
FORMAT_NUMBER,
|
||||||
|
} from "../../../data/alarm_control_panel";
|
||||||
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"];
|
||||||
|
const ARM_ACTIONS = ["arm_away", "arm_home"];
|
||||||
|
const DISARM_ACTIONS = ["disarm"];
|
||||||
|
|
||||||
|
@customElement("more-info-alarm_control_panel")
|
||||||
|
export class MoreInfoAlarmControlPanel extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
|
@query("#alarmCode") private _input?: HaTextField;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this.hass || !this.stateObj) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${!this.stateObj.attributes.code_format
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<div class="center">
|
||||||
|
<ha-textfield
|
||||||
|
id="alarmCode"
|
||||||
|
.label=${this.hass.localize("ui.card.alarm_control_panel.code")}
|
||||||
|
type="password"
|
||||||
|
.inputmode=${this.stateObj.attributes.code_format ===
|
||||||
|
FORMAT_NUMBER
|
||||||
|
? "numeric"
|
||||||
|
: "text"}
|
||||||
|
></ha-textfield>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
${this.stateObj.attributes.code_format !== FORMAT_NUMBER
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<div id="keypad">
|
||||||
|
${BUTTONS.map((value) =>
|
||||||
|
value === ""
|
||||||
|
? html`<mwc-button disabled></mwc-button>`
|
||||||
|
: html`
|
||||||
|
<mwc-button
|
||||||
|
.value=${value}
|
||||||
|
@click=${this._handlePadClick}
|
||||||
|
outlined
|
||||||
|
class=${classMap({
|
||||||
|
numberkey: value !== "clear",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
${value === "clear"
|
||||||
|
? this.hass!.localize(
|
||||||
|
`ui.card.alarm_control_panel.clear_code`
|
||||||
|
)
|
||||||
|
: value}
|
||||||
|
</mwc-button>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
<div class="actions">
|
||||||
|
${(this.stateObj.state === "disarmed"
|
||||||
|
? ARM_ACTIONS
|
||||||
|
: DISARM_ACTIONS
|
||||||
|
).map(
|
||||||
|
(stateAction) => html`
|
||||||
|
<mwc-button
|
||||||
|
.action=${stateAction}
|
||||||
|
@click=${this._handleActionClick}
|
||||||
|
outlined
|
||||||
|
>
|
||||||
|
${this.hass!.localize(
|
||||||
|
`ui.card.alarm_control_panel.${stateAction}`
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handlePadClick(e: MouseEvent): void {
|
||||||
|
const val = (e.currentTarget! as any).value;
|
||||||
|
this._input!.value = val === "clear" ? "" : this._input!.value + val;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleActionClick(e: MouseEvent): void {
|
||||||
|
const input = this._input;
|
||||||
|
callAlarmAction(
|
||||||
|
this.hass!,
|
||||||
|
this.stateObj!.entity_id,
|
||||||
|
(e.currentTarget! as any).action,
|
||||||
|
input?.value || undefined
|
||||||
|
);
|
||||||
|
if (input) {
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
margin: 8px;
|
||||||
|
max-width: 150px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keypad {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#keypad mwc-button {
|
||||||
|
padding: 8px;
|
||||||
|
width: 30%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions mwc-button {
|
||||||
|
margin: 0 4px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mwc-button#disarm {
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
mwc-button.numberkey {
|
||||||
|
--mdc-typography-button-font-size: var(--keypad-font-size, 0.875rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"more-info-alarm_control_panel": MoreInfoAlarmControlPanel;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -9,9 +10,11 @@ import {
|
|||||||
import { property } from "lit/decorators";
|
import { property } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||||
import "../../../components/ha-climate-control";
|
import "../../../components/ha-climate-control";
|
||||||
|
import "../../../components/ha-select";
|
||||||
import "../../../components/ha-slider";
|
import "../../../components/ha-slider";
|
||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
import {
|
import {
|
||||||
@@ -26,9 +29,6 @@ import {
|
|||||||
compareClimateHvacModes,
|
compareClimateHvacModes,
|
||||||
} from "../../../data/climate";
|
} from "../../../data/climate";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
|
||||||
|
|
||||||
class MoreInfoClimate extends LitElement {
|
class MoreInfoClimate extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -168,7 +168,7 @@ class MoreInfoClimate extends LitElement {
|
|||||||
|
|
||||||
<div class="container-hvac_modes">
|
<div class="container-hvac_modes">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${hass.localize("ui.card.climate.operation")}
|
.label=${hass.localize("ui.card.climate.operation")}
|
||||||
.value=${stateObj.state}
|
.value=${stateObj.state}
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
@@ -186,14 +186,14 @@ class MoreInfoClimate extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${supportPresetMode && stateObj.attributes.preset_modes
|
${supportPresetMode && stateObj.attributes.preset_modes
|
||||||
? html`
|
? html`
|
||||||
<div class="container-preset_modes">
|
<div class="container-preset_modes">
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${hass.localize("ui.card.climate.preset_mode")}
|
.label=${hass.localize("ui.card.climate.preset_mode")}
|
||||||
.value=${stateObj.attributes.preset_mode}
|
.value=${stateObj.attributes.preset_mode}
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
@@ -210,14 +210,14 @@ class MoreInfoClimate extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${supportFanMode && stateObj.attributes.fan_modes
|
${supportFanMode && stateObj.attributes.fan_modes
|
||||||
? html`
|
? html`
|
||||||
<div class="container-fan_list">
|
<div class="container-fan_list">
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${hass.localize("ui.card.climate.fan_mode")}
|
.label=${hass.localize("ui.card.climate.fan_mode")}
|
||||||
.value=${stateObj.attributes.fan_mode}
|
.value=${stateObj.attributes.fan_mode}
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
@@ -234,14 +234,14 @@ class MoreInfoClimate extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${supportSwingMode && stateObj.attributes.swing_modes
|
${supportSwingMode && stateObj.attributes.swing_modes
|
||||||
? html`
|
? html`
|
||||||
<div class="container-swing_list">
|
<div class="container-swing_list">
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${hass.localize("ui.card.climate.swing_mode")}
|
.label=${hass.localize("ui.card.climate.swing_mode")}
|
||||||
.value=${stateObj.attributes.swing_mode}
|
.value=${stateObj.attributes.swing_mode}
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
@@ -254,7 +254,7 @@ class MoreInfoClimate extends LitElement {
|
|||||||
<mwc-list-item .value=${mode}>${mode}</mwc-list-item>
|
<mwc-list-item .value=${mode}>${mode}</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
@@ -427,7 +427,7 @@ class MoreInfoClimate extends LitElement {
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-select {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
@@ -1,148 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import "../../../components/ha-circular-progress";
|
|
||||||
import "../../../components/ha-markdown";
|
|
||||||
|
|
||||||
class MoreInfoConfigurator extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex"></style>
|
|
||||||
<style>
|
|
||||||
p {
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
p > img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.error {
|
|
||||||
color: #c62828;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.submit {
|
|
||||||
text-align: center;
|
|
||||||
height: 41px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-circular-progress {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="layout vertical">
|
|
||||||
<template is="dom-if" if="[[isConfigurable]]">
|
|
||||||
<ha-markdown
|
|
||||||
breaks
|
|
||||||
content="[[stateObj.attributes.description]]"
|
|
||||||
></ha-markdown>
|
|
||||||
|
|
||||||
<p class="error" hidden$="[[!stateObj.attributes.errors]]">
|
|
||||||
[[stateObj.attributes.errors]]
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<template is="dom-repeat" items="[[stateObj.attributes.fields]]">
|
|
||||||
<paper-input
|
|
||||||
label="[[item.name]]"
|
|
||||||
name="[[item.id]]"
|
|
||||||
type="[[item.type]]"
|
|
||||||
on-change="fieldChanged"
|
|
||||||
></paper-input>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<p class="submit" hidden$="[[!stateObj.attributes.submit_caption]]">
|
|
||||||
<mwc-button
|
|
||||||
raised=""
|
|
||||||
disabled="[[isConfiguring]]"
|
|
||||||
on-click="submitClicked"
|
|
||||||
>
|
|
||||||
<ha-circular-progress
|
|
||||||
active="[[isConfiguring]]"
|
|
||||||
hidden="[[!isConfiguring]]"
|
|
||||||
alt="Configuring"
|
|
||||||
></ha-circular-progress>
|
|
||||||
[[stateObj.attributes.submit_caption]]
|
|
||||||
</mwc-button>
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
action: {
|
|
||||||
type: String,
|
|
||||||
value: "display",
|
|
||||||
},
|
|
||||||
|
|
||||||
isConfigurable: {
|
|
||||||
type: Boolean,
|
|
||||||
computed: "computeIsConfigurable(stateObj)",
|
|
||||||
},
|
|
||||||
|
|
||||||
isConfiguring: {
|
|
||||||
type: Boolean,
|
|
||||||
value: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
fieldInput: {
|
|
||||||
type: Object,
|
|
||||||
value: function () {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computeIsConfigurable(stateObj) {
|
|
||||||
return stateObj.state === "configure";
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldChanged(ev) {
|
|
||||||
const el = ev.target;
|
|
||||||
this.fieldInput[el.name] = el.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
submitClicked() {
|
|
||||||
const data = {
|
|
||||||
configure_id: this.stateObj.attributes.configure_id,
|
|
||||||
fields: this.fieldInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.isConfiguring = true;
|
|
||||||
|
|
||||||
this.hass.callService("configurator", "configure", data).then(
|
|
||||||
() => {
|
|
||||||
this.isConfiguring = false;
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.isConfiguring = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("more-info-configurator", MoreInfoConfigurator);
|
|
128
src/dialogs/more-info/controls/more-info-configurator.ts
Normal file
128
src/dialogs/more-info/controls/more-info-configurator.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import "../../../components/ha-alert";
|
||||||
|
import "../../../components/ha-circular-progress";
|
||||||
|
import "../../../components/ha-markdown";
|
||||||
|
import "../../../components/ha-textfield";
|
||||||
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
@customElement("more-info-configurator")
|
||||||
|
export class MoreInfoConfigurator extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
|
@state() private _isConfiguring = false;
|
||||||
|
|
||||||
|
private _fieldInput = {};
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (this.stateObj?.state !== "configure") {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="container">
|
||||||
|
<ha-markdown
|
||||||
|
breaks
|
||||||
|
.content=${this.stateObj.attributes.description}
|
||||||
|
></ha-markdown>
|
||||||
|
|
||||||
|
${this.stateObj.attributes.errors
|
||||||
|
? html`<ha-alert alert-type="error">
|
||||||
|
${this.stateObj.attributes.errors}
|
||||||
|
</ha-alert>`
|
||||||
|
: ""}
|
||||||
|
${this.stateObj.attributes.fields.map(
|
||||||
|
(field) => html`<ha-textfield
|
||||||
|
.label=${field.name}
|
||||||
|
.name=${field.id}
|
||||||
|
.type=${field.type}
|
||||||
|
@change=${this._fieldChanged}
|
||||||
|
></ha-textfield>`
|
||||||
|
)}
|
||||||
|
${this.stateObj.attributes.submit_caption
|
||||||
|
? html`<p class="submit">
|
||||||
|
<mwc-button
|
||||||
|
raised
|
||||||
|
.disabled=${this._isConfiguring}
|
||||||
|
@click=${this._submitClicked}
|
||||||
|
>
|
||||||
|
${this._isConfiguring
|
||||||
|
? html`<ha-circular-progress
|
||||||
|
active
|
||||||
|
alt="Configuring"
|
||||||
|
></ha-circular-progress>`
|
||||||
|
: ""}
|
||||||
|
${this.stateObj.attributes.submit_caption}
|
||||||
|
</mwc-button>
|
||||||
|
</p>`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _fieldChanged(ev) {
|
||||||
|
const el = ev.target;
|
||||||
|
this._fieldInput[el.name] = el.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _submitClicked() {
|
||||||
|
const data = {
|
||||||
|
configure_id: this.stateObj!.attributes.configure_id,
|
||||||
|
fields: this._fieldInput,
|
||||||
|
};
|
||||||
|
|
||||||
|
this._isConfiguring = true;
|
||||||
|
|
||||||
|
this.hass.callService("configurator", "configure", data).then(
|
||||||
|
() => {
|
||||||
|
this._isConfiguring = false;
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this._isConfiguring = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
p > img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.submit {
|
||||||
|
text-align: center;
|
||||||
|
height: 41px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-circular-progress {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"more-info-configurator": MoreInfoConfigurator;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
/* eslint-plugin-disable lit */
|
/* eslint-plugin-disable lit */
|
||||||
@@ -8,12 +9,11 @@ import "../../../components/ha-attributes";
|
|||||||
import "../../../components/ha-icon";
|
import "../../../components/ha-icon";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-labeled-slider";
|
import "../../../components/ha-labeled-slider";
|
||||||
|
import "../../../components/ha-select";
|
||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
import { SUPPORT_SET_SPEED } from "../../../data/fan";
|
import { SUPPORT_SET_SPEED } from "../../../data/fan";
|
||||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @appliesMixin EventsMixin
|
* @appliesMixin EventsMixin
|
||||||
@@ -37,7 +37,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-select {
|
ha-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -57,7 +57,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container-preset_modes">
|
<div class="container-preset_modes">
|
||||||
<mwc-select
|
<ha-select
|
||||||
label="[[localize('ui.card.fan.preset_mode')]]"
|
label="[[localize('ui.card.fan.preset_mode')]]"
|
||||||
value="[[stateObj.attributes.preset_mode]]"
|
value="[[stateObj.attributes.preset_mode]]"
|
||||||
on-selected="presetModeChanged"
|
on-selected="presetModeChanged"
|
||||||
@@ -71,7 +71,7 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
>
|
>
|
||||||
<mwc-list-item value="[[item]]">[[item]]</mwc-list-item>
|
<mwc-list-item value="[[item]]">[[item]]</mwc-list-item>
|
||||||
</template>
|
</template>
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container-oscillating">
|
<div class="container-oscillating">
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -12,6 +13,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
|||||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||||
|
import "../../../components/ha-select";
|
||||||
import "../../../components/ha-slider";
|
import "../../../components/ha-slider";
|
||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
import {
|
import {
|
||||||
@@ -19,8 +21,6 @@ import {
|
|||||||
HUMIDIFIER_SUPPORT_MODES,
|
HUMIDIFIER_SUPPORT_MODES,
|
||||||
} from "../../../data/humidifier";
|
} from "../../../data/humidifier";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import "@material/mwc-list/mwc-list";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
|
|
||||||
class MoreInfoHumidifier extends LitElement {
|
class MoreInfoHumidifier extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -67,26 +67,24 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
|
|
||||||
${supportModes
|
${supportModes
|
||||||
? html`
|
? html`
|
||||||
<div class="container-modes">
|
<ha-select
|
||||||
<mwc-list
|
.label=${hass.localize("ui.card.humidifier.mode")}
|
||||||
.label=${hass.localize("ui.card.humidifier.mode")}
|
.value=${stateObj.attributes.mode}
|
||||||
.value=${stateObj.attributes.mode}
|
fixedMenuPosition
|
||||||
fixedMenuPosition
|
naturalMenuWidth
|
||||||
naturalMenuWidth
|
@selected=${this._handleModeChanged}
|
||||||
@selected=${this._handleModeChanged}
|
@closed=${stopPropagation}
|
||||||
@closed=${stopPropagation}
|
>
|
||||||
>
|
${stateObj.attributes.available_modes!.map(
|
||||||
${stateObj.attributes.available_modes!.map(
|
(mode) => html`
|
||||||
(mode) => html`
|
<mwc-list-item .value=${mode}>
|
||||||
<mwc-list-item .value=${mode}>
|
${hass.localize(
|
||||||
${hass.localize(
|
`state_attributes.humidifier.mode.${mode}`
|
||||||
`state_attributes.humidifier.mode.${mode}`
|
) || mode}
|
||||||
) || mode}
|
</mwc-list-item>
|
||||||
</mwc-list-item>
|
`
|
||||||
`
|
)}
|
||||||
)}
|
</ha-select>
|
||||||
</mwc-list>
|
|
||||||
</div>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
@@ -170,11 +168,7 @@ class MoreInfoHumidifier extends LitElement {
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-select {
|
ha-select {
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
ha-slider {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,7 +4,10 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import "../../../components/ha-date-input";
|
import "../../../components/ha-date-input";
|
||||||
import "../../../components/ha-time-input";
|
import "../../../components/ha-time-input";
|
||||||
import { UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
import { UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||||
import { setInputDateTimeValue } from "../../../data/input_datetime";
|
import {
|
||||||
|
setInputDateTimeValue,
|
||||||
|
stateToIsoDateString,
|
||||||
|
} from "../../../data/input_datetime";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
@customElement("more-info-input_datetime")
|
@customElement("more-info-input_datetime")
|
||||||
@@ -24,7 +27,7 @@ class MoreInfoInputDatetime extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<ha-date-input
|
<ha-date-input
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.value=${`${this.stateObj.attributes.year}-${this.stateObj.attributes.month}-${this.stateObj.attributes.day}`}
|
.value=${stateToIsoDateString(this.stateObj)}
|
||||||
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
|
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj.state)}
|
||||||
@value-changed=${this._dateChanged}
|
@value-changed=${this._dateChanged}
|
||||||
>
|
>
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import { mdiPalette } from "@mdi/js";
|
import { mdiPalette } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -18,6 +17,7 @@ import "../../../components/ha-button-toggle-group";
|
|||||||
import "../../../components/ha-color-picker";
|
import "../../../components/ha-color-picker";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-labeled-slider";
|
import "../../../components/ha-labeled-slider";
|
||||||
|
import "../../../components/ha-select";
|
||||||
import {
|
import {
|
||||||
getLightCurrentModeRgbColor,
|
getLightCurrentModeRgbColor,
|
||||||
LightColorModes,
|
LightColorModes,
|
||||||
@@ -208,7 +208,7 @@ class MoreInfoLight extends LitElement {
|
|||||||
this.stateObj!.attributes.effect_list?.length
|
this.stateObj!.attributes.effect_list?.length
|
||||||
? html`
|
? html`
|
||||||
<hr />
|
<hr />
|
||||||
<mwc-select
|
<ha-select
|
||||||
.label=${this.hass.localize("ui.card.light.effect")}
|
.label=${this.hass.localize("ui.card.light.effect")}
|
||||||
.value=${this.stateObj.attributes.effect || ""}
|
.value=${this.stateObj.attributes.effect || ""}
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
@@ -223,7 +223,7 @@ class MoreInfoLight extends LitElement {
|
|||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
</ha-select>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
`
|
`
|
||||||
|
@@ -1,80 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import "../../../components/ha-attributes";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
*/
|
|
||||||
class MoreInfoLock extends LocalizeMixin(PolymerElement) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style>
|
|
||||||
paper-input {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template is="dom-if" if="[[stateObj.attributes.code_format]]">
|
|
||||||
<paper-input
|
|
||||||
label="[[localize('ui.card.lock.code')]]"
|
|
||||||
value="{{enteredCode}}"
|
|
||||||
pattern="[[stateObj.attributes.code_format]]"
|
|
||||||
type="password"
|
|
||||||
></paper-input>
|
|
||||||
<mwc-button
|
|
||||||
on-click="callService"
|
|
||||||
data-service="unlock"
|
|
||||||
hidden$="[[!isLocked]]"
|
|
||||||
>[[localize('ui.card.lock.unlock')]]</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
on-click="callService"
|
|
||||||
data-service="lock"
|
|
||||||
hidden$="[[isLocked]]"
|
|
||||||
>[[localize('ui.card.lock.lock')]]</mwc-button
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
<ha-attributes
|
|
||||||
hass="[[hass]]"
|
|
||||||
state-obj="[[stateObj]]"
|
|
||||||
extra-filters="code_format"
|
|
||||||
></ha-attributes>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: Object,
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
observer: "stateObjChanged",
|
|
||||||
},
|
|
||||||
enteredCode: {
|
|
||||||
type: String,
|
|
||||||
value: "",
|
|
||||||
},
|
|
||||||
isLocked: Boolean,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
stateObjChanged(newVal) {
|
|
||||||
if (newVal) {
|
|
||||||
this.isLocked = newVal.state === "locked";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callService(ev) {
|
|
||||||
const service = ev.target.getAttribute("data-service");
|
|
||||||
const data = {
|
|
||||||
entity_id: this.stateObj.entity_id,
|
|
||||||
code: this.enteredCode,
|
|
||||||
};
|
|
||||||
this.hass.callService("lock", service, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("more-info-lock", MoreInfoLock);
|
|
70
src/dialogs/more-info/controls/more-info-lock.ts
Normal file
70
src/dialogs/more-info/controls/more-info-lock.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import "../../../components/ha-attributes";
|
||||||
|
import "../../../components/ha-textfield";
|
||||||
|
import type { HaTextField } from "../../../components/ha-textfield";
|
||||||
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
@customElement("more-info-lock")
|
||||||
|
class MoreInfoLock extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||||
|
|
||||||
|
@query("ha-textfield") private _textfield?: HaTextField;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this.hass || !this.stateObj) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
${this.stateObj.attributes.code_format
|
||||||
|
? html`
|
||||||
|
<ha-textfield
|
||||||
|
.label=${this.hass.localize("ui.card.lock.code")}
|
||||||
|
.pattern=${this.stateObj.attributes.code_format}
|
||||||
|
type="password"
|
||||||
|
></ha-textfield>
|
||||||
|
${this.stateObj.state === "locked"
|
||||||
|
? html`<mwc-button
|
||||||
|
@click=${this._callService}
|
||||||
|
data-service="unlock"
|
||||||
|
>${this.hass.localize("ui.card.lock.unlock")}</mwc-button
|
||||||
|
>`
|
||||||
|
: html`<mwc-button @click=${this._callService} data-service="lock"
|
||||||
|
>${this.hass.localize("ui.card.lock.lock")}</mwc-button
|
||||||
|
>`}
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<ha-attributes
|
||||||
|
.hass=${this.hass}
|
||||||
|
.stateObj=${this.stateObj}
|
||||||
|
extra-filters="code_format"
|
||||||
|
></ha-attributes>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _callService(ev) {
|
||||||
|
const service = ev.target.getAttribute("data-service");
|
||||||
|
const data = {
|
||||||
|
entity_id: this.stateObj!.entity_id,
|
||||||
|
code: this._textfield?.value,
|
||||||
|
};
|
||||||
|
this.hass.callService("lock", service, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"more-info-lock": MoreInfoLock;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,5 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import {
|
import {
|
||||||
mdiLoginVariant,
|
mdiLoginVariant,
|
||||||
mdiMusicNote,
|
mdiMusicNote,
|
||||||
@@ -10,7 +9,6 @@ import {
|
|||||||
mdiVolumeOff,
|
mdiVolumeOff,
|
||||||
mdiVolumePlus,
|
mdiVolumePlus,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
@@ -18,6 +16,7 @@ import { stopPropagation } from "../../../common/dom/stop_propagation";
|
|||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
|
import "../../../components/ha-select";
|
||||||
import "../../../components/ha-slider";
|
import "../../../components/ha-slider";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
||||||
@@ -51,42 +50,40 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
const controls = computeMediaControls(stateObj);
|
const controls = computeMediaControls(stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${!controls
|
<div class="controls">
|
||||||
? ""
|
<div class="basic-controls">
|
||||||
: html`
|
${!controls
|
||||||
<div class="controls">
|
? ""
|
||||||
<div class="basic-controls">
|
: controls.map(
|
||||||
${controls!.map(
|
(control) => html`
|
||||||
(control) => html`
|
<ha-icon-button
|
||||||
<ha-icon-button
|
action=${control.action}
|
||||||
action=${control.action}
|
@click=${this._handleClick}
|
||||||
@click=${this._handleClick}
|
.path=${control.icon}
|
||||||
.path=${control.icon}
|
.label=${this.hass.localize(
|
||||||
.label=${this.hass.localize(
|
`ui.card.media_player.${control.action}`
|
||||||
`ui.card.media_player.${control.action}`
|
)}
|
||||||
)}
|
>
|
||||||
>
|
</ha-icon-button>
|
||||||
</ha-icon-button>
|
`
|
||||||
`
|
)}
|
||||||
|
</div>
|
||||||
|
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
|
||||||
|
? html`
|
||||||
|
<mwc-button
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.card.media_player.browse_media"
|
||||||
)}
|
)}
|
||||||
</div>
|
@click=${this._showBrowseMedia}
|
||||||
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
|
>
|
||||||
? html`
|
<ha-svg-icon
|
||||||
<mwc-button
|
.path=${mdiPlayBoxMultiple}
|
||||||
.label=${this.hass.localize(
|
slot="icon"
|
||||||
"ui.card.media_player.browse_media"
|
></ha-svg-icon>
|
||||||
)}
|
</mwc-button>
|
||||||
@click=${this._showBrowseMedia}
|
`
|
||||||
>
|
: ""}
|
||||||
<ha-svg-icon
|
</div>
|
||||||
.path=${mdiPlayBoxMultiple}
|
|
||||||
slot="icon"
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
${(supportsFeature(stateObj, SUPPORT_VOLUME_SET) ||
|
${(supportsFeature(stateObj, SUPPORT_VOLUME_SET) ||
|
||||||
supportsFeature(stateObj, SUPPORT_VOLUME_BUTTONS)) &&
|
supportsFeature(stateObj, SUPPORT_VOLUME_BUTTONS)) &&
|
||||||
![UNAVAILABLE, UNKNOWN, "off"].includes(stateObj.state)
|
![UNAVAILABLE, UNKNOWN, "off"].includes(stateObj.state)
|
||||||
@@ -136,12 +133,9 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
stateObj.attributes.source_list?.length
|
stateObj.attributes.source_list?.length
|
||||||
? html`
|
? html`
|
||||||
<div class="source-input">
|
<div class="source-input">
|
||||||
<ha-svg-icon
|
<ha-select
|
||||||
class="source-input"
|
|
||||||
.path=${mdiLoginVariant}
|
|
||||||
></ha-svg-icon>
|
|
||||||
<mwc-select
|
|
||||||
.label=${this.hass.localize("ui.card.media_player.source")}
|
.label=${this.hass.localize("ui.card.media_player.source")}
|
||||||
|
icon
|
||||||
.value=${stateObj.attributes.source!}
|
.value=${stateObj.attributes.source!}
|
||||||
@selected=${this._handleSourceChanged}
|
@selected=${this._handleSourceChanged}
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
@@ -154,7 +148,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
<mwc-list-item .value=${source}>${source}</mwc-list-item>
|
<mwc-list-item .value=${source}>${source}</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
<ha-svg-icon .path=${mdiLoginVariant} slot="icon"></ha-svg-icon>
|
||||||
|
</ha-select>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
@@ -162,10 +157,10 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
stateObj.attributes.sound_mode_list?.length
|
stateObj.attributes.sound_mode_list?.length
|
||||||
? html`
|
? html`
|
||||||
<div class="sound-input">
|
<div class="sound-input">
|
||||||
<ha-svg-icon .path=${mdiMusicNote}></ha-svg-icon>
|
<ha-select
|
||||||
<mwc-select
|
|
||||||
.label=${this.hass.localize("ui.card.media_player.sound_mode")}
|
.label=${this.hass.localize("ui.card.media_player.sound_mode")}
|
||||||
.value=${stateObj.attributes.sound_mode!}
|
.value=${stateObj.attributes.sound_mode!}
|
||||||
|
icon
|
||||||
fixedMenuPosition
|
fixedMenuPosition
|
||||||
naturalMenuWidth
|
naturalMenuWidth
|
||||||
@selected=${this._handleSoundModeChanged}
|
@selected=${this._handleSoundModeChanged}
|
||||||
@@ -176,7 +171,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
<mwc-list-item .value=${mode}>${mode}</mwc-list-item>
|
<mwc-list-item .value=${mode}>${mode}</mwc-list-item>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</mwc-select>
|
<ha-svg-icon .path=${mdiMusicNote} slot="icon"></ha-svg-icon>
|
||||||
|
</ha-select>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
@@ -217,14 +213,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.source-input ha-svg-icon,
|
.source-input ha-select,
|
||||||
.sound-input ha-svg-icon {
|
.sound-input ha-select {
|
||||||
padding: 7px;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.source-input mwc-select,
|
|
||||||
.sound-input mwc-select {
|
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user