Skip to content

Commit 4c90842

Browse files
authored
Feature: discard changes for block workspace (#18930)
* make getHasUnpersistedChanges public * Discard changes impl for Block Workspace
1 parent 9c03422 commit 4c90842

File tree

3 files changed

+59
-2
lines changed

3 files changed

+59
-2
lines changed

src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/json-string-comparison.function.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
* Meaning no class instances can take part in this data.
99
*/
1010
export function jsonStringComparison(a: unknown, b: unknown): boolean {
11-
return JSON.stringify(a) === JSON.stringify(b);
11+
return (a === undefined && b === undefined) || JSON.stringify(a) === JSON.stringify(b);
1212
}

src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ export class UmbBlockElementManager<LayoutDataType extends UmbBlockLayoutBaseMod
119119
return this.#data.getCurrent();
120120
}
121121

122+
setPersistedData(data: UmbBlockDataModel | undefined) {
123+
this.#data.setPersisted(data);
124+
}
125+
122126
/**
123127
* Check if there are unpersisted changes.
124128
* @returns { boolean } true if there are unpersisted changes.

src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
observeMultiple,
1717
} from '@umbraco-cms/backoffice/observable-api';
1818
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
19-
import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal';
19+
import { UMB_DISCARD_CHANGES_MODAL, UMB_MODAL_CONTEXT, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
2020
import { decodeFilePath, UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils';
2121
import {
2222
UMB_BLOCK_ENTRIES_CONTEXT,
@@ -81,6 +81,8 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
8181
const manifest = workspaceArgs.manifest;
8282
this.#entityType = manifest.meta?.entityType;
8383

84+
window.addEventListener('willchangestate', this.#onWillNavigate);
85+
8486
this.addValidationContext(this.content.validation);
8587
this.addValidationContext(this.settings.validation);
8688

@@ -223,6 +225,51 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
223225
);
224226
}
225227

228+
#allowNavigateAway = false;
229+
#onWillNavigate = async (e: CustomEvent) => {
230+
const newUrl = e.detail.url;
231+
232+
if (this.#allowNavigateAway) {
233+
return true;
234+
}
235+
236+
if (this._checkWillNavigateAway(newUrl) && this.getHasUnpersistedChanges()) {
237+
/* Since ours modals are async while events are synchronous, we need to prevent the default behavior of the event, even if the modal hasn’t been resolved yet.
238+
Once the modal is resolved (the user accepted to discard the changes and navigate away from the route), we will push a new history state.
239+
This push will make the "willchangestate" event happen again and due to this somewhat "backward" behavior,
240+
we set an "allowNavigateAway"-flag to prevent the "discard-changes" functionality from running in a loop.*/
241+
e.preventDefault();
242+
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT).catch(() => undefined);
243+
const modal = modalManager?.open(this, UMB_DISCARD_CHANGES_MODAL);
244+
if (modal) {
245+
try {
246+
// navigate to the new url when discarding changes
247+
await modal.onSubmit();
248+
this.#allowNavigateAway = true;
249+
history.pushState({}, '', e.detail.url);
250+
return true;
251+
} catch {
252+
return false;
253+
}
254+
} else {
255+
console.error('No modal manager found!');
256+
}
257+
}
258+
259+
return true;
260+
};
261+
262+
/**
263+
* Check if the workspace is about to navigate away.
264+
* @protected
265+
* @param {string} newUrl The new url that the workspace is navigating to.
266+
* @returns { boolean} true if the workspace is navigating away.
267+
* @memberof UmbEntityWorkspaceContextBase
268+
*/
269+
protected _checkWillNavigateAway(newUrl: string): boolean {
270+
return !newUrl.includes(this.routes.getActiveLocalPath());
271+
}
272+
226273
setEditorSize(editorSize: UUIModalSidebarSize) {
227274
this.#modalContext?.setModalSize(editorSize);
228275
}
@@ -236,6 +283,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
236283
this.#initialSettings = undefined;
237284
this.content.resetState();
238285
this.settings.resetState();
286+
this.#allowNavigateAway = false;
239287
this.removeUmbControllerByAlias(UmbWorkspaceIsNewRedirectControllerAlias);
240288
}
241289

@@ -471,6 +519,10 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
471519
}
472520

473521
const settingsData = this.settings.getData();
522+
this.content.setPersistedData(contentData);
523+
if (settingsData) {
524+
this.settings.setPersistedData(settingsData);
525+
}
474526

475527
if (!this.#liveEditingMode) {
476528
if (this.getIsNew() === true) {
@@ -542,6 +594,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
542594

543595
public override destroy(): void {
544596
super.destroy();
597+
window.removeEventListener('willchangestate', this.#onWillNavigate);
545598
this.#layout?.destroy();
546599
this.#name?.destroy();
547600
this.#layout = undefined as any;

0 commit comments

Comments
 (0)