From 91777d45b0b7af10c4eecf580b20978777294a03 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 5 Dec 2024 19:49:27 +0100 Subject: [PATCH] Prevent leaving the editor if there are unsaved changes --- src/mixins/prevent-unsaved-mixin.ts | 55 +++++++++++++++++++ .../config/automation/ha-automation-editor.ts | 13 ++++- src/panels/config/scene/ha-scene-editor.ts | 13 ++++- src/panels/config/script/ha-script-editor.ts | 13 ++++- 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 src/mixins/prevent-unsaved-mixin.ts diff --git a/src/mixins/prevent-unsaved-mixin.ts b/src/mixins/prevent-unsaved-mixin.ts new file mode 100644 index 0000000000..7bc4ce8a91 --- /dev/null +++ b/src/mixins/prevent-unsaved-mixin.ts @@ -0,0 +1,55 @@ +import type { LitElement } from "lit"; +import type { Constructor } from "../types"; +import { isNavigationClick } from "../common/dom/is-navigation-click"; +import { navigate } from "../common/navigate"; + +export const PreventUnsavedMixin = >( + superClass: T +) => + class extends superClass { + private _handleClick = async (e: MouseEvent) => { + const href = isNavigationClick(e); + + if (!href) { + return; + } + + e.preventDefault(); + const result = await this.promptDiscardChanges(); + if (result) { + navigate(href); + } + }; + + private _handleUnload = (e: BeforeUnloadEvent) => { + if (this.isDirty()) { + e.preventDefault(); + } + }; + + public connectedCallback(): void { + super.connectedCallback(); + + document.body.addEventListener("mousedown", this._handleClick, { + capture: true, + }); + window.addEventListener("beforeunload", this._handleUnload); + } + + public disconnectedCallback(): void { + super.disconnectedCallback(); + + document.body.removeEventListener("click", this._handleClick, { + capture: true, + }); + window.removeEventListener("beforeunload", this._handleUnload); + } + + protected isDirty(): boolean { + return false; + } + + protected async promptDiscardChanges(): Promise { + return true; + } + }; diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index de9ef7e788..fd2c514e43 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -64,6 +64,7 @@ import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-a import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename"; import "./blueprint-automation-editor"; import "./manual-automation-editor"; +import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin"; declare global { interface HTMLElementTagNameMap { @@ -82,7 +83,9 @@ declare global { } } -export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { +export class HaAutomationEditor extends PreventUnsavedMixin( + KeyboardShortcutMixin(LitElement) +) { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public automationId: string | null = null; @@ -847,6 +850,14 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) { }; } + protected isDirty() { + return this._dirty; + } + + protected async promptDiscardChanges() { + return this.confirmUnsavedChanged(); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/scene/ha-scene-editor.ts b/src/panels/config/scene/ha-scene-editor.ts index ddcb1ca7e8..f34aeff1a1 100644 --- a/src/panels/config/scene/ha-scene-editor.ts +++ b/src/panels/config/scene/ha-scene-editor.ts @@ -77,6 +77,7 @@ import { haStyle } from "../../../resources/styles"; import type { HomeAssistant, Route } from "../../../types"; import { showToast } from "../../../util/toast"; import "../ha-config-section"; +import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin"; interface DeviceEntities { id: string; @@ -89,8 +90,8 @@ interface DeviceEntitiesLookup { } @customElement("ha-scene-editor") -export class HaSceneEditor extends SubscribeMixin( - KeyboardShortcutMixin(LitElement) +export class HaSceneEditor extends PreventUnsavedMixin( + SubscribeMixin(KeyboardShortcutMixin(LitElement)) ) { @property({ attribute: false }) public hass!: HomeAssistant; @@ -1225,6 +1226,14 @@ export class HaSceneEditor extends SubscribeMixin( }); } + protected isDirty() { + return this._dirty; + } + + protected async promptDiscardChanges() { + return this.confirmUnsavedChanged(); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index 0337604713..a7ab8c131c 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -57,8 +57,11 @@ import "./blueprint-script-editor"; import "./manual-script-editor"; import type { HaManualScriptEditor } from "./manual-script-editor"; import { substituteBlueprint } from "../../../data/blueprint"; +import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin"; -export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { +export class HaScriptEditor extends PreventUnsavedMixin( + KeyboardShortcutMixin(LitElement) +) { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public scriptId: string | null = null; @@ -813,6 +816,14 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) { }; } + protected isDirty() { + return this._dirty; + } + + protected async promptDiscardChanges() { + return this.confirmUnsavedChanged(); + } + static get styles(): CSSResultGroup { return [ haStyle,