Skip to content

Commit

Permalink
Migrate add/edit resources dialog to @material/web (#21933)
Browse files Browse the repository at this point in the history
* Remove dashboard resources options from advanced mode

* Add ha-dialog-new, use it for dashboard resources

* Add ha-dialog-new shake; Move resources delete to table

* Improve ha-dialog-new, resource-detail

* Rename ha-dialog-new to ha-md-dialog

* Update src/panels/config/lovelace/resources/dialog-lovelace-resource-detail.ts

Fix dialogClosed method naming

Co-authored-by: Bram Kragten <[email protected]>

* Add ha-md-dialog polyfill

* Fix ha-md-dialog for iOS 12

* Fix ha-md-dialog polyfill loading

* Fix ha-md-dialog open prop

* Fix ha-md-dialog legacy loading

* Improve ha-md-dialog legacy loading

* Fix multiple polyfill loads in ha-md-dialog

* Fix polyfill handleOpen in ha-md-dialog

* Improve polyfill handleOpen in ha-md-dialog

* Improve polyfill handleOpen ordering in ha-md-dialog

---------

Co-authored-by: Bram Kragten <[email protected]>
  • Loading branch information
wendevlin and bramkragten authored Sep 16, 2024
1 parent ab91a4b commit 9e4dc0d
Show file tree
Hide file tree
Showing 9 changed files with 287 additions and 115 deletions.
6 changes: 6 additions & 0 deletions build-scripts/gulp/gather-static.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ function copyPolyfills(staticDir) {
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js.map"),
staticPath("polyfills/")
);

// dialog-polyfill css
copyFileDir(
npmPath("dialog-polyfill/dialog-polyfill.css"),
staticPath("polyfills/")
);
}

function copyLoaderJS(staticDir) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"date-fns-tz": "3.1.3",
"deep-clone-simple": "1.1.1",
"deep-freeze": "0.0.1",
"dialog-polyfill": "0.5.6",
"element-internals-polyfill": "1.3.11",
"fuse.js": "7.0.0",
"google-timezones-json": "1.2.0",
Expand Down
146 changes: 146 additions & 0 deletions src/components/ha-md-dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { MdDialog } from "@material/web/dialog/dialog";
import { css } from "lit";
import { customElement, property } from "lit/decorators";

let DIALOG_POLYFILL: Promise<typeof import("dialog-polyfill")>;

/**
* Based on the home assistant design: https://design.home-assistant.io/#components/ha-dialogs
*
*/
@customElement("ha-md-dialog")
export class HaMdDialog extends MdDialog {
/**
* When true the dialog will not close when the user presses the esc key or press out of the dialog.
*/
@property({ attribute: "disable-cancel-action", type: Boolean })
public disableCancelAction = false;

private _polyfillDialogRegistered = false;

constructor() {
super();

this.addEventListener("cancel", this._handleCancel);

if (typeof HTMLDialogElement !== "function") {
this.addEventListener("open", this._handleOpen);

if (!DIALOG_POLYFILL) {
DIALOG_POLYFILL = import("dialog-polyfill");
}
}

// if browser doesn't support animate API disable open/close animations
if (this.animate === undefined) {
this.quick = true;
}
}

// prevent open in older browsers and wait for polyfill to load
private async _handleOpen(openEvent: Event) {
openEvent.preventDefault();

if (this._polyfillDialogRegistered) {
return;
}

this._polyfillDialogRegistered = true;
this._loadPolyfillStylesheet("/static/polyfills/dialog-polyfill.css");
const dialog = this.shadowRoot?.querySelector(
"dialog"
) as HTMLDialogElement;

const dialogPolyfill = await DIALOG_POLYFILL;
dialogPolyfill.default.registerDialog(dialog);
this.removeEventListener("open", this._handleOpen);

this.show();
}

private async _loadPolyfillStylesheet(href) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = href;

return new Promise<void>((resolve, reject) => {
link.onload = () => resolve();
link.onerror = () =>
reject(new Error(`Stylesheet failed to load: ${href}`));

this.shadowRoot?.appendChild(link);
});
}

_handleCancel(closeEvent: Event) {
if (this.disableCancelAction) {
closeEvent.preventDefault();
const dialogElement = this.shadowRoot?.querySelector("dialog");
if (this.animate !== undefined) {
dialogElement?.animate(
[
{
transform: "rotate(-1deg)",
"animation-timing-function": "ease-in",
},
{
transform: "rotate(1.5deg)",
"animation-timing-function": "ease-out",
},
{
transform: "rotate(0deg)",
"animation-timing-function": "ease-in",
},
],
{
duration: 200,
iterations: 2,
}
);
}
}
}

static override styles = [
...super.styles,
css`
:host {
--md-dialog-container-color: var(--card-background-color);
--md-dialog-headline-color: var(--primary-text-color);
--md-dialog-supporting-text-color: var(--primary-text-color);
--md-sys-color-scrim: #000000;
--md-dialog-headline-weight: 400;
--md-dialog-headline-size: 1.574rem;
--md-dialog-supporting-text-size: 1rem;
--md-dialog-supporting-text-line-height: 1.5rem;
@media all and (max-width: 450px), all and (max-height: 500px) {
min-width: calc(
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
);
max-width: calc(
100vw - env(safe-area-inset-right) - env(safe-area-inset-left)
);
min-height: 100%;
max-height: 100%;
border-radius: 0;
}
}
:host ::slotted(ha-dialog-header) {
display: contents;
}
.scrim {
z-index: 10; // overlay navigation
}
`,
];
}

declare global {
interface HTMLElementTagNameMap {
"ha-md-dialog": HaMdDialog;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -322,22 +322,16 @@ export class HaConfigLovelaceDashboards extends LitElement {
hasFab
clickable
>
${this.hass.userData?.showAdvanced
? html`
<ha-button-menu slot="toolbar-icon" activatable>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-clickable-list-item href="/config/lovelace/resources">
${this.hass.localize(
"ui.panel.config.lovelace.resources.caption"
)}
</ha-clickable-list-item>
</ha-button-menu>
`
: ""}
<ha-button-menu slot="toolbar-icon" activatable>
<ha-icon-button
slot="trigger"
.label=${this.hass.localize("ui.common.menu")}
.path=${mdiDotsVertical}
></ha-icon-button>
<ha-clickable-list-item href="/config/lovelace/resources">
${this.hass.localize("ui.panel.config.lovelace.resources.caption")}
</ha-clickable-list-item>
</ha-button-menu>
<ha-fab
slot="fab"
.label=${this.hass.localize(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import "@material/mwc-button/mwc-button";
import { CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { html, LitElement, nothing } from "lit";
import { customElement, property, state, query } from "lit/decorators";
import memoizeOne from "memoize-one";
import { mdiClose } from "@mdi/js";
import { fireEvent } from "../../../../common/dom/fire_event";
import { createCloseHeading } from "../../../../components/ha-dialog";
import "../../../../components/ha-md-dialog";
import type { HaMdDialog } from "../../../../components/ha-md-dialog";
import "../../../../components/ha-dialog-header";
import "../../../../components/ha-form/ha-form";
import "../../../../components/ha-icon-button";
import { SchemaUnion } from "../../../../components/ha-form/types";
import { LovelaceResourcesMutableParams } from "../../../../data/lovelace/resource";
import { haStyleDialog } from "../../../../resources/styles";
import { HomeAssistant } from "../../../../types";
import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail";

Expand Down Expand Up @@ -40,6 +43,8 @@ export class DialogLovelaceResourceDetail extends LitElement {

@state() private _submitting = false;

@query("ha-md-dialog") private _dialog?: HaMdDialog;

public showDialog(params: LovelaceResourceDetailsDialogParams): void {
this._params = params;
this._error = undefined;
Expand All @@ -55,32 +60,52 @@ export class DialogLovelaceResourceDetail extends LitElement {
}
}

public closeDialog(): void {
private _dialogClosed(): void {
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}

public closeDialog(): void {
this._dialog?.close();
}

protected render() {
if (!this._params) {
return nothing;
}
const urlInvalid = !this._data?.url || this._data.url.trim() === "";

const dialogTitle =
this._params.resource?.url ||
this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.new_resource"
);

const ariaLabel = this._params.resource?.url
? this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.edit_resource"
)
: this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.new_resource"
);

return html`
<ha-dialog
<ha-md-dialog
open
@closed=${this.closeDialog}
scrimClickAction
escapeKeyAction
.heading=${createCloseHeading(
this.hass,
this._params.resource
? this._params.resource.url
: this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.new_resource"
)
)}
disable-cancel-action
@closed=${this._dialogClosed}
.ariaLabel=${ariaLabel}
>
<div>
<ha-dialog-header slot="headline">
<ha-icon-button
slot="navigationIcon"
.label=${this.hass.localize("ui.dialogs.generic.close") ?? "Close"}
.path=${mdiClose}
@click=${this.closeDialog}
></ha-icon-button>
<span slot="title" .title=${dialogTitle}> ${dialogTitle} </span>
</ha-dialog-header>
<div slot="content">
<ha-alert
alert-type="warning"
.title=${this.hass!.localize(
Expand All @@ -101,34 +126,24 @@ export class DialogLovelaceResourceDetail extends LitElement {
@value-changed=${this._valueChanged}
></ha-form>
</div>
${this._params.resource
? html`
<mwc-button
slot="secondaryAction"
class="warning"
@click=${this._deleteResource}
.disabled=${this._submitting}
>
${this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.delete"
<div slot="actions">
<mwc-button @click=${this.closeDialog}>
${this.hass!.localize("ui.common.cancel")}
</mwc-button>
<mwc-button
@click=${this._updateResource}
.disabled=${urlInvalid || !this._data?.res_type || this._submitting}
>
${this._params.resource
? this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.update"
)
: this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.create"
)}
</mwc-button>
`
: nothing}
<mwc-button
slot="primaryAction"
@click=${this._updateResource}
.disabled=${urlInvalid || !this._data?.res_type || this._submitting}
>
${this._params.resource
? this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.update"
)
: this.hass!.localize(
"ui.panel.config.lovelace.resources.detail.create"
)}
</mwc-button>
</ha-dialog>
</mwc-button>
</div>
</ha-md-dialog>
`;
}

Expand Down Expand Up @@ -231,21 +246,6 @@ export class DialogLovelaceResourceDetail extends LitElement {
this._submitting = false;
}
}

private async _deleteResource() {
this._submitting = true;
try {
if (await this._params!.removeResource()) {
this.closeDialog();
}
} finally {
this._submitting = false;
}
}

static get styles(): CSSResultGroup {
return haStyleDialog;
}
}

declare global {
Expand Down
Loading

0 comments on commit 9e4dc0d

Please sign in to comment.