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 { 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``];
}
}

View File

@ -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;
}
}