Migrate add/edit resources dialog to @material/web (#21933)
* 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 <mail@bramkragten.nl> * 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 <mail@bramkragten.nl>pull/21994/head
parent
ab91a4b814
commit
9e4dc0d39e
|
@ -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) {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { mdiPlus } from "@mdi/js";
|
||||
import { mdiDelete, mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
|
@ -109,6 +109,20 @@ export class HaConfigLovelaceRescources extends LitElement {
|
|||
) || resource.type}
|
||||
`,
|
||||
},
|
||||
delete: {
|
||||
title: "",
|
||||
type: "icon-button",
|
||||
minWidth: "48px",
|
||||
maxWidth: "48px",
|
||||
showNarrow: true,
|
||||
template: (resource) =>
|
||||
html`<ha-icon-button
|
||||
@click=${this._removeResource}
|
||||
.label=${this.hass.localize("ui.common.delete")}
|
||||
.path=${mdiDelete}
|
||||
.resource=${resource}
|
||||
></ha-icon-button>`,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -235,46 +249,49 @@ export class HaConfigLovelaceRescources extends LitElement {
|
|||
);
|
||||
loadLovelaceResources([updated], this.hass!);
|
||||
},
|
||||
removeResource: async () => {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.confirm_delete_title"
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.confirm_delete_text",
|
||||
{ url: resource!.url }
|
||||
),
|
||||
dismissText: this.hass!.localize("ui.common.cancel"),
|
||||
confirmText: this.hass!.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
}))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteResource(this.hass!, resource!.id);
|
||||
this._resources = this._resources!.filter((res) => res !== resource);
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.refresh_header"
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.refresh_body"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.refresh"),
|
||||
dismissText: this.hass.localize("ui.common.not_now"),
|
||||
confirm: () => location.reload(),
|
||||
});
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _removeResource = async (event: any) => {
|
||||
const resource = event.currentTarget.resource as LovelaceResource;
|
||||
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.confirm_delete_title"
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.confirm_delete_text",
|
||||
{ url: resource.url }
|
||||
),
|
||||
dismissText: this.hass!.localize("ui.common.cancel"),
|
||||
confirmText: this.hass!.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
}))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteResource(this.hass!, resource.id);
|
||||
this._resources = this._resources!.filter(({ id }) => id !== resource.id);
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.refresh_header"
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.refresh_body"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.refresh"),
|
||||
dismissText: this.hass.localize("ui.common.not_now"),
|
||||
confirm: () => location.reload(),
|
||||
});
|
||||
return true;
|
||||
} catch (err: any) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
private _handleSortingChanged(ev: CustomEvent) {
|
||||
this._activeSorting = ev.detail;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ export interface LovelaceResourceDetailsDialogParams {
|
|||
updateResource: (
|
||||
updates: Partial<LovelaceResourcesMutableParams>
|
||||
) => Promise<unknown>;
|
||||
removeResource: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const loadResourceDetailDialog = () =>
|
||||
|
|
|
@ -2584,6 +2584,7 @@
|
|||
"cant_edit_yaml": "You are using your dashboard in YAML mode, therefore you cannot manage your resources through the UI. Manage them in configuration.yaml.",
|
||||
"detail": {
|
||||
"new_resource": "Add new resource",
|
||||
"edit_resource": "Edit resource",
|
||||
"dismiss": "Close",
|
||||
"warning_header": "Be cautious!",
|
||||
"warning_text": "Adding resources can be dangerous, make sure you know the source of the resource and trust them. Bad resources could seriously harm your system.",
|
||||
|
|
|
@ -7033,6 +7033,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dialog-polyfill@npm:0.5.6":
|
||||
version: 0.5.6
|
||||
resolution: "dialog-polyfill@npm:0.5.6"
|
||||
checksum: 10/42428793b04fd2e0a67dfb75838703488d7d05f73663c3251441ad6ed154b8dc71d65ed03d5a0ba4a83c6167c2e6f791cbe1574d0dca37dac1405ce3816033ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"didyoumean2@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "didyoumean2@npm:4.1.0"
|
||||
|
@ -9013,6 +9020,7 @@ __metadata:
|
|||
deep-clone-simple: "npm:1.1.1"
|
||||
deep-freeze: "npm:0.0.1"
|
||||
del: "npm:7.1.0"
|
||||
dialog-polyfill: "npm:0.5.6"
|
||||
element-internals-polyfill: "npm:1.3.11"
|
||||
eslint: "npm:8.57.0"
|
||||
eslint-config-airbnb-base: "npm:15.0.0"
|
||||
|
|
Loading…
Reference in New Issue