Convert lovelace config dialogs to ha-form (#11910)

This commit is contained in:
Bram Kragten 2022-03-04 23:15:10 +01:00 committed by GitHub
parent 8f8017ecff
commit adefc7a4e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 240 additions and 277 deletions

View File

@ -1,20 +1,18 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../common/dom/fire_event";
import { slugify } from "../../../../common/string/slugify"; import { slugify } from "../../../../common/string/slugify";
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
import { createCloseHeading } from "../../../../components/ha-dialog"; import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-formfield"; import "../../../../components/ha-form/ha-form";
import "../../../../components/ha-icon-picker"; import { HaFormSchema } from "../../../../components/ha-form/types";
import "../../../../components/ha-switch"; import { CoreFrontendUserData } from "../../../../data/frontend";
import type { HaSwitch } from "../../../../components/ha-switch";
import { import {
LovelaceDashboard, LovelaceDashboard,
LovelaceDashboardCreateParams, LovelaceDashboardCreateParams,
LovelaceDashboardMutableParams,
} from "../../../../data/lovelace"; } from "../../../../data/lovelace";
import { DEFAULT_PANEL, setDefaultPanel } from "../../../../data/panel"; import { DEFAULT_PANEL, setDefaultPanel } from "../../../../data/panel";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { haStyleDialog } from "../../../../resources/styles"; import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail"; import { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
@ -25,62 +23,54 @@ export class DialogLovelaceDashboardDetail extends LitElement {
@state() private _params?: LovelaceDashboardDetailsDialogParams; @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 _error?: Record<string, string>;
@state() private _title!: string;
@state()
private _requireAdmin!: LovelaceDashboard["require_admin"];
@state() private _error?: string;
@state() private _submitting = false; @state() private _submitting = false;
public async showDialog( public showDialog(params: LovelaceDashboardDetailsDialogParams): void {
params: LovelaceDashboardDetailsDialogParams
): Promise<void> {
this._params = params; this._params = params;
this._error = undefined; this._error = undefined;
this._urlPath = this._params.urlPath || ""; this._urlPathChanged = false;
if (this._params.dashboard) { if (this._params.dashboard) {
this._showInSidebar = !!this._params.dashboard.show_in_sidebar; this._data = this._params.dashboard;
this._icon = this._params.dashboard.icon || "";
this._title = this._params.dashboard.title || "";
this._requireAdmin = this._params.dashboard.require_admin || false;
} else { } else {
this._showInSidebar = true; this._data = {
this._icon = ""; show_in_sidebar: true,
this._title = ""; icon: "",
this._requireAdmin = false; 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 { protected render(): TemplateResult {
if (!this._params) { if (!this._params || !this._data) {
return html``; return html``;
} }
const defaultPanelUrlPath = this.hass.defaultPanel; const defaultPanelUrlPath = this.hass.defaultPanel;
const urlInvalid = const titleInvalid = !this._data.title || !this._data.title.trim();
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);
return html` return html`
<ha-dialog <ha-dialog
open open
@closed=${this._close} @closed=${this.closeDialog}
scrimClickAction scrimClickAction
escapeKeyAction escapeKeyAction
.heading=${createCloseHeading( .heading=${createCloseHeading(
this.hass, this.hass,
this._params.urlPath this._params.urlPath
? this._title || ? this._data.title ||
this.hass.localize( this.hass.localize(
"ui.panel.config.lovelace.dashboards.detail.edit_dashboard" "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" "ui.panel.config.lovelace.dashboards.cant_edit_default"
) )
: html` : html`
${this._error <ha-form
? html` <div class="error">${this._error}</div> ` .schema=${this._schema(this._params, this.hass.userData)}
: ""} .data=${this._data}
<div class="form"> .hass=${this.hass}
<paper-input .error=${this._error}
.value=${this._title} .computeLabel=${this._computeLabel}
@value-changed=${this._titleChanged} @value-changed=${this._valueChanged}
.label=${this.hass.localize( ></ha-form>
"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>
`} `}
</div> </div>
${this._params.urlPath ${this._params.urlPath
@ -206,7 +134,9 @@ export class DialogLovelaceDashboardDetail extends LitElement {
<mwc-button <mwc-button
slot="primaryAction" slot="primaryAction"
@click=${this._updateDashboard} @click=${this._updateDashboard}
.disabled=${urlInvalid || titleInvalid || this._submitting} .disabled=${(this._error && "url_path" in this._error) ||
titleInvalid ||
this._submitting}
dialogInitialFocus dialogInitialFocus
> >
${this._params.urlPath ${this._params.urlPath
@ -223,41 +153,97 @@ export class DialogLovelaceDashboardDetail extends LitElement {
`; `;
} }
private _urlChanged(ev: PolymerChangedEvent<string>) { private _schema = memoizeOne(
this._error = undefined; (
this._urlPath = ev.detail.value; 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>) { private _computeLabel = (entry: HaFormSchema): string =>
this._error = undefined; this.hass.localize(
this._icon = ev.detail.value; `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._error = undefined;
this._title = ev.detail.value; const value = ev.detail.value;
if (!this.hass.userData?.showAdvanced) { if (value.url_path !== this._data?.url_path) {
this._fillUrlPath(); 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() { private _fillUrlPath(title: string) {
if ((this.hass.userData?.showAdvanced && this._urlPath) || !this._title) { if ((this.hass.userData?.showAdvanced && this._urlPathChanged) || !title) {
return; return;
} }
const slugifyTitle = slugify(this._title, "-"); const slugifyTitle = slugify(title, "-");
this._urlPath = slugifyTitle.includes("-") this._data = {
...this._data,
url_path: slugifyTitle.includes("-")
? slugifyTitle ? slugifyTitle
: `lovelace-${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;
} }
private _toggleDefault() { private _toggleDefault() {
@ -273,29 +259,20 @@ export class DialogLovelaceDashboardDetail extends LitElement {
private async _updateDashboard() { private async _updateDashboard() {
if (this._params?.urlPath && !this._params.dashboard?.id) { if (this._params?.urlPath && !this._params.dashboard?.id) {
this._close(); this.closeDialog();
} }
this._submitting = true; this._submitting = true;
try { 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) { if (this._params!.dashboard) {
await this._params!.updateDashboard(values); await this._params!.updateDashboard(this._data as LovelaceDashboard);
} else { } else {
(values as LovelaceDashboardCreateParams).url_path =
this._urlPath.trim();
(values as LovelaceDashboardCreateParams).mode = "storage";
await this._params!.createDashboard( await this._params!.createDashboard(
values as LovelaceDashboardCreateParams this._data as LovelaceDashboardCreateParams
); );
} }
this._close(); this.closeDialog();
} catch (err: any) { } catch (err: any) {
this._error = err?.message || "Unknown error"; this._error = { base: err?.message || "Unknown error" };
} finally { } finally {
this._submitting = false; this._submitting = false;
} }
@ -305,26 +282,15 @@ export class DialogLovelaceDashboardDetail extends LitElement {
this._submitting = true; this._submitting = true;
try { try {
if (await this._params!.removeDashboard()) { if (await this._params!.removeDashboard()) {
this._close(); this.closeDialog();
} }
} finally { } finally {
this._submitting = false; this._submitting = false;
} }
} }
private _close(): void {
this._params = undefined;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [haStyleDialog, css``];
haStyleDialog,
css`
ha-switch {
padding: 16px 0;
}
`,
];
} }
} }

View File

@ -1,21 +1,20 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import "@material/mwc-list/mwc-list-item"; import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; 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 { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-select"; import "../../../../components/ha-form/ha-form";
import { import { HaFormSchema } from "../../../../components/ha-form/types";
LovelaceResource, import { LovelaceResourcesMutableParams } from "../../../../data/lovelace";
LovelaceResourcesMutableParams,
} from "../../../../data/lovelace";
import { PolymerChangedEvent } from "../../../../polymer-types";
import { haStyleDialog } from "../../../../resources/styles"; import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types"; import { HomeAssistant } from "../../../../types";
import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail"; 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() || ""; const ext = url.split(".").pop() || "";
if (ext === "css") { if (ext === "css") {
@ -35,38 +34,41 @@ export class DialogLovelaceResourceDetail extends LitElement {
@state() private _params?: LovelaceResourceDetailsDialogParams; @state() private _params?: LovelaceResourceDetailsDialogParams;
@state() private _url!: LovelaceResource["url"]; @state() private _data?: Partial<LovelaceResourcesMutableParams>;
@state() private _type?: LovelaceResource["type"]; @state() private _error?: Record<string, string>;
@state() private _error?: string;
@state() private _submitting = false; @state() private _submitting = false;
public async showDialog( public showDialog(params: LovelaceResourceDetailsDialogParams): void {
params: LovelaceResourceDetailsDialogParams
): Promise<void> {
this._params = params; this._params = params;
this._error = undefined; this._error = undefined;
if (this._params.resource) { if (this._params.resource) {
this._url = this._params.resource.url || ""; this._data = {
this._type = this._params.resource.type || undefined; url: this._params.resource.url,
res_type: this._params.resource.type,
};
} else { } else {
this._url = ""; this._data = {
this._type = undefined; url: "",
};
} }
await this.updateComplete; }
public closeDialog(): void {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._params) { if (!this._params) {
return html``; return html``;
} }
const urlInvalid = this._url.trim() === ""; const urlInvalid = !this._data?.url || this._data.url.trim() === "";
return html` return html`
<ha-dialog <ha-dialog
open open
@closed=${this._close} @closed=${this.closeDialog}
scrimClickAction scrimClickAction
escapeKeyAction escapeKeyAction
.heading=${createCloseHeading( .heading=${createCloseHeading(
@ -79,68 +81,25 @@ export class DialogLovelaceResourceDetail extends LitElement {
)} )}
> >
<div> <div>
${this._error ? html` <div class="error">${this._error}</div> ` : ""} <ha-alert
<div class="form"> alert-type="warning"
<h3 class="warning"> .title=${this.hass!.localize(
${this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.warning_header" "ui.panel.config.lovelace.resources.detail.warning_header"
)} )}
</h3> >
${this.hass!.localize( ${this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.warning_text" "ui.panel.config.lovelace.resources.detail.warning_text"
)} )}
<paper-input </ha-alert>
.value=${this._url}
@value-changed=${this._urlChanged} <ha-form
.label=${this.hass!.localize( .schema=${this._schema(this._data)}
"ui.panel.config.lovelace.resources.detail.url" .data=${this._data}
)} .hass=${this.hass}
.errorMessage=${this.hass!.localize( .error=${this._error}
"ui.panel.config.lovelace.resources.detail.url_error_msg" .computeLabel=${this._computeLabel}
)} @value-changed=${this._valueChanged}
.invalid=${urlInvalid} ></ha-form>
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>
</div> </div>
${this._params.resource ${this._params.resource
? html` ? html`
@ -159,7 +118,7 @@ export class DialogLovelaceResourceDetail extends LitElement {
<mwc-button <mwc-button
slot="primaryAction" slot="primaryAction"
@click=${this._updateResource} @click=${this._updateResource}
.disabled=${urlInvalid || !this._type || this._submitting} .disabled=${urlInvalid || !this._data?.res_type || this._submitting}
> >
${this._params.resource ${this._params.resource
? this.hass!.localize( ? this.hass!.localize(
@ -173,37 +132,86 @@ export class DialogLovelaceResourceDetail extends LitElement {
`; `;
} }
private _urlChanged(ev: PolymerChangedEvent<string>) { private _schema = memoizeOne((data) => [
this._error = undefined; {
this._url = ev.detail.value; name: "url",
if (!this._type) { required: true,
this._type = detectResourceType(this._url); 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 _typeChanged(ev) { private _computeLabel = (entry: HaFormSchema): string =>
this._type = ev.target.value; 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 async _updateResource() { private async _updateResource() {
if (!this._type) { if (!this._data?.res_type) {
return; return;
} }
this._submitting = true; this._submitting = true;
try { try {
const values: LovelaceResourcesMutableParams = {
url: this._url.trim(),
res_type: this._type,
};
if (this._params!.resource) { if (this._params!.resource) {
await this._params!.updateResource(values); await this._params!.updateResource(this._data!);
} else { } else {
await this._params!.createResource(values); await this._params!.createResource(
this._data! as LovelaceResourcesMutableParams
);
} }
this._params = undefined; this._params = undefined;
} catch (err: any) { } catch (err: any) {
this._error = err?.message || "Unknown error"; this._error = { base: err?.message || "Unknown error" };
} finally { } finally {
this._submitting = false; this._submitting = false;
} }
@ -213,26 +221,15 @@ export class DialogLovelaceResourceDetail extends LitElement {
this._submitting = true; this._submitting = true;
try { try {
if (await this._params!.removeResource()) { if (await this._params!.removeResource()) {
this._close(); this.closeDialog();
} }
} finally { } finally {
this._submitting = false; this._submitting = false;
} }
} }
private _close(): void {
this._params = undefined;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return haStyleDialog;
haStyleDialog,
css`
.warning {
color: var(--error-color);
}
`,
];
} }
} }