mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
Merge pull request #11969 from home-assistant/patch-release
This commit is contained in:
commit
e9003ac35e
@ -38,6 +38,7 @@ const SCHEMAS: {
|
||||
select: "Select",
|
||||
icon: "Icon",
|
||||
media: "Media",
|
||||
location: "Location",
|
||||
},
|
||||
schema: [
|
||||
{ name: "addon", selector: { addon: {} } },
|
||||
@ -75,6 +76,10 @@ const SCHEMAS: {
|
||||
media: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "location",
|
||||
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -168,6 +168,11 @@ const SCHEMAS: {
|
||||
},
|
||||
icon: { name: "Icon", selector: { icon: {} } },
|
||||
media: { name: "Media", selector: { media: {} } },
|
||||
location: { name: "Location", selector: { location: {} } },
|
||||
location_radius: {
|
||||
name: "Location with radius",
|
||||
selector: { location: { radius: true, icon: "mdi:home" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { mdiFolderUpload } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input-container";
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
|
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220301.0
|
||||
version = 20220301.1
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
|
@ -41,7 +41,7 @@ export class HaDateInput extends LitElement {
|
||||
return html`<ha-textfield
|
||||
.label=${this.label}
|
||||
.disabled=${this.disabled}
|
||||
iconTrailing="calendar"
|
||||
iconTrailing
|
||||
@click=${this._openDialog}
|
||||
.value=${this.value
|
||||
? formatDateNumeric(new Date(this.value), this.locale)
|
||||
|
@ -1,6 +1,13 @@
|
||||
import { mdiChevronDown } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
@ -16,11 +23,21 @@ class HaExpansionPanel extends LitElement {
|
||||
|
||||
@property() secondary?: string;
|
||||
|
||||
@state() _showContent = this.expanded;
|
||||
|
||||
@query(".container") private _container!: HTMLDivElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="summary" @click=${this._toggleContainer}>
|
||||
<div
|
||||
id="summary"
|
||||
@click=${this._toggleContainer}
|
||||
@keydown=${this._toggleContainer}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-expanded=${this.expanded}
|
||||
aria-controls="sect1"
|
||||
>
|
||||
<slot class="header" name="header">
|
||||
${this.header}
|
||||
<slot class="secondary" name="secondary">${this.secondary}</slot>
|
||||
@ -33,21 +50,37 @@ class HaExpansionPanel extends LitElement {
|
||||
<div
|
||||
class="container ${classMap({ expanded: this.expanded })}"
|
||||
@transitionend=${this._handleTransitionEnd}
|
||||
role="region"
|
||||
aria-labelledby="summary"
|
||||
aria-hidden=${!this.expanded}
|
||||
tabindex="-1"
|
||||
>
|
||||
<slot></slot>
|
||||
${this._showContent ? html`<slot></slot>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleTransitionEnd() {
|
||||
this._container.style.removeProperty("height");
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
if (changedProps.has("expanded") && this.expanded) {
|
||||
this._showContent = this.expanded;
|
||||
}
|
||||
}
|
||||
|
||||
private async _toggleContainer(): Promise<void> {
|
||||
private _handleTransitionEnd() {
|
||||
this._container.style.removeProperty("height");
|
||||
this._showContent = this.expanded;
|
||||
}
|
||||
|
||||
private async _toggleContainer(ev): Promise<void> {
|
||||
if (ev.type === "keydown" && ev.key !== "Enter" && ev.key !== " ") {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
const newExpanded = !this.expanded;
|
||||
fireEvent(this, "expanded-will-change", { expanded: newExpanded });
|
||||
|
||||
if (newExpanded) {
|
||||
this._showContent = true;
|
||||
// allow for dynamic content to be rendered
|
||||
await nextRender();
|
||||
}
|
||||
@ -80,17 +113,21 @@ class HaExpansionPanel extends LitElement {
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
#summary {
|
||||
display: flex;
|
||||
padding: var(--expansion-panel-summary-padding, 0);
|
||||
padding: var(--expansion-panel-summary-padding, 0 8px);
|
||||
min-height: 48px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#summary:focus {
|
||||
background: var(--input-fill-color);
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
@ -103,6 +140,7 @@ class HaExpansionPanel extends LitElement {
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: var(--expansion-panel-content-padding, 0 8px);
|
||||
overflow: hidden;
|
||||
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
height: 0px;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { styles } from "@material/mwc-textfield/mwc-textfield.css";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import "@polymer/iron-input/iron-input";
|
||||
import "@polymer/paper-input/paper-input-container";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@ -21,7 +20,7 @@ export class HaFileUpload extends LitElement {
|
||||
|
||||
@property() public accept!: string;
|
||||
|
||||
@property() public icon!: string;
|
||||
@property() public icon?: string;
|
||||
|
||||
@property() public label!: string;
|
||||
|
||||
@ -39,15 +38,7 @@ export class HaFileUpload extends LitElement {
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
if (this.autoOpenFileDialog) {
|
||||
this._input?.click();
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("_drag") && !this.uploading) {
|
||||
(
|
||||
this.shadowRoot!.querySelector("paper-input-container") as any
|
||||
)._setFocused(this._drag);
|
||||
this._openFilePicker();
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,51 +51,75 @@ export class HaFileUpload extends LitElement {
|
||||
active
|
||||
></ha-circular-progress>`
|
||||
: html`
|
||||
<label for="input">
|
||||
<paper-input-container
|
||||
.alwaysFloatLabel=${Boolean(this.value)}
|
||||
@drop=${this._handleDrop}
|
||||
@dragenter=${this._handleDragStart}
|
||||
@dragover=${this._handleDragStart}
|
||||
@dragleave=${this._handleDragEnd}
|
||||
@dragend=${this._handleDragEnd}
|
||||
class=${classMap({
|
||||
dragged: this._drag,
|
||||
})}
|
||||
<label
|
||||
for="input"
|
||||
class="mdc-text-field mdc-text-field--filled ${classMap({
|
||||
"mdc-text-field--focused": this._drag,
|
||||
"mdc-text-field--with-leading-icon": Boolean(this.icon),
|
||||
"mdc-text-field--with-trailing-icon": Boolean(this.value),
|
||||
})}"
|
||||
@drop=${this._handleDrop}
|
||||
@dragenter=${this._handleDragStart}
|
||||
@dragover=${this._handleDragStart}
|
||||
@dragleave=${this._handleDragEnd}
|
||||
@dragend=${this._handleDragEnd}
|
||||
>
|
||||
<span class="mdc-text-field__ripple"></span>
|
||||
<span
|
||||
class="mdc-floating-label ${this.value || this._drag
|
||||
? "mdc-floating-label--float-above"
|
||||
: ""}"
|
||||
id="label"
|
||||
>${this.label}</span
|
||||
>
|
||||
<label for="input" slot="label"> ${this.label} </label>
|
||||
<iron-input slot="input">
|
||||
<input
|
||||
id="input"
|
||||
type="file"
|
||||
class="file"
|
||||
accept=${this.accept}
|
||||
@change=${this._handleFilePicked}
|
||||
/>
|
||||
${this.value}
|
||||
</iron-input>
|
||||
${this.value
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
@click=${this._clearValue}
|
||||
.label=${this.hass?.localize("ui.common.close") ||
|
||||
"close"}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
.path=${this.icon}
|
||||
></ha-icon-button>
|
||||
`}
|
||||
</paper-input-container>
|
||||
${this.icon
|
||||
? html`<span
|
||||
class="mdc-text-field__icon mdc-text-field__icon--leading"
|
||||
tabindex="-1"
|
||||
>
|
||||
<ha-icon-button
|
||||
@click=${this._openFilePicker}
|
||||
.path=${this.icon}
|
||||
></ha-icon-button>
|
||||
</span>`
|
||||
: ""}
|
||||
<div class="value">${this.value}</div>
|
||||
<input
|
||||
id="input"
|
||||
type="file"
|
||||
class="mdc-text-field__input file"
|
||||
accept=${this.accept}
|
||||
@change=${this._handleFilePicked}
|
||||
aria-labelledby="label"
|
||||
/>
|
||||
${this.value
|
||||
? html`<span
|
||||
class="mdc-text-field__icon mdc-text-field__icon--trailing"
|
||||
tabindex="1"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
@click=${this._clearValue}
|
||||
.label=${this.hass?.localize("ui.common.close") ||
|
||||
"close"}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
</span>`
|
||||
: ""}
|
||||
<span
|
||||
class="mdc-line-ripple ${this._drag
|
||||
? "mdc-line-ripple--active"
|
||||
: ""}"
|
||||
></span>
|
||||
</label>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
private _openFilePicker() {
|
||||
this._input?.click();
|
||||
}
|
||||
|
||||
private _handleDrop(ev: DragEvent) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
@ -137,40 +152,66 @@ export class HaFileUpload extends LitElement {
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
paper-input-container {
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
margin: 0 -8px;
|
||||
}
|
||||
paper-input-container.dragged:before {
|
||||
position: var(--layout-fit_-_position);
|
||||
top: var(--layout-fit_-_top);
|
||||
right: var(--layout-fit_-_right);
|
||||
bottom: var(--layout-fit_-_bottom);
|
||||
left: var(--layout-fit_-_left);
|
||||
background: currentColor;
|
||||
content: "";
|
||||
opacity: var(--dark-divider-opacity);
|
||||
pointer-events: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
input.file {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
max-width: 125px;
|
||||
max-height: 125px;
|
||||
}
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
text-align-last: center;
|
||||
}
|
||||
`;
|
||||
return [
|
||||
styles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.mdc-text-field--filled {
|
||||
height: auto;
|
||||
padding-top: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.mdc-text-field--filled.mdc-text-field--with-trailing-icon {
|
||||
padding-top: 28px;
|
||||
}
|
||||
.mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.mdc-text-field--filled.mdc-text-field--with-trailing-icon
|
||||
.mdc-text-field__icon {
|
||||
align-self: flex-end;
|
||||
}
|
||||
.mdc-text-field__icon--leading {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.mdc-text-field--filled .mdc-floating-label--float-above {
|
||||
transform: scale(0.75);
|
||||
top: 8px;
|
||||
}
|
||||
.dragged:before {
|
||||
position: var(--layout-fit_-_position);
|
||||
top: var(--layout-fit_-_top);
|
||||
right: var(--layout-fit_-_right);
|
||||
bottom: var(--layout-fit_-_bottom);
|
||||
left: var(--layout-fit_-_left);
|
||||
background: currentColor;
|
||||
content: "";
|
||||
opacity: var(--dark-divider-opacity);
|
||||
pointer-events: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.value {
|
||||
width: 100%;
|
||||
}
|
||||
input.file {
|
||||
display: none;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 125px;
|
||||
}
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: block;
|
||||
text-align-last: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,9 @@ export class HaFormConstant extends LitElement implements HaFormElement {
|
||||
@property() public label!: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<span class="label">${this.label}</span>: ${this.schema.value}`;
|
||||
return html`<span class="label">${this.label}</span>${this.schema.value
|
||||
? `: ${this.schema.value}`
|
||||
: ""}`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@ -25,6 +25,8 @@ import { HomeAssistant } from "../../types";
|
||||
const getValue = (obj, item) =>
|
||||
obj ? (!item.name ? obj : obj[item.name]) : null;
|
||||
|
||||
const getError = (obj, item) => (obj && item.name ? obj[item.name] : null);
|
||||
|
||||
let selectorImported = false;
|
||||
|
||||
@customElement("ha-form")
|
||||
@ -84,7 +86,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
`
|
||||
: ""}
|
||||
${this.schema.map((item) => {
|
||||
const error = getValue(this.error, item);
|
||||
const error = getError(this.error, item);
|
||||
|
||||
return html`
|
||||
${error
|
||||
|
@ -40,7 +40,7 @@ export interface HaFormSelector extends HaFormBaseSchema {
|
||||
|
||||
export interface HaFormConstantSchema extends HaFormBaseSchema {
|
||||
type: "constant";
|
||||
value: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
||||
|
@ -44,6 +44,9 @@ export class HaSelect extends SelectBase {
|
||||
.mdc-select:not(.mdc-select--disabled) .mdc-select__icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.mdc-select__anchor {
|
||||
width: var(--ha-select-min-width, 200px);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
80
src/components/ha-selector/ha-selector-location.ts
Normal file
80
src/components/ha-selector/ha-selector-location.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
LocationSelector,
|
||||
LocationSelectorValue,
|
||||
} from "../../data/selector";
|
||||
import "../../panels/lovelace/components/hui-theme-select-editor";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { MarkerLocation } from "../map/ha-locations-editor";
|
||||
import "../map/ha-locations-editor";
|
||||
|
||||
@customElement("ha-selector-location")
|
||||
export class HaLocationSelector extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public selector!: LocationSelector;
|
||||
|
||||
@property() public value?: LocationSelectorValue;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-locations-editor
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
.locations=${this._location(this.selector, this.value)}
|
||||
@location-updated=${this._locationChanged}
|
||||
@radius-updated=${this._radiusChanged}
|
||||
></ha-locations-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
private _location = memoizeOne(
|
||||
(
|
||||
selector: LocationSelector,
|
||||
value?: LocationSelectorValue
|
||||
): MarkerLocation[] => {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const zoneRadiusColor = selector.location.radius
|
||||
? computedStyles.getPropertyValue("--zone-radius-color") ||
|
||||
computedStyles.getPropertyValue("--accent-color")
|
||||
: undefined;
|
||||
return [
|
||||
{
|
||||
id: "location",
|
||||
latitude: value?.latitude || this.hass.config.latitude,
|
||||
longitude: value?.longitude || this.hass.config.longitude,
|
||||
radius: selector.location.radius ? value?.radius || 1000 : undefined,
|
||||
radius_color: zoneRadiusColor,
|
||||
icon: selector.location.icon,
|
||||
location_editable: true,
|
||||
radius_editable: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
private _locationChanged(ev: CustomEvent) {
|
||||
const [latitude, longitude] = ev.detail.location;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.value, latitude, longitude },
|
||||
});
|
||||
}
|
||||
|
||||
private _radiusChanged(ev: CustomEvent) {
|
||||
const radius = ev.detail.radius;
|
||||
fireEvent(this, "value-changed", { value: { ...this.value, radius } });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-selector-location": HaLocationSelector;
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import "./ha-selector-time";
|
||||
import "./ha-selector-icon";
|
||||
import "./ha-selector-media";
|
||||
import "./ha-selector-theme";
|
||||
import "./ha-selector-location";
|
||||
|
||||
@customElement("ha-selector")
|
||||
export class HaSelector extends LitElement {
|
||||
|
@ -9,6 +9,12 @@ export class HaTextField extends TextFieldBase {
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
// @ts-ignore
|
||||
@property({ type: Boolean }) public icon?: boolean;
|
||||
|
||||
// @ts-ignore
|
||||
@property({ type: Boolean }) public iconTrailing?: boolean;
|
||||
|
||||
override updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
@ -53,6 +59,11 @@ export class HaTextField extends TextFieldBase {
|
||||
padding-right: var(--text-field-suffix-padding-right, 0px);
|
||||
}
|
||||
|
||||
.mdc-text-field:not(.mdc-text-field--disabled)
|
||||
.mdc-text-field__affix--suffix {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.mdc-text-field__icon {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class SearchInput extends LitElement {
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label || "Search"}
|
||||
.value=${this.filter || ""}
|
||||
.icon=${true}
|
||||
icon
|
||||
.iconTrailing=${this.filter || this.suffix}
|
||||
@input=${this._filterInputChanged}
|
||||
>
|
||||
|
@ -29,7 +29,7 @@ export const createImage = async (
|
||||
body: fd,
|
||||
});
|
||||
if (resp.status === 413) {
|
||||
throw new Error("Uploaded image is too large");
|
||||
throw new Error(`Uploaded image is too large (${file.name})`);
|
||||
} else if (resp.status !== 200) {
|
||||
throw new Error("Unknown error");
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ export const uploadLocalMedia = async (
|
||||
}
|
||||
);
|
||||
if (resp.status === 413) {
|
||||
throw new Error("Uploaded image is too large");
|
||||
throw new Error(`Uploaded file is too large (${file.name})`);
|
||||
} else if (resp.status !== 200) {
|
||||
throw new Error("Unknown error");
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ export type Selector =
|
||||
| SelectSelector
|
||||
| IconSelector
|
||||
| MediaSelector
|
||||
| ThemeSelector;
|
||||
| ThemeSelector
|
||||
| LocationSelector;
|
||||
|
||||
export interface EntitySelector {
|
||||
entity: {
|
||||
@ -164,6 +165,16 @@ export interface MediaSelector {
|
||||
media: {};
|
||||
}
|
||||
|
||||
export interface LocationSelector {
|
||||
location: { radius?: boolean; icon?: string };
|
||||
}
|
||||
|
||||
export interface LocationSelectorValue {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
export interface MediaSelectorValue {
|
||||
entity_id?: string;
|
||||
media_content_id?: string;
|
||||
|
@ -12,12 +12,12 @@ export interface Zone {
|
||||
}
|
||||
|
||||
export interface ZoneMutableParams {
|
||||
name: string;
|
||||
icon?: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
name: string;
|
||||
passive: boolean;
|
||||
radius: number;
|
||||
passive?: boolean;
|
||||
radius?: number;
|
||||
}
|
||||
|
||||
export const fetchZones = (hass: HomeAssistant) =>
|
||||
|
@ -117,13 +117,17 @@ class DataEntryFlowDialog extends LitElement {
|
||||
);
|
||||
} catch (err: any) {
|
||||
this.closeDialog();
|
||||
let message = err.message || err.body || "Unknown error";
|
||||
if (typeof message !== "string") {
|
||||
message = JSON.stringify(message);
|
||||
}
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error"
|
||||
),
|
||||
text: `${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.could_not_load"
|
||||
)}: ${err.message || err.body}`,
|
||||
)}: ${message}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -373,13 +377,20 @@ class DataEntryFlowDialog extends LitElement {
|
||||
step = await this._params!.flowConfig.createFlow(this.hass, handler);
|
||||
} catch (err: any) {
|
||||
this.closeDialog();
|
||||
const message =
|
||||
err?.status_code === 404
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.no_config_flow"
|
||||
)
|
||||
: `${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.could_not_load"
|
||||
)}: ${err?.body?.message || err?.message}`;
|
||||
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error"
|
||||
),
|
||||
text: `${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.could_not_load"
|
||||
)}: ${err.message || err.body}`,
|
||||
text: message,
|
||||
});
|
||||
return;
|
||||
} finally {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@ -12,6 +13,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-select";
|
||||
import "../../../components/ha-slider";
|
||||
import "../../../components/ha-switch";
|
||||
import {
|
||||
@ -19,8 +21,6 @@ import {
|
||||
HUMIDIFIER_SUPPORT_MODES,
|
||||
} from "../../../data/humidifier";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
|
||||
class MoreInfoHumidifier extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -67,26 +67,24 @@ class MoreInfoHumidifier extends LitElement {
|
||||
|
||||
${supportModes
|
||||
? html`
|
||||
<div class="container-modes">
|
||||
<mwc-list
|
||||
.label=${hass.localize("ui.card.humidifier.mode")}
|
||||
.value=${stateObj.attributes.mode}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
@selected=${this._handleModeChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${stateObj.attributes.available_modes!.map(
|
||||
(mode) => html`
|
||||
<mwc-list-item .value=${mode}>
|
||||
${hass.localize(
|
||||
`state_attributes.humidifier.mode.${mode}`
|
||||
) || mode}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
</div>
|
||||
<ha-select
|
||||
.label=${hass.localize("ui.card.humidifier.mode")}
|
||||
.value=${stateObj.attributes.mode}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
@selected=${this._handleModeChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${stateObj.attributes.available_modes!.map(
|
||||
(mode) => html`
|
||||
<mwc-list-item .value=${mode}>
|
||||
${hass.localize(
|
||||
`state_attributes.humidifier.mode.${mode}`
|
||||
) || mode}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-select>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
|
@ -86,11 +86,11 @@ export class QuickBar extends LitElement {
|
||||
|
||||
@state() private _search = "";
|
||||
|
||||
@state() private _opened = false;
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _commandMode = false;
|
||||
|
||||
@state() private _done = false;
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _narrow = false;
|
||||
|
||||
@ -109,12 +109,12 @@ export class QuickBar extends LitElement {
|
||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||
).matches;
|
||||
this._initializeItemsIfNeeded();
|
||||
this._opened = true;
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._open = false;
|
||||
this._opened = false;
|
||||
this._done = false;
|
||||
this._focusSet = false;
|
||||
this._filter = "";
|
||||
this._search = "";
|
||||
@ -133,7 +133,7 @@ export class QuickBar extends LitElement {
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this._opened) {
|
||||
if (!this._open) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ export class QuickBar extends LitElement {
|
||||
"ui.dialogs.quick-bar.filter_placeholder"
|
||||
)}
|
||||
.value=${this._commandMode ? `>${this._search}` : this._search}
|
||||
.icon=${true}
|
||||
icon
|
||||
.iconTrailing=${this._search !== undefined || this._narrow}
|
||||
@input=${this._handleSearchChange}
|
||||
@keydown=${this._handleInputKeyDown}
|
||||
@ -218,24 +218,26 @@ export class QuickBar extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<mwc-list>
|
||||
<lit-virtualizer
|
||||
scroller
|
||||
@keydown=${this._handleListItemKeyDown}
|
||||
@rangechange=${this._handleRangeChanged}
|
||||
@click=${this._handleItemClick}
|
||||
class="ha-scrollbar"
|
||||
style=${styleMap({
|
||||
height: this._narrow
|
||||
? "calc(100vh - 56px)"
|
||||
: `${Math.min(
|
||||
items.length * (this._commandMode ? 56 : 72) + 26,
|
||||
this._done ? 500 : 0
|
||||
)}px`,
|
||||
})}
|
||||
.items=${items}
|
||||
.renderItem=${this._renderItem}
|
||||
>
|
||||
</lit-virtualizer>
|
||||
${this._opened
|
||||
? html`<lit-virtualizer
|
||||
scroller
|
||||
@keydown=${this._handleListItemKeyDown}
|
||||
@rangechange=${this._handleRangeChanged}
|
||||
@click=${this._handleItemClick}
|
||||
class="ha-scrollbar"
|
||||
style=${styleMap({
|
||||
height: this._narrow
|
||||
? "calc(100vh - 56px)"
|
||||
: `${Math.min(
|
||||
items.length * (this._commandMode ? 56 : 72) + 26,
|
||||
500
|
||||
)}px`,
|
||||
})}
|
||||
.items=${items}
|
||||
.renderItem=${this._renderItem}
|
||||
>
|
||||
</lit-virtualizer>`
|
||||
: ""}
|
||||
</mwc-list>
|
||||
`}
|
||||
${this._hint ? html`<div class="hint">${this._hint}</div>` : ""}
|
||||
@ -252,9 +254,7 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
private _handleOpened() {
|
||||
this.updateComplete.then(() => {
|
||||
this._done = true;
|
||||
});
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private async _handleRangeChanged(e) {
|
||||
@ -454,9 +454,10 @@ export class QuickBar extends LitElement {
|
||||
}
|
||||
|
||||
private _handleItemClick(ev) {
|
||||
const listItem = ev.target.closest("mwc-list-item");
|
||||
this.processItemAndCloseDialog(
|
||||
(ev.target as any).item,
|
||||
Number((ev.target as HTMLElement).getAttribute("index"))
|
||||
listItem.item,
|
||||
Number(listItem.getAttribute("index"))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiMicrophone } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@ -10,12 +9,16 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { SpeechRecognition } from "../../common/dom/speech-recognition";
|
||||
import { uid } from "../../common/util/uid";
|
||||
import "../../components/ha-dialog";
|
||||
import type { HaDialog } from "../../components/ha-dialog";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../components/ha-textfield";
|
||||
import {
|
||||
AgentInfo,
|
||||
getAgentInfo,
|
||||
@ -24,9 +27,6 @@ import {
|
||||
} from "../../data/conversation";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../../components/ha-dialog";
|
||||
import type { HaDialog } from "../../components/ha-dialog";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
|
||||
interface Message {
|
||||
who: string;
|
||||
@ -127,18 +127,19 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
: ""}
|
||||
</div>
|
||||
<div class="input" slot="primaryAction">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
@keyup=${this._handleKeyUp}
|
||||
.label=${this.hass.localize(
|
||||
`ui.dialogs.voice_command.${
|
||||
SpeechRecognition ? "label_voice" : "label"
|
||||
}`
|
||||
)}
|
||||
autofocus
|
||||
dialogInitialFocus
|
||||
iconTrailing
|
||||
>
|
||||
${SpeechRecognition
|
||||
? html`
|
||||
<span suffix="" slot="suffix">
|
||||
<span slot="trailingIcon">
|
||||
${this.results
|
||||
? html`
|
||||
<div class="bouncer">
|
||||
@ -155,7 +156,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
</span>
|
||||
`
|
||||
: ""}
|
||||
</paper-input>
|
||||
</ha-textfield>
|
||||
${this._agentInfo && this._agentInfo.attribution
|
||||
? html`
|
||||
<a
|
||||
@ -195,7 +196,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
}
|
||||
|
||||
private _handleKeyUp(ev: KeyboardEvent) {
|
||||
const input = ev.target as PaperInputElement;
|
||||
const input = ev.target as HaTextField;
|
||||
if (ev.keyCode === 13 && input.value) {
|
||||
this._processText(input.value);
|
||||
input.value = "";
|
||||
@ -327,6 +328,7 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
css`
|
||||
ha-icon-button {
|
||||
color: var(--secondary-text-color);
|
||||
margin-right: -24px;
|
||||
}
|
||||
|
||||
ha-icon-button[active] {
|
||||
@ -338,7 +340,9 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
--secondary-action-button-flex: 0;
|
||||
--mdc-dialog-max-width: 450px;
|
||||
}
|
||||
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
a.button {
|
||||
text-decoration: none;
|
||||
}
|
||||
@ -406,7 +410,6 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
.double-bounce1,
|
||||
.double-bounce2 {
|
||||
|
@ -1,7 +1,6 @@
|
||||
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 { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
@ -11,6 +10,7 @@ import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-selector/ha-selector";
|
||||
import "../../../components/ha-settings-row";
|
||||
import "../../../components/ha-textfield";
|
||||
import {
|
||||
BlueprintAutomationConfig,
|
||||
triggerAutomationActions,
|
||||
@ -38,6 +38,8 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
|
||||
@state() private _blueprints?: Blueprints;
|
||||
|
||||
@state() private _showDescription = false;
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._getBlueprints();
|
||||
@ -50,6 +52,17 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
return this._blueprints[this.config.use_blueprint.path];
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
if (
|
||||
!this._showDescription &&
|
||||
changedProps.has("config") &&
|
||||
this.config.description
|
||||
) {
|
||||
this._showDescription = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const blueprint = this._blueprint;
|
||||
return html`
|
||||
@ -64,26 +77,39 @@ export class HaBlueprintAutomationEditor 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
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.placeholder"
|
||||
)}
|
||||
name="description"
|
||||
.value=${this.config.description}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-textarea>
|
||||
</ha-textfield>
|
||||
${this._showDescription
|
||||
? html`
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.placeholder"
|
||||
)}
|
||||
name="description"
|
||||
autogrow
|
||||
.value=${this.config.description || ""}
|
||||
@change=${this._valueChanged}
|
||||
></ha-textarea>
|
||||
`
|
||||
: html`
|
||||
<div class="link-button-row">
|
||||
<button class="link" @click=${this._addDescription}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.add"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
${this.stateObj
|
||||
? html`
|
||||
@ -173,15 +199,14 @@ export class HaBlueprintAutomationEditor 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>`}
|
||||
@input=${this._inputChanged}
|
||||
></ha-textfield>`}
|
||||
</ha-settings-row>`
|
||||
)
|
||||
: html`<p class="padding">
|
||||
@ -221,7 +246,7 @@ export class HaBlueprintAutomationEditor 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) ||
|
||||
@ -262,6 +287,10 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _addDescription() {
|
||||
this._showDescription = true;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@ -273,9 +302,16 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
.padding {
|
||||
padding: 16px;
|
||||
}
|
||||
.link-button-row {
|
||||
padding: 14px;
|
||||
}
|
||||
.blueprint-picker-container {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
ha-textarea,
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
h3 {
|
||||
margin: 16px;
|
||||
}
|
||||
@ -292,9 +328,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
--paper-time-input-justify-content: flex-end;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row paper-input {
|
||||
width: 60%;
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row ha-textfield,
|
||||
:host(:not([narrow])) ha-settings-row ha-selector {
|
||||
width: 60%;
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-circular-progress";
|
||||
import "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../../components/ha-textfield";
|
||||
import type { AutomationConfig } from "../../../../data/automation";
|
||||
import { convertThingTalk } from "../../../../data/cloud";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
@ -12,7 +13,6 @@ import type { HomeAssistant } from "../../../../types";
|
||||
import "./ha-thingtalk-placeholders";
|
||||
import type { PlaceholderValues } from "./ha-thingtalk-placeholders";
|
||||
import type { ThingtalkDialogParams } from "./show-dialog-thingtalk";
|
||||
import "../../../../components/ha-dialog";
|
||||
|
||||
export interface Placeholder {
|
||||
name: string;
|
||||
@ -38,7 +38,7 @@ class DialogThingtalk extends LitElement {
|
||||
|
||||
@state() private _placeholders?: PlaceholderContainer;
|
||||
|
||||
@query("#input") private _input?: PaperInputElement;
|
||||
@query("#input") private _input?: HaTextField;
|
||||
|
||||
private _value?: string;
|
||||
|
||||
@ -58,7 +58,7 @@ class DialogThingtalk extends LitElement {
|
||||
this._placeholders = undefined;
|
||||
this._params = undefined;
|
||||
if (this._input) {
|
||||
this._input.value = null;
|
||||
this._input.value = "";
|
||||
}
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
@ -127,13 +127,13 @@ class DialogThingtalk extends LitElement {
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
id="input"
|
||||
label="What should this automation do?"
|
||||
.value=${this._value}
|
||||
autofocus
|
||||
@keyup=${this._handleKeyUp}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<a
|
||||
href="https://almond.stanford.edu/"
|
||||
target="_blank"
|
||||
|
@ -88,7 +88,7 @@ export class HaWebhookTrigger extends LitElement {
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.webhook.webhook_id_helper"
|
||||
)}
|
||||
.iconTrailing=${true}
|
||||
iconTrailing
|
||||
.value=${webhookId || ""}
|
||||
@input=${this._valueChanged}
|
||||
>
|
||||
|
@ -1,11 +1,12 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { state } from "lit/decorators";
|
||||
import { query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import type { HaTextField } from "../../../../components/ha-textfield";
|
||||
import "../../../../components/ha-textfield";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { documentationUrl } from "../../../../util/documentation-url";
|
||||
import { WebhookDialogParams } from "./show-dialog-manage-cloudhook";
|
||||
@ -17,6 +18,8 @@ export class DialogManageCloudhook extends LitElement {
|
||||
|
||||
@state() private _params?: WebhookDialogParams;
|
||||
|
||||
@query("ha-textfield") _input!: HaTextField;
|
||||
|
||||
public showDialog(params: WebhookDialogParams) {
|
||||
this._params = params;
|
||||
}
|
||||
@ -53,12 +56,12 @@ export class DialogManageCloudhook extends LitElement {
|
||||
"ui.panel.config.cloud.dialog_cloudhook.available_at"
|
||||
)}
|
||||
</p>
|
||||
<paper-input
|
||||
label=${inputLabel}
|
||||
value=${cloudhook.cloudhook_url}
|
||||
<ha-textfield
|
||||
.label=${inputLabel}
|
||||
.value=${cloudhook.cloudhook_url}
|
||||
@click=${this._copyClipboard}
|
||||
@blur=${this._restoreLabel}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
<p>
|
||||
${cloudhook.managed
|
||||
? html`
|
||||
@ -98,10 +101,6 @@ export class DialogManageCloudhook extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private get _paperInput(): PaperInputElement {
|
||||
return this.shadowRoot!.querySelector("paper-input")!;
|
||||
}
|
||||
|
||||
private async _disableWebhook() {
|
||||
showConfirmationDialog(this, {
|
||||
text: this.hass!.localize(
|
||||
@ -117,14 +116,10 @@ export class DialogManageCloudhook extends LitElement {
|
||||
}
|
||||
|
||||
private _copyClipboard(ev: FocusEvent) {
|
||||
// paper-input -> iron-input -> input
|
||||
const paperInput = ev.currentTarget as PaperInputElement;
|
||||
const input = (paperInput.inputElement as any)
|
||||
.inputElement as HTMLInputElement;
|
||||
input.setSelectionRange(0, input.value.length);
|
||||
const textField = ev.currentTarget as HaTextField;
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
paperInput.label = this.hass!.localize(
|
||||
copyToClipboard(textField.value);
|
||||
textField.label = this.hass!.localize(
|
||||
"ui.panel.config.cloud.dialog_cloudhook.copied_to_clipboard"
|
||||
);
|
||||
} catch (err: any) {
|
||||
@ -133,18 +128,19 @@ export class DialogManageCloudhook extends LitElement {
|
||||
}
|
||||
|
||||
private _restoreLabel() {
|
||||
this._paperInput.label = inputLabel;
|
||||
this._input.label = inputLabel;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
width: 650px;
|
||||
}
|
||||
paper-input {
|
||||
margin-top: -8px;
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
button.link {
|
||||
color: var(--primary-color);
|
||||
|
@ -19,6 +19,7 @@ import "../../../../components/entity/ha-statistic-picker";
|
||||
import "../../../../components/entity/ha-entity-picker";
|
||||
import "../../../../components/ha-radio";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-textfield";
|
||||
import type { HaRadio } from "../../../../components/ha-radio";
|
||||
|
||||
@customElement("dialog-energy-gas-settings")
|
||||
@ -188,20 +189,19 @@ export class DialogEnergyGasSettings
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
${this._costs === "number"
|
||||
? html`<paper-input
|
||||
? html`<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.energy.gas.dialog.cost_number_input`,
|
||||
{ unit }
|
||||
)}
|
||||
no-label-float
|
||||
class="price-options"
|
||||
step=".01"
|
||||
type="number"
|
||||
.value=${this._source.number_energy_price}
|
||||
@value-changed=${this._numberPriceChanged}
|
||||
@change=${this._numberPriceChanged}
|
||||
.suffix=${`${this.hass.config.currency}/${unit}`}
|
||||
>
|
||||
<span slot="suffix">${this.hass.config.currency}/${unit}</span>
|
||||
</paper-input>`
|
||||
</ha-textfield>`
|
||||
: ""}
|
||||
|
||||
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
@ -223,10 +223,10 @@ export class DialogEnergyGasSettings
|
||||
this._costs = input.value as any;
|
||||
}
|
||||
|
||||
private _numberPriceChanged(ev: CustomEvent) {
|
||||
private _numberPriceChanged(ev) {
|
||||
this._source = {
|
||||
...this._source!,
|
||||
number_energy_price: Number(ev.detail.value),
|
||||
number_energy_price: Number(ev.target.value),
|
||||
entity_energy_price: null,
|
||||
stat_cost: null,
|
||||
};
|
||||
@ -295,13 +295,10 @@ export class DialogEnergyGasSettings
|
||||
ha-formfield {
|
||||
display: block;
|
||||
}
|
||||
ha-statistic-picker {
|
||||
width: 100%;
|
||||
}
|
||||
.price-options {
|
||||
display: block;
|
||||
padding-left: 52px;
|
||||
margin-top: -16px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -190,24 +190,21 @@ export class DialogEnergyGridFlowSettings
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
${this._costs === "number"
|
||||
? html`<paper-input
|
||||
? html`<ha-textfield
|
||||
.label=${this.hass.localize(
|
||||
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_input`
|
||||
)}
|
||||
no-label-float
|
||||
class="price-options"
|
||||
step=".01"
|
||||
type="number"
|
||||
.value=${this._source.number_energy_price}
|
||||
@value-changed=${this._numberPriceChanged}
|
||||
.suffix=${this.hass.localize(
|
||||
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_suffix`,
|
||||
{ currency: this.hass.config.currency }
|
||||
)}
|
||||
@change=${this._numberPriceChanged}
|
||||
>
|
||||
<span slot="suffix"
|
||||
>${this.hass.localize(
|
||||
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.cost_number_suffix`,
|
||||
{ currency: this.hass.config.currency }
|
||||
)}</span
|
||||
>
|
||||
</paper-input>`
|
||||
</ha-textfield>`
|
||||
: ""}
|
||||
|
||||
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||
@ -302,13 +299,10 @@ export class DialogEnergyGridFlowSettings
|
||||
ha-formfield {
|
||||
display: block;
|
||||
}
|
||||
ha-statistic-picker {
|
||||
width: 100%;
|
||||
}
|
||||
.price-options {
|
||||
display: block;
|
||||
padding-left: 52px;
|
||||
margin-top: -16px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -30,6 +30,7 @@ import "../../../components/ha-check-list-item";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
|
||||
import {
|
||||
getConfigFlowHandlers,
|
||||
getConfigFlowInProgressCollection,
|
||||
localizeConfigFlowTitle,
|
||||
subscribeConfigFlowInProgress,
|
||||
@ -51,7 +52,10 @@ import {
|
||||
} from "../../../data/integration";
|
||||
import { scanUSBDevices } from "../../../data/usb";
|
||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
@ -652,6 +656,19 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
if (!domain) {
|
||||
return;
|
||||
}
|
||||
const handlers = await getConfigFlowHandlers(this.hass);
|
||||
|
||||
if (!handlers.includes(domain)) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.error"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.no_config_flow"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const localize = await localizePromise;
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
|
@ -1,7 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { mdiAlertCircle, mdiCheckCircle, mdiQrcodeScan } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
@ -11,28 +9,30 @@ import { HaCheckbox } from "../../../../../components/ha-checkbox";
|
||||
import "../../../../../components/ha-circular-progress";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-qr-scanner";
|
||||
import "../../../../../components/ha-radio";
|
||||
import "../../../../../components/ha-switch";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../../../components/ha-textfield";
|
||||
import {
|
||||
zwaveGrantSecurityClasses,
|
||||
InclusionStrategy,
|
||||
MINIMUM_QR_STRING_LENGTH,
|
||||
zwaveParseQrCode,
|
||||
PlannedProvisioningEntry,
|
||||
provisionZwaveSmartStartNode,
|
||||
QRProvisioningInformation,
|
||||
RequestedGrant,
|
||||
SecurityClass,
|
||||
stopZwaveInclusion,
|
||||
subscribeAddZwaveNode,
|
||||
ZWaveFeature,
|
||||
zwaveGrantSecurityClasses,
|
||||
zwaveParseQrCode,
|
||||
zwaveSupportsFeature,
|
||||
zwaveValidateDskAndEnterPin,
|
||||
ZWaveFeature,
|
||||
PlannedProvisioningEntry,
|
||||
} from "../../../../../data/zwave_js";
|
||||
import { haStyle, haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ZWaveJSAddNodeDialogParams } from "./show-dialog-zwave_js-add-node";
|
||||
import "../../../../../components/ha-qr-scanner";
|
||||
|
||||
export interface ZWaveJSAddNodeDevice {
|
||||
id: string;
|
||||
@ -98,7 +98,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
this._startInclusion();
|
||||
}
|
||||
|
||||
@query("#pin-input") private _pinInput?: PaperInputElement;
|
||||
@query("#pin-input") private _pinInput?: HaTextField;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._entryId) {
|
||||
@ -202,12 +202,11 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
: ""
|
||||
}
|
||||
<div class="flex-container">
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
label="PIN"
|
||||
id="pin-input"
|
||||
@keyup=${this._handlePinKeyUp}
|
||||
no-label-float
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
${this._dsk}
|
||||
</div>
|
||||
<mwc-button
|
||||
@ -814,6 +813,9 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
width: 68px;
|
||||
height: 48px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import "../../../../../components/ha-select";
|
||||
import "../../../../../components/ha-settings-row";
|
||||
import "../../../../../components/ha-svg-icon";
|
||||
import "../../../../../components/ha-switch";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
@ -265,7 +266,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
||||
|
||||
if (item.configuration_value_type === "manual_entry") {
|
||||
return html`${labelAndDescription}
|
||||
<paper-input
|
||||
<ha-textfield
|
||||
type="number"
|
||||
.value=${item.value}
|
||||
.min=${item.metadata.min}
|
||||
@ -274,12 +275,12 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
||||
.propertyKey=${item.property_key}
|
||||
.key=${id}
|
||||
.disabled=${!item.metadata.writeable}
|
||||
@value-changed=${this._numericInputChanged}
|
||||
@input=${this._numericInputChanged}
|
||||
>
|
||||
${item.metadata.unit
|
||||
? html`<span slot="suffix">${item.metadata.unit}</span>`
|
||||
: ""}
|
||||
</paper-input> `;
|
||||
</ha-textfield>`;
|
||||
}
|
||||
|
||||
if (item.configuration_value_type === "enumerated") {
|
||||
@ -492,7 +493,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) ha-settings-row paper-input {
|
||||
:host(:not([narrow])) ha-settings-row ha-textfield {
|
||||
width: 30%;
|
||||
text-align: right;
|
||||
}
|
||||
|
@ -1,20 +1,18 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { slugify } from "../../../../common/string/slugify";
|
||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-formfield";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import { HaFormSchema } from "../../../../components/ha-form/types";
|
||||
import { CoreFrontendUserData } from "../../../../data/frontend";
|
||||
import {
|
||||
LovelaceDashboard,
|
||||
LovelaceDashboardCreateParams,
|
||||
LovelaceDashboardMutableParams,
|
||||
} from "../../../../data/lovelace";
|
||||
import { DEFAULT_PANEL, setDefaultPanel } from "../../../../data/panel";
|
||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
|
||||
@ -25,62 +23,54 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
|
||||
@state() private _params?: LovelaceDashboardDetailsDialogParams;
|
||||
|
||||
@state() private _urlPath!: LovelaceDashboard["url_path"];
|
||||
@state() private _urlPathChanged = false;
|
||||
|
||||
@state() private _showInSidebar!: boolean;
|
||||
@state() private _data?: Partial<LovelaceDashboard>;
|
||||
|
||||
@state() private _icon!: string;
|
||||
|
||||
@state() private _title!: string;
|
||||
|
||||
@state()
|
||||
private _requireAdmin!: LovelaceDashboard["require_admin"];
|
||||
|
||||
@state() private _error?: string;
|
||||
@state() private _error?: Record<string, string>;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
public async showDialog(
|
||||
params: LovelaceDashboardDetailsDialogParams
|
||||
): Promise<void> {
|
||||
public showDialog(params: LovelaceDashboardDetailsDialogParams): void {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._urlPath = this._params.urlPath || "";
|
||||
this._urlPathChanged = false;
|
||||
if (this._params.dashboard) {
|
||||
this._showInSidebar = !!this._params.dashboard.show_in_sidebar;
|
||||
this._icon = this._params.dashboard.icon || "";
|
||||
this._title = this._params.dashboard.title || "";
|
||||
this._requireAdmin = this._params.dashboard.require_admin || false;
|
||||
this._data = this._params.dashboard;
|
||||
} else {
|
||||
this._showInSidebar = true;
|
||||
this._icon = "";
|
||||
this._title = "";
|
||||
this._requireAdmin = false;
|
||||
this._data = {
|
||||
show_in_sidebar: true,
|
||||
icon: "",
|
||||
title: "",
|
||||
require_admin: false,
|
||||
mode: "storage",
|
||||
};
|
||||
}
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params = undefined;
|
||||
this._data = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
if (!this._params || !this._data) {
|
||||
return html``;
|
||||
}
|
||||
const defaultPanelUrlPath = this.hass.defaultPanel;
|
||||
const urlInvalid =
|
||||
this._params.urlPath !== "lovelace" &&
|
||||
!/^[a-zA-Z0-9_-]+-[a-zA-Z0-9_-]+$/.test(this._urlPath);
|
||||
const titleInvalid = !this._title.trim();
|
||||
const dir = computeRTLDirection(this.hass);
|
||||
const titleInvalid = !this._data.title || !this._data.title.trim();
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this._close}
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._params.urlPath
|
||||
? this._title ||
|
||||
? this._data.title ||
|
||||
this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.edit_dashboard"
|
||||
)
|
||||
@ -99,76 +89,14 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
"ui.panel.config.lovelace.dashboards.cant_edit_default"
|
||||
)
|
||||
: html`
|
||||
${this._error
|
||||
? html` <div class="error">${this._error}</div> `
|
||||
: ""}
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._title}
|
||||
@value-changed=${this._titleChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.title"
|
||||
)}
|
||||
@blur=${this.hass.userData?.showAdvanced
|
||||
? this._fillUrlPath
|
||||
: undefined}
|
||||
.invalid=${titleInvalid}
|
||||
.errorMessage=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.title_required"
|
||||
)}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
@value-changed=${this._iconChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.icon"
|
||||
)}
|
||||
></ha-icon-picker>
|
||||
${!this._params.dashboard && this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-input
|
||||
.value=${this._urlPath}
|
||||
@value-changed=${this._urlChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.url"
|
||||
)}
|
||||
.errorMessage=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.url_error_msg"
|
||||
)}
|
||||
.invalid=${urlInvalid}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
<div>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.show_sidebar"
|
||||
)}
|
||||
.dir=${dir}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._showInSidebar}
|
||||
@change=${this._showSidebarChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<div>
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.require_admin"
|
||||
)}
|
||||
.dir=${dir}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._requireAdmin}
|
||||
@change=${this._requireAdminChanged}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
</div>
|
||||
<ha-form
|
||||
.schema=${this._schema(this._params, this.hass.userData)}
|
||||
.data=${this._data}
|
||||
.hass=${this.hass}
|
||||
.error=${this._error}
|
||||
.computeLabel=${this._computeLabel}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
`}
|
||||
</div>
|
||||
${this._params.urlPath
|
||||
@ -206,7 +134,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateDashboard}
|
||||
.disabled=${urlInvalid || titleInvalid || this._submitting}
|
||||
.disabled=${(this._error && "url_path" in this._error) ||
|
||||
titleInvalid ||
|
||||
this._submitting}
|
||||
dialogInitialFocus
|
||||
>
|
||||
${this._params.urlPath
|
||||
@ -223,41 +153,97 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _urlChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._urlPath = ev.detail.value;
|
||||
}
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
params: LovelaceDashboardDetailsDialogParams,
|
||||
userData: CoreFrontendUserData | null | undefined
|
||||
) =>
|
||||
[
|
||||
{
|
||||
name: "title",
|
||||
required: true,
|
||||
selector: {
|
||||
text: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
required: true,
|
||||
selector: {
|
||||
icon: {},
|
||||
},
|
||||
},
|
||||
!params.dashboard &&
|
||||
userData?.showAdvanced && {
|
||||
name: "url_path",
|
||||
required: true,
|
||||
selector: { text: {} },
|
||||
},
|
||||
{
|
||||
name: "require_admin",
|
||||
required: true,
|
||||
selector: {
|
||||
boolean: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "show_in_sidebar",
|
||||
required: true,
|
||||
selector: {
|
||||
boolean: {},
|
||||
},
|
||||
},
|
||||
].filter(Boolean)
|
||||
);
|
||||
|
||||
private _iconChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._icon = ev.detail.value;
|
||||
}
|
||||
private _computeLabel = (entry: HaFormSchema): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.lovelace.dashboards.detail.${
|
||||
entry.name === "show_in_sidebar"
|
||||
? "show_sidebar"
|
||||
: entry.name === "url_path"
|
||||
? "url"
|
||||
: entry.name
|
||||
}`
|
||||
);
|
||||
|
||||
private _titleChanged(ev: PolymerChangedEvent<string>) {
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._error = undefined;
|
||||
this._title = ev.detail.value;
|
||||
if (!this.hass.userData?.showAdvanced) {
|
||||
this._fillUrlPath();
|
||||
const value = ev.detail.value;
|
||||
if (value.url_path !== this._data?.url_path) {
|
||||
this._urlPathChanged = true;
|
||||
if (
|
||||
!value.url_path ||
|
||||
value.url_path === "lovelace" ||
|
||||
!/^[a-zA-Z0-9_-]+-[a-zA-Z0-9_-]+$/.test(value.url_path)
|
||||
) {
|
||||
this._error = {
|
||||
url_path: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.url_error_msg"
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
if (value.title !== this._data?.title) {
|
||||
this._data = value;
|
||||
this._fillUrlPath(value.title);
|
||||
} else {
|
||||
this._data = value;
|
||||
}
|
||||
}
|
||||
|
||||
private _fillUrlPath() {
|
||||
if ((this.hass.userData?.showAdvanced && this._urlPath) || !this._title) {
|
||||
private _fillUrlPath(title: string) {
|
||||
if ((this.hass.userData?.showAdvanced && this._urlPathChanged) || !title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const slugifyTitle = slugify(this._title, "-");
|
||||
this._urlPath = slugifyTitle.includes("-")
|
||||
? slugifyTitle
|
||||
: `lovelace-${slugifyTitle}`;
|
||||
}
|
||||
|
||||
private _showSidebarChanged(ev: Event) {
|
||||
this._showInSidebar = (ev.target as HaSwitch).checked;
|
||||
}
|
||||
|
||||
private _requireAdminChanged(ev: Event) {
|
||||
this._requireAdmin = (ev.target as HaSwitch).checked;
|
||||
const slugifyTitle = slugify(title, "-");
|
||||
this._data = {
|
||||
...this._data,
|
||||
url_path: slugifyTitle.includes("-")
|
||||
? slugifyTitle
|
||||
: `lovelace-${slugifyTitle}`,
|
||||
};
|
||||
}
|
||||
|
||||
private _toggleDefault() {
|
||||
@ -273,29 +259,20 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
|
||||
private async _updateDashboard() {
|
||||
if (this._params?.urlPath && !this._params.dashboard?.id) {
|
||||
this._close();
|
||||
this.closeDialog();
|
||||
}
|
||||
this._submitting = true;
|
||||
try {
|
||||
const values: Partial<LovelaceDashboardMutableParams> = {
|
||||
require_admin: this._requireAdmin,
|
||||
show_in_sidebar: this._showInSidebar,
|
||||
icon: this._icon || undefined,
|
||||
title: this._title,
|
||||
};
|
||||
if (this._params!.dashboard) {
|
||||
await this._params!.updateDashboard(values);
|
||||
await this._params!.updateDashboard(this._data as LovelaceDashboard);
|
||||
} else {
|
||||
(values as LovelaceDashboardCreateParams).url_path =
|
||||
this._urlPath.trim();
|
||||
(values as LovelaceDashboardCreateParams).mode = "storage";
|
||||
await this._params!.createDashboard(
|
||||
values as LovelaceDashboardCreateParams
|
||||
this._data as LovelaceDashboardCreateParams
|
||||
);
|
||||
}
|
||||
this._close();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error = err?.message || "Unknown error";
|
||||
this._error = { base: err?.message || "Unknown error" };
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
@ -305,26 +282,15 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
this._submitting = true;
|
||||
try {
|
||||
if (await this._params!.removeDashboard()) {
|
||||
this._close();
|
||||
this.closeDialog();
|
||||
}
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
this._params = undefined;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-switch {
|
||||
padding: 16px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
return [haStyleDialog, css``];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,21 +1,20 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-select";
|
||||
import {
|
||||
LovelaceResource,
|
||||
LovelaceResourcesMutableParams,
|
||||
} from "../../../../data/lovelace";
|
||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import { HaFormSchema } from "../../../../components/ha-form/types";
|
||||
import { LovelaceResourcesMutableParams } from "../../../../data/lovelace";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail";
|
||||
|
||||
const detectResourceType = (url: string) => {
|
||||
const detectResourceType = (url?: string) => {
|
||||
if (!url) {
|
||||
return undefined;
|
||||
}
|
||||
const ext = url.split(".").pop() || "";
|
||||
|
||||
if (ext === "css") {
|
||||
@ -35,38 +34,41 @@ export class DialogLovelaceResourceDetail extends LitElement {
|
||||
|
||||
@state() private _params?: LovelaceResourceDetailsDialogParams;
|
||||
|
||||
@state() private _url!: LovelaceResource["url"];
|
||||
@state() private _data?: Partial<LovelaceResourcesMutableParams>;
|
||||
|
||||
@state() private _type?: LovelaceResource["type"];
|
||||
|
||||
@state() private _error?: string;
|
||||
@state() private _error?: Record<string, string>;
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
public async showDialog(
|
||||
params: LovelaceResourceDetailsDialogParams
|
||||
): Promise<void> {
|
||||
public showDialog(params: LovelaceResourceDetailsDialogParams): void {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
if (this._params.resource) {
|
||||
this._url = this._params.resource.url || "";
|
||||
this._type = this._params.resource.type || undefined;
|
||||
this._data = {
|
||||
url: this._params.resource.url,
|
||||
res_type: this._params.resource.type,
|
||||
};
|
||||
} else {
|
||||
this._url = "";
|
||||
this._type = undefined;
|
||||
this._data = {
|
||||
url: "",
|
||||
};
|
||||
}
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
const urlInvalid = this._url.trim() === "";
|
||||
const urlInvalid = !this._data?.url || this._data.url.trim() === "";
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this._close}
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
@ -79,68 +81,25 @@ export class DialogLovelaceResourceDetail extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<div class="form">
|
||||
<h3 class="warning">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.warning_header"
|
||||
)}
|
||||
</h3>
|
||||
<ha-alert
|
||||
alert-type="warning"
|
||||
.title=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.warning_header"
|
||||
)}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.warning_text"
|
||||
)}
|
||||
<paper-input
|
||||
.value=${this._url}
|
||||
@value-changed=${this._urlChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.url"
|
||||
)}
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.url_error_msg"
|
||||
)}
|
||||
.invalid=${urlInvalid}
|
||||
dialogInitialFocus
|
||||
></paper-input>
|
||||
<br />
|
||||
<ha-select
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.type"
|
||||
)}
|
||||
.value=${this._type}
|
||||
@selected=${this._typeChanged}
|
||||
@closed=${stopPropagation}
|
||||
.invalid=${!this._type}
|
||||
>
|
||||
<mwc-list-item value="module">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.module"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
${this._type === "js"
|
||||
? html`
|
||||
<mwc-list-item value="js">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.js"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
<mwc-list-item value="css">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.css"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
${this._type === "html"
|
||||
? html`
|
||||
<mwc-list-item value="html">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.html"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
: ""}
|
||||
</ha-select>
|
||||
</div>
|
||||
</ha-alert>
|
||||
|
||||
<ha-form
|
||||
.schema=${this._schema(this._data)}
|
||||
.data=${this._data}
|
||||
.hass=${this.hass}
|
||||
.error=${this._error}
|
||||
.computeLabel=${this._computeLabel}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
</div>
|
||||
${this._params.resource
|
||||
? html`
|
||||
@ -159,7 +118,7 @@ export class DialogLovelaceResourceDetail extends LitElement {
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateResource}
|
||||
.disabled=${urlInvalid || !this._type || this._submitting}
|
||||
.disabled=${urlInvalid || !this._data?.res_type || this._submitting}
|
||||
>
|
||||
${this._params.resource
|
||||
? this.hass!.localize(
|
||||
@ -173,37 +132,86 @@ export class DialogLovelaceResourceDetail extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _urlChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._url = ev.detail.value;
|
||||
if (!this._type) {
|
||||
this._type = detectResourceType(this._url);
|
||||
private _schema = memoizeOne((data) => [
|
||||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
selector: {
|
||||
text: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "res_type",
|
||||
required: true,
|
||||
selector: {
|
||||
select: {
|
||||
options: [
|
||||
{
|
||||
value: "module",
|
||||
label: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.module"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "css",
|
||||
label: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.css"
|
||||
),
|
||||
},
|
||||
data.type === "js" && {
|
||||
value: "js",
|
||||
label: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.js"
|
||||
),
|
||||
},
|
||||
data.type === "html" && {
|
||||
value: "html",
|
||||
label: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.html"
|
||||
),
|
||||
},
|
||||
].filter(Boolean),
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
private _computeLabel = (entry: HaFormSchema): string =>
|
||||
this.hass.localize(
|
||||
`ui.panel.config.lovelace.resources.detail.${entry.name}`
|
||||
);
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._data = ev.detail.value;
|
||||
if (!this._data!.res_type) {
|
||||
const type = detectResourceType(this._data!.url);
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
this._data = {
|
||||
...this._data,
|
||||
res_type: type,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _typeChanged(ev) {
|
||||
this._type = ev.target.value;
|
||||
}
|
||||
|
||||
private async _updateResource() {
|
||||
if (!this._type) {
|
||||
if (!this._data?.res_type) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._submitting = true;
|
||||
try {
|
||||
const values: LovelaceResourcesMutableParams = {
|
||||
url: this._url.trim(),
|
||||
res_type: this._type,
|
||||
};
|
||||
if (this._params!.resource) {
|
||||
await this._params!.updateResource(values);
|
||||
await this._params!.updateResource(this._data!);
|
||||
} else {
|
||||
await this._params!.createResource(values);
|
||||
await this._params!.createResource(
|
||||
this._data! as LovelaceResourcesMutableParams
|
||||
);
|
||||
}
|
||||
this._params = undefined;
|
||||
} catch (err: any) {
|
||||
this._error = err?.message || "Unknown error";
|
||||
this._error = { base: err?.message || "Unknown error" };
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
@ -213,26 +221,15 @@ export class DialogLovelaceResourceDetail extends LitElement {
|
||||
this._submitting = true;
|
||||
try {
|
||||
if (await this._params!.removeResource()) {
|
||||
this._close();
|
||||
this.closeDialog();
|
||||
}
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
this._params = undefined;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.warning {
|
||||
color: var(--error-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
return haStyleDialog;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -464,6 +464,9 @@ class DialogPersonDetail extends LitElement {
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
ha-picture-upload {
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-formfield {
|
||||
display: block;
|
||||
padding: 16px 0;
|
||||
|
@ -186,9 +186,7 @@ export class HaBlueprintScriptEditor extends LitElement {
|
||||
--paper-time-input-justify-content: flex-end;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row paper-input {
|
||||
width: 60%;
|
||||
}
|
||||
:host(:not([narrow])) ha-settings-row ha-textfield,
|
||||
:host(:not([narrow])) ha-settings-row ha-selector {
|
||||
width: 60%;
|
||||
}
|
||||
|
@ -1,17 +1,12 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/map/ha-locations-editor";
|
||||
import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
|
||||
import "../../../components/ha-form/ha-form";
|
||||
import { HaFormSchema } from "../../../components/ha-form/types";
|
||||
import { getZoneEditorInitData, ZoneMutableParams } from "../../../data/zone";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -20,19 +15,9 @@ import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
||||
class DialogZoneDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _name!: string;
|
||||
@state() private _error?: Record<string, string>;
|
||||
|
||||
@state() private _icon!: string;
|
||||
|
||||
@state() private _latitude!: number;
|
||||
|
||||
@state() private _longitude!: number;
|
||||
|
||||
@state() private _passive!: boolean;
|
||||
|
||||
@state() private _radius!: number;
|
||||
|
||||
@state() private _error?: string;
|
||||
@state() private _data?: ZoneMutableParams;
|
||||
|
||||
@state() private _params?: ZoneDetailDialogParams;
|
||||
|
||||
@ -42,13 +27,7 @@ class DialogZoneDetail extends LitElement {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
if (this._params.entry) {
|
||||
this._name = this._params.entry.name || "";
|
||||
this._icon = this._params.entry.icon || "";
|
||||
this._latitude = this._params.entry.latitude || this.hass.config.latitude;
|
||||
this._longitude =
|
||||
this._params.entry.longitude || this.hass.config.longitude;
|
||||
this._passive = this._params.entry.passive || false;
|
||||
this._radius = this._params.entry.radius || 100;
|
||||
this._data = this._params.entry;
|
||||
} else {
|
||||
const initConfig = getZoneEditorInitData();
|
||||
let movedHomeLocation;
|
||||
@ -59,30 +38,34 @@ class DialogZoneDetail extends LitElement {
|
||||
Math.random() * 500 * (Math.random() < 0.5 ? -1 : 1)
|
||||
);
|
||||
}
|
||||
this._latitude = initConfig?.latitude || movedHomeLocation[0];
|
||||
this._longitude = initConfig?.longitude || movedHomeLocation[1];
|
||||
this._name = initConfig?.name || "";
|
||||
this._icon = initConfig?.icon || "mdi:map-marker";
|
||||
|
||||
this._passive = false;
|
||||
this._radius = 100;
|
||||
this._data = {
|
||||
latitude: initConfig?.latitude || movedHomeLocation[0],
|
||||
longitude: initConfig?.longitude || movedHomeLocation[1],
|
||||
name: initConfig?.name || "",
|
||||
icon: initConfig?.icon || "mdi:map-marker",
|
||||
passive: false,
|
||||
radius: 100,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._params = undefined;
|
||||
this._data = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
if (!this._params || !this._data) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = this._name.trim() === "";
|
||||
const iconInvalid = Boolean(this._icon && !this._icon.trim().includes(":"));
|
||||
const latInvalid = String(this._latitude) === "";
|
||||
const lngInvalid = String(this._longitude) === "";
|
||||
const radiusInvalid = String(this._radius) === "";
|
||||
const nameInvalid = this._data.name.trim() === "";
|
||||
const iconInvalid = Boolean(
|
||||
this._data.icon && !this._data.icon.trim().includes(":")
|
||||
);
|
||||
const latInvalid = String(this._data.latitude) === "";
|
||||
const lngInvalid = String(this._data.longitude) === "";
|
||||
const radiusInvalid = String(this._data.radius) === "";
|
||||
|
||||
const valid =
|
||||
!nameInvalid &&
|
||||
@ -105,96 +88,15 @@ class DialogZoneDetail extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
<div class="form">
|
||||
<paper-input
|
||||
dialogInitialFocus
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize("ui.panel.config.zone.detail.name")}
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.required_error_msg"
|
||||
)}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize("ui.panel.config.zone.detail.icon")}
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.icon_error_msg"
|
||||
)}
|
||||
.invalid=${iconInvalid}
|
||||
></ha-icon-picker>
|
||||
<ha-locations-editor
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
.locations=${this._location(
|
||||
this._latitude,
|
||||
this._longitude,
|
||||
this._radius,
|
||||
this._passive,
|
||||
this._icon
|
||||
)}
|
||||
@location-updated=${this._locationChanged}
|
||||
@radius-updated=${this._radiusChanged}
|
||||
></ha-locations-editor>
|
||||
<div class="location">
|
||||
<paper-input
|
||||
.value=${this._latitude}
|
||||
.configValue=${"latitude"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.latitude"
|
||||
)}
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.required_error_msg"
|
||||
)}
|
||||
.invalid=${latInvalid}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.value=${this._longitude}
|
||||
.configValue=${"longitude"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.longitude"
|
||||
)}
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.required_error_msg"
|
||||
)}
|
||||
.invalid=${lngInvalid}
|
||||
></paper-input>
|
||||
</div>
|
||||
<paper-input
|
||||
.value=${this._radius}
|
||||
.configValue=${"radius"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.radius"
|
||||
)}
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.required_error_msg"
|
||||
)}
|
||||
.invalid=${radiusInvalid}
|
||||
></paper-input>
|
||||
<p>
|
||||
${this.hass!.localize("ui.panel.config.zone.detail.passive_note")}
|
||||
</p>
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.passive"
|
||||
)}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${this._passive}
|
||||
@change=${this._passiveChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.schema=${this._schema(this._data.icon)}
|
||||
.data=${this._formData(this._data)}
|
||||
.error=${this._error}
|
||||
.computeLabel=${this._computeLabel}
|
||||
class=${this._data.passive ? "passive" : ""}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-form>
|
||||
</div>
|
||||
${this._params.entry
|
||||
? html`
|
||||
@ -221,74 +123,94 @@ class DialogZoneDetail extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _location = memoizeOne(
|
||||
(
|
||||
lat: number,
|
||||
lng: number,
|
||||
radius: number,
|
||||
passive: boolean,
|
||||
icon: string
|
||||
): MarkerLocation[] => {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const zoneRadiusColor = computedStyles.getPropertyValue("--accent-color");
|
||||
const passiveRadiusColor = computedStyles.getPropertyValue(
|
||||
"--secondary-text-color"
|
||||
);
|
||||
return [
|
||||
private _schema = memoizeOne((icon?: string): HaFormSchema[] => [
|
||||
{
|
||||
name: "name",
|
||||
required: true,
|
||||
selector: {
|
||||
text: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
required: false,
|
||||
selector: {
|
||||
icon: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "location",
|
||||
required: true,
|
||||
selector: { location: { radius: true, icon } },
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
id: "location",
|
||||
latitude: Number(lat),
|
||||
longitude: Number(lng),
|
||||
radius,
|
||||
radius_color: passive ? passiveRadiusColor : zoneRadiusColor,
|
||||
icon,
|
||||
location_editable: true,
|
||||
radius_editable: true,
|
||||
name: "latitude",
|
||||
required: true,
|
||||
selector: { text: {} },
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
{
|
||||
name: "longitude",
|
||||
required: true,
|
||||
|
||||
private _locationChanged(ev: CustomEvent) {
|
||||
[this._latitude, this._longitude] = ev.detail.location;
|
||||
}
|
||||
selector: { text: {} },
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "passive_note", type: "constant" },
|
||||
{ name: "passive", selector: { boolean: {} } },
|
||||
{
|
||||
name: "radius",
|
||||
required: false,
|
||||
selector: { number: { min: 0, max: 999999, mode: "box" } },
|
||||
},
|
||||
]);
|
||||
|
||||
private _radiusChanged(ev: CustomEvent) {
|
||||
this._radius = ev.detail.radius;
|
||||
}
|
||||
|
||||
private _passiveChanged(ev) {
|
||||
this._passive = ev.target.checked;
|
||||
}
|
||||
private _formData = memoizeOne((data: ZoneMutableParams) => ({
|
||||
...data,
|
||||
location: {
|
||||
latitude: data.latitude,
|
||||
longitude: data.longitude,
|
||||
radius: data.radius,
|
||||
},
|
||||
}));
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
const configValue = (ev.target as any).configValue;
|
||||
|
||||
this._error = undefined;
|
||||
this[`_${configValue}`] = ev.detail.value;
|
||||
const value = ev.detail.value;
|
||||
if (
|
||||
value.location.latitude !== this._data!.latitude ||
|
||||
value.location.longitude !== this._data!.longitude ||
|
||||
value.location.radius !== this._data!.radius
|
||||
) {
|
||||
value.latitude = value.location.latitude;
|
||||
value.longitude = value.location.longitude;
|
||||
value.radius = Math.round(value.location.radius);
|
||||
}
|
||||
delete value.location;
|
||||
if (!value.icon) {
|
||||
delete value.icon;
|
||||
}
|
||||
this._data = value;
|
||||
}
|
||||
|
||||
private _computeLabel = (entry: HaFormSchema): string =>
|
||||
this.hass.localize(`ui.panel.config.zone.detail.${entry.name}`);
|
||||
|
||||
private async _updateEntry() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
const values: ZoneMutableParams = {
|
||||
name: this._name.trim(),
|
||||
latitude: this._latitude,
|
||||
longitude: this._longitude,
|
||||
passive: this._passive,
|
||||
radius: this._radius,
|
||||
};
|
||||
if (this._icon) {
|
||||
values.icon = this._icon.trim();
|
||||
}
|
||||
if (this._params!.entry) {
|
||||
await this._params!.updateEntry!(values);
|
||||
await this._params!.updateEntry!(this._data!);
|
||||
} else {
|
||||
await this._params!.createEntry(values);
|
||||
await this._params!.createEntry(this._data!);
|
||||
}
|
||||
this._params = undefined;
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error = err ? err.message : "Unknown error";
|
||||
this._error = { base: err ? err.message : "Unknown error" };
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
@ -309,24 +231,18 @@ class DialogZoneDetail extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.location {
|
||||
display: flex;
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 600px;
|
||||
}
|
||||
.location > * {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: calc(
|
||||
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
|
||||
);
|
||||
}
|
||||
}
|
||||
.location > *:first-child {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.location > *:last-child {
|
||||
margin-left: 4px;
|
||||
}
|
||||
ha-locations-editor {
|
||||
margin-top: 16px;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
ha-form.passive {
|
||||
--zone-radius-color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EditorTarget } from "../editor/types";
|
||||
import "../../../components/ha-textfield";
|
||||
|
||||
@customElement("hui-input-list-editor")
|
||||
export class HuiInputListEditor extends LitElement {
|
||||
@ -23,30 +23,31 @@ export class HuiInputListEditor extends LitElement {
|
||||
return html`
|
||||
${this.value.map(
|
||||
(listEntry, index) => html`
|
||||
<paper-input
|
||||
label=${this.inputLabel}
|
||||
<ha-textfield
|
||||
.label=${this.inputLabel}
|
||||
.value=${listEntry}
|
||||
.configValue=${"entry"}
|
||||
.index=${index}
|
||||
@value-changed=${this._valueChanged}
|
||||
@input=${this._valueChanged}
|
||||
@blur=${this._consolidateEntries}
|
||||
@keydown=${this._handleKeyDown}
|
||||
iconTrailing
|
||||
><ha-icon-button
|
||||
slot="suffix"
|
||||
slot="trailingIcon"
|
||||
class="clear-button"
|
||||
.path=${mdiClose}
|
||||
no-ripple
|
||||
@click=${this._removeEntry}
|
||||
.label=${this.hass!.localize("ui.common.clear")}
|
||||
>Clear</ha-icon-button
|
||||
></paper-input
|
||||
>
|
||||
>
|
||||
</ha-icon-button>
|
||||
</ha-textfield>
|
||||
`
|
||||
)}
|
||||
<paper-input
|
||||
label=${this.inputLabel}
|
||||
<ha-textfield
|
||||
.label=${this.inputLabel}
|
||||
@change=${this._addEntry}
|
||||
></paper-input>
|
||||
></ha-textfield>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -103,10 +104,12 @@ export class HuiInputListEditor extends LitElement {
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
margin-right: -24px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import {
|
||||
@ -24,7 +23,7 @@ import { EntityConfig } from "../../entity-rows/types";
|
||||
import { LovelaceCardEditor } from "../../types";
|
||||
import { processEditorEntities } from "../process-editor-entities";
|
||||
import { entitiesConfigStruct } from "../structs/entities-struct";
|
||||
import { EditorTarget, EntitiesEditorEvent } from "../types";
|
||||
import { EntitiesEditorEvent } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { HaFormSchema } from "../../../../components/ha-form/types";
|
||||
@ -127,10 +126,6 @@ export class HuiMapCardEditor extends LitElement implements LovelaceCardEditor {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
const target = ev.target! as EditorTarget;
|
||||
if (!target.configValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = ev.detail.value;
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { assert, object, optional, string, assign } from "superstruct";
|
||||
|
@ -153,7 +153,7 @@ export class HuiStatisticsGraphCardEditor
|
||||
.pickedStatisticLabel=${`Statistic`}
|
||||
.value=${this._configEntities}
|
||||
.configValue=${"entities"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@value-changed=${this._entitiesChanged}
|
||||
></ha-statistics-picker>
|
||||
</div>
|
||||
`;
|
||||
@ -163,6 +163,12 @@ export class HuiStatisticsGraphCardEditor
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
}
|
||||
|
||||
private _entitiesChanged(ev: CustomEvent): void {
|
||||
fireEvent(this, "config-changed", {
|
||||
config: { ...this._config!, entities: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (schema: HaFormSchema) =>
|
||||
this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.generic.${schema.name}`
|
||||
|
@ -87,6 +87,7 @@ class HuiInputSelectEntityRow extends LitElement implements LovelaceRow {
|
||||
}
|
||||
ha-select {
|
||||
width: 100%;
|
||||
--ha-select-min-width: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -2605,6 +2605,7 @@
|
||||
"finish": "Finish",
|
||||
"submit": "Submit",
|
||||
"next": "Next",
|
||||
"no_config_flow": "This integration does not support configuration via the UI. If you followed this link from the Home Assistant website, make sure you run the latest version of Home Assistant.",
|
||||
"not_all_required_fields": "Not all required fields are filled in.",
|
||||
"error_saving_area": "Error saving area: {error}",
|
||||
"created_config": "Created configuration for {name}.",
|
||||
|
Loading…
x
Reference in New Issue
Block a user