mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-12 18:59:26 +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",
|
||||
state: "loaded",
|
||||
supports_options: false,
|
||||
supports_remove_device: false,
|
||||
supports_unload: true,
|
||||
disabled_by: null,
|
||||
pref_disable_new_entities: false,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
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 { formatDate } from "../../../src/common/datetime/format_date";
|
||||
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
|
||||
@@ -92,6 +92,8 @@ export class SupervisorBackupContent extends LitElement {
|
||||
|
||||
@property() public confirmBackupPassword = "";
|
||||
|
||||
@query("paper-input, ha-radio, ha-checkbox", true) private _focusTarget;
|
||||
|
||||
public willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
if (!this.hasUpdated) {
|
||||
@@ -109,6 +111,10 @@ export class SupervisorBackupContent extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
public override focus() {
|
||||
this._focusTarget?.focus();
|
||||
}
|
||||
|
||||
private _localize = (string: string) =>
|
||||
this.supervisor?.localize(`backup.${string}`) ||
|
||||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
|
||||
@@ -169,24 +175,23 @@ export class SupervisorBackupContent extends LitElement {
|
||||
: ""}
|
||||
${this.backupType === "partial"
|
||||
? html`<div class="partial-picker">
|
||||
${this.backup && this.backup.homeassistant
|
||||
? html`
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
label="Home Assistant"
|
||||
.iconPath=${mdiHomeAssistant}
|
||||
.version=${this.backup.homeassistant}
|
||||
>
|
||||
</supervisor-formfield-label>`}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.homeAssistant}
|
||||
@click=${this.toggleHomeAssistant}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
`
|
||||
: ""}
|
||||
<ha-formfield
|
||||
.label=${html`<supervisor-formfield-label
|
||||
label="Home Assistant"
|
||||
.iconPath=${mdiHomeAssistant}
|
||||
.version=${this.backup
|
||||
? this.backup.homeassistant
|
||||
: this.hass.config.version}
|
||||
>
|
||||
</supervisor-formfield-label>`}
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.homeAssistant}
|
||||
@click=${this.toggleHomeAssistant}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
|
||||
${foldersSection?.templates.length
|
||||
? html`
|
||||
<ha-formfield
|
||||
|
@@ -64,6 +64,7 @@ export class DialogHassioBackupUpload
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
dialogInitialFocus
|
||||
></ha-icon-button>
|
||||
</ha-header-bar>
|
||||
</div>
|
||||
|
@@ -92,6 +92,7 @@ class HassioBackupDialog
|
||||
.backup=${this._backup}
|
||||
.onboarding=${this._dialogParams.onboarding || false}
|
||||
.localize=${this._dialogParams.localize}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</supervisor-backup-content>`}
|
||||
${this._error
|
||||
|
@@ -61,6 +61,7 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</supervisor-backup-content>`}
|
||||
${this._error
|
||||
|
@@ -94,6 +94,7 @@ class HassioDatadiskDialog extends LitElement {
|
||||
"dialog.datadisk_move.select_device"
|
||||
)}
|
||||
@selected=${this._select_device}
|
||||
dialogInitialFocus
|
||||
>
|
||||
${this.devices.map(
|
||||
(device) =>
|
||||
@@ -111,7 +112,11 @@ class HassioDatadiskDialog extends LitElement {
|
||||
"dialog.datadisk_move.no_devices"
|
||||
)}
|
||||
|
||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
dialogInitialFocus
|
||||
>
|
||||
${this.dialogParams.supervisor.localize(
|
||||
"dialog.datadisk_move.cancel"
|
||||
)}
|
||||
|
@@ -80,7 +80,7 @@ class HassioHardwareDialog extends LitElement {
|
||||
></ha-icon-button>
|
||||
<search-input
|
||||
.hass=${this.hass}
|
||||
autofocus
|
||||
dialogInitialFocus
|
||||
no-label-float
|
||||
.filter=${this._filter}
|
||||
@value-changed=${this._handleSearchChange}
|
||||
|
@@ -37,7 +37,10 @@ class HassioMarkdownDialog extends LitElement {
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(this.hass, this.title)}
|
||||
>
|
||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
||||
<ha-markdown
|
||||
.content=${this.content || ""}
|
||||
dialogInitialFocus
|
||||
></ha-markdown>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
@@ -119,6 +119,7 @@ export class DialogHassioNetwork
|
||||
html`<mwc-tab
|
||||
.id=${device.interface}
|
||||
.label=${device.interface}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</mwc-tab>`
|
||||
)}
|
||||
@@ -315,6 +316,7 @@ export class DialogHassioNetwork
|
||||
value="auto"
|
||||
name="${version}method"
|
||||
.checked=${this._interface![version]?.method === "auto"}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
|
@@ -80,6 +80,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
.schema=${SCHEMA}
|
||||
@value-changed=${this._valueChanged}
|
||||
.computeLabel=${this._computeLabel}
|
||||
dialogInitialFocus
|
||||
></ha-form>
|
||||
<div class="action">
|
||||
<mwc-button
|
||||
@@ -124,7 +125,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
</ha-alert>
|
||||
`}
|
||||
<div class="action">
|
||||
<mwc-button @click=${this._addRegistry}>
|
||||
<mwc-button @click=${this._addRegistry} dialogInitialFocus>
|
||||
${this.supervisor.localize(
|
||||
"dialog.registries.add_new_registry"
|
||||
)}
|
||||
|
@@ -139,6 +139,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
"dialog.repositories.add"
|
||||
)}
|
||||
@keydown=${this._handleKeyAdd}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._processing
|
||||
|
@@ -3,7 +3,6 @@ import "@material/mwc-list/mwc-list";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiCalendar } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -19,6 +18,7 @@ import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./date-range-picker";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-textfield";
|
||||
|
||||
export interface DateRangePickerRanges {
|
||||
[key: string]: [Date, Date];
|
||||
@@ -61,7 +61,7 @@ export class HaDateRangePicker extends LitElement {
|
||||
>
|
||||
<div slot="input" class="date-range-inputs">
|
||||
<ha-svg-icon .path=${mdiCalendar}></ha-svg-icon>
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${formatDateTime(this.startDate, this.hass.locale)}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.date-range-picker.start_date"
|
||||
@@ -69,16 +69,16 @@ export class HaDateRangePicker extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleInputClick}
|
||||
readonly
|
||||
></paper-input>
|
||||
<paper-input
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${formatDateTime(this.endDate, this.hass.locale)}
|
||||
label=${this.hass.localize(
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.date-range-picker.end_date"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleInputClick}
|
||||
readonly
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
</div>
|
||||
${this.ranges
|
||||
? html`<div
|
||||
@@ -158,13 +158,13 @@ export class HaDateRangePicker extends LitElement {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
paper-input {
|
||||
ha-textfield {
|
||||
display: inline-block;
|
||||
max-width: 250px;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
paper-input:last-child {
|
||||
ha-textfield:last-child {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ export class HaDateRangePicker extends LitElement {
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
paper-input {
|
||||
ha-textfield {
|
||||
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 { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../ha-alert";
|
||||
import "./ha-form-boolean";
|
||||
import "./ha-form-constant";
|
||||
import "./ha-form-grid";
|
||||
import "./ha-form-float";
|
||||
import "./ha-form-integer";
|
||||
import "./ha-form-multi_select";
|
||||
@@ -14,17 +22,18 @@ import "./ha-form-string";
|
||||
import { HaFormElement, HaFormDataContainer, HaFormSchema } 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;
|
||||
|
||||
@customElement("ha-form")
|
||||
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>;
|
||||
|
||||
@@ -64,7 +73,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="root">
|
||||
${this.error && this.error.base
|
||||
@@ -101,6 +110,9 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
data: getValue(this.data, item),
|
||||
label: this._computeLabel(item, this.data),
|
||||
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();
|
||||
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", {
|
||||
value: { ...this.data, [schema.name]: ev.detail.value },
|
||||
value: { ...this.data, ...newValue },
|
||||
});
|
||||
});
|
||||
return root;
|
||||
|
@@ -11,7 +11,8 @@ export type HaFormSchema =
|
||||
| HaFormSelectSchema
|
||||
| HaFormMultiSelectSchema
|
||||
| HaFormTimeSchema
|
||||
| HaFormSelector;
|
||||
| HaFormSelector
|
||||
| HaFormGridSchema;
|
||||
|
||||
export interface HaFormBaseSchema {
|
||||
name: string;
|
||||
@@ -25,6 +26,12 @@ export interface HaFormBaseSchema {
|
||||
};
|
||||
}
|
||||
|
||||
export interface HaFormGridSchema extends HaFormBaseSchema {
|
||||
type: "grid";
|
||||
name: "";
|
||||
schema: HaFormSchema[];
|
||||
}
|
||||
|
||||
export interface HaFormSelector extends HaFormBaseSchema {
|
||||
type?: never;
|
||||
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 {
|
||||
return css`
|
||||
:host {
|
||||
height: 56px;
|
||||
display: flex;
|
||||
}
|
||||
ha-formfield {
|
||||
width: 100%;
|
||||
margin: 16px 0;
|
||||
--mdc-typography-body2-font-size: 1em;
|
||||
}
|
||||
`;
|
||||
|
@@ -22,6 +22,8 @@ export class HaIconSelector extends LitElement {
|
||||
<ha-icon-picker
|
||||
.label=${this.label}
|
||||
.value=${this.value}
|
||||
.fallbackPath=${this.selector.icon.fallbackPath}
|
||||
.placeholder=${this.selector.icon.placeholder}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-icon-picker>
|
||||
`;
|
||||
|
@@ -69,10 +69,13 @@ export class HaTextSelector extends LitElement {
|
||||
}
|
||||
|
||||
private _handleChange(ev) {
|
||||
const value = ev.target.value;
|
||||
let value = ev.target.value;
|
||||
if (this.value === value) {
|
||||
return;
|
||||
}
|
||||
if (value === "" && !this.required) {
|
||||
value = undefined;
|
||||
}
|
||||
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 { customElement, property } from "lit/decorators";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { Selector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import type { Selector } from "../../data/selector";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "./ha-selector-action";
|
||||
import "./ha-selector-addon";
|
||||
import "./ha-selector-area";
|
||||
@@ -19,6 +19,7 @@ import "./ha-selector-text";
|
||||
import "./ha-selector-time";
|
||||
import "./ha-selector-icon";
|
||||
import "./ha-selector-media";
|
||||
import "./ha-selector-theme";
|
||||
|
||||
@customElement("ha-selector")
|
||||
export class HaSelector extends LitElement {
|
||||
|
@@ -43,7 +43,7 @@ class HaWebRtcPlayer extends LitElement {
|
||||
|
||||
private _remoteStream?: MediaStream;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
protected override render(): TemplateResult {
|
||||
if (this._error) {
|
||||
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();
|
||||
this._cleanUp();
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues<this>) {
|
||||
protected override updated(changedProperties: PropertyValues<this>) {
|
||||
if (!changedProperties.has("entityid")) {
|
||||
return;
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ export interface ConfigEntry {
|
||||
| "not_loaded"
|
||||
| "failed_unload";
|
||||
supports_options: boolean;
|
||||
supports_remove_device: boolean;
|
||||
supports_unload: boolean;
|
||||
pref_disable_new_entities: boolean;
|
||||
pref_disable_polling: boolean;
|
||||
|
@@ -1,4 +1,5 @@
|
||||
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 { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
@@ -77,12 +78,26 @@ export const updateDeviceRegistryEntry = (
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const fetchDeviceRegistry = (conn) =>
|
||||
conn.sendMessagePromise({
|
||||
export const removeConfigEntryFromDevice = (
|
||||
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",
|
||||
});
|
||||
|
||||
const subscribeDeviceRegistryUpdates = (conn, store) =>
|
||||
const subscribeDeviceRegistryUpdates = (
|
||||
conn: Connection,
|
||||
store: Store<DeviceRegistryEntry[]>
|
||||
) =>
|
||||
conn.subscribeEvents(
|
||||
debounce(
|
||||
() =>
|
||||
|
@@ -33,7 +33,8 @@ import type { HomeAssistant } from "../types";
|
||||
import { UNAVAILABLE_STATES } from "./entity";
|
||||
|
||||
interface MediaPlayerEntityAttributes extends HassEntityAttributeBase {
|
||||
media_content_type?: any;
|
||||
media_content_id?: string;
|
||||
media_content_type?: string;
|
||||
media_artist?: string;
|
||||
media_playlist?: string;
|
||||
media_series_title?: string;
|
||||
@@ -339,7 +340,7 @@ export const computeMediaControls = (
|
||||
};
|
||||
|
||||
export const formatMediaTime = (seconds: number | undefined): string => {
|
||||
if (seconds === undefined) {
|
||||
if (seconds === undefined || seconds === Infinity) {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@@ -14,7 +14,8 @@ export type Selector =
|
||||
| ObjectSelector
|
||||
| SelectSelector
|
||||
| IconSelector
|
||||
| MediaSelector;
|
||||
| MediaSelector
|
||||
| ThemeSelector;
|
||||
|
||||
export interface EntitySelector {
|
||||
entity: {
|
||||
@@ -147,8 +148,15 @@ export interface SelectSelector {
|
||||
}
|
||||
|
||||
export interface IconSelector {
|
||||
icon: {
|
||||
placeholder?: string;
|
||||
fallbackPath?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ThemeSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
icon: {};
|
||||
theme: {};
|
||||
}
|
||||
|
||||
export interface MediaSelector {
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-switch";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import "../../components/ha-textfield";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { DialogBoxParams } from "./show-dialog-box";
|
||||
@@ -71,18 +70,18 @@ class DialogBox extends LitElement {
|
||||
: ""}
|
||||
${this._params.prompt
|
||||
? html`
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
dialogInitialFocus
|
||||
.value=${this._value}
|
||||
@keyup=${this._handleKeyUp}
|
||||
@value-changed=${this._valueChanged}
|
||||
@change=${this._valueChanged}
|
||||
.label=${this._params.inputLabel
|
||||
? this._params.inputLabel
|
||||
: ""}
|
||||
.type=${this._params.inputType
|
||||
? this._params.inputType
|
||||
: "text"}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -107,8 +106,8 @@ class DialogBox extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._value = ev.detail.value;
|
||||
private _valueChanged(ev) {
|
||||
this._value = ev.target.value;
|
||||
}
|
||||
|
||||
private _dismiss(): void {
|
||||
|
@@ -10,7 +10,6 @@ import {
|
||||
mdiVolumeOff,
|
||||
mdiVolumePlus,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
|
@@ -30,7 +30,7 @@ import { HomeAssistant } from "../types";
|
||||
import "./action-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")
|
||||
class OnboardingIntegrations extends LitElement {
|
||||
|
@@ -73,7 +73,7 @@ export const handleChangeEvent = (element: ActionElement, ev: CustomEvent) => {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
const newVal = ev.detail.value;
|
||||
const newVal = ev.detail?.value || (ev.target as any).value;
|
||||
|
||||
if ((element.action[name] || "") === newVal) {
|
||||
return;
|
||||
@@ -376,7 +376,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
margin: 4px 0;
|
||||
}
|
||||
mwc-select {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -143,9 +143,12 @@ export class HaDeviceAction extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-device-picker {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-device-action-picker {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../../components/ha-service-picker";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import "../../../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor";
|
||||
import type { EventAction } from "../../../../../data/script";
|
||||
@@ -40,14 +40,13 @@ export class HaEventAction extends LitElement implements ActionElement {
|
||||
const { event, event_data } = this.action;
|
||||
|
||||
return html`
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.event.event"
|
||||
)}
|
||||
name="event"
|
||||
.value=${event}
|
||||
@value-changed=${this._eventChanged}
|
||||
></paper-input>
|
||||
@change=${this._eventChanged}
|
||||
></ha-textfield>
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
@@ -72,9 +71,17 @@ export class HaEventAction extends LitElement implements ActionElement {
|
||||
private _eventChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
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 {
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
@@ -10,10 +9,11 @@ import {
|
||||
WhileRepeat,
|
||||
} from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { Condition } from "../../../../lovelace/common/validate-condition";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
||||
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"];
|
||||
|
||||
@@ -53,14 +53,16 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
)}
|
||||
</mwc-select>
|
||||
${type === "count"
|
||||
? html`<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.repeat.type.count.label"
|
||||
)}
|
||||
name="count"
|
||||
.value=${(action as CountRepeat).count || "0"}
|
||||
@value-changed=${this._countChanged}
|
||||
></paper-input>`
|
||||
? html`
|
||||
<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.repeat.type.count.label"
|
||||
)}
|
||||
name="count"
|
||||
.value=${(action as CountRepeat).count || "0"}
|
||||
@change=${this._countChanged}
|
||||
></ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
${type === "while"
|
||||
? html` <h3>
|
||||
@@ -142,7 +144,7 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
}
|
||||
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html, LitElement } from "lit";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-formfield";
|
||||
@@ -26,14 +25,14 @@ export class HaWaitForTriggerAction
|
||||
const { wait_for_trigger, continue_on_timeout, timeout } = this.action;
|
||||
|
||||
return html`
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.wait_for_trigger.timeout"
|
||||
)}
|
||||
.name=${"timeout"}
|
||||
.value=${timeout}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
.value=${timeout || ""}
|
||||
@change=${this._valueChanged}
|
||||
></ha-textfield>
|
||||
<br />
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
@@ -63,6 +62,15 @@ export class HaWaitForTriggerAction
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-textfield {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,66 +1,60 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { WaitAction } from "../../../../../data/script";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
import type { WaitAction } from "../../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
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")
|
||||
export class HaWaitAction extends LitElement implements ActionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public action!: WaitAction;
|
||||
@property({ attribute: false }) public action!: WaitAction;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { wait_template: "" };
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { wait_template, timeout, continue_on_timeout } = this.action;
|
||||
|
||||
return html`
|
||||
<paper-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.wait_template.wait_template"
|
||||
)}
|
||||
name="wait_template"
|
||||
.value=${wait_template}
|
||||
@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>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this.action}
|
||||
.schema=${SCHEMA}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _continueChanged(ev) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.action, continue_on_timeout: ev.target.checked },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
private _computeLabelCallback = (schema: HaFormSchema): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.type.wait_template.${
|
||||
schema.name === "continue_on_timeout" ? "continue_timeout" : schema.name
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -147,7 +147,7 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
haStyle,
|
||||
css`
|
||||
mwc-select {
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -9,6 +9,7 @@ import type { StateCondition } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { forDictStruct } from "../../structs";
|
||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
|
||||
const stateConditionStruct = object({
|
||||
condition: literal("state"),
|
||||
|
@@ -7,6 +7,7 @@ import type { HomeAssistant } from "../../../../../types";
|
||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
|
||||
@customElement("ha-automation-condition-sun")
|
||||
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 { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import type { HaFormSchema } from "../../../../../components/ha-form/types";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
|
||||
const DAYS = {
|
||||
mon: 1,
|
||||
|
@@ -9,7 +9,6 @@ import {
|
||||
} from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@@ -201,7 +200,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
${this._config
|
||||
? html`
|
||||
${this.narrow
|
||||
? html` <span slot="header">${this._config?.alias}</span> `
|
||||
? html`<span slot="header">${this._config?.alias}</span>`
|
||||
: ""}
|
||||
<div
|
||||
class="content ${classMap({
|
||||
@@ -210,27 +209,31 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
@subscribe-automation-config=${this._subscribeAutomationConfig}
|
||||
>
|
||||
${this._errors
|
||||
? html` <div class="errors">${this._errors}</div> `
|
||||
? html`<div class="errors">${this._errors}</div>`
|
||||
: ""}
|
||||
${this._mode === "gui"
|
||||
? html`
|
||||
${"use_blueprint" in this._config
|
||||
? html`<blueprint-automation-editor
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.stateObj=${stateObj}
|
||||
.config=${this._config}
|
||||
@value-changed=${this._valueChanged}
|
||||
></blueprint-automation-editor>`
|
||||
: html`<manual-automation-editor
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.stateObj=${stateObj}
|
||||
.config=${this._config}
|
||||
@value-changed=${this._valueChanged}
|
||||
></manual-automation-editor>`}
|
||||
? html`
|
||||
<blueprint-automation-editor
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.stateObj=${stateObj}
|
||||
.config=${this._config}
|
||||
@value-changed=${this._valueChanged}
|
||||
></blueprint-automation-editor>
|
||||
`
|
||||
: html`
|
||||
<manual-automation-editor
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.stateObj=${stateObj}
|
||||
.config=${this._config}
|
||||
@value-changed=${this._valueChanged}
|
||||
></manual-automation-editor>
|
||||
`}
|
||||
`
|
||||
: this._mode === "yaml"
|
||||
? html`
|
||||
|
@@ -7,7 +7,6 @@ import {
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-textarea";
|
||||
import "../../../components/ha-textfield";
|
||||
import {
|
||||
Condition,
|
||||
ManualAutomationConfig,
|
||||
@@ -14,7 +15,7 @@ import {
|
||||
} from "../../../data/automation";
|
||||
import { Action, MODES, MODES_MAX } from "../../../data/script";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import "../ha-config-section";
|
||||
import "./action/ha-automation-action";
|
||||
@@ -36,7 +37,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
protected render() {
|
||||
return html`<ha-config-section vertical .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html` <span slot="header">${this.config.alias}</span> `
|
||||
? html`<span slot="header">${this.config.alias}</span>`
|
||||
: ""}
|
||||
<span slot="introduction">
|
||||
${this.hass.localize(
|
||||
@@ -45,16 +46,16 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
</span>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.alias"
|
||||
)}
|
||||
name="alias"
|
||||
.value=${this.config.alias}
|
||||
@value-changed=${this._valueChanged}
|
||||
.value=${this.config.alias || ""}
|
||||
@change=${this._valueChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<paper-textarea
|
||||
</ha-textfield>
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
@@ -62,9 +63,9 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
"ui.panel.config.automation.editor.description.placeholder"
|
||||
)}
|
||||
name="description"
|
||||
.value=${this.config.description}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-textarea>
|
||||
.value=${this.config.description || ""}
|
||||
@change=${this._valueChanged}
|
||||
></ha-textarea>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.modes.description",
|
||||
@@ -98,16 +99,18 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
)}
|
||||
</mwc-select>
|
||||
${this.config.mode && MODES_MAX.includes(this.config.mode)
|
||||
? html`<paper-input
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.max.${this.config.mode}`
|
||||
)}
|
||||
type="number"
|
||||
name="max"
|
||||
.value=${this.config.max || "10"}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>`
|
||||
? html`
|
||||
<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.max.${this.config.mode}`
|
||||
)}
|
||||
type="number"
|
||||
name="max"
|
||||
.value=${this.config.max || "10"}
|
||||
@change=${this._valueChanged}
|
||||
>
|
||||
</ha-textfield>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
${this.stateObj
|
||||
@@ -243,7 +246,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
let newVal = ev.detail.value;
|
||||
let newVal = target.value;
|
||||
if (target.type === "number") {
|
||||
newVal = Number(newVal);
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-textfield";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import type { Trigger } from "../../../../data/automation";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
@@ -200,14 +201,14 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
${showId
|
||||
? html`
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.id"
|
||||
)}
|
||||
.value=${this.trigger.id}
|
||||
@value-changed=${this._idChanged}
|
||||
.value=${this.trigger.id || ""}
|
||||
@change=${this._idChanged}
|
||||
>
|
||||
</paper-input>
|
||||
</ha-textfield>
|
||||
`
|
||||
: ""}
|
||||
<div @ui-mode-not-available=${this._handleUiModeNotAvailable}>
|
||||
@@ -287,7 +288,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
}
|
||||
|
||||
private _idChanged(ev: CustomEvent) {
|
||||
const newId = ev.detail.value;
|
||||
const newId = (ev.target as any).value;
|
||||
if (newId === (this.trigger.id ?? "")) {
|
||||
return;
|
||||
}
|
||||
@@ -333,7 +334,11 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
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 { html, LitElement } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import "../../../../../components/ha-yaml-editor";
|
||||
import "../../../../../components/user/ha-users-picker";
|
||||
import { EventTrigger } from "../../../../../data/automation";
|
||||
@@ -24,14 +24,14 @@ export class HaEventTrigger extends LitElement implements TriggerElement {
|
||||
protected render() {
|
||||
const { event_type, event_data, context } = this.trigger;
|
||||
return html`
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.event.event_type"
|
||||
)}
|
||||
name="event_type"
|
||||
.value=${event_type}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
@change=${this._valueChanged}
|
||||
></ha-textfield>
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
@@ -97,6 +97,14 @@ export class HaEventTrigger extends LitElement implements TriggerElement {
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "../../../components/ha-newsletter";
|
||||
import { mdiCloudLock, mdiDotsVertical, mdiMagnify } from "@mdi/js";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { ActionDetail } from "@material/mwc-list";
|
||||
@@ -134,6 +135,7 @@ class HaConfigDashboard extends LitElement {
|
||||
.pages=${configSections.dashboard}
|
||||
></ha-config-navigation>
|
||||
</ha-card>`}
|
||||
<ha-newsletter .hass=${this.hass}></ha-newsletter>
|
||||
</ha-config-section>
|
||||
</ha-app-layout>
|
||||
`;
|
||||
|
@@ -27,6 +27,7 @@ import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
updateDeviceRegistryEntry,
|
||||
removeConfigEntryFromDevice,
|
||||
} from "../../../data/device_registry";
|
||||
import {
|
||||
fetchDiagnosticHandler,
|
||||
@@ -95,6 +96,8 @@ export class HaConfigDevicePage extends LitElement {
|
||||
| number
|
||||
| (TemplateResult | string)[];
|
||||
|
||||
@state() private _deleteButtons?: (TemplateResult | string)[];
|
||||
|
||||
private _device = memoizeOne(
|
||||
(
|
||||
deviceId: string,
|
||||
@@ -186,10 +189,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
changedProps.has("entries")
|
||||
) {
|
||||
this._diagnosticDownloadLinks = undefined;
|
||||
this._deleteButtons = undefined;
|
||||
}
|
||||
|
||||
if (
|
||||
this._diagnosticDownloadLinks ||
|
||||
(this._diagnosticDownloadLinks && this._deleteButtons) ||
|
||||
!this.devices ||
|
||||
!this.deviceId ||
|
||||
!this.entries
|
||||
@@ -198,7 +202,9 @@ export class HaConfigDevicePage extends LitElement {
|
||||
}
|
||||
|
||||
this._diagnosticDownloadLinks = Math.random();
|
||||
this._deleteButtons = []; // To prevent re-rendering if no delete buttons
|
||||
this._renderDiagnosticButtons(this._diagnosticDownloadLinks);
|
||||
this._renderDeleteButtons();
|
||||
}
|
||||
|
||||
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) {
|
||||
super.firstUpdated(changedProps);
|
||||
loadDeviceRegistryDetailDialog();
|
||||
@@ -375,6 +430,9 @@ export class HaConfigDevicePage extends LitElement {
|
||||
if (Array.isArray(this._diagnosticDownloadLinks)) {
|
||||
deviceActions.push(...this._diagnosticDownloadLinks);
|
||||
}
|
||||
if (this._deleteButtons) {
|
||||
deviceActions.push(...this._deleteButtons);
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
|
@@ -369,7 +369,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
</a>`
|
||||
: ""}
|
||||
${!item.disabled_by &&
|
||||
item.state === "loaded" &&
|
||||
(item.state === "loaded" || item.state === "setup_retry") &&
|
||||
item.supports_unload &&
|
||||
item.source !== "system"
|
||||
? 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 { customElement, property, state } from "lit/decorators";
|
||||
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 "../../../../../components/buttons/ha-call-api-button";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-circular-progress";
|
||||
import "../../../../../components/ha-icon";
|
||||
@@ -18,30 +20,28 @@ import {
|
||||
fetchDeviceRegistry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../../../../data/device_registry";
|
||||
import {
|
||||
migrateZwave,
|
||||
ZWaveJsMigrationData,
|
||||
fetchZwaveNetworkStatus as fetchZwaveJsNetworkStatus,
|
||||
fetchZwaveNodeStatus,
|
||||
getZwaveJsIdentifiersFromDevice,
|
||||
subscribeZwaveNodeReady,
|
||||
} from "../../../../../data/zwave_js";
|
||||
import {
|
||||
fetchMigrationConfig,
|
||||
fetchNetworkStatus,
|
||||
startZwaveJsConfigFlow,
|
||||
ZWaveMigrationConfig,
|
||||
ZWaveNetworkStatus,
|
||||
ZWAVE_NETWORK_STATE_STOPPED,
|
||||
fetchNetworkStatus,
|
||||
} 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 { showAlertDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../../../layouts/hass-subpage";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import { computeStateDomain } from "../../../../../common/entity/compute_state_domain";
|
||||
import "../../../../../components/ha-alert";
|
||||
|
||||
@customElement("zwave-migration")
|
||||
export class ZwaveMigration extends LitElement {
|
||||
@@ -155,7 +155,7 @@ export class ZwaveMigration extends LitElement {
|
||||
.filter(
|
||||
(entityState) =>
|
||||
computeStateDomain(entityState) === "zwave" &&
|
||||
entityState.state !== "ready"
|
||||
!["ready", "sleeping"].includes(entityState.state)
|
||||
)
|
||||
.map(
|
||||
(entityState) =>
|
||||
@@ -430,6 +430,10 @@ export class ZwaveMigration extends LitElement {
|
||||
const nodesNotReady = (await Promise.all(nodeStatePromisses)).filter(
|
||||
(node) => !node.ready
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("waiting for nodes to be ready", nodesNotReady);
|
||||
|
||||
this._getMigrationData();
|
||||
if (nodesNotReady.length === 0) {
|
||||
this._waitingOnDevices = [];
|
||||
@@ -445,10 +449,19 @@ export class ZwaveMigration extends LitElement {
|
||||
}
|
||||
)
|
||||
);
|
||||
const deviceReg = await fetchDeviceRegistry(this.hass);
|
||||
this._waitingOnDevices = deviceReg
|
||||
.map((device) => getZwaveJsIdentifiersFromDevice(device))
|
||||
.filter(Boolean);
|
||||
const deviceReg: DeviceRegistryEntry[] = await fetchDeviceRegistry(
|
||||
this.hass.connection
|
||||
);
|
||||
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() {
|
||||
|
@@ -28,12 +28,13 @@ import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/device/ha-device-picker";
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-textfield";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
@@ -288,14 +289,14 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
</div>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
.value=${this._config.name}
|
||||
.name=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
label=${this.hass.localize(
|
||||
@change=${this._valueChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.scene.editor.name"
|
||||
)}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<ha-icon-picker
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.scene.editor.icon"
|
||||
@@ -701,14 +702,14 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
private _valueChanged(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
const target = ev.target as any;
|
||||
const name = target.name;
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
let newVal = ev.detail.value;
|
||||
let newVal = (ev as CustomEvent).detail?.value ?? target.value;
|
||||
if (target.type === "number") {
|
||||
newVal = Number(newVal);
|
||||
}
|
||||
@@ -990,6 +991,9 @@ export class HaSceneEditor extends SubscribeMixin(
|
||||
display: block;
|
||||
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 { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
@@ -6,6 +5,7 @@ import "../../../components/ha-blueprint-picker";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-textfield";
|
||||
import "../../../components/ha-selector/ha-selector";
|
||||
import "../../../components/ha-settings-row";
|
||||
|
||||
@@ -101,15 +101,14 @@ export class HaBlueprintScriptEditor extends LitElement {
|
||||
value?.default}
|
||||
@value-changed=${this._inputChanged}
|
||||
></ha-selector>`
|
||||
: html`<paper-input
|
||||
: html`<ha-textfield
|
||||
.key=${key}
|
||||
required
|
||||
.value=${(this.config.use_blueprint.input &&
|
||||
this.config.use_blueprint.input[key]) ??
|
||||
value?.default}
|
||||
@value-changed=${this._inputChanged}
|
||||
no-label-float
|
||||
></paper-input>`}
|
||||
@change=${this._inputChanged}
|
||||
></ha-textfield>`}
|
||||
</ha-settings-row>`
|
||||
)
|
||||
: html`<p class="padding">
|
||||
@@ -145,7 +144,7 @@ export class HaBlueprintScriptEditor extends LitElement {
|
||||
ev.stopPropagation();
|
||||
const target = ev.target as any;
|
||||
const key = target.key;
|
||||
const value = ev.detail.value;
|
||||
const value = ev.detail?.value ?? target.value;
|
||||
if (
|
||||
(this.config.use_blueprint.input &&
|
||||
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 { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { load } from "js-yaml";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-code-editor";
|
||||
@@ -52,10 +51,6 @@ class HaPanelDevTemplate extends LitElement {
|
||||
|
||||
private _template = "";
|
||||
|
||||
private _context_data = "";
|
||||
|
||||
private _context_variables?;
|
||||
|
||||
private _inited = false;
|
||||
|
||||
public connectedCallback() {
|
||||
@@ -70,14 +65,8 @@ class HaPanelDevTemplate extends LitElement {
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
if (localStorage) {
|
||||
if (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;
|
||||
}
|
||||
if (localStorage && localStorage["panel-dev-template-template"]) {
|
||||
this._template = localStorage["panel-dev-template-template"];
|
||||
} else {
|
||||
this._template = DEMO_TEMPLATE;
|
||||
}
|
||||
@@ -152,17 +141,6 @@ class HaPanelDevTemplate extends LitElement {
|
||||
"ui.panel.developer-tools.tabs.templates.reset"
|
||||
)}
|
||||
</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 class="render-pane">
|
||||
@@ -324,15 +302,6 @@ class HaPanelDevTemplate extends LitElement {
|
||||
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) {
|
||||
this._template = ev.detail.value;
|
||||
if (this._error) {
|
||||
@@ -345,9 +314,6 @@ class HaPanelDevTemplate extends LitElement {
|
||||
this._rendering = true;
|
||||
await this._unsubscribeTemplate();
|
||||
try {
|
||||
if (this._context_data && !this._context_variables) {
|
||||
this._context_variables = load(this._context_data);
|
||||
}
|
||||
this._unsubRenderTemplate = subscribeRenderTemplate(
|
||||
this.hass.connection,
|
||||
(result) => {
|
||||
@@ -356,7 +322,6 @@ class HaPanelDevTemplate extends LitElement {
|
||||
},
|
||||
{
|
||||
template: this._template,
|
||||
variables: this._context_variables,
|
||||
timeout: 3,
|
||||
}
|
||||
);
|
||||
@@ -395,16 +360,12 @@ class HaPanelDevTemplate extends LitElement {
|
||||
if (!this._inited) {
|
||||
return;
|
||||
}
|
||||
localStorage["panel-dev-template-context-data"] = this._context_data;
|
||||
localStorage["panel-dev-template-template"] = this._template;
|
||||
}
|
||||
|
||||
private _restoreDemo() {
|
||||
this._context_data = "";
|
||||
this._context_variables = undefined;
|
||||
this._template = DEMO_TEMPLATE;
|
||||
this._subscribeTemplate();
|
||||
delete localStorage["panel-dev-template-context-data"];
|
||||
delete localStorage["panel-dev-template-template"];
|
||||
}
|
||||
}
|
||||
|
@@ -81,7 +81,7 @@ class HaPanelHistory extends LitElement {
|
||||
</app-header>
|
||||
|
||||
<div class="flex content">
|
||||
<div class="flex layout horizontal wrap">
|
||||
<div class="filters">
|
||||
<ha-date-range-picker
|
||||
.hass=${this.hass}
|
||||
?disabled=${this._isLoading}
|
||||
@@ -247,6 +247,12 @@ class HaPanelHistory extends LitElement {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 8px 16px 0;
|
||||
}
|
||||
|
||||
ha-date-range-picker {
|
||||
margin-right: 16px;
|
||||
max-width: 100%;
|
||||
|
@@ -335,7 +335,7 @@ export class HaPanelLogbook extends LitElement {
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding: 0 16px;
|
||||
padding: 8px 16px 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .filters {
|
||||
|
@@ -39,7 +39,7 @@ export class HuiThemeSelectEditor extends LitElement {
|
||||
.sort()
|
||||
.map(
|
||||
(theme) =>
|
||||
html` <mwc-list-item .value=${theme}>${theme}</mwc-list-item> `
|
||||
html`<mwc-list-item .value=${theme}>${theme}</mwc-list-item>`
|
||||
)}
|
||||
</mwc-select>
|
||||
`;
|
||||
@@ -57,7 +57,7 @@ export class HuiThemeSelectEditor extends LitElement {
|
||||
if (!this.hass || ev.target.value === "") {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
@@ -1,18 +1,14 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-area-picker";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { AreaCardConfig } from "../../cards/types";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { AreaCardConfig } from "../../cards/types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { EditorTarget } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import "../../../../components/ha-formfield";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import type { HaFormSchema } from "../../../../components/ha-form/types";
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
@@ -38,21 +34,18 @@ export class HuiAreaCardEditor
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
get _area(): string {
|
||||
return this._config!.area || "";
|
||||
}
|
||||
|
||||
get _navigation_path(): string {
|
||||
return this._config!.navigation_path || "";
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
return this._config!.theme || "";
|
||||
}
|
||||
|
||||
get _show_camera(): boolean {
|
||||
return this._config!.show_camera || false;
|
||||
}
|
||||
private _schema = memoizeOne((): HaFormSchema[] => [
|
||||
{ name: "area", selector: { area: {} } },
|
||||
{ name: "show_camera", required: false, selector: { boolean: {} } },
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{ name: "navigation_path", required: false, selector: { text: {} } },
|
||||
{ name: "theme", required: false, selector: { theme: {} } },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
@@ -60,78 +53,34 @@ export class HuiAreaCardEditor
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="card-config">
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._area}
|
||||
.placeholder=${this._area}
|
||||
.configValue=${"area"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.area.name"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-area-picker>
|
||||
<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>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._config}
|
||||
.schema=${this._schema()}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
const value =
|
||||
target.checked !== undefined ? target.checked : ev.detail.value;
|
||||
|
||||
if (this[`_${target.configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newConfig;
|
||||
if (target.configValue) {
|
||||
if (!value) {
|
||||
newConfig = { ...this._config };
|
||||
delete newConfig[target.configValue!];
|
||||
} else {
|
||||
newConfig = {
|
||||
...this._config,
|
||||
[target.configValue!]: value,
|
||||
};
|
||||
}
|
||||
}
|
||||
fireEvent(this, "config-changed", { config: newConfig });
|
||||
const config = ev.detail.value;
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return configElementStyle;
|
||||
}
|
||||
private _computeLabelCallback = (schema: HaFormSchema) => {
|
||||
switch (schema.name) {
|
||||
case "area":
|
||||
return this.hass!.localize("ui.panel.lovelace.editor.card.area.name");
|
||||
case "navigation_path":
|
||||
return this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.action-editor.navigation_path"
|
||||
);
|
||||
}
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.area.${schema.name}`
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,22 +1,18 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
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 { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||
import "../../../../components/entity/ha-entity-attribute-picker";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { EntityCardConfig } from "../../cards/types";
|
||||
import "../../components/hui-action-editor";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import type { HaFormSchema } from "../../../../components/ha-form/types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { EntityCardConfig } from "../../cards/types";
|
||||
import { headerFooterConfigStructs } from "../../header-footer/structs";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { EditorTarget, EntitiesEditorEvent } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
|
||||
const cardConfigStruct = assign(
|
||||
baseLovelaceCardConfig,
|
||||
@@ -46,174 +42,82 @@ export class HuiEntityCardEditor
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
get _entity(): string {
|
||||
return this._config!.entity || "";
|
||||
}
|
||||
private _schema = memoizeOne(
|
||||
(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 || "";
|
||||
}
|
||||
|
||||
get _icon(): string {
|
||||
return this._config!.icon || "";
|
||||
}
|
||||
|
||||
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 || "";
|
||||
}
|
||||
{
|
||||
name: "attribute",
|
||||
selector: { attribute: { entity_id: entity } },
|
||||
},
|
||||
{ name: "unit", selector: { text: {} } },
|
||||
{ name: "theme", selector: { theme: {} } },
|
||||
{ name: "state_color", selector: { boolean: {} } },
|
||||
],
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
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`
|
||||
<div class="card-config">
|
||||
<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}
|
||||
.value=${this._entity}
|
||||
.configValue=${"entity"}
|
||||
@change=${this._valueChanged}
|
||||
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}
|
||||
></paper-input>
|
||||
<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>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${this._config}
|
||||
.schema=${schema}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.currentTarget! as EditorTarget;
|
||||
|
||||
if (
|
||||
this[`_${target.configValue}`] === target.value ||
|
||||
this[`_${target.configValue}`] === target.config
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
this._config = { ...this._config };
|
||||
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 });
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
const config = ev.detail.value;
|
||||
Object.keys(config).forEach((k) => config[k] === "" && delete config[k]);
|
||||
fireEvent(this, "config-changed", { config });
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return configElementStyle;
|
||||
}
|
||||
private _computeLabelCallback = (schema: HaFormSchema) => {
|
||||
if (schema.name === "entity") {
|
||||
return `${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.entity"
|
||||
)} (${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.card.config.required"
|
||||
)})`;
|
||||
}
|
||||
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -5,61 +5,60 @@ import {
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_PLAY,
|
||||
} from "../../data/media-player";
|
||||
import { resolveMediaSource } from "../../data/media_source";
|
||||
import { ResolvedMediaSource } from "../../data/media_source";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
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(
|
||||
public hass: HomeAssistant,
|
||||
public item: MediaPlayerItem,
|
||||
public resolved: ResolvedMediaSource,
|
||||
private onChange: () => void
|
||||
) {}
|
||||
|
||||
public async initialize() {
|
||||
const resolvedUrl: any = await resolveMediaSource(
|
||||
this.hass,
|
||||
this.item.media_content_id
|
||||
);
|
||||
|
||||
const player = new Audio(resolvedUrl.url);
|
||||
) {
|
||||
const player = new Audio(this.resolved.url);
|
||||
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("ended", this._handleChange);
|
||||
player.addEventListener("canplaythrough", () => {
|
||||
if (this.stopped) {
|
||||
if (this._removed) {
|
||||
return;
|
||||
}
|
||||
this.player = player;
|
||||
player.play();
|
||||
if (this.buffering) {
|
||||
player.play();
|
||||
}
|
||||
this.onChange();
|
||||
});
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
private _handleChange = () => {
|
||||
if (!this.stopped) {
|
||||
if (!this._removed) {
|
||||
this.onChange();
|
||||
}
|
||||
};
|
||||
|
||||
public pause() {
|
||||
if (this.player) {
|
||||
this.player.pause();
|
||||
}
|
||||
this.buffering = false;
|
||||
this.player.pause();
|
||||
}
|
||||
|
||||
public play() {
|
||||
if (this.player) {
|
||||
this.player.play();
|
||||
}
|
||||
this.player.play();
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.stopped = true;
|
||||
public remove() {
|
||||
this._removed = true;
|
||||
// @ts-ignore
|
||||
this.onChange = undefined;
|
||||
if (this.player) {
|
||||
@@ -68,9 +67,7 @@ export class BrowserMediaPlayer {
|
||||
}
|
||||
|
||||
public get isPlaying(): boolean {
|
||||
return (
|
||||
this.player !== undefined && !this.player.paused && !this.player.ended
|
||||
);
|
||||
return this.buffering || (!this.player.paused && !this.player.ended);
|
||||
}
|
||||
|
||||
static idleStateObj(): MediaPlayerEntity {
|
||||
@@ -88,19 +85,19 @@ export class BrowserMediaPlayer {
|
||||
toStateObj(): MediaPlayerEntity {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
|
||||
const base = BrowserMediaPlayer.idleStateObj();
|
||||
if (!this.player) {
|
||||
return base;
|
||||
}
|
||||
base.state = this.isPlaying ? "playing" : "paused";
|
||||
base.attributes = {
|
||||
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,
|
||||
// eslint-disable-next-line no-bitwise
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { until } from "lit/directives/until";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_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 { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import "../../components/ha-button-menu";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-icon-button";
|
||||
import { UNAVAILABLE_STATES } from "../../data/entity";
|
||||
import {
|
||||
@@ -43,6 +45,7 @@ import {
|
||||
SUPPORT_PLAY,
|
||||
SUPPORT_STOP,
|
||||
} from "../../data/media-player";
|
||||
import { ResolvedMediaSource } from "../../data/media_source";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../lovelace/components/hui-marquee";
|
||||
import { BrowserMediaPlayer } from "./browser-media-player";
|
||||
@@ -54,7 +57,7 @@ declare global {
|
||||
}
|
||||
|
||||
@customElement("ha-bar-media-player")
|
||||
class BarMediaPlayer extends LitElement {
|
||||
export class BarMediaPlayer extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public entityId!: string;
|
||||
@@ -68,6 +71,8 @@ class BarMediaPlayer extends LitElement {
|
||||
|
||||
@state() private _marqueeActive = false;
|
||||
|
||||
@state() private _newMediaExpected = false;
|
||||
|
||||
@state() private _browserPlayer?: BrowserMediaPlayer;
|
||||
|
||||
private _progressInterval?: number;
|
||||
@@ -98,32 +103,54 @@ class BarMediaPlayer extends LitElement {
|
||||
clearInterval(this._progressInterval);
|
||||
this._progressInterval = undefined;
|
||||
}
|
||||
|
||||
if (this._browserPlayer) {
|
||||
this._browserPlayer.stop();
|
||||
this._browserPlayer = undefined;
|
||||
}
|
||||
this._tearDownBrowserPlayer();
|
||||
}
|
||||
|
||||
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) {
|
||||
throw Error("Only browser supported");
|
||||
}
|
||||
if (this._browserPlayer) {
|
||||
this._browserPlayer.stop();
|
||||
}
|
||||
this._browserPlayer = new BrowserMediaPlayer(this.hass, item, () =>
|
||||
this.requestUpdate("_browserPlayer")
|
||||
this._tearDownBrowserPlayer();
|
||||
this._browserPlayer = new BrowserMediaPlayer(
|
||||
this.hass,
|
||||
item,
|
||||
resolved,
|
||||
() => this.requestUpdate("_browserPlayer")
|
||||
);
|
||||
await this._browserPlayer.initialize();
|
||||
this._newMediaExpected = false;
|
||||
}
|
||||
|
||||
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 stateObj = this._stateObj;
|
||||
const controls = !stateObj
|
||||
? undefined
|
||||
: !this.narrow
|
||||
|
||||
if (!stateObj) {
|
||||
return this._renderChoosePlayer(stateObj);
|
||||
}
|
||||
|
||||
const controls = !this.narrow
|
||||
? computeMediaControls(stateObj)
|
||||
: (stateObj.state === "playing" &&
|
||||
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
||||
@@ -152,16 +179,14 @@ class BarMediaPlayer extends LitElement {
|
||||
},
|
||||
]
|
||||
: [{}];
|
||||
const mediaDescription = stateObj ? computeMediaDescription(stateObj) : "";
|
||||
const mediaDuration = formatMediaTime(stateObj?.attributes.media_duration);
|
||||
const mediaDescription = computeMediaDescription(stateObj);
|
||||
const mediaDuration = formatMediaTime(stateObj.attributes.media_duration);
|
||||
const mediaTitleClean = cleanupMediaTitle(
|
||||
stateObj?.attributes.media_title || ""
|
||||
stateObj.attributes.media_title || ""
|
||||
);
|
||||
|
||||
const mediaArt = stateObj
|
||||
? stateObj.attributes.entity_picture_local ||
|
||||
stateObj.attributes.entity_picture
|
||||
: undefined;
|
||||
const mediaArt =
|
||||
stateObj.attributes.entity_picture_local ||
|
||||
stateObj.attributes.entity_picture;
|
||||
|
||||
return html`
|
||||
<div
|
||||
@@ -177,7 +202,10 @@ class BarMediaPlayer extends LitElement {
|
||||
<hui-marquee
|
||||
.text=${mediaTitleClean ||
|
||||
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}
|
||||
@mouseover=${this._marqueeMouseOver}
|
||||
@mouseleave=${this._marqueeMouseLeave}
|
||||
@@ -188,97 +216,124 @@ class BarMediaPlayer extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls-progress">
|
||||
<div class="controls">
|
||||
${controls === undefined
|
||||
? ""
|
||||
: controls.map(
|
||||
(control) => html`
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize(
|
||||
`ui.card.media_player.${control.action}`
|
||||
)}
|
||||
.path=${control.icon}
|
||||
action=${control.action}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
${this.narrow
|
||||
? html`<mwc-linear-progress></mwc-linear-progress>`
|
||||
${this._browserPlayer?.buffering
|
||||
? html` <ha-circular-progress active></ha-circular-progress> `
|
||||
: html`
|
||||
<div class="progress">
|
||||
<div id="CurrentProgress"></div>
|
||||
<mwc-linear-progress wide></mwc-linear-progress>
|
||||
<div>${mediaDuration}</div>
|
||||
<div class="controls">
|
||||
${controls === undefined
|
||||
? ""
|
||||
: controls.map(
|
||||
(control) => html`
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize(
|
||||
`ui.card.media_player.${control.action}`
|
||||
)}
|
||||
.path=${control.icon}
|
||||
action=${control.action}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
${stateObj.attributes.media_duration === Infinity
|
||||
? html``
|
||||
: this.narrow
|
||||
? html`<mwc-linear-progress></mwc-linear-progress>`
|
||||
: html`
|
||||
<div class="progress">
|
||||
<div id="CurrentProgress"></div>
|
||||
<mwc-linear-progress wide></mwc-linear-progress>
|
||||
<div>${mediaDuration}</div>
|
||||
</div>
|
||||
`}
|
||||
`}
|
||||
</div>
|
||||
<div class="choose-player ${isBrowser ? "browser" : ""}">
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
${this.narrow
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${isBrowser
|
||||
? mdiMonitor
|
||||
: domainIcon(computeDomain(this.entityId), stateObj)}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: html`
|
||||
<mwc-button
|
||||
slot="trigger"
|
||||
.label=${this.narrow
|
||||
? ""
|
||||
: `${stateObj ? computeStateName(stateObj) : this.entityId}
|
||||
${this._renderChoosePlayer(stateObj)}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderChoosePlayer(stateObj: MediaPlayerEntity | undefined) {
|
||||
const isBrowser = this.entityId === BROWSER_PLAYER;
|
||||
return html`
|
||||
<div class="choose-player ${isBrowser ? "browser" : ""}">
|
||||
<ha-button-menu corner="BOTTOM_START">
|
||||
${
|
||||
this.narrow
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${isBrowser
|
||||
? mdiMonitor
|
||||
: domainIcon(computeDomain(this.entityId), stateObj)}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: html`
|
||||
<mwc-button
|
||||
slot="trigger"
|
||||
.label=${this.narrow
|
||||
? ""
|
||||
: `${
|
||||
stateObj
|
||||
? computeStateName(stateObj)
|
||||
: this.entityId
|
||||
}
|
||||
`}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${isBrowser
|
||||
? mdiMonitor
|
||||
: domainIcon(computeDomain(this.entityId), stateObj)}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
slot="trailingIcon"
|
||||
.path=${mdiChevronDown}
|
||||
></ha-svg-icon>
|
||||
</mwc-button>
|
||||
`
|
||||
}
|
||||
<mwc-list-item
|
||||
.player=${BROWSER_PLAYER}
|
||||
?selected=${isBrowser}
|
||||
@click=${this._selectPlayer}
|
||||
>
|
||||
${this.hass.localize("ui.components.media-browser.web-browser")}
|
||||
</mwc-list-item>
|
||||
${this._mediaPlayerEntities.map(
|
||||
(source) => html`
|
||||
<mwc-list-item
|
||||
?selected=${source.entity_id === this.entityId}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(source.state)}
|
||||
.player=${source.entity_id}
|
||||
@click=${this._selectPlayer}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${isBrowser
|
||||
? mdiMonitor
|
||||
: domainIcon(computeDomain(this.entityId), stateObj)}
|
||||
></ha-svg-icon>
|
||||
<ha-svg-icon
|
||||
slot="trailingIcon"
|
||||
.path=${mdiChevronDown}
|
||||
></ha-svg-icon>
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-list-item
|
||||
.player=${BROWSER_PLAYER}
|
||||
?selected=${isBrowser}
|
||||
@click=${this._selectPlayer}
|
||||
>
|
||||
${this.hass.localize("ui.components.media-browser.web-browser")}
|
||||
</mwc-list-item>
|
||||
${this._mediaPlayerEntities.map(
|
||||
(source) => html`
|
||||
<mwc-list-item
|
||||
?selected=${source.entity_id === this.entityId}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(source.state)}
|
||||
.player=${source.entity_id}
|
||||
@click=${this._selectPlayer}
|
||||
>
|
||||
${computeStateName(source)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
${computeStateName(source)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
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 (
|
||||
changedProps.has("entityId") &&
|
||||
this.entityId !== BROWSER_PLAYER &&
|
||||
this._browserPlayer
|
||||
!oldHass ||
|
||||
oldHass.states[this.entityId] !== this.hass.states[this.entityId]
|
||||
) {
|
||||
this._browserPlayer?.stop();
|
||||
this._browserPlayer = undefined;
|
||||
this._newMediaExpected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +382,13 @@ class BarMediaPlayer extends LitElement {
|
||||
return this.hass!.states[this.entityId] as MediaPlayerEntity | undefined;
|
||||
}
|
||||
|
||||
private _tearDownBrowserPlayer() {
|
||||
if (this._browserPlayer) {
|
||||
this._browserPlayer.remove();
|
||||
this._browserPlayer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _openMoreInfo() {
|
||||
if (this._browserPlayer) {
|
||||
return;
|
||||
|
@@ -37,6 +37,7 @@ import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../types";
|
||||
import "./ha-bar-media-player";
|
||||
import type { BarMediaPlayer } from "./ha-bar-media-player";
|
||||
import { showWebBrowserPlayMediaDialog } from "./show-media-player-dialog";
|
||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import {
|
||||
@@ -79,6 +80,8 @@ class PanelMediaBrowser extends LitElement {
|
||||
|
||||
@query("ha-media-player-browse") private _browser!: HaMediaPlayerBrowse;
|
||||
|
||||
@query("ha-bar-media-player") private _player!: BarMediaPlayer;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-app-layout>
|
||||
@@ -235,15 +238,23 @@ class PanelMediaBrowser extends LitElement {
|
||||
ev: HASSDomEvent<MediaPickedEvent>
|
||||
): Promise<void> {
|
||||
const item = ev.detail.item;
|
||||
|
||||
if (this._entityId !== BROWSER_PLAYER) {
|
||||
this.hass!.callService("media_player", "play_media", {
|
||||
entity_id: this._entityId,
|
||||
media_content_id: item.media_content_id,
|
||||
media_content_type: item.media_content_type,
|
||||
});
|
||||
this._player.showResolvingNewMediaPicked();
|
||||
try {
|
||||
await this.hass!.callService("media_player", "play_media", {
|
||||
entity_id: this._entityId,
|
||||
media_content_id: item.media_content_id,
|
||||
media_content_type: item.media_content_type,
|
||||
});
|
||||
} catch (err) {
|
||||
this._player.hideResolvingNewMediaPicked();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// We won't cancel current media being played if we're going to
|
||||
// open a camera.
|
||||
if (isCameraMediaSource(item.media_content_id)) {
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: getEntityIdFromCameraMediaSource(item.media_content_id),
|
||||
@@ -251,15 +262,15 @@ class PanelMediaBrowser extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
this._player.showResolvingNewMediaPicked();
|
||||
|
||||
const resolvedUrl = await resolveMediaSource(
|
||||
this.hass,
|
||||
item.media_content_id
|
||||
);
|
||||
|
||||
if (resolvedUrl.mime_type.startsWith("audio/")) {
|
||||
await this.shadowRoot!.querySelector("ha-bar-media-player")!.playItem(
|
||||
item
|
||||
);
|
||||
this._player.playItem(item, resolvedUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -155,6 +155,10 @@ const REDIRECTS: Redirects = {
|
||||
component: "history",
|
||||
redirect: "/history",
|
||||
},
|
||||
media_browser: {
|
||||
component: "media_source",
|
||||
redirect: "/media-browser",
|
||||
},
|
||||
};
|
||||
|
||||
export type ParamType = "url" | "string";
|
||||
@@ -178,7 +182,7 @@ class HaPanelMy extends LitElement {
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
const path = this.route.path.substr(1);
|
||||
const path = this.route.path.substring(1);
|
||||
|
||||
if (path.startsWith("supervisor")) {
|
||||
if (!isComponentLoaded(this.hass, "hassio")) {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -11,8 +10,10 @@ import {
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-textfield";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../../components/ha-alert";
|
||||
|
||||
@customElement("ha-change-password-card")
|
||||
class HaChangePasswordCard extends LitElement {
|
||||
@@ -24,11 +25,11 @@ class HaChangePasswordCard extends LitElement {
|
||||
|
||||
@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 {
|
||||
return html`
|
||||
@@ -40,46 +41,48 @@ class HaChangePasswordCard extends LitElement {
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._errorMsg
|
||||
? html` <div class="error">${this._errorMsg}</div> `
|
||||
? html`<ha-alert alert-type="error">${this._errorMsg}</ha-alert>`
|
||||
: ""}
|
||||
${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"
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.change_password.current_password"
|
||||
)}
|
||||
type="password"
|
||||
.value=${this._currentPassword}
|
||||
@value-changed=${this._currentPasswordChanged}
|
||||
@input=${this._currentPasswordChanged}
|
||||
required
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
|
||||
${this._currentPassword
|
||||
? html` <paper-input
|
||||
? html`<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.change_password.new_password"
|
||||
)}
|
||||
name="password"
|
||||
type="password"
|
||||
.value=${this._password}
|
||||
@value-changed=${this._newPasswordChanged}
|
||||
@change=${this._newPasswordChanged}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
<paper-input
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.change_password.confirm_new_password"
|
||||
)}
|
||||
name="passwordConfirm"
|
||||
type="password"
|
||||
.value=${this._passwordConfirm}
|
||||
@value-changed=${this._newPasswordConfirmChanged}
|
||||
@input=${this._newPasswordConfirmChanged}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>`
|
||||
></ha-textfield>`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
@@ -101,16 +104,16 @@ class HaChangePasswordCard extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _currentPasswordChanged(ev: CustomEvent) {
|
||||
this._currentPassword = ev.detail.value;
|
||||
private _currentPasswordChanged(ev) {
|
||||
this._currentPassword = ev.target.value;
|
||||
}
|
||||
|
||||
private _newPasswordChanged(ev: CustomEvent) {
|
||||
this._password = ev.detail.value;
|
||||
private _newPasswordChanged(ev) {
|
||||
this._password = ev.target.value;
|
||||
}
|
||||
|
||||
private _newPasswordConfirmChanged(ev: CustomEvent) {
|
||||
this._passwordConfirm = ev.detail.value;
|
||||
private _newPasswordConfirmChanged(ev) {
|
||||
this._passwordConfirm = ev.target.value;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
@@ -162,23 +165,21 @@ class HaChangePasswordCard extends LitElement {
|
||||
this._statusMsg = this.hass.localize(
|
||||
"ui.panel.profile.change_password.success"
|
||||
);
|
||||
this._currentPassword = undefined;
|
||||
this._password = undefined;
|
||||
this._passwordConfirm = undefined;
|
||||
this._currentPassword = "";
|
||||
this._password = "";
|
||||
this._passwordConfirm = "";
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
.status {
|
||||
color: var(--primary-color);
|
||||
ha-textfield {
|
||||
margin-top: 8px;
|
||||
display: block;
|
||||
}
|
||||
#currentPassword {
|
||||
margin-top: -8px;
|
||||
margin-top: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,5 +1,6 @@
|
||||
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 {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -14,14 +15,13 @@ import "../../components/ha-formfield";
|
||||
import "../../components/ha-radio";
|
||||
import type { HaRadio } from "../../components/ha-radio";
|
||||
import "../../components/ha-settings-row";
|
||||
import "../../components/ha-textfield";
|
||||
import {
|
||||
DEFAULT_PRIMARY_COLOR,
|
||||
DEFAULT_ACCENT_COLOR,
|
||||
DEFAULT_PRIMARY_COLOR,
|
||||
} from "../../resources/ha-style";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
import "@material/mwc-select/mwc-select";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
|
||||
@customElement("ha-pick-theme-row")
|
||||
export class HaPickThemeRow extends LitElement {
|
||||
@@ -115,8 +115,8 @@ export class HaPickThemeRow extends LitElement {
|
||||
</ha-radio>
|
||||
</ha-formfield>
|
||||
${curTheme === "default"
|
||||
? html` <div class="color-pickers">
|
||||
<paper-input
|
||||
? html`<div class="color-pickers">
|
||||
<ha-textfield
|
||||
.value=${themeSettings?.primaryColor ||
|
||||
DEFAULT_PRIMARY_COLOR}
|
||||
type="color"
|
||||
@@ -125,8 +125,8 @@ export class HaPickThemeRow extends LitElement {
|
||||
)}
|
||||
.name=${"primaryColor"}
|
||||
@change=${this._handleColorChange}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
></ha-textfield>
|
||||
<ha-textfield
|
||||
.value=${themeSettings?.accentColor || DEFAULT_ACCENT_COLOR}
|
||||
type="color"
|
||||
.label=${this.hass.localize(
|
||||
@@ -134,7 +134,7 @@ export class HaPickThemeRow extends LitElement {
|
||||
)}
|
||||
.name=${"accentColor"}
|
||||
@change=${this._handleColorChange}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
${themeSettings?.primaryColor || themeSettings?.accentColor
|
||||
? html` <mwc-button @click=${this._resetColors}>
|
||||
${this.hass.localize("ui.panel.profile.themes.reset")}
|
||||
@@ -228,7 +228,8 @@ export class HaPickThemeRow extends LitElement {
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
paper-input {
|
||||
ha-textfield {
|
||||
--text-field-padding: 8px;
|
||||
min-width: 75px;
|
||||
flex-grow: 1;
|
||||
margin: 0 4px;
|
||||
|
@@ -972,6 +972,13 @@
|
||||
"triggered": "Triggered {name}",
|
||||
"dismiss": "Dismiss"
|
||||
},
|
||||
"newsletter": {
|
||||
"newsletter": "Newsletter",
|
||||
"email": "Email",
|
||||
"validation": "A valid email address is required",
|
||||
"subscribe": "Subscribe",
|
||||
"thanks": "Thanks for subscribing!"
|
||||
},
|
||||
"sidebar": {
|
||||
"external_app_configuration": "App Configuration",
|
||||
"sidebar_toggle": "Sidebar Toggle",
|
||||
@@ -2287,6 +2294,8 @@
|
||||
"open_configuration_url_service": "Visit service",
|
||||
"download_diagnostics": "Download diagnostics",
|
||||
"download_diagnostics_integration": "Download {integration} diagnostics",
|
||||
"delete_device": "Delete device",
|
||||
"delete_device_integration": "Remove {integration} from device",
|
||||
"type": {
|
||||
"device_heading": "Device",
|
||||
"device": "device",
|
||||
@@ -4093,7 +4102,6 @@
|
||||
"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:",
|
||||
"entity": "Entity",
|
||||
"context_data": "Context data for template",
|
||||
"domain": "Domain"
|
||||
},
|
||||
"statistics": {
|
||||
|
Reference in New Issue
Block a user