mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-09 19:59:43 +00:00
Compare commits
31 Commits
context-fo
...
newsletter
Author | SHA1 | Date | |
---|---|---|---|
![]() |
79593ce937 | ||
![]() |
a0d27a5a83 | ||
![]() |
1c4f6dc3f1 | ||
![]() |
7d9a60973c | ||
![]() |
b1f369a355 | ||
![]() |
e6d1e86c64 | ||
![]() |
eb1f94c370 | ||
![]() |
27750b8b5d | ||
![]() |
564a725284 | ||
![]() |
a5ee610af5 | ||
![]() |
eaf97ee7f5 | ||
![]() |
a14d75deec | ||
![]() |
72b5721c88 | ||
![]() |
94b4b818aa | ||
![]() |
98699b640a | ||
![]() |
decc0d3e0d | ||
![]() |
2281f5bafa | ||
![]() |
6cac7eeff0 | ||
![]() |
794bc161c8 | ||
![]() |
28cd9b6408 | ||
![]() |
9b4c6eea63 | ||
![]() |
bc6ef7780c | ||
![]() |
b29563a254 | ||
![]() |
fe8a1152c4 | ||
![]() |
26689a0a85 | ||
![]() |
4f6a241817 | ||
![]() |
246724c59e | ||
![]() |
8f5c9295d3 | ||
![]() |
0abafff4c9 | ||
![]() |
f88ce269a7 | ||
![]() |
0dc56d7983 |
@@ -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,
|
||||||
|
@@ -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,13 +175,13 @@ 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
|
|
||||||
? html`
|
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
.label=${html`<supervisor-formfield-label
|
.label=${html`<supervisor-formfield-label
|
||||||
label="Home Assistant"
|
label="Home Assistant"
|
||||||
.iconPath=${mdiHomeAssistant}
|
.iconPath=${mdiHomeAssistant}
|
||||||
.version=${this.backup.homeassistant}
|
.version=${this.backup
|
||||||
|
? this.backup.homeassistant
|
||||||
|
: this.hass.config.version}
|
||||||
>
|
>
|
||||||
</supervisor-formfield-label>`}
|
</supervisor-formfield-label>`}
|
||||||
>
|
>
|
||||||
@@ -185,8 +191,7 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
>
|
>
|
||||||
</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
|
||||||
|
@@ -94,6 +94,7 @@ class HassioDatadiskDialog extends LitElement {
|
|||||||
"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) =>
|
||||||
@@ -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"
|
||||||
)}
|
)}
|
||||||
|
@@ -80,7 +80,7 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<search-input
|
<search-input
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
autofocus
|
dialogInitialFocus
|
||||||
no-label-float
|
no-label-float
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@value-changed=${this._handleSearchChange}
|
@value-changed=${this._handleSearchChange}
|
||||||
|
@@ -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"
|
||||||
)}
|
)}
|
||||||
|
@@ -139,6 +139,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
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
73
src/components/ha-form/ha-form-grid.ts
Normal file
73
src/components/ha-form/ha-form-grid.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import "./ha-form";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, 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() {
|
||||||
|
this.setAttribute("own-margin", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
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,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,18 @@ 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;
|
||||||
|
|
||||||
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 +73,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
|
||||||
@@ -101,6 +110,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 +127,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,12 @@ export interface HaFormBaseSchema {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HaFormGridSchema extends HaFormBaseSchema {
|
||||||
|
type: "grid";
|
||||||
|
name: "";
|
||||||
|
schema: HaFormSchema[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface HaFormSelector extends HaFormBaseSchema {
|
export interface HaFormSelector extends HaFormBaseSchema {
|
||||||
type?: never;
|
type?: never;
|
||||||
selector: Selector;
|
selector: Selector;
|
||||||
|
145
src/components/ha-newsletter.ts
Normal file
145
src/components/ha-newsletter.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import "./ha-icon-button";
|
||||||
|
import "./ha-circular-progress";
|
||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import "./ha-card";
|
||||||
|
import "./ha-textfield";
|
||||||
|
import { LitElement, TemplateResult, html, CSSResultGroup, css } from "lit";
|
||||||
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import { mdiClose } from "@mdi/js";
|
||||||
|
import type { HaTextField } from "./ha-textfield";
|
||||||
|
import type { HomeAssistant } from "../types";
|
||||||
|
import { LocalStorage } from "../common/decorators/local-storage";
|
||||||
|
|
||||||
|
@customElement("ha-newsletter")
|
||||||
|
class HaNewsletter extends LitElement {
|
||||||
|
@property({ attribute: false }) hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@query("ha-textfield")
|
||||||
|
private _emailField?: HaTextField;
|
||||||
|
|
||||||
|
@LocalStorage("dismissNewsletter", true)
|
||||||
|
private _dismissNewsletter = false;
|
||||||
|
|
||||||
|
private _requestStatus?: "inprogress" | "complete";
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (this._dismissNewsletter) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
<div class="header">
|
||||||
|
${this.hass.localize("ui.newsletter.newsletter")}
|
||||||
|
<ha-icon-button
|
||||||
|
label="Dismiss"
|
||||||
|
.path=${mdiClose}
|
||||||
|
@click=${this._dismiss}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
<div class="newsletter">
|
||||||
|
${this._requestStatus === "complete"
|
||||||
|
? html`<span>${this.hass.localize("ui.newsletter.thanks")}</span>`
|
||||||
|
: html`
|
||||||
|
<ha-textfield
|
||||||
|
required
|
||||||
|
type="email"
|
||||||
|
.label=${this.hass.localize("ui.newsletter.email")}
|
||||||
|
.validationMessage=${this.hass.localize(
|
||||||
|
"ui.newsletter.validation"
|
||||||
|
)}
|
||||||
|
></ha-textfield>
|
||||||
|
${this._requestStatus === "inprogress"
|
||||||
|
? html`
|
||||||
|
<ha-circular-progress
|
||||||
|
active
|
||||||
|
alt="Loading"
|
||||||
|
></ha-circular-progress>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<mwc-button
|
||||||
|
raised
|
||||||
|
.label=${this.hass.localize("ui.newsletter.subscribe")}
|
||||||
|
@click=${this._subscribe}
|
||||||
|
></mwc-button>
|
||||||
|
`}
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _subscribe(): void {
|
||||||
|
if (!this._emailField?.reportValidity()) {
|
||||||
|
this._emailField!.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._requestStatus = "inprogress";
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
`https://newsletter.home-assistant.io/subscribe?email=${
|
||||||
|
this._emailField!.value
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this._requestStatus = "complete";
|
||||||
|
setTimeout(this._dismiss, 2000);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
// Reset request so user can re-enter email
|
||||||
|
this._requestStatus = undefined;
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _dismiss(): void {
|
||||||
|
this._dismissNewsletter = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
.newsletter {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0 16px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: var(--ha-card-header-color, --primary-text-color);
|
||||||
|
font-family: var(--ha-card-header-font-family, inherit);
|
||||||
|
font-size: var(--ha-card-header-font-size, 24px);
|
||||||
|
letter-spacing: -0.012em;
|
||||||
|
line-height: 48px;
|
||||||
|
padding: 12px 16px 16px;
|
||||||
|
margin-block-start: 0px;
|
||||||
|
margin-block-end: 0px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-textfield {
|
||||||
|
flex: 1;
|
||||||
|
display: block;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
mwc-button {
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ha-icon-button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-newsletter": HaNewsletter;
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -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>
|
||||||
`;
|
`;
|
||||||
|
@@ -69,10 +69,13 @@ 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,7 @@ 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";
|
||||||
|
|
||||||
@customElement("ha-selector")
|
@customElement("ha-selector")
|
||||||
export class HaSelector extends LitElement {
|
export class HaSelector extends LitElement {
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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(
|
||||||
() =>
|
() =>
|
||||||
|
@@ -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;
|
||||||
@@ -339,7 +340,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 "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,7 +14,8 @@ export type Selector =
|
|||||||
| ObjectSelector
|
| ObjectSelector
|
||||||
| SelectSelector
|
| SelectSelector
|
||||||
| IconSelector
|
| IconSelector
|
||||||
| MediaSelector;
|
| MediaSelector
|
||||||
|
| ThemeSelector;
|
||||||
|
|
||||||
export interface EntitySelector {
|
export interface EntitySelector {
|
||||||
entity: {
|
entity: {
|
||||||
@@ -147,8 +148,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 {
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
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, state } from "lit/decorators";
|
import { customElement, property, 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 "../../components/ha-dialog";
|
import "../../components/ha-dialog";
|
||||||
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";
|
||||||
@@ -71,18 +70,18 @@ class DialogBox extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${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 +106,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 {
|
||||||
|
@@ -10,7 +10,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";
|
||||||
|
@@ -30,7 +30,7 @@ import { HomeAssistant } from "../types";
|
|||||||
import "./action-badge";
|
import "./action-badge";
|
||||||
import "./integration-badge";
|
import "./integration-badge";
|
||||||
|
|
||||||
const HIDDEN_DOMAINS = new Set(["met", "rpi_power", "hassio"]);
|
const HIDDEN_DOMAINS = new Set(["hassio", "met", "radio_browser", "rpi_power"]);
|
||||||
|
|
||||||
@customElement("onboarding-integrations")
|
@customElement("onboarding-integrations")
|
||||||
class OnboardingIntegrations extends LitElement {
|
class OnboardingIntegrations extends LitElement {
|
||||||
|
@@ -73,7 +73,7 @@ export const handleChangeEvent = (element: ActionElement, ev: CustomEvent) => {
|
|||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newVal = ev.detail.value;
|
const newVal = ev.detail?.value || (ev.target as any).value;
|
||||||
|
|
||||||
if ((element.action[name] || "") === newVal) {
|
if ((element.action[name] || "") === newVal) {
|
||||||
return;
|
return;
|
||||||
@@ -376,7 +376,7 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
mwc-select {
|
mwc-select {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -143,9 +143,12 @@ export class HaDeviceAction extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
ha-device-picker {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
ha-device-action-picker {
|
ha-device-action-picker {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||||
import { html, LitElement, PropertyValues } 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 "../../../../../components/entity/ha-entity-picker";
|
import "../../../../../components/entity/ha-entity-picker";
|
||||||
import "../../../../../components/ha-service-picker";
|
import "../../../../../components/ha-service-picker";
|
||||||
|
import "../../../../../components/ha-textfield";
|
||||||
import "../../../../../components/ha-yaml-editor";
|
import "../../../../../components/ha-yaml-editor";
|
||||||
import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor";
|
import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor";
|
||||||
import type { EventAction } from "../../../../../data/script";
|
import type { EventAction } from "../../../../../data/script";
|
||||||
@@ -40,14 +40,13 @@ export class HaEventAction extends LitElement implements ActionElement {
|
|||||||
const { event, event_data } = this.action;
|
const { event, event_data } = this.action;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.type.event.event"
|
"ui.panel.config.automation.editor.actions.type.event.event"
|
||||||
)}
|
)}
|
||||||
name="event"
|
|
||||||
.value=${event}
|
.value=${event}
|
||||||
@value-changed=${this._eventChanged}
|
@change=${this._eventChanged}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -72,9 +71,17 @@ export class HaEventAction extends LitElement implements ActionElement {
|
|||||||
private _eventChanged(ev: CustomEvent): void {
|
private _eventChanged(ev: CustomEvent): void {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { ...this.action, event: ev.detail.value },
|
value: { ...this.action, event: (ev.target as any).value },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
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";
|
||||||
@@ -10,10 +9,11 @@ import {
|
|||||||
WhileRepeat,
|
WhileRepeat,
|
||||||
} from "../../../../../data/script";
|
} from "../../../../../data/script";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import { Condition } from "../../../../lovelace/common/validate-condition";
|
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
||||||
import "../ha-automation-action";
|
import "../ha-automation-action";
|
||||||
import { ActionElement } from "../ha-automation-action-row";
|
import "../../../../../components/ha-textfield";
|
||||||
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
|
||||||
const OPTIONS = ["count", "while", "until"];
|
const OPTIONS = ["count", "while", "until"];
|
||||||
|
|
||||||
@@ -53,14 +53,16 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
)}
|
)}
|
||||||
</mwc-select>
|
</mwc-select>
|
||||||
${type === "count"
|
${type === "count"
|
||||||
? html`<paper-input
|
? html`
|
||||||
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.type.repeat.type.count.label"
|
"ui.panel.config.automation.editor.actions.type.repeat.type.count.label"
|
||||||
)}
|
)}
|
||||||
name="count"
|
name="count"
|
||||||
.value=${(action as CountRepeat).count || "0"}
|
.value=${(action as CountRepeat).count || "0"}
|
||||||
@value-changed=${this._countChanged}
|
@change=${this._countChanged}
|
||||||
></paper-input>`
|
></ha-textfield>
|
||||||
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${type === "while"
|
${type === "while"
|
||||||
? html` <h3>
|
? html` <h3>
|
||||||
@@ -142,7 +144,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _countChanged(ev: CustomEvent): void {
|
private _countChanged(ev: CustomEvent): void {
|
||||||
const newVal = ev.detail.value;
|
const newVal = (ev.target as any).value;
|
||||||
if ((this.action.repeat as CountRepeat).count === newVal) {
|
if ((this.action.repeat as CountRepeat).count === newVal) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "../../../../../components/ha-textfield";
|
||||||
import "@polymer/paper-input/paper-textarea";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { 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 "../../../../../components/ha-formfield";
|
import "../../../../../components/ha-formfield";
|
||||||
@@ -26,14 +25,14 @@ export class HaWaitForTriggerAction
|
|||||||
const { wait_for_trigger, continue_on_timeout, timeout } = this.action;
|
const { wait_for_trigger, continue_on_timeout, timeout } = this.action;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.type.wait_for_trigger.timeout"
|
"ui.panel.config.automation.editor.actions.type.wait_for_trigger.timeout"
|
||||||
)}
|
)}
|
||||||
.name=${"timeout"}
|
.name=${"timeout"}
|
||||||
.value=${timeout}
|
.value=${timeout || ""}
|
||||||
@value-changed=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<br />
|
<br />
|
||||||
<ha-formfield
|
<ha-formfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -63,6 +62,15 @@ export class HaWaitForTriggerAction
|
|||||||
private _valueChanged(ev: CustomEvent): void {
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
handleChangeEvent(this, ev);
|
handleChangeEvent(this, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,66 +1,60 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import "@polymer/paper-input/paper-textarea";
|
|
||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||||
import { WaitAction } from "../../../../../data/script";
|
import type { WaitAction } from "../../../../../data/script";
|
||||||
import { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
import type { ActionElement } from "../ha-automation-action-row";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
|
||||||
|
const SCHEMA: HaFormSchema[] = [
|
||||||
|
{
|
||||||
|
name: "wait_template",
|
||||||
|
selector: {
|
||||||
|
text: {
|
||||||
|
multiline: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timeout",
|
||||||
|
required: false,
|
||||||
|
selector: {
|
||||||
|
text: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "continue_on_timeout",
|
||||||
|
selector: { boolean: {} },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
@customElement("ha-automation-action-wait_template")
|
@customElement("ha-automation-action-wait_template")
|
||||||
export class HaWaitAction extends LitElement implements ActionElement {
|
export class HaWaitAction extends LitElement implements ActionElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public action!: WaitAction;
|
@property({ attribute: false }) public action!: WaitAction;
|
||||||
|
|
||||||
public static get defaultConfig() {
|
public static get defaultConfig() {
|
||||||
return { wait_template: "" };
|
return { wait_template: "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const { wait_template, timeout, continue_on_timeout } = this.action;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-textarea
|
<ha-form
|
||||||
.label=${this.hass.localize(
|
.hass=${this.hass}
|
||||||
"ui.panel.config.automation.editor.actions.type.wait_template.wait_template"
|
.data=${this.action}
|
||||||
)}
|
.schema=${SCHEMA}
|
||||||
name="wait_template"
|
.computeLabel=${this._computeLabelCallback}
|
||||||
.value=${wait_template}
|
></ha-form>
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
dir="ltr"
|
|
||||||
></paper-textarea>
|
|
||||||
<paper-input
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.wait_template.timeout"
|
|
||||||
)}
|
|
||||||
.name=${"timeout"}
|
|
||||||
.value=${timeout}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></paper-input>
|
|
||||||
<br />
|
|
||||||
<ha-formfield
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.actions.type.wait_template.continue_timeout"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ha-switch
|
|
||||||
.checked=${continue_on_timeout}
|
|
||||||
@change=${this._continueChanged}
|
|
||||||
></ha-switch>
|
|
||||||
</ha-formfield>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _continueChanged(ev) {
|
private _computeLabelCallback = (schema: HaFormSchema): string =>
|
||||||
fireEvent(this, "value-changed", {
|
this.hass.localize(
|
||||||
value: { ...this.action, continue_on_timeout: ev.target.checked },
|
`ui.panel.config.automation.editor.actions.type.wait_template.${
|
||||||
});
|
schema.name === "continue_on_timeout" ? "continue_timeout" : schema.name
|
||||||
}
|
}`
|
||||||
|
);
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
|
||||||
handleChangeEvent(this, ev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -147,7 +147,7 @@ export default class HaAutomationConditionEditor extends LitElement {
|
|||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
mwc-select {
|
mwc-select {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -9,6 +9,7 @@ import type { StateCondition } from "../../../../../data/automation";
|
|||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import { forDictStruct } from "../../structs";
|
import { forDictStruct } from "../../structs";
|
||||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
|
||||||
const stateConditionStruct = object({
|
const stateConditionStruct = object({
|
||||||
condition: literal("state"),
|
condition: literal("state"),
|
||||||
|
@@ -7,6 +7,7 @@ import type { HomeAssistant } from "../../../../../types";
|
|||||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
|
||||||
@customElement("ha-automation-condition-sun")
|
@customElement("ha-automation-condition-sun")
|
||||||
export class HaSunCondition extends LitElement implements ConditionElement {
|
export class HaSunCondition extends LitElement implements ConditionElement {
|
||||||
|
@@ -7,6 +7,7 @@ import type { HomeAssistant } from "../../../../../types";
|
|||||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||||
|
import "../../../../../components/ha-form/ha-form";
|
||||||
|
|
||||||
const DAYS = {
|
const DAYS = {
|
||||||
mon: 1,
|
mon: 1,
|
||||||
|
@@ -9,7 +9,6 @@ import {
|
|||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import "@polymer/app-layout/app-header/app-header";
|
import "@polymer/app-layout/app-header/app-header";
|
||||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||||
import "@polymer/paper-input/paper-textarea";
|
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@@ -215,22 +214,26 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
${this._mode === "gui"
|
${this._mode === "gui"
|
||||||
? html`
|
? html`
|
||||||
${"use_blueprint" in this._config
|
${"use_blueprint" in this._config
|
||||||
? html`<blueprint-automation-editor
|
? html`
|
||||||
|
<blueprint-automation-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.isWide=${this.isWide}
|
.isWide=${this.isWide}
|
||||||
.stateObj=${stateObj}
|
.stateObj=${stateObj}
|
||||||
.config=${this._config}
|
.config=${this._config}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></blueprint-automation-editor>`
|
></blueprint-automation-editor>
|
||||||
: html`<manual-automation-editor
|
`
|
||||||
|
: html`
|
||||||
|
<manual-automation-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.isWide=${this.isWide}
|
.isWide=${this.isWide}
|
||||||
.stateObj=${stateObj}
|
.stateObj=${stateObj}
|
||||||
.config=${this._config}
|
.config=${this._config}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></manual-automation-editor>`}
|
></manual-automation-editor>
|
||||||
|
`}
|
||||||
`
|
`
|
||||||
: this._mode === "yaml"
|
: this._mode === "yaml"
|
||||||
? html`
|
? html`
|
||||||
|
@@ -7,7 +7,6 @@ import {
|
|||||||
mdiPlayCircleOutline,
|
mdiPlayCircleOutline,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
|
||||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { 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";
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@polymer/paper-input/paper-textarea";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
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 "../../../components/entity/ha-entity-toggle";
|
import "../../../components/entity/ha-entity-toggle";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
|
import "../../../components/ha-textarea";
|
||||||
|
import "../../../components/ha-textfield";
|
||||||
import {
|
import {
|
||||||
Condition,
|
Condition,
|
||||||
ManualAutomationConfig,
|
ManualAutomationConfig,
|
||||||
@@ -14,7 +15,7 @@ import {
|
|||||||
} from "../../../data/automation";
|
} from "../../../data/automation";
|
||||||
import { Action, MODES, MODES_MAX } from "../../../data/script";
|
import { Action, MODES, MODES_MAX } from "../../../data/script";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { documentationUrl } from "../../../util/documentation-url";
|
import { documentationUrl } from "../../../util/documentation-url";
|
||||||
import "../ha-config-section";
|
import "../ha-config-section";
|
||||||
import "./action/ha-automation-action";
|
import "./action/ha-automation-action";
|
||||||
@@ -45,16 +46,16 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
</span>
|
</span>
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.alias"
|
"ui.panel.config.automation.editor.alias"
|
||||||
)}
|
)}
|
||||||
name="alias"
|
name="alias"
|
||||||
.value=${this.config.alias}
|
.value=${this.config.alias || ""}
|
||||||
@value-changed=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
>
|
>
|
||||||
</paper-input>
|
</ha-textfield>
|
||||||
<paper-textarea
|
<ha-textarea
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.description.label"
|
"ui.panel.config.automation.editor.description.label"
|
||||||
)}
|
)}
|
||||||
@@ -62,9 +63,9 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
"ui.panel.config.automation.editor.description.placeholder"
|
"ui.panel.config.automation.editor.description.placeholder"
|
||||||
)}
|
)}
|
||||||
name="description"
|
name="description"
|
||||||
.value=${this.config.description}
|
.value=${this.config.description || ""}
|
||||||
@value-changed=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
></paper-textarea>
|
></ha-textarea>
|
||||||
<p>
|
<p>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.modes.description",
|
"ui.panel.config.automation.editor.modes.description",
|
||||||
@@ -98,16 +99,18 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</mwc-select>
|
</mwc-select>
|
||||||
${this.config.mode && MODES_MAX.includes(this.config.mode)
|
${this.config.mode && MODES_MAX.includes(this.config.mode)
|
||||||
? html`<paper-input
|
? html`
|
||||||
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
`ui.panel.config.automation.editor.max.${this.config.mode}`
|
`ui.panel.config.automation.editor.max.${this.config.mode}`
|
||||||
)}
|
)}
|
||||||
type="number"
|
type="number"
|
||||||
name="max"
|
name="max"
|
||||||
.value=${this.config.max || "10"}
|
.value=${this.config.max || "10"}
|
||||||
@value-changed=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
>
|
>
|
||||||
</paper-input>`
|
</ha-textfield>
|
||||||
|
`
|
||||||
: html``}
|
: html``}
|
||||||
</div>
|
</div>
|
||||||
${this.stateObj
|
${this.stateObj
|
||||||
@@ -243,7 +246,7 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let newVal = ev.detail.value;
|
let newVal = target.value;
|
||||||
if (target.type === "number") {
|
if (target.type === "number") {
|
||||||
newVal = Number(newVal);
|
newVal = Number(newVal);
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ import { LocalizeFunc } from "../../../../common/translations/localize";
|
|||||||
import "../../../../components/ha-button-menu";
|
import "../../../../components/ha-button-menu";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import "../../../../components/ha-alert";
|
import "../../../../components/ha-alert";
|
||||||
|
import "../../../../components/ha-textfield";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
import type { Trigger } from "../../../../data/automation";
|
import type { Trigger } from "../../../../data/automation";
|
||||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||||
@@ -200,14 +201,14 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
|
|
||||||
${showId
|
${showId
|
||||||
? html`
|
? html`
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.triggers.id"
|
"ui.panel.config.automation.editor.triggers.id"
|
||||||
)}
|
)}
|
||||||
.value=${this.trigger.id}
|
.value=${this.trigger.id || ""}
|
||||||
@value-changed=${this._idChanged}
|
@change=${this._idChanged}
|
||||||
>
|
>
|
||||||
</paper-input>
|
</ha-textfield>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div @ui-mode-not-available=${this._handleUiModeNotAvailable}>
|
<div @ui-mode-not-available=${this._handleUiModeNotAvailable}>
|
||||||
@@ -287,7 +288,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _idChanged(ev: CustomEvent) {
|
private _idChanged(ev: CustomEvent) {
|
||||||
const newId = ev.detail.value;
|
const newId = (ev.target as any).value;
|
||||||
if (newId === (this.trigger.id ?? "")) {
|
if (newId === (this.trigger.id ?? "")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -333,7 +334,11 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||||
}
|
}
|
||||||
mwc-select {
|
mwc-select {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { 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 "../../../../../components/ha-textfield";
|
||||||
import "../../../../../components/ha-yaml-editor";
|
import "../../../../../components/ha-yaml-editor";
|
||||||
import "../../../../../components/user/ha-users-picker";
|
import "../../../../../components/user/ha-users-picker";
|
||||||
import { EventTrigger } from "../../../../../data/automation";
|
import { EventTrigger } from "../../../../../data/automation";
|
||||||
@@ -24,14 +24,14 @@ export class HaEventTrigger extends LitElement implements TriggerElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
const { event_type, event_data, context } = this.trigger;
|
const { event_type, event_data, context } = this.trigger;
|
||||||
return html`
|
return html`
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.triggers.type.event.event_type"
|
"ui.panel.config.automation.editor.triggers.type.event.event_type"
|
||||||
)}
|
)}
|
||||||
name="event_type"
|
name="event_type"
|
||||||
.value=${event_type}
|
.value=${event_type}
|
||||||
@value-changed=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<ha-yaml-editor
|
<ha-yaml-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -97,6 +97,14 @@ export class HaEventTrigger extends LitElement implements TriggerElement {
|
|||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return css`
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import "../../../components/ha-newsletter";
|
||||||
import { mdiCloudLock, mdiDotsVertical, mdiMagnify } from "@mdi/js";
|
import { mdiCloudLock, mdiDotsVertical, mdiMagnify } from "@mdi/js";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import type { ActionDetail } from "@material/mwc-list";
|
import type { ActionDetail } from "@material/mwc-list";
|
||||||
@@ -134,6 +135,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
.pages=${configSections.dashboard}
|
.pages=${configSections.dashboard}
|
||||||
></ha-config-navigation>
|
></ha-config-navigation>
|
||||||
</ha-card>`}
|
</ha-card>`}
|
||||||
|
<ha-newsletter .hass=${this.hass}></ha-newsletter>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
</ha-app-layout>
|
</ha-app-layout>
|
||||||
`;
|
`;
|
||||||
|
@@ -27,6 +27,7 @@ import {
|
|||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
updateDeviceRegistryEntry,
|
updateDeviceRegistryEntry,
|
||||||
|
removeConfigEntryFromDevice,
|
||||||
} from "../../../data/device_registry";
|
} from "../../../data/device_registry";
|
||||||
import {
|
import {
|
||||||
fetchDiagnosticHandler,
|
fetchDiagnosticHandler,
|
||||||
@@ -95,6 +96,8 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
| number
|
| number
|
||||||
| (TemplateResult | string)[];
|
| (TemplateResult | string)[];
|
||||||
|
|
||||||
|
@state() private _deleteButtons?: (TemplateResult | string)[];
|
||||||
|
|
||||||
private _device = memoizeOne(
|
private _device = memoizeOne(
|
||||||
(
|
(
|
||||||
deviceId: string,
|
deviceId: string,
|
||||||
@@ -186,10 +189,11 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
changedProps.has("entries")
|
changedProps.has("entries")
|
||||||
) {
|
) {
|
||||||
this._diagnosticDownloadLinks = undefined;
|
this._diagnosticDownloadLinks = undefined;
|
||||||
|
this._deleteButtons = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this._diagnosticDownloadLinks ||
|
(this._diagnosticDownloadLinks && this._deleteButtons) ||
|
||||||
!this.devices ||
|
!this.devices ||
|
||||||
!this.deviceId ||
|
!this.deviceId ||
|
||||||
!this.entries
|
!this.entries
|
||||||
@@ -198,7 +202,9 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._diagnosticDownloadLinks = Math.random();
|
this._diagnosticDownloadLinks = Math.random();
|
||||||
|
this._deleteButtons = []; // To prevent re-rendering if no delete buttons
|
||||||
this._renderDiagnosticButtons(this._diagnosticDownloadLinks);
|
this._renderDiagnosticButtons(this._diagnosticDownloadLinks);
|
||||||
|
this._renderDeleteButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _renderDiagnosticButtons(requestId: number): Promise<void> {
|
private async _renderDiagnosticButtons(requestId: number): Promise<void> {
|
||||||
@@ -263,6 +269,55 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _renderDeleteButtons() {
|
||||||
|
const device = this._device(this.deviceId, this.devices);
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttons: TemplateResult[] = [];
|
||||||
|
this._integrations(device, this.entries).forEach((entry) => {
|
||||||
|
if (entry.state !== "loaded" || !entry.supports_remove_device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buttons.push(html`
|
||||||
|
<mwc-button
|
||||||
|
class="warning"
|
||||||
|
.entryId=${entry.entry_id}
|
||||||
|
@click=${this._confirmDeleteEntry}
|
||||||
|
>
|
||||||
|
${buttons.length > 1
|
||||||
|
? this.hass.localize(
|
||||||
|
`ui.panel.config.devices.delete_device_integration`,
|
||||||
|
{
|
||||||
|
integration: domainToName(this.hass.localize, entry.domain),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
: this.hass.localize(`ui.panel.config.devices.delete_device`)}
|
||||||
|
</mwc-button>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (buttons.length > 0) {
|
||||||
|
this._deleteButtons = buttons;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _confirmDeleteEntry(e: MouseEvent): Promise<void> {
|
||||||
|
const entryId = (e.currentTarget as any).entryId;
|
||||||
|
|
||||||
|
const confirmed = await showConfirmationDialog(this, {
|
||||||
|
text: this.hass.localize("ui.panel.config.devices.confirm_delete"),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await removeConfigEntryFromDevice(this.hass!, this.deviceId, entryId);
|
||||||
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
loadDeviceRegistryDetailDialog();
|
loadDeviceRegistryDetailDialog();
|
||||||
@@ -375,6 +430,9 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
if (Array.isArray(this._diagnosticDownloadLinks)) {
|
if (Array.isArray(this._diagnosticDownloadLinks)) {
|
||||||
deviceActions.push(...this._diagnosticDownloadLinks);
|
deviceActions.push(...this._diagnosticDownloadLinks);
|
||||||
}
|
}
|
||||||
|
if (this._deleteButtons) {
|
||||||
|
deviceActions.push(...this._deleteButtons);
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
|
@@ -369,7 +369,7 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
</a>`
|
</a>`
|
||||||
: ""}
|
: ""}
|
||||||
${!item.disabled_by &&
|
${!item.disabled_by &&
|
||||||
item.state === "loaded" &&
|
(item.state === "loaded" || item.state === "setup_retry") &&
|
||||||
item.supports_unload &&
|
item.supports_unload &&
|
||||||
item.source !== "system"
|
item.source !== "system"
|
||||||
? html`<mwc-list-item @request-selected=${this._handleReload}>
|
? html`<mwc-list-item @request-selected=${this._handleReload}>
|
||||||
|
@@ -5,9 +5,11 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
|||||||
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 { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||||
|
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../../../common/entity/compute_state_name";
|
||||||
import "../../../../../components/buttons/ha-call-api-button";
|
import "../../../../../components/buttons/ha-call-api-button";
|
||||||
import "../../../../../components/buttons/ha-call-service-button";
|
import "../../../../../components/buttons/ha-call-service-button";
|
||||||
|
import "../../../../../components/ha-alert";
|
||||||
import "../../../../../components/ha-card";
|
import "../../../../../components/ha-card";
|
||||||
import "../../../../../components/ha-circular-progress";
|
import "../../../../../components/ha-circular-progress";
|
||||||
import "../../../../../components/ha-icon";
|
import "../../../../../components/ha-icon";
|
||||||
@@ -18,30 +20,28 @@ import {
|
|||||||
fetchDeviceRegistry,
|
fetchDeviceRegistry,
|
||||||
subscribeDeviceRegistry,
|
subscribeDeviceRegistry,
|
||||||
} from "../../../../../data/device_registry";
|
} from "../../../../../data/device_registry";
|
||||||
import {
|
|
||||||
migrateZwave,
|
|
||||||
ZWaveJsMigrationData,
|
|
||||||
fetchZwaveNetworkStatus as fetchZwaveJsNetworkStatus,
|
|
||||||
fetchZwaveNodeStatus,
|
|
||||||
getZwaveJsIdentifiersFromDevice,
|
|
||||||
subscribeZwaveNodeReady,
|
|
||||||
} from "../../../../../data/zwave_js";
|
|
||||||
import {
|
import {
|
||||||
fetchMigrationConfig,
|
fetchMigrationConfig,
|
||||||
|
fetchNetworkStatus,
|
||||||
startZwaveJsConfigFlow,
|
startZwaveJsConfigFlow,
|
||||||
ZWaveMigrationConfig,
|
ZWaveMigrationConfig,
|
||||||
ZWaveNetworkStatus,
|
ZWaveNetworkStatus,
|
||||||
ZWAVE_NETWORK_STATE_STOPPED,
|
ZWAVE_NETWORK_STATE_STOPPED,
|
||||||
fetchNetworkStatus,
|
|
||||||
} from "../../../../../data/zwave";
|
} from "../../../../../data/zwave";
|
||||||
|
import {
|
||||||
|
fetchZwaveNetworkStatus as fetchZwaveJsNetworkStatus,
|
||||||
|
fetchZwaveNodeStatus,
|
||||||
|
getZwaveJsIdentifiersFromDevice,
|
||||||
|
migrateZwave,
|
||||||
|
subscribeZwaveNodeReady,
|
||||||
|
ZWaveJsMigrationData,
|
||||||
|
} from "../../../../../data/zwave_js";
|
||||||
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
|
import { showConfigFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-config-flow";
|
||||||
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||||
import "../../../../../layouts/hass-subpage";
|
import "../../../../../layouts/hass-subpage";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../../../types";
|
import type { HomeAssistant, Route } from "../../../../../types";
|
||||||
import "../../../ha-config-section";
|
import "../../../ha-config-section";
|
||||||
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
|
|
||||||
import "../../../../../components/ha-alert";
|
|
||||||
|
|
||||||
@customElement("zwave-migration")
|
@customElement("zwave-migration")
|
||||||
export class ZwaveMigration extends LitElement {
|
export class ZwaveMigration extends LitElement {
|
||||||
@@ -155,7 +155,7 @@ export class ZwaveMigration extends LitElement {
|
|||||||
.filter(
|
.filter(
|
||||||
(entityState) =>
|
(entityState) =>
|
||||||
computeStateDomain(entityState) === "zwave" &&
|
computeStateDomain(entityState) === "zwave" &&
|
||||||
entityState.state !== "ready"
|
!["ready", "sleeping"].includes(entityState.state)
|
||||||
)
|
)
|
||||||
.map(
|
.map(
|
||||||
(entityState) =>
|
(entityState) =>
|
||||||
@@ -430,6 +430,10 @@ export class ZwaveMigration extends LitElement {
|
|||||||
const nodesNotReady = (await Promise.all(nodeStatePromisses)).filter(
|
const nodesNotReady = (await Promise.all(nodeStatePromisses)).filter(
|
||||||
(node) => !node.ready
|
(node) => !node.ready
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log("waiting for nodes to be ready", nodesNotReady);
|
||||||
|
|
||||||
this._getMigrationData();
|
this._getMigrationData();
|
||||||
if (nodesNotReady.length === 0) {
|
if (nodesNotReady.length === 0) {
|
||||||
this._waitingOnDevices = [];
|
this._waitingOnDevices = [];
|
||||||
@@ -445,10 +449,19 @@ export class ZwaveMigration extends LitElement {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const deviceReg = await fetchDeviceRegistry(this.hass);
|
const deviceReg: DeviceRegistryEntry[] = await fetchDeviceRegistry(
|
||||||
this._waitingOnDevices = deviceReg
|
this.hass.connection
|
||||||
.map((device) => getZwaveJsIdentifiersFromDevice(device))
|
);
|
||||||
.filter(Boolean);
|
this._waitingOnDevices = deviceReg.filter((device) => {
|
||||||
|
const identifiers = getZwaveJsIdentifiersFromDevice(device);
|
||||||
|
if (
|
||||||
|
!identifiers ||
|
||||||
|
Number(identifiers.home_id) !== networkStatus.controller.home_id
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return nodesNotReady.some((node) => identifiers.node_id === node.node_id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getMigrationData() {
|
private async _getMigrationData() {
|
||||||
|
@@ -28,12 +28,13 @@ import { navigate } from "../../../common/navigate";
|
|||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
import "../../../components/device/ha-device-picker";
|
import "../../../components/device/ha-device-picker";
|
||||||
import "../../../components/entity/ha-entities-picker";
|
import "../../../components/entity/ha-entities-picker";
|
||||||
|
import "../../../components/ha-area-picker";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-icon-picker";
|
import "../../../components/ha-icon-picker";
|
||||||
import "../../../components/ha-area-picker";
|
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
|
import "../../../components/ha-textfield";
|
||||||
import {
|
import {
|
||||||
computeDeviceName,
|
computeDeviceName,
|
||||||
DeviceRegistryEntry,
|
DeviceRegistryEntry,
|
||||||
@@ -288,14 +289,14 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
</div>
|
</div>
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.value=${this._config.name}
|
.value=${this._config.name}
|
||||||
.name=${"name"}
|
.name=${"name"}
|
||||||
@value-changed=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.scene.editor.name"
|
"ui.panel.config.scene.editor.name"
|
||||||
)}
|
)}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<ha-icon-picker
|
<ha-icon-picker
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.scene.editor.icon"
|
"ui.panel.config.scene.editor.icon"
|
||||||
@@ -701,14 +702,14 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
private _valueChanged(ev: Event) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const target = ev.target as any;
|
const target = ev.target as any;
|
||||||
const name = target.name;
|
const name = target.name;
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let newVal = ev.detail.value;
|
let newVal = (ev as CustomEvent).detail?.value ?? target.value;
|
||||||
if (target.type === "number") {
|
if (target.type === "number") {
|
||||||
newVal = Number(newVal);
|
newVal = Number(newVal);
|
||||||
}
|
}
|
||||||
@@ -990,6 +991,9 @@ export class HaSceneEditor extends SubscribeMixin(
|
|||||||
display: block;
|
display: block;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
ha-textfield {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
@@ -6,6 +5,7 @@ import "../../../components/ha-blueprint-picker";
|
|||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-circular-progress";
|
import "../../../components/ha-circular-progress";
|
||||||
import "../../../components/ha-markdown";
|
import "../../../components/ha-markdown";
|
||||||
|
import "../../../components/ha-textfield";
|
||||||
import "../../../components/ha-selector/ha-selector";
|
import "../../../components/ha-selector/ha-selector";
|
||||||
import "../../../components/ha-settings-row";
|
import "../../../components/ha-settings-row";
|
||||||
|
|
||||||
@@ -101,15 +101,14 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||||||
value?.default}
|
value?.default}
|
||||||
@value-changed=${this._inputChanged}
|
@value-changed=${this._inputChanged}
|
||||||
></ha-selector>`
|
></ha-selector>`
|
||||||
: html`<paper-input
|
: html`<ha-textfield
|
||||||
.key=${key}
|
.key=${key}
|
||||||
required
|
required
|
||||||
.value=${(this.config.use_blueprint.input &&
|
.value=${(this.config.use_blueprint.input &&
|
||||||
this.config.use_blueprint.input[key]) ??
|
this.config.use_blueprint.input[key]) ??
|
||||||
value?.default}
|
value?.default}
|
||||||
@value-changed=${this._inputChanged}
|
@change=${this._inputChanged}
|
||||||
no-label-float
|
></ha-textfield>`}
|
||||||
></paper-input>`}
|
|
||||||
</ha-settings-row>`
|
</ha-settings-row>`
|
||||||
)
|
)
|
||||||
: html`<p class="padding">
|
: html`<p class="padding">
|
||||||
@@ -145,7 +144,7 @@ export class HaBlueprintScriptEditor extends LitElement {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const target = ev.target as any;
|
const target = ev.target as any;
|
||||||
const key = target.key;
|
const key = target.key;
|
||||||
const value = ev.detail.value;
|
const value = ev.detail?.value ?? target.value;
|
||||||
if (
|
if (
|
||||||
(this.config.use_blueprint.input &&
|
(this.config.use_blueprint.input &&
|
||||||
this.config.use_blueprint.input[key] === value) ||
|
this.config.use_blueprint.input[key] === value) ||
|
||||||
|
@@ -3,7 +3,6 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
|||||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { load } from "js-yaml";
|
|
||||||
import { debounce } from "../../../common/util/debounce";
|
import { debounce } from "../../../common/util/debounce";
|
||||||
import "../../../components/ha-circular-progress";
|
import "../../../components/ha-circular-progress";
|
||||||
import "../../../components/ha-code-editor";
|
import "../../../components/ha-code-editor";
|
||||||
@@ -52,10 +51,6 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
|
|
||||||
private _template = "";
|
private _template = "";
|
||||||
|
|
||||||
private _context_data = "";
|
|
||||||
|
|
||||||
private _context_variables?;
|
|
||||||
|
|
||||||
private _inited = false;
|
private _inited = false;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
@@ -70,14 +65,8 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
if (localStorage) {
|
if (localStorage && localStorage["panel-dev-template-template"]) {
|
||||||
if (localStorage["panel-dev-template-template"]) {
|
|
||||||
this._template = localStorage["panel-dev-template-template"];
|
this._template = localStorage["panel-dev-template-template"];
|
||||||
}
|
|
||||||
if (localStorage["panel-dev-template-context-data"]) {
|
|
||||||
this._context_data = localStorage["panel-dev-template-context-data"];
|
|
||||||
this._context_variables = undefined;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this._template = DEMO_TEMPLATE;
|
this._template = DEMO_TEMPLATE;
|
||||||
}
|
}
|
||||||
@@ -152,17 +141,6 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
"ui.panel.developer-tools.tabs.templates.reset"
|
"ui.panel.developer-tools.tabs.templates.reset"
|
||||||
)}
|
)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
<p>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.developer-tools.tabs.templates.context_data"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<ha-code-editor
|
|
||||||
mode="yaml"
|
|
||||||
.value=${this._context_data}
|
|
||||||
@value-changed=${this._contextDataChanged}
|
|
||||||
dir="ltr"
|
|
||||||
></ha-code-editor>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="render-pane">
|
<div class="render-pane">
|
||||||
@@ -324,15 +302,6 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
private _contextDataChanged(ev) {
|
|
||||||
this._context_data = ev.detail.value;
|
|
||||||
this._context_variables = undefined;
|
|
||||||
if (this._error) {
|
|
||||||
this._error = undefined;
|
|
||||||
}
|
|
||||||
this._debounceRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _templateChanged(ev) {
|
private _templateChanged(ev) {
|
||||||
this._template = ev.detail.value;
|
this._template = ev.detail.value;
|
||||||
if (this._error) {
|
if (this._error) {
|
||||||
@@ -345,9 +314,6 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
this._rendering = true;
|
this._rendering = true;
|
||||||
await this._unsubscribeTemplate();
|
await this._unsubscribeTemplate();
|
||||||
try {
|
try {
|
||||||
if (this._context_data && !this._context_variables) {
|
|
||||||
this._context_variables = load(this._context_data);
|
|
||||||
}
|
|
||||||
this._unsubRenderTemplate = subscribeRenderTemplate(
|
this._unsubRenderTemplate = subscribeRenderTemplate(
|
||||||
this.hass.connection,
|
this.hass.connection,
|
||||||
(result) => {
|
(result) => {
|
||||||
@@ -356,7 +322,6 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
template: this._template,
|
template: this._template,
|
||||||
variables: this._context_variables,
|
|
||||||
timeout: 3,
|
timeout: 3,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -395,16 +360,12 @@ class HaPanelDevTemplate extends LitElement {
|
|||||||
if (!this._inited) {
|
if (!this._inited) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
localStorage["panel-dev-template-context-data"] = this._context_data;
|
|
||||||
localStorage["panel-dev-template-template"] = this._template;
|
localStorage["panel-dev-template-template"] = this._template;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _restoreDemo() {
|
private _restoreDemo() {
|
||||||
this._context_data = "";
|
|
||||||
this._context_variables = undefined;
|
|
||||||
this._template = DEMO_TEMPLATE;
|
this._template = DEMO_TEMPLATE;
|
||||||
this._subscribeTemplate();
|
this._subscribeTemplate();
|
||||||
delete localStorage["panel-dev-template-context-data"];
|
|
||||||
delete localStorage["panel-dev-template-template"];
|
delete localStorage["panel-dev-template-template"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -81,7 +81,7 @@ class HaPanelHistory extends LitElement {
|
|||||||
</app-header>
|
</app-header>
|
||||||
|
|
||||||
<div class="flex content">
|
<div class="flex content">
|
||||||
<div class="flex layout horizontal wrap">
|
<div class="filters">
|
||||||
<ha-date-range-picker
|
<ha-date-range-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
?disabled=${this._isLoading}
|
?disabled=${this._isLoading}
|
||||||
@@ -247,6 +247,12 @@ class HaPanelHistory extends LitElement {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 8px 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
ha-date-range-picker {
|
ha-date-range-picker {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@@ -335,7 +335,7 @@ export class HaPanelLogbook extends LitElement {
|
|||||||
.filters {
|
.filters {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
padding: 0 16px;
|
padding: 8px 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([narrow]) .filters {
|
:host([narrow]) .filters {
|
||||||
|
@@ -57,7 +57,7 @@ export class HuiThemeSelectEditor extends LitElement {
|
|||||||
if (!this.hass || ev.target.value === "") {
|
if (!this.hass || ev.target.value === "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.value = ev.target.value === "remove" ? "" : ev.target.selected;
|
this.value = ev.target.value === "remove" ? undefined : ev.target.value;
|
||||||
fireEvent(this, "value-changed", { value: this.value });
|
fireEvent(this, "value-changed", { value: this.value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,14 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "../../../../components/ha-form/ha-form";
|
||||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import "../../../../components/ha-area-picker";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import type { AreaCardConfig } from "../../cards/types";
|
||||||
import { AreaCardConfig } from "../../cards/types";
|
import type { LovelaceCardEditor } from "../../types";
|
||||||
import "../../components/hui-theme-select-editor";
|
|
||||||
import { LovelaceCardEditor } from "../../types";
|
|
||||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||||
import { EditorTarget } from "../types";
|
import type { HaFormSchema } from "../../../../components/ha-form/types";
|
||||||
import { configElementStyle } from "./config-elements-style";
|
|
||||||
import "../../../../components/ha-formfield";
|
|
||||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
|
||||||
|
|
||||||
const cardConfigStruct = assign(
|
const cardConfigStruct = assign(
|
||||||
baseLovelaceCardConfig,
|
baseLovelaceCardConfig,
|
||||||
@@ -38,21 +34,18 @@ export class HuiAreaCardEditor
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
get _area(): string {
|
private _schema = memoizeOne((): HaFormSchema[] => [
|
||||||
return this._config!.area || "";
|
{ name: "area", selector: { area: {} } },
|
||||||
}
|
{ name: "show_camera", required: false, selector: { boolean: {} } },
|
||||||
|
{
|
||||||
get _navigation_path(): string {
|
name: "",
|
||||||
return this._config!.navigation_path || "";
|
type: "grid",
|
||||||
}
|
schema: [
|
||||||
|
{ name: "navigation_path", required: false, selector: { text: {} } },
|
||||||
get _theme(): string {
|
{ name: "theme", required: false, selector: { theme: {} } },
|
||||||
return this._config!.theme || "";
|
],
|
||||||
}
|
},
|
||||||
|
]);
|
||||||
get _show_camera(): boolean {
|
|
||||||
return this._config!.show_camera || false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
@@ -60,79 +53,35 @@ export class HuiAreaCardEditor
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="card-config">
|
<ha-form
|
||||||
<ha-area-picker
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._area}
|
.data=${this._config}
|
||||||
.placeholder=${this._area}
|
.schema=${this._schema()}
|
||||||
.configValue=${"area"}
|
.computeLabel=${this._computeLabelCallback}
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.area.name"
|
|
||||||
)}
|
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-area-picker>
|
></ha-form>
|
||||||
<ha-formfield
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.area.show_camera"
|
|
||||||
)}
|
|
||||||
.dir=${computeRTLDirection(this.hass)}
|
|
||||||
>
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._show_camera}
|
|
||||||
.configValue=${"show_camera"}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
></ha-switch>
|
|
||||||
</ha-formfield>
|
|
||||||
<paper-input
|
|
||||||
.label=${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.action-editor.navigation_path"
|
|
||||||
)}
|
|
||||||
.value=${this._navigation_path}
|
|
||||||
.configValue=${"navigation_path"}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
>
|
|
||||||
</paper-input>
|
|
||||||
<hui-theme-select-editor
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${this._theme}
|
|
||||||
.configValue=${"theme"}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></hui-theme-select-editor>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
if (!this._config || !this.hass) {
|
const config = ev.detail.value;
|
||||||
return;
|
fireEvent(this, "config-changed", { config });
|
||||||
}
|
|
||||||
const target = ev.target! as EditorTarget;
|
|
||||||
const value =
|
|
||||||
target.checked !== undefined ? target.checked : ev.detail.value;
|
|
||||||
|
|
||||||
if (this[`_${target.configValue}`] === value) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let newConfig;
|
private _computeLabelCallback = (schema: HaFormSchema) => {
|
||||||
if (target.configValue) {
|
switch (schema.name) {
|
||||||
if (!value) {
|
case "area":
|
||||||
newConfig = { ...this._config };
|
return this.hass!.localize("ui.panel.lovelace.editor.card.area.name");
|
||||||
delete newConfig[target.configValue!];
|
case "navigation_path":
|
||||||
} else {
|
return this.hass!.localize(
|
||||||
newConfig = {
|
"ui.panel.lovelace.editor.action-editor.navigation_path"
|
||||||
...this._config,
|
);
|
||||||
[target.configValue!]: value,
|
}
|
||||||
|
return this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.area.${schema.name}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
fireEvent(this, "config-changed", { config: newConfig });
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return configElementStyle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
@@ -1,22 +1,18 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "../../../../components/ha-form/ha-form";
|
||||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { 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 { assert, assign, boolean, object, optional, string } from "superstruct";
|
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
||||||
|
import type { HassEntity } from "home-assistant-js-websocket/dist/types";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||||
import "../../../../components/entity/ha-entity-attribute-picker";
|
import type { HaFormSchema } from "../../../../components/ha-form/types";
|
||||||
import "../../../../components/ha-icon-picker";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import type { EntityCardConfig } from "../../cards/types";
|
||||||
import { EntityCardConfig } from "../../cards/types";
|
|
||||||
import "../../components/hui-action-editor";
|
|
||||||
import "../../components/hui-entity-editor";
|
|
||||||
import "../../components/hui-theme-select-editor";
|
|
||||||
import { headerFooterConfigStructs } from "../../header-footer/structs";
|
import { headerFooterConfigStructs } from "../../header-footer/structs";
|
||||||
import { LovelaceCardEditor } from "../../types";
|
import type { LovelaceCardEditor } from "../../types";
|
||||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||||
import { EditorTarget, EntitiesEditorEvent } from "../types";
|
|
||||||
import { configElementStyle } from "./config-elements-style";
|
|
||||||
|
|
||||||
const cardConfigStruct = assign(
|
const cardConfigStruct = assign(
|
||||||
baseLovelaceCardConfig,
|
baseLovelaceCardConfig,
|
||||||
@@ -46,175 +42,83 @@ export class HuiEntityCardEditor
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
get _entity(): string {
|
private _schema = memoizeOne(
|
||||||
return this._config!.entity || "";
|
(entity: string, icon: string, entityState: HassEntity): HaFormSchema[] => [
|
||||||
}
|
{ name: "entity", required: true, selector: { entity: {} } },
|
||||||
|
{
|
||||||
|
type: "grid",
|
||||||
|
name: "",
|
||||||
|
schema: [
|
||||||
|
{ name: "name", selector: { text: {} } },
|
||||||
|
{
|
||||||
|
name: "icon",
|
||||||
|
selector: {
|
||||||
|
icon: {
|
||||||
|
placeholder: icon || entityState?.attributes.icon,
|
||||||
|
fallbackPath:
|
||||||
|
!icon && !entityState?.attributes.icon && entityState
|
||||||
|
? domainIcon(computeDomain(entity), entityState)
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
get _name(): string {
|
{
|
||||||
return this._config!.name || "";
|
name: "attribute",
|
||||||
}
|
selector: { attribute: { entity_id: entity } },
|
||||||
|
},
|
||||||
get _icon(): string {
|
{ name: "unit", selector: { text: {} } },
|
||||||
return this._config!.icon || "";
|
{ name: "theme", selector: { theme: {} } },
|
||||||
}
|
{ name: "state_color", selector: { boolean: {} } },
|
||||||
|
],
|
||||||
get _attribute(): string {
|
},
|
||||||
return this._config!.attribute || "";
|
]
|
||||||
}
|
);
|
||||||
|
|
||||||
get _unit(): string {
|
|
||||||
return this._config!.unit || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
get _state_color(): boolean {
|
|
||||||
return this._config!.state_color ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
get _theme(): string {
|
|
||||||
return this._config!.theme || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const entityState = this.hass.states[this._entity];
|
|
||||||
|
const entityState = this.hass.states[this._config.entity];
|
||||||
|
|
||||||
|
const schema = this._schema(
|
||||||
|
this._config.entity,
|
||||||
|
this._config.icon,
|
||||||
|
entityState
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="card-config">
|
<ha-form
|
||||||
<ha-entity-picker
|
|
||||||
.label="${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.generic.entity"
|
|
||||||
)} (${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.config.required"
|
|
||||||
)})"
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._entity}
|
.data=${this._config}
|
||||||
.configValue=${"entity"}
|
.schema=${schema}
|
||||||
@change=${this._valueChanged}
|
.computeLabel=${this._computeLabelCallback}
|
||||||
allow-custom-entity
|
|
||||||
></ha-entity-picker>
|
|
||||||
<div class="side-by-side">
|
|
||||||
<paper-input
|
|
||||||
.label="${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.generic.name"
|
|
||||||
)} (${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.config.optional"
|
|
||||||
)})"
|
|
||||||
.value=${this._name}
|
|
||||||
.configValue=${"name"}
|
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></paper-input>
|
></ha-form>
|
||||||
<ha-icon-picker
|
|
||||||
.label="${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.generic.icon"
|
|
||||||
)} (${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.config.optional"
|
|
||||||
)})"
|
|
||||||
.value=${this._icon}
|
|
||||||
.placeholder=${this._icon || entityState?.attributes.icon}
|
|
||||||
.fallbackPath=${!this._icon &&
|
|
||||||
!entityState?.attributes.icon &&
|
|
||||||
entityState
|
|
||||||
? domainIcon(computeDomain(entityState.entity_id), entityState)
|
|
||||||
: undefined}
|
|
||||||
.configValue=${"icon"}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></ha-icon-picker>
|
|
||||||
</div>
|
|
||||||
<div class="side-by-side">
|
|
||||||
<ha-entity-attribute-picker
|
|
||||||
.hass=${this.hass}
|
|
||||||
.entityId=${this._entity}
|
|
||||||
.label="${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.generic.attribute"
|
|
||||||
)} (${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.config.optional"
|
|
||||||
)})"
|
|
||||||
.value=${this._attribute}
|
|
||||||
.configValue=${"attribute"}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></ha-entity-attribute-picker>
|
|
||||||
<paper-input
|
|
||||||
.label="${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.generic.unit"
|
|
||||||
)} (${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.config.optional"
|
|
||||||
)})"
|
|
||||||
.value=${this._unit}
|
|
||||||
.configValue=${"unit"}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></paper-input>
|
|
||||||
</div>
|
|
||||||
<div class="side-by-side">
|
|
||||||
<hui-theme-select-editor
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${this._theme}
|
|
||||||
.configValue=${"theme"}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
>
|
|
||||||
</hui-theme-select-editor>
|
|
||||||
<ha-formfield
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.lovelace.editor.card.generic.state_color"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._config!.state_color}
|
|
||||||
.configValue=${"state_color"}
|
|
||||||
@change=${this._valueChanged}
|
|
||||||
>
|
|
||||||
</ha-switch>
|
|
||||||
</ha-formfield>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
if (!this._config || !this.hass) {
|
const config = ev.detail.value;
|
||||||
return;
|
Object.keys(config).forEach((k) => config[k] === "" && delete config[k]);
|
||||||
|
fireEvent(this, "config-changed", { config });
|
||||||
}
|
}
|
||||||
const target = ev.currentTarget! as EditorTarget;
|
|
||||||
|
|
||||||
if (
|
private _computeLabelCallback = (schema: HaFormSchema) => {
|
||||||
this[`_${target.configValue}`] === target.value ||
|
if (schema.name === "entity") {
|
||||||
this[`_${target.configValue}`] === target.config
|
return `${this.hass!.localize(
|
||||||
) {
|
"ui.panel.lovelace.editor.card.generic.entity"
|
||||||
return;
|
)} (${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.card.config.required"
|
||||||
|
)})`;
|
||||||
}
|
}
|
||||||
if (target.configValue) {
|
|
||||||
if (target.value === "") {
|
return this.hass!.localize(
|
||||||
this._config = { ...this._config };
|
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||||
delete this._config[target.configValue!];
|
);
|
||||||
} else {
|
|
||||||
let newValue: string | undefined;
|
|
||||||
if (
|
|
||||||
target.configValue === "icon_height" &&
|
|
||||||
!isNaN(Number(target.value))
|
|
||||||
) {
|
|
||||||
newValue = `${String(target.value)}px`;
|
|
||||||
}
|
|
||||||
this._config = {
|
|
||||||
...this._config,
|
|
||||||
[target.configValue!]:
|
|
||||||
target.checked !== undefined
|
|
||||||
? target.checked
|
|
||||||
: newValue !== undefined
|
|
||||||
? newValue
|
|
||||||
: target.value
|
|
||||||
? target.value
|
|
||||||
: target.config,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
fireEvent(this, "config-changed", { config: this._config });
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return configElementStyle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
@@ -5,61 +5,60 @@ import {
|
|||||||
SUPPORT_PAUSE,
|
SUPPORT_PAUSE,
|
||||||
SUPPORT_PLAY,
|
SUPPORT_PLAY,
|
||||||
} from "../../data/media-player";
|
} from "../../data/media-player";
|
||||||
import { resolveMediaSource } from "../../data/media_source";
|
import { ResolvedMediaSource } from "../../data/media_source";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
export class BrowserMediaPlayer {
|
export class BrowserMediaPlayer {
|
||||||
private player?: HTMLAudioElement;
|
private player: HTMLAudioElement;
|
||||||
|
|
||||||
private stopped = false;
|
// We pretend we're playing while still buffering.
|
||||||
|
public buffering = true;
|
||||||
|
|
||||||
|
private _removed = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public hass: HomeAssistant,
|
public hass: HomeAssistant,
|
||||||
public item: MediaPlayerItem,
|
public item: MediaPlayerItem,
|
||||||
|
public resolved: ResolvedMediaSource,
|
||||||
private onChange: () => void
|
private onChange: () => void
|
||||||
) {}
|
) {
|
||||||
|
const player = new Audio(this.resolved.url);
|
||||||
public async initialize() {
|
|
||||||
const resolvedUrl: any = await resolveMediaSource(
|
|
||||||
this.hass,
|
|
||||||
this.item.media_content_id
|
|
||||||
);
|
|
||||||
|
|
||||||
const player = new Audio(resolvedUrl.url);
|
|
||||||
player.addEventListener("play", this._handleChange);
|
player.addEventListener("play", this._handleChange);
|
||||||
player.addEventListener("playing", this._handleChange);
|
player.addEventListener("playing", () => {
|
||||||
|
this.buffering = false;
|
||||||
|
this._handleChange();
|
||||||
|
});
|
||||||
player.addEventListener("pause", this._handleChange);
|
player.addEventListener("pause", this._handleChange);
|
||||||
player.addEventListener("ended", this._handleChange);
|
player.addEventListener("ended", this._handleChange);
|
||||||
player.addEventListener("canplaythrough", () => {
|
player.addEventListener("canplaythrough", () => {
|
||||||
if (this.stopped) {
|
if (this._removed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.player = player;
|
if (this.buffering) {
|
||||||
player.play();
|
player.play();
|
||||||
|
}
|
||||||
this.onChange();
|
this.onChange();
|
||||||
});
|
});
|
||||||
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleChange = () => {
|
private _handleChange = () => {
|
||||||
if (!this.stopped) {
|
if (!this._removed) {
|
||||||
this.onChange();
|
this.onChange();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public pause() {
|
public pause() {
|
||||||
if (this.player) {
|
this.buffering = false;
|
||||||
this.player.pause();
|
this.player.pause();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public play() {
|
public play() {
|
||||||
if (this.player) {
|
|
||||||
this.player.play();
|
this.player.play();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public stop() {
|
public remove() {
|
||||||
this.stopped = true;
|
this._removed = true;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.onChange = undefined;
|
this.onChange = undefined;
|
||||||
if (this.player) {
|
if (this.player) {
|
||||||
@@ -68,9 +67,7 @@ export class BrowserMediaPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get isPlaying(): boolean {
|
public get isPlaying(): boolean {
|
||||||
return (
|
return this.buffering || (!this.player.paused && !this.player.ended);
|
||||||
this.player !== undefined && !this.player.paused && !this.player.ended
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static idleStateObj(): MediaPlayerEntity {
|
static idleStateObj(): MediaPlayerEntity {
|
||||||
@@ -88,19 +85,19 @@ export class BrowserMediaPlayer {
|
|||||||
toStateObj(): MediaPlayerEntity {
|
toStateObj(): MediaPlayerEntity {
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
|
||||||
const base = BrowserMediaPlayer.idleStateObj();
|
const base = BrowserMediaPlayer.idleStateObj();
|
||||||
if (!this.player) {
|
|
||||||
return base;
|
|
||||||
}
|
|
||||||
base.state = this.isPlaying ? "playing" : "paused";
|
base.state = this.isPlaying ? "playing" : "paused";
|
||||||
base.attributes = {
|
base.attributes = {
|
||||||
media_title: this.item.title,
|
media_title: this.item.title,
|
||||||
media_duration: this.player.duration,
|
|
||||||
media_position: this.player.currentTime,
|
|
||||||
media_position_updated_at: base.last_updated,
|
|
||||||
entity_picture: this.item.thumbnail,
|
entity_picture: this.item.thumbnail,
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
supported_features: SUPPORT_PLAY | SUPPORT_PAUSE,
|
supported_features: SUPPORT_PLAY | SUPPORT_PAUSE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.player.duration) {
|
||||||
|
base.attributes.media_duration = this.player.duration;
|
||||||
|
base.attributes.media_position = this.player.currentTime;
|
||||||
|
base.attributes.media_position_updated_at = base.last_updated;
|
||||||
|
}
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@ import {
|
|||||||
} from "lit";
|
} 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";
|
||||||
|
import { until } from "lit/directives/until";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../common/entity/compute_domain";
|
import { computeDomain } from "../../common/entity/compute_domain";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
@@ -27,6 +28,7 @@ import { computeStateName } from "../../common/entity/compute_state_name";
|
|||||||
import { domainIcon } from "../../common/entity/domain_icon";
|
import { domainIcon } from "../../common/entity/domain_icon";
|
||||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||||
import "../../components/ha-button-menu";
|
import "../../components/ha-button-menu";
|
||||||
|
import "../../components/ha-circular-progress";
|
||||||
import "../../components/ha-icon-button";
|
import "../../components/ha-icon-button";
|
||||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||||
import {
|
import {
|
||||||
@@ -43,6 +45,7 @@ import {
|
|||||||
SUPPORT_PLAY,
|
SUPPORT_PLAY,
|
||||||
SUPPORT_STOP,
|
SUPPORT_STOP,
|
||||||
} from "../../data/media-player";
|
} from "../../data/media-player";
|
||||||
|
import { ResolvedMediaSource } from "../../data/media_source";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../lovelace/components/hui-marquee";
|
import "../lovelace/components/hui-marquee";
|
||||||
import { BrowserMediaPlayer } from "./browser-media-player";
|
import { BrowserMediaPlayer } from "./browser-media-player";
|
||||||
@@ -54,7 +57,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ha-bar-media-player")
|
@customElement("ha-bar-media-player")
|
||||||
class BarMediaPlayer extends LitElement {
|
export class BarMediaPlayer extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public entityId!: string;
|
@property({ attribute: false }) public entityId!: string;
|
||||||
@@ -68,6 +71,8 @@ class BarMediaPlayer extends LitElement {
|
|||||||
|
|
||||||
@state() private _marqueeActive = false;
|
@state() private _marqueeActive = false;
|
||||||
|
|
||||||
|
@state() private _newMediaExpected = false;
|
||||||
|
|
||||||
@state() private _browserPlayer?: BrowserMediaPlayer;
|
@state() private _browserPlayer?: BrowserMediaPlayer;
|
||||||
|
|
||||||
private _progressInterval?: number;
|
private _progressInterval?: number;
|
||||||
@@ -98,32 +103,54 @@ class BarMediaPlayer extends LitElement {
|
|||||||
clearInterval(this._progressInterval);
|
clearInterval(this._progressInterval);
|
||||||
this._progressInterval = undefined;
|
this._progressInterval = undefined;
|
||||||
}
|
}
|
||||||
|
this._tearDownBrowserPlayer();
|
||||||
if (this._browserPlayer) {
|
|
||||||
this._browserPlayer.stop();
|
|
||||||
this._browserPlayer = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async playItem(item: MediaPlayerItem) {
|
public showResolvingNewMediaPicked() {
|
||||||
|
this._tearDownBrowserPlayer();
|
||||||
|
this._newMediaExpected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hideResolvingNewMediaPicked() {
|
||||||
|
this._newMediaExpected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public playItem(item: MediaPlayerItem, resolved: ResolvedMediaSource) {
|
||||||
if (this.entityId !== BROWSER_PLAYER) {
|
if (this.entityId !== BROWSER_PLAYER) {
|
||||||
throw Error("Only browser supported");
|
throw Error("Only browser supported");
|
||||||
}
|
}
|
||||||
if (this._browserPlayer) {
|
this._tearDownBrowserPlayer();
|
||||||
this._browserPlayer.stop();
|
this._browserPlayer = new BrowserMediaPlayer(
|
||||||
}
|
this.hass,
|
||||||
this._browserPlayer = new BrowserMediaPlayer(this.hass, item, () =>
|
item,
|
||||||
this.requestUpdate("_browserPlayer")
|
resolved,
|
||||||
|
() => this.requestUpdate("_browserPlayer")
|
||||||
);
|
);
|
||||||
await this._browserPlayer.initialize();
|
this._newMediaExpected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
if (this._newMediaExpected) {
|
||||||
|
return html`
|
||||||
|
<div class="controls-progress">
|
||||||
|
${until(
|
||||||
|
// Only show spinner after 500ms
|
||||||
|
new Promise((resolve) => setTimeout(resolve, 500)).then(
|
||||||
|
() => html`<ha-circular-progress active></ha-circular-progress>`
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
const isBrowser = this.entityId === BROWSER_PLAYER;
|
const isBrowser = this.entityId === BROWSER_PLAYER;
|
||||||
const stateObj = this._stateObj;
|
const stateObj = this._stateObj;
|
||||||
const controls = !stateObj
|
|
||||||
? undefined
|
if (!stateObj) {
|
||||||
: !this.narrow
|
return this._renderChoosePlayer(stateObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
const controls = !this.narrow
|
||||||
? computeMediaControls(stateObj)
|
? computeMediaControls(stateObj)
|
||||||
: (stateObj.state === "playing" &&
|
: (stateObj.state === "playing" &&
|
||||||
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
||||||
@@ -152,16 +179,14 @@ class BarMediaPlayer extends LitElement {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [{}];
|
: [{}];
|
||||||
const mediaDescription = stateObj ? computeMediaDescription(stateObj) : "";
|
const mediaDescription = computeMediaDescription(stateObj);
|
||||||
const mediaDuration = formatMediaTime(stateObj?.attributes.media_duration);
|
const mediaDuration = formatMediaTime(stateObj.attributes.media_duration);
|
||||||
const mediaTitleClean = cleanupMediaTitle(
|
const mediaTitleClean = cleanupMediaTitle(
|
||||||
stateObj?.attributes.media_title || ""
|
stateObj.attributes.media_title || ""
|
||||||
);
|
);
|
||||||
|
const mediaArt =
|
||||||
const mediaArt = stateObj
|
stateObj.attributes.entity_picture_local ||
|
||||||
? stateObj.attributes.entity_picture_local ||
|
stateObj.attributes.entity_picture;
|
||||||
stateObj.attributes.entity_picture
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@@ -177,7 +202,10 @@ class BarMediaPlayer extends LitElement {
|
|||||||
<hui-marquee
|
<hui-marquee
|
||||||
.text=${mediaTitleClean ||
|
.text=${mediaTitleClean ||
|
||||||
mediaDescription ||
|
mediaDescription ||
|
||||||
this.hass.localize(`ui.card.media_player.nothing_playing`)}
|
cleanupMediaTitle(stateObj.attributes.media_content_id) ||
|
||||||
|
(stateObj.state !== "playing" && stateObj.state !== "on"
|
||||||
|
? this.hass.localize(`ui.card.media_player.nothing_playing`)
|
||||||
|
: "")}
|
||||||
.active=${this._marqueeActive}
|
.active=${this._marqueeActive}
|
||||||
@mouseover=${this._marqueeMouseOver}
|
@mouseover=${this._marqueeMouseOver}
|
||||||
@mouseleave=${this._marqueeMouseLeave}
|
@mouseleave=${this._marqueeMouseLeave}
|
||||||
@@ -188,6 +216,9 @@ class BarMediaPlayer extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-progress">
|
<div class="controls-progress">
|
||||||
|
${this._browserPlayer?.buffering
|
||||||
|
? html` <ha-circular-progress active></ha-circular-progress> `
|
||||||
|
: html`
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
${controls === undefined
|
${controls === undefined
|
||||||
? ""
|
? ""
|
||||||
@@ -205,7 +236,9 @@ class BarMediaPlayer extends LitElement {
|
|||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
${this.narrow
|
${stateObj.attributes.media_duration === Infinity
|
||||||
|
? html``
|
||||||
|
: this.narrow
|
||||||
? html`<mwc-linear-progress></mwc-linear-progress>`
|
? html`<mwc-linear-progress></mwc-linear-progress>`
|
||||||
: html`
|
: html`
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
@@ -214,10 +247,19 @@ class BarMediaPlayer extends LitElement {
|
|||||||
<div>${mediaDuration}</div>
|
<div>${mediaDuration}</div>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
`}
|
||||||
</div>
|
</div>
|
||||||
|
${this._renderChoosePlayer(stateObj)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderChoosePlayer(stateObj: MediaPlayerEntity | undefined) {
|
||||||
|
const isBrowser = this.entityId === BROWSER_PLAYER;
|
||||||
|
return html`
|
||||||
<div class="choose-player ${isBrowser ? "browser" : ""}">
|
<div class="choose-player ${isBrowser ? "browser" : ""}">
|
||||||
<ha-button-menu corner="BOTTOM_START">
|
<ha-button-menu corner="BOTTOM_START">
|
||||||
${this.narrow
|
${
|
||||||
|
this.narrow
|
||||||
? html`
|
? html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
slot="trigger"
|
slot="trigger"
|
||||||
@@ -231,7 +273,11 @@ class BarMediaPlayer extends LitElement {
|
|||||||
slot="trigger"
|
slot="trigger"
|
||||||
.label=${this.narrow
|
.label=${this.narrow
|
||||||
? ""
|
? ""
|
||||||
: `${stateObj ? computeStateName(stateObj) : this.entityId}
|
: `${
|
||||||
|
stateObj
|
||||||
|
? computeStateName(stateObj)
|
||||||
|
: this.entityId
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
@@ -245,7 +291,8 @@ class BarMediaPlayer extends LitElement {
|
|||||||
.path=${mdiChevronDown}
|
.path=${mdiChevronDown}
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`}
|
`
|
||||||
|
}
|
||||||
<mwc-list-item
|
<mwc-list-item
|
||||||
.player=${BROWSER_PLAYER}
|
.player=${BROWSER_PLAYER}
|
||||||
?selected=${isBrowser}
|
?selected=${isBrowser}
|
||||||
@@ -267,18 +314,26 @@ class BarMediaPlayer extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
public willUpdate(changedProps: PropertyValues) {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
if (changedProps.has("entityId")) {
|
||||||
|
this._tearDownBrowserPlayer();
|
||||||
|
}
|
||||||
|
if (!changedProps.has("hass") || this.entityId === BROWSER_PLAYER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Reset new media expected if media player state changes
|
||||||
|
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||||
if (
|
if (
|
||||||
changedProps.has("entityId") &&
|
!oldHass ||
|
||||||
this.entityId !== BROWSER_PLAYER &&
|
oldHass.states[this.entityId] !== this.hass.states[this.entityId]
|
||||||
this._browserPlayer
|
|
||||||
) {
|
) {
|
||||||
this._browserPlayer?.stop();
|
this._newMediaExpected = false;
|
||||||
this._browserPlayer = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,6 +382,13 @@ class BarMediaPlayer extends LitElement {
|
|||||||
return this.hass!.states[this.entityId] as MediaPlayerEntity | undefined;
|
return this.hass!.states[this.entityId] as MediaPlayerEntity | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _tearDownBrowserPlayer() {
|
||||||
|
if (this._browserPlayer) {
|
||||||
|
this._browserPlayer.remove();
|
||||||
|
this._browserPlayer = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _openMoreInfo() {
|
private _openMoreInfo() {
|
||||||
if (this._browserPlayer) {
|
if (this._browserPlayer) {
|
||||||
return;
|
return;
|
||||||
|
@@ -37,6 +37,7 @@ import "../../layouts/ha-app-layout";
|
|||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../types";
|
import type { HomeAssistant, Route } from "../../types";
|
||||||
import "./ha-bar-media-player";
|
import "./ha-bar-media-player";
|
||||||
|
import type { BarMediaPlayer } from "./ha-bar-media-player";
|
||||||
import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog";
|
import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog";
|
||||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||||
import {
|
import {
|
||||||
@@ -79,6 +80,8 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
|
|
||||||
@query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse;
|
@query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse;
|
||||||
|
|
||||||
|
@query("ha-bar-media-player") private _player!: BarMediaPlayer;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ha-app-layout>
|
<ha-app-layout>
|
||||||
@@ -235,15 +238,23 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
ev: HASSDomEvent<MediaPickedEvent>
|
ev: HASSDomEvent<MediaPickedEvent>
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const item = ev.detail.item;
|
const item = ev.detail.item;
|
||||||
|
|
||||||
if (this._entityId !== BROWSER_PLAYER) {
|
if (this._entityId !== BROWSER_PLAYER) {
|
||||||
this.hass!.callService("media_player", "play_media", {
|
this._player.showResolvingNewMediaPicked();
|
||||||
|
try {
|
||||||
|
await this.hass!.callService("media_player", "play_media", {
|
||||||
entity_id: this._entityId,
|
entity_id: this._entityId,
|
||||||
media_content_id: item.media_content_id,
|
media_content_id: item.media_content_id,
|
||||||
media_content_type: item.media_content_type,
|
media_content_type: item.media_content_type,
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this._player.hideResolvingNewMediaPicked();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We won't cancel current media being played if we're going to
|
||||||
|
// open a camera.
|
||||||
if (isCameraMediaSource(item.media_content_id)) {
|
if (isCameraMediaSource(item.media_content_id)) {
|
||||||
fireEvent(this, "hass-more-info", {
|
fireEvent(this, "hass-more-info", {
|
||||||
entityId: getEntityIdFromCameraMediaSource(item.media_content_id),
|
entityId: getEntityIdFromCameraMediaSource(item.media_content_id),
|
||||||
@@ -251,15 +262,15 @@ class PanelMediaBrowser extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._player.showResolvingNewMediaPicked();
|
||||||
|
|
||||||
const resolvedUrl = await resolveMediaSource(
|
const resolvedUrl = await resolveMediaSource(
|
||||||
this.hass,
|
this.hass,
|
||||||
item.media_content_id
|
item.media_content_id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (resolvedUrl.mime_type.startsWith("audio/")) {
|
if (resolvedUrl.mime_type.startsWith("audio/")) {
|
||||||
await this.shadowRoot!.querySelector("ha-bar-media-player")!.playItem(
|
this._player.playItem(item, resolvedUrl);
|
||||||
item
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -155,6 +155,10 @@ const REDIRECTS: Redirects = {
|
|||||||
component: "history",
|
component: "history",
|
||||||
redirect: "/history",
|
redirect: "/history",
|
||||||
},
|
},
|
||||||
|
media_browser: {
|
||||||
|
component: "media_source",
|
||||||
|
redirect: "/media-browser",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ParamType = "url" | "string";
|
export type ParamType = "url" | "string";
|
||||||
@@ -178,7 +182,7 @@ class HaPanelMy extends LitElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
const path = this.route.path.substr(1);
|
const path = this.route.path.substring(1);
|
||||||
|
|
||||||
if (path.startsWith("supervisor")) {
|
if (path.startsWith("supervisor")) {
|
||||||
if (!isComponentLoaded(this.hass, "hassio")) {
|
if (!isComponentLoaded(this.hass, "hassio")) {
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -11,8 +10,10 @@ import {
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../components/ha-card";
|
import "../../components/ha-card";
|
||||||
import "../../components/ha-circular-progress";
|
import "../../components/ha-circular-progress";
|
||||||
|
import "../../components/ha-textfield";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import "../../components/ha-alert";
|
||||||
|
|
||||||
@customElement("ha-change-password-card")
|
@customElement("ha-change-password-card")
|
||||||
class HaChangePasswordCard extends LitElement {
|
class HaChangePasswordCard extends LitElement {
|
||||||
@@ -24,11 +25,11 @@ class HaChangePasswordCard extends LitElement {
|
|||||||
|
|
||||||
@state() private _errorMsg?: string;
|
@state() private _errorMsg?: string;
|
||||||
|
|
||||||
@state() private _currentPassword?: string;
|
@state() private _currentPassword = "";
|
||||||
|
|
||||||
@state() private _password?: string;
|
@state() private _password = "";
|
||||||
|
|
||||||
@state() private _passwordConfirm?: string;
|
@state() private _passwordConfirm = "";
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
@@ -40,46 +41,48 @@ class HaChangePasswordCard extends LitElement {
|
|||||||
>
|
>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
${this._errorMsg
|
${this._errorMsg
|
||||||
? html` <div class="error">${this._errorMsg}</div> `
|
? html`<ha-alert alert-type="error">${this._errorMsg}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${this._statusMsg
|
${this._statusMsg
|
||||||
? html` <div class="status">${this._statusMsg}</div> `
|
? html`<ha-alert alert-type="success"
|
||||||
|
>${this._statusMsg}</ha-alert
|
||||||
|
>`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<paper-input
|
<ha-textfield
|
||||||
id="currentPassword"
|
id="currentPassword"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.profile.change_password.current_password"
|
"ui.panel.profile.change_password.current_password"
|
||||||
)}
|
)}
|
||||||
type="password"
|
type="password"
|
||||||
.value=${this._currentPassword}
|
.value=${this._currentPassword}
|
||||||
@value-changed=${this._currentPasswordChanged}
|
@input=${this._currentPasswordChanged}
|
||||||
required
|
required
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
|
|
||||||
${this._currentPassword
|
${this._currentPassword
|
||||||
? html` <paper-input
|
? html`<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.profile.change_password.new_password"
|
"ui.panel.profile.change_password.new_password"
|
||||||
)}
|
)}
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
.value=${this._password}
|
.value=${this._password}
|
||||||
@value-changed=${this._newPasswordChanged}
|
@change=${this._newPasswordChanged}
|
||||||
required
|
required
|
||||||
auto-validate
|
auto-validate
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.profile.change_password.confirm_new_password"
|
"ui.panel.profile.change_password.confirm_new_password"
|
||||||
)}
|
)}
|
||||||
name="passwordConfirm"
|
name="passwordConfirm"
|
||||||
type="password"
|
type="password"
|
||||||
.value=${this._passwordConfirm}
|
.value=${this._passwordConfirm}
|
||||||
@value-changed=${this._newPasswordConfirmChanged}
|
@input=${this._newPasswordConfirmChanged}
|
||||||
required
|
required
|
||||||
auto-validate
|
auto-validate
|
||||||
></paper-input>`
|
></ha-textfield>`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -101,16 +104,16 @@ class HaChangePasswordCard extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _currentPasswordChanged(ev: CustomEvent) {
|
private _currentPasswordChanged(ev) {
|
||||||
this._currentPassword = ev.detail.value;
|
this._currentPassword = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _newPasswordChanged(ev: CustomEvent) {
|
private _newPasswordChanged(ev) {
|
||||||
this._password = ev.detail.value;
|
this._password = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _newPasswordConfirmChanged(ev: CustomEvent) {
|
private _newPasswordConfirmChanged(ev) {
|
||||||
this._passwordConfirm = ev.detail.value;
|
this._passwordConfirm = ev.target.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
protected firstUpdated(changedProps: PropertyValues) {
|
||||||
@@ -162,23 +165,21 @@ class HaChangePasswordCard extends LitElement {
|
|||||||
this._statusMsg = this.hass.localize(
|
this._statusMsg = this.hass.localize(
|
||||||
"ui.panel.profile.change_password.success"
|
"ui.panel.profile.change_password.success"
|
||||||
);
|
);
|
||||||
this._currentPassword = undefined;
|
this._currentPassword = "";
|
||||||
this._password = undefined;
|
this._password = "";
|
||||||
this._passwordConfirm = undefined;
|
this._passwordConfirm = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
.error {
|
ha-textfield {
|
||||||
color: var(--error-color);
|
margin-top: 8px;
|
||||||
}
|
display: block;
|
||||||
.status {
|
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
}
|
||||||
#currentPassword {
|
#currentPassword {
|
||||||
margin-top: -8px;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
|
import "@material/mwc-select/mwc-select";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -14,14 +15,13 @@ import "../../components/ha-formfield";
|
|||||||
import "../../components/ha-radio";
|
import "../../components/ha-radio";
|
||||||
import type { HaRadio } from "../../components/ha-radio";
|
import type { HaRadio } from "../../components/ha-radio";
|
||||||
import "../../components/ha-settings-row";
|
import "../../components/ha-settings-row";
|
||||||
|
import "../../components/ha-textfield";
|
||||||
import {
|
import {
|
||||||
DEFAULT_PRIMARY_COLOR,
|
|
||||||
DEFAULT_ACCENT_COLOR,
|
DEFAULT_ACCENT_COLOR,
|
||||||
|
DEFAULT_PRIMARY_COLOR,
|
||||||
} from "../../resources/ha-style";
|
} from "../../resources/ha-style";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { documentationUrl } from "../../util/documentation-url";
|
import { documentationUrl } from "../../util/documentation-url";
|
||||||
import "@material/mwc-select/mwc-select";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
|
|
||||||
@customElement("ha-pick-theme-row")
|
@customElement("ha-pick-theme-row")
|
||||||
export class HaPickThemeRow extends LitElement {
|
export class HaPickThemeRow extends LitElement {
|
||||||
@@ -116,7 +116,7 @@ export class HaPickThemeRow extends LitElement {
|
|||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
${curTheme === "default"
|
${curTheme === "default"
|
||||||
? html`<div class="color-pickers">
|
? html`<div class="color-pickers">
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.value=${themeSettings?.primaryColor ||
|
.value=${themeSettings?.primaryColor ||
|
||||||
DEFAULT_PRIMARY_COLOR}
|
DEFAULT_PRIMARY_COLOR}
|
||||||
type="color"
|
type="color"
|
||||||
@@ -125,8 +125,8 @@ export class HaPickThemeRow extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.name=${"primaryColor"}
|
.name=${"primaryColor"}
|
||||||
@change=${this._handleColorChange}
|
@change=${this._handleColorChange}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
<paper-input
|
<ha-textfield
|
||||||
.value=${themeSettings?.accentColor || DEFAULT_ACCENT_COLOR}
|
.value=${themeSettings?.accentColor || DEFAULT_ACCENT_COLOR}
|
||||||
type="color"
|
type="color"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
@@ -134,7 +134,7 @@ export class HaPickThemeRow extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.name=${"accentColor"}
|
.name=${"accentColor"}
|
||||||
@change=${this._handleColorChange}
|
@change=${this._handleColorChange}
|
||||||
></paper-input>
|
></ha-textfield>
|
||||||
${themeSettings?.primaryColor || themeSettings?.accentColor
|
${themeSettings?.primaryColor || themeSettings?.accentColor
|
||||||
? html` <mwc-button @click=${this._resetColors}>
|
? html` <mwc-button @click=${this._resetColors}>
|
||||||
${this.hass.localize("ui.panel.profile.themes.reset")}
|
${this.hass.localize("ui.panel.profile.themes.reset")}
|
||||||
@@ -228,7 +228,8 @@ export class HaPickThemeRow extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
paper-input {
|
ha-textfield {
|
||||||
|
--text-field-padding: 8px;
|
||||||
min-width: 75px;
|
min-width: 75px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
|
@@ -972,6 +972,13 @@
|
|||||||
"triggered": "Triggered {name}",
|
"triggered": "Triggered {name}",
|
||||||
"dismiss": "Dismiss"
|
"dismiss": "Dismiss"
|
||||||
},
|
},
|
||||||
|
"newsletter": {
|
||||||
|
"newsletter": "Newsletter",
|
||||||
|
"email": "Email",
|
||||||
|
"validation": "A valid email address is required",
|
||||||
|
"subscribe": "Subscribe",
|
||||||
|
"thanks": "Thanks for subscribing!"
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"external_app_configuration": "App Configuration",
|
"external_app_configuration": "App Configuration",
|
||||||
"sidebar_toggle": "Sidebar Toggle",
|
"sidebar_toggle": "Sidebar Toggle",
|
||||||
@@ -2287,6 +2294,8 @@
|
|||||||
"open_configuration_url_service": "Visit service",
|
"open_configuration_url_service": "Visit service",
|
||||||
"download_diagnostics": "Download diagnostics",
|
"download_diagnostics": "Download diagnostics",
|
||||||
"download_diagnostics_integration": "Download {integration} diagnostics",
|
"download_diagnostics_integration": "Download {integration} diagnostics",
|
||||||
|
"delete_device": "Delete device",
|
||||||
|
"delete_device_integration": "Remove {integration} from device",
|
||||||
"type": {
|
"type": {
|
||||||
"device_heading": "Device",
|
"device_heading": "Device",
|
||||||
"device": "device",
|
"device": "device",
|
||||||
@@ -4093,7 +4102,6 @@
|
|||||||
"no_listeners": "This template does not listen for any events and will not update automatically.",
|
"no_listeners": "This template does not listen for any events and will not update automatically.",
|
||||||
"listeners": "This template listens for the following state changed events:",
|
"listeners": "This template listens for the following state changed events:",
|
||||||
"entity": "Entity",
|
"entity": "Entity",
|
||||||
"context_data": "Context data for template",
|
|
||||||
"domain": "Domain"
|
"domain": "Domain"
|
||||||
},
|
},
|
||||||
"statistics": {
|
"statistics": {
|
||||||
|
Reference in New Issue
Block a user