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

Prevent leaving the editor if there are unsaved changes #23170

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
Loading