Skip to content

Commit

Permalink
Save changes before closing interaction (#12166)
Browse files Browse the repository at this point in the history
- Question to save all changes, cancel or discard all changes before closing
- Display what is not yet saved.
- Reorder options and reword the question and options to be clear of the outcome of each action
- Implemented using ConfirmDialogSave extending ConfirmDialog

Contributed by STMicroelectronics

Signed-off-by: Emil Hammarstedt <emil.hammarstedt@st.com>
  • Loading branch information
emilhammarstedtst authored Feb 23, 2023
1 parent a3b9735 commit 7fd510e
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 3 deletions.
12 changes: 9 additions & 3 deletions packages/core/src/browser/common-frontend-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import { QuickInputService, QuickPickItem, QuickPickItemOrSeparator } from './qu
import { AsyncLocalizationProvider } from '../common/i18n/localization';
import { nls } from '../common/nls';
import { CurrentWidgetCommandAdapter } from './shell/current-widget-command-adapter';
import { ConfirmDialog, confirmExit, Dialog } from './dialogs';
import { ConfirmDialog, confirmExitWithOrWithoutSaving, Dialog } from './dialogs';
import { WindowService } from './window/window-service';
import { FrontendApplicationConfigProvider } from './frontend-application-config-provider';
import { DecorationStyle } from './decoration-style';
Expand Down Expand Up @@ -1144,13 +1144,19 @@ export class CommonFrontendContribution implements FrontendApplicationContributi
onWillStop(): OnWillStopAction | undefined {
try {
if (this.shouldPreventClose || this.shell.canSaveAll()) {
return { reason: 'Dirty editors present', action: () => confirmExit() };
const captionsToSave = this.unsavedTabsCaptions();

return { reason: 'Dirty editors present', action: async () => confirmExitWithOrWithoutSaving(captionsToSave, async () => this.shell.saveAll()) };
}
} finally {
this.shouldPreventClose = false;
}
}

protected unsavedTabsCaptions(): string[] {
return this.shell.widgets
.filter(widget => this.saveResourceService.canSave(widget))
.map(widget => widget.title.label);
}
protected async configureDisplayLanguage(): Promise<void> {
const languageInfo = await this.languageQuickPickService.pickDisplayLanguage();
if (languageInfo && !nls.isSelectedLocale(languageInfo.languageId) && await this.confirmRestart(
Expand Down
72 changes: 72 additions & 0 deletions packages/core/src/browser/dialogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,78 @@ export async function confirmExit(): Promise<boolean> {
return safeToExit === true;
}

export class ConfirmSaveDialogProps extends ConfirmDialogProps {
readonly save: string;
performSave: () => Promise<void>;
}

export class ConfirmSaveDialog extends ConfirmDialog {

protected saveButton: HTMLButtonElement | undefined;
constructor(
@inject(ConfirmSaveDialogProps) protected override readonly props: ConfirmSaveDialogProps
) {
super(props);
this.contentNode.appendChild(this.createMessageNode(this.props.msg));
// reorder buttons
this.controlPanel.childNodes.forEach(child => this.controlPanel.removeChild(child));
[this.acceptButton, this.closeButton].forEach(child => {
if (typeof child !== 'undefined') {
this.controlPanel.appendChild(child);
}
});
this.appendSaveButton(props.save).addEventListener('click', async () => {
await props.performSave();
this.acceptButton?.click();
});
}

protected appendSaveButton(text: string = Dialog.OK): HTMLButtonElement {
this.saveButton = this.createButton(text);
this.controlPanel.appendChild(this.saveButton);
this.saveButton.classList.add('main');
return this.saveButton;
}

protected override onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
if (this.saveButton) {
this.saveButton.focus();
}
}

}

export async function confirmExitWithOrWithoutSaving(captionsToSave: string[], performSave: () => Promise<void>): Promise<boolean> {
const div: HTMLElement = document.createElement('div');
div.innerText = nls.localizeByDefault("Your changes will be lost if you don't save them.");

if (captionsToSave.length > 0) {
const span = document.createElement('span');
span.appendChild(document.createElement('br'));
captionsToSave.forEach(cap => {
const b = document.createElement('b');
b.innerText = cap;
span.appendChild(b);
span.appendChild(document.createElement('br'));
});
span.appendChild(document.createElement('br'));
div.appendChild(span);
const safeToExit = await new ConfirmSaveDialog({
title: nls.localizeByDefault('Do you want to save the changes to the following {0} files?', captionsToSave.length),
msg: div,
ok: nls.localizeByDefault("Don't Save"),
save: nls.localizeByDefault('Save All'),
cancel: Dialog.CANCEL,
performSave: performSave
}).open();
return safeToExit === true;
} else {
// fallback if not passed with an empty caption-list.
return confirmExit();
}

}
@injectable()
export class SingleTextInputDialogProps extends DialogProps {
readonly confirmButtonLabel?: string;
Expand Down

0 comments on commit 7fd510e

Please sign in to comment.