Skip to content

Commit

Permalink
Save changes before closing interaction.
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 committed Feb 9, 2023
1 parent d383fe2 commit 70d1614
Show file tree
Hide file tree
Showing 2 changed files with 71 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: () => confirmExitWithOrWithoutSaving(captionsToSave, () => 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 languageId = await this.languageQuickPickService.pickDisplayLanguage();
if (languageId && !nls.isSelectedLocale(languageId) && await this.confirmRestart()) {
Expand Down
62 changes: 62 additions & 0 deletions packages/core/src/browser/dialogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,68 @@ export async function confirmExit(): Promise<boolean> {
return safeToExit === true;
}

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

export class ConfirmDialogSave extends ConfirmDialog {

protected saveButton: HTMLButtonElement | undefined;
constructor(
@inject(ConfirmDialogSaveProps) protected override readonly props: ConfirmDialogSaveProps
) {
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', () => { 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;
}

}

export async function confirmExitWithOrWithoutSaving(captionsToSave: string[], performSave: () => void): Promise<boolean> {
let msg: string | HTMLElement = nls.localize('theia/core/quitMessage', 'Any unsaved changes will be lost.');

if (typeof captionsToSave !== 'undefined') {
const div = document.createElement('div');
div.innerText = nls.localize('theia/core/quitMessageFileList', 'The following have not yet been saved') + ':';
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);
msg = div;
}

const safeToExit = await new ConfirmDialogSave({
title: nls.localizeByDefault('Are you sure you want to quit?'),
msg: msg,
ok: nls.localize('theia/core/quitOptionWithSaving', 'Quit without saving'),
save: nls.localize('theia/core/quitOptionWithoutSaving', 'Save all and quit'),
cancel: Dialog.CANCEL,
performSave: performSave
}).open();
return safeToExit === true;
}
@injectable()
export class SingleTextInputDialogProps extends DialogProps {
readonly confirmButtonLabel?: string;
Expand Down

0 comments on commit 70d1614

Please sign in to comment.