Fixed customize design route-based modal handling + added unsaved change confirmation

refs https://github.com/TryGhost/Team/issues/559
refs https://github.com/TryGhost/Team/issues/1045

- removed need for `{{will-destroy}}` in the modal template by using `willTransition()` in the route that opened the modal instead
- used the property of new modals acting as promises to implement an unsaved changes confirmation modal
  - added `confirmUnsavedChanges()` method that opens a "are you sure you want to leave?" modal that is treated as a promise. If the modal is closed by the "Leave" button the promise returns true in which case we rollback the unsaved changes. The modal is returned so that other behaviour can use this method and wait for the confirmation result
  - added `willTransition()` route hook that calls `confirmUnsavedChanges()` when there are unsaved changes and will only continue to transition if the "Leave" button is pressed
  - added `beforeClose()` hook to the customize modal when opening so that we can prevent the customize modal from closing when "Stay" is clicked in the confirmation modal
- updated `activate()` hook to store the modal instance so it can be closed later
- updated `deactivate()` hook to call `.close()` on the modal instance rather than using private data/methods on the modals service
This commit is contained in:
Kevin Ansfield 2021-09-16 20:26:49 +01:00
parent 4f2af95afe
commit 2b0d1ee357
4 changed files with 76 additions and 12 deletions

View File

@ -0,0 +1,19 @@
<div class="modal-content">
<header class="modal-header" data-test-modal="unsaved-settings">
<h1>Are you sure you want to leave this page?</h1>
</header>
<a class="close" href="" role="button" title="Close" {{on "click" @close}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
<div class="modal-body">
<p>
Hey there! It looks like you didn't save the changes you made.
</p>
<p>Save before you go!</p>
</div>
<div class="modal-footer">
<button {{on "click" (fn @close false)}} class="gh-btn" data-test-stay-button><span>Stay</span></button>
<button {{on "click" (fn @close true)}} class="gh-btn gh-btn-red" data-test-leave-button><span>Leave</span></button>
</div>
</div>

View File

@ -1,4 +1,4 @@
<div class="modal-content" {{will-destroy this.willClose}}>
<div class="modal-content">
<div class="modal-body gh-ps-modal-body">
<div class="gh-branding-settings-header">
<h4>Customize</h4>

View File

@ -5,7 +5,6 @@ import {inject as service} from '@ember/service';
export default class ModalsDesignAdvancedComponent extends Component {
@service config;
@service settings;
@service router;
previewIframe = null;
@ -13,9 +12,4 @@ export default class ModalsDesignAdvancedComponent extends Component {
registerPreviewIframe(iframe) {
this.previewIframe = iframe;
}
@action
willClose() {
this.router.transitionTo('settings.design');
}
}

View File

@ -1,9 +1,12 @@
import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
import {action} from '@ember/object';
import {bind} from '@ember/runloop';
import {inject as service} from '@ember/service';
export default class SettingsDesignCustomizeRoute extends AuthenticatedRoute {
@service feature;
@service modals;
@service settings;
beforeModel() {
super.beforeModel(...arguments);
@ -18,14 +21,62 @@ export default class SettingsDesignCustomizeRoute extends AuthenticatedRoute {
}
activate() {
this.modals.open('modals/design/customize', {}, {
className: 'fullscreen-modal-full-overlay fullscreen-modal-branding-modal'
this.customizeModal = this.modals.open('modals/design/customize', {}, {
className: 'fullscreen-modal-full-overlay fullscreen-modal-branding-modal',
beforeClose: bind(this, this.beforeModalClose)
});
}
@action
async willTransition(transition) {
if (this.settings.get('hasDirtyAttributes')) {
transition.abort();
const shouldLeave = await this.confirmUnsavedChanges();
if (shouldLeave) {
return transition.retry();
}
} else {
return true;
}
}
deactivate() {
this.modals._stack.reverse().forEach((modal) => {
modal._componentInstance.closeModal();
});
this.customizeModal?.close();
this.customizeModal = null;
this.confirmModal = null;
}
async beforeModalClose() {
const shouldLeave = await this.confirmUnsavedChanges();
if (shouldLeave === true) {
this.transitionTo('settings.design');
} else {
// prevent modal from closing
return false;
}
}
confirmUnsavedChanges() {
if (!this.settings.get('hasDirtyAttributes')) {
return Promise.resolve(true);
}
if (!this.confirmModal) {
this.confirmModal = this.modals.open('modals/confirm-unsaved-changes', {}, {
className: 'fullscreen-modal-action fullscreen-modal-wide'
}).then((discardChanges) => {
if (discardChanges === true) {
this.settings.rollbackAttributes();
}
return discardChanges;
}).finally(() => {
this.confirmModal = null;
});
}
return this.confirmModal;
}
}