Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate add/edit resources dialog to @material/web #21933

Merged
merged 21 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b932a70
Remove dashboard resources options from advanced mode
wendevlin Sep 5, 2024
5bbb6d3
Add ha-dialog-new, use it for dashboard resources
wendevlin Sep 5, 2024
7865137
Merge branch 'dev' of github.com:home-assistant/frontend into add-edi…
wendevlin Sep 9, 2024
c0df09d
Add ha-dialog-new shake; Move resources delete to table
wendevlin Sep 9, 2024
b852100
Improve ha-dialog-new, resource-detail
wendevlin Sep 10, 2024
a4cc65d
Merge branch 'dev' of github.com:home-assistant/frontend into add-edi…
wendevlin Sep 10, 2024
2d81667
Rename ha-dialog-new to ha-md-dialog
wendevlin Sep 11, 2024
c65d22a
Update src/panels/config/lovelace/resources/dialog-lovelace-resource-…
wendevlin Sep 11, 2024
e6b2010
Add ha-md-dialog polyfill
wendevlin Sep 12, 2024
a07a2a0
Fix ha-md-dialog for iOS 12
wendevlin Sep 12, 2024
b87cbdf
Merge branch 'dev' of github.com:home-assistant/frontend into add-edi…
wendevlin Sep 13, 2024
e46ff38
Fix ha-md-dialog polyfill loading
wendevlin Sep 13, 2024
0cdda82
Fix ha-md-dialog open prop
wendevlin Sep 13, 2024
d040a71
Fix ha-md-dialog legacy loading
wendevlin Sep 13, 2024
7e9cc39
Merge branch 'dev' of github.com:home-assistant/frontend into add-edi…
wendevlin Sep 13, 2024
1b2bd99
Improve ha-md-dialog legacy loading
wendevlin Sep 13, 2024
67379da
Merge branch 'dev' of github.com:home-assistant/frontend into add-edi…
wendevlin Sep 16, 2024
5df1580
Fix multiple polyfill loads in ha-md-dialog
wendevlin Sep 16, 2024
69e85f2
Fix polyfill handleOpen in ha-md-dialog
wendevlin Sep 16, 2024
2678c8e
Improve polyfill handleOpen in ha-md-dialog
wendevlin Sep 16, 2024
82c2144
Improve polyfill handleOpen ordering in ha-md-dialog
wendevlin Sep 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
147 changes: 147 additions & 0 deletions src/components/ha-md-dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
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);

wendevlin marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}

private async _handleOpen(openEvent: Event) {
// prevent open in older browsers and wait for polyfill to load
if (
typeof HTMLDialogElement !== "function" &&
!this._polyfillDialogRegistered
wendevlin marked this conversation as resolved.
Show resolved Hide resolved
) {
openEvent.preventDefault();

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._polyfillDialogRegistered = true;
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) {
wendevlin marked this conversation as resolved.
Show resolved Hide resolved
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
Loading