From 70d1614937a5d5a4318c8d23b9bcebbfdaab9c23 Mon Sep 17 00:00:00 2001 From: Emil Hammarstedt Date: Thu, 9 Feb 2023 13:52:05 +0100 Subject: [PATCH] Save changes before closing interaction. - 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 --- .../browser/common-frontend-contribution.ts | 12 +++- packages/core/src/browser/dialogs.ts | 62 +++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts index d95502c1e2fe9..ddb17b3c3c36e 100644 --- a/packages/core/src/browser/common-frontend-contribution.ts +++ b/packages/core/src/browser/common-frontend-contribution.ts @@ -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'; @@ -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 { const languageId = await this.languageQuickPickService.pickDisplayLanguage(); if (languageId && !nls.isSelectedLocale(languageId) && await this.confirmRestart()) { diff --git a/packages/core/src/browser/dialogs.ts b/packages/core/src/browser/dialogs.ts index 854ec0c883986..02abc907f7737 100644 --- a/packages/core/src/browser/dialogs.ts +++ b/packages/core/src/browser/dialogs.ts @@ -401,6 +401,68 @@ export async function confirmExit(): Promise { 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 { + 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;