Skip to content

Commit

Permalink
Prevent leaving the editor if there are unsaved changes (#23170)
Browse files Browse the repository at this point in the history
* Prevent leaving the editor if there are unsaved changes

* Process code review

* use first composePath target

* fix function calls

* Use query instead

* Remove id on sidebar

* suggestions

---------

Co-authored-by: Bram Kragten <[email protected]>
  • Loading branch information
bramkragten authored Dec 11, 2024
2 parents e5e1688 + 3436a02 commit c8f58c7
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 4 deletions.
2 changes: 2 additions & 0 deletions src/components/ha-sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import "./ha-menu-button";
import "./ha-sortable";
import "./ha-svg-icon";
import "./user/ha-user-badge";
import { preventDefault } from "../common/dom/prevent_default";

const SHOW_AFTER_SPACER = ["config", "developer-tools"];

Expand Down Expand Up @@ -404,6 +405,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
@focusout=${this._listboxFocusOut}
@scroll=${this._listboxScroll}
@keydown=${this._listboxKeydown}
@iron-activate=${preventDefault}
>
${this.editMode
? this._renderPanelsEdit(beforeSpacer)
Expand Down
57 changes: 57 additions & 0 deletions src/mixins/prevent-unsaved-mixin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { LitElement, PropertyValues } from "lit";
import { isNavigationClick } from "../common/dom/is-navigation-click";
import type { Constructor } from "../types";

export const PreventUnsavedMixin = <T extends Constructor<LitElement>>(
superClass: T
) =>
class extends superClass {
private _handleClick = async (e: MouseEvent) => {
// get the right target, otherwise the composedPath would return <home-assistant> in the new event
const target = e.composedPath()[0];
if (!isNavigationClick(e)) {
return;
}

const result = await this.promptDiscardChanges();
if (result) {
this._removeListeners();
if (target) {
const newEvent = new MouseEvent(e.type, e);
target.dispatchEvent(newEvent);
}
}
};

private _handleUnload = (e: BeforeUnloadEvent) => e.preventDefault();

private _removeListeners() {
window.removeEventListener("click", this._handleClick, true);
window.removeEventListener("beforeunload", this._handleUnload);
}

public willUpdate(changedProperties: PropertyValues): void {
super.willUpdate(changedProperties);

if (this.isDirty) {
window.addEventListener("click", this._handleClick, true);
window.addEventListener("beforeunload", this._handleUnload);
} else {
this._removeListeners();
}
}

public disconnectedCallback(): void {
super.disconnectedCallback();

this._removeListeners();
}

protected get isDirty(): boolean {
return false;
}

protected async promptDiscardChanges(): Promise<boolean> {
return true;
}
};
13 changes: 12 additions & 1 deletion src/panels/config/automation/ha-automation-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -847,6 +850,14 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
};
}

protected get isDirty() {
return this._dirty;
}

protected async promptDiscardChanges() {
return this._confirmUnsavedChanged();
}

static get styles(): CSSResultGroup {
return [
haStyle,
Expand Down
13 changes: 11 additions & 2 deletions src/panels/config/scene/ha-scene-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -1225,6 +1226,14 @@ export class HaSceneEditor extends SubscribeMixin(
});
}

protected get isDirty() {
return this._dirty;
}

protected async promptDiscardChanges() {
return this._confirmUnsavedChanged();
}

static get styles(): CSSResultGroup {
return [
haStyle,
Expand Down
13 changes: 12 additions & 1 deletion src/panels/config/script/ha-script-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -813,6 +816,14 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
};
}

protected get isDirty() {
return this._dirty;
}

protected async promptDiscardChanges() {
return this._confirmUnsavedChanged();
}

static get styles(): CSSResultGroup {
return [
haStyle,
Expand Down

0 comments on commit c8f58c7

Please sign in to comment.