Unified the sketch close and the app quit logic.

Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
This commit is contained in:
Akos Kitta 2022-08-25 15:35:54 +02:00 committed by Akos Kitta
parent 0c87fa9877
commit 40425d49e0
5 changed files with 113 additions and 64 deletions

View File

@ -1,6 +1,13 @@
import { injectable } from '@theia/core/shared/inversify'; import { injectable } from '@theia/core/shared/inversify';
import * as remote from '@theia/core/electron-shared/@electron/remote'; import * as remote from '@theia/core/electron-shared/@electron/remote';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor'; import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import type { MaybePromise } from '@theia/core/lib/common/types';
import type {
FrontendApplication,
OnWillStopAction,
} from '@theia/core/lib/browser/frontend-application';
import { nls } from '@theia/core/lib/common/nls';
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
import { ArduinoMenus } from '../menu/arduino-menus'; import { ArduinoMenus } from '../menu/arduino-menus';
import { import {
SketchContribution, SketchContribution,
@ -11,17 +18,21 @@ import {
Sketch, Sketch,
URI, URI,
} from './contribution'; } from './contribution';
import { nls } from '@theia/core/lib/common';
import { Dialog } from '@theia/core/lib/browser/dialogs'; import { Dialog } from '@theia/core/lib/browser/dialogs';
import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl'; import { CurrentSketch } from '../../common/protocol/sketches-service-client-impl';
import { SaveAsSketch } from './save-as-sketch'; import { SaveAsSketch } from './save-as-sketch';
import type { OnWillStopAction } from '@theia/core/lib/browser/frontend-application';
/** /**
* Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window. * Closes the `current` closeable editor, or any closeable current widget from the main area, or the current sketch window.
*/ */
@injectable() @injectable()
export class Close extends SketchContribution { export class Close extends SketchContribution {
private shell: ApplicationShell | undefined;
override onStart(app: FrontendApplication): MaybePromise<void> {
this.shell = app.shell;
}
override registerCommands(registry: CommandRegistry): void { override registerCommands(registry: CommandRegistry): void {
registry.registerCommand(Close.Commands.CLOSE, { registry.registerCommand(Close.Commands.CLOSE, {
execute: () => remote.getCurrentWindow().close(), execute: () => remote.getCurrentWindow().close(),
@ -46,20 +57,41 @@ export class Close extends SketchContribution {
// `FrontendApplicationContribution#onWillStop` // `FrontendApplicationContribution#onWillStop`
onWillStop(): OnWillStopAction { onWillStop(): OnWillStopAction {
return { return {
reason: 'temp-sketch', reason: 'save-sketch',
action: () => { action: () => {
return this.showSaveTempSketchDialog(); return this.showSaveSketchDialog();
}, },
}; };
} }
private async showSaveTempSketchDialog(): Promise<boolean> { /**
const sketch = await this.sketchServiceClient.currentSketch(); * If returns with `true`, IDE2 will close. Otherwise, it won't.
if (!CurrentSketch.isValid(sketch)) { */
private async showSaveSketchDialog(): Promise<boolean> {
const sketch = await this.isCurrentSketchTemp();
if (!sketch) {
// Normal close workflow: if there are dirty editors prompt the user.
if (!this.shell) {
console.error(
`Could not get the application shell. Something went wrong.`
);
return true; return true;
} }
const isTemp = await this.sketchService.isTemp(sketch); if (this.shell.canSaveAll()) {
if (!isTemp) { const prompt = await this.prompt(false);
switch (prompt) {
case Prompt.DoNotSave:
return true;
case Prompt.Cancel:
return false;
case Prompt.Save: {
await this.shell.saveAll();
return true;
}
default:
throw new Error(`Unexpected prompt: ${prompt}`);
}
}
return true; return true;
} }
@ -71,11 +103,36 @@ export class Close extends SketchContribution {
return true; return true;
} }
const messageBoxResult = await remote.dialog.showMessageBox( const prompt = await this.prompt(true);
switch (prompt) {
case Prompt.DoNotSave:
return true;
case Prompt.Cancel:
return false;
case Prompt.Save: {
// If `save as` was canceled by user, the result will be `undefined`, otherwise the new URI.
const result = await this.commandService.executeCommand(
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
{
execOnlyIfTemp: false,
openAfterMove: false,
wipeOriginal: true,
markAsRecentlyOpened: true,
}
);
return !!result;
}
default:
throw new Error(`Unexpected prompt: ${prompt}`);
}
}
private async prompt(isTemp: boolean): Promise<Prompt> {
const { response } = await remote.dialog.showMessageBox(
remote.getCurrentWindow(), remote.getCurrentWindow(),
{ {
message: nls.localize( message: nls.localize(
'arduino/sketch/saveTempSketch', 'arduino/sketch/saveSketch',
'Save your sketch to open it again later.' 'Save your sketch to open it again later.'
), ),
title: nls.localize( title: nls.localize(
@ -84,24 +141,32 @@ export class Close extends SketchContribution {
), ),
type: 'question', type: 'question',
buttons: [ buttons: [
Dialog.CANCEL,
nls.localizeByDefault('Save As...'),
nls.localizeByDefault("Don't Save"), nls.localizeByDefault("Don't Save"),
Dialog.CANCEL,
nls.localizeByDefault(isTemp ? 'Save As...' : 'Save'),
], ],
defaultId: 2, // `Save`/`Save As...` button index is the default.
} }
); );
const result = messageBoxResult.response; switch (response) {
if (result === 2) { case 0:
return true; return Prompt.DoNotSave;
} else if (result === 1) { case 1:
return !!(await this.commandService.executeCommand( return Prompt.Cancel;
SaveAsSketch.Commands.SAVE_AS_SKETCH.id, case 2:
{ return Prompt.Save;
execOnlyIfTemp: false, default:
openAfterMove: false, throw new Error(`Unexpected response: ${response}`);
wipeOriginal: true, }
}
private async isCurrentSketchTemp(): Promise<false | Sketch> {
const currentSketch = await this.sketchServiceClient.currentSketch();
if (CurrentSketch.isValid(currentSketch)) {
const isTemp = await this.sketchService.isTemp(currentSketch);
if (isTemp) {
return currentSketch;
} }
));
} }
return false; return false;
} }
@ -128,6 +193,12 @@ export class Close extends SketchContribution {
} }
} }
enum Prompt {
Save,
DoNotSave,
Cancel,
}
export namespace Close { export namespace Close {
export namespace Commands { export namespace Commands {
export const CLOSE: Command = { export const CLOSE: Command = {

View File

@ -57,6 +57,7 @@ export class SaveAsSketch extends SketchContribution {
execOnlyIfTemp, execOnlyIfTemp,
openAfterMove, openAfterMove,
wipeOriginal, wipeOriginal,
markAsRecentlyOpened,
}: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT }: SaveAsSketch.Options = SaveAsSketch.Options.DEFAULT
): Promise<boolean> { ): Promise<boolean> {
const sketch = await this.sketchServiceClient.currentSketch(); const sketch = await this.sketchServiceClient.currentSketch();
@ -102,6 +103,9 @@ export class SaveAsSketch extends SketchContribution {
}); });
if (workspaceUri) { if (workspaceUri) {
await this.saveOntoCopiedSketch(sketch.mainFileUri, sketch.uri, workspaceUri); await this.saveOntoCopiedSketch(sketch.mainFileUri, sketch.uri, workspaceUri);
if (markAsRecentlyOpened) {
this.sketchService.markAsRecentlyOpened(workspaceUri);
}
} }
if (workspaceUri && openAfterMove) { if (workspaceUri && openAfterMove) {
this.windowService.setSafeToShutDown(); this.windowService.setSafeToShutDown();
@ -171,12 +175,14 @@ export namespace SaveAsSketch {
* Ignored if `openAfterMove` is `false`. * Ignored if `openAfterMove` is `false`.
*/ */
readonly wipeOriginal?: boolean; readonly wipeOriginal?: boolean;
readonly markAsRecentlyOpened?: boolean;
} }
export namespace Options { export namespace Options {
export const DEFAULT: Options = { export const DEFAULT: Options = {
execOnlyIfTemp: false, execOnlyIfTemp: false,
openAfterMove: true, openAfterMove: true,
wipeOriginal: false, wipeOriginal: false,
markAsRecentlyOpened: false,
}; };
} }
} }

View File

@ -1,6 +1,5 @@
import { injectable, inject } from '@theia/core/shared/inversify'; import { injectable, inject } from '@theia/core/shared/inversify';
import { EditorWidget } from '@theia/editor/lib/browser'; import { EditorWidget } from '@theia/editor/lib/browser';
import { CommandService } from '@theia/core/lib/common/command';
import { MessageService } from '@theia/core/lib/common/message-service'; import { MessageService } from '@theia/core/lib/common/message-service';
import { OutputWidget } from '@theia/output/lib/browser/output-widget'; import { OutputWidget } from '@theia/output/lib/browser/output-widget';
import { import {
@ -15,9 +14,9 @@ import {
TabBar, TabBar,
Widget, Widget,
SHELL_TABBAR_CONTEXT_MENU, SHELL_TABBAR_CONTEXT_MENU,
SaveOptions,
} from '@theia/core/lib/browser'; } from '@theia/core/lib/browser';
import { Sketch } from '../../../common/protocol'; import { Sketch } from '../../../common/protocol';
import { SaveAsSketch } from '../../contributions/save-as-sketch';
import { import {
CurrentSketch, CurrentSketch,
SketchesServiceClientImpl, SketchesServiceClientImpl,
@ -28,9 +27,6 @@ import { ToolbarAwareTabBar } from './tab-bars';
@injectable() @injectable()
export class ApplicationShell extends TheiaApplicationShell { export class ApplicationShell extends TheiaApplicationShell {
@inject(CommandService)
private readonly commandService: CommandService;
@inject(MessageService) @inject(MessageService)
private readonly messageService: MessageService; private readonly messageService: MessageService;
@ -106,7 +102,7 @@ export class ApplicationShell extends TheiaApplicationShell {
return topPanel; return topPanel;
} }
override async saveAll(): Promise<void> { override async saveAll(options?: SaveOptions): Promise<void> {
if ( if (
this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE this.connectionStatusService.currentStatus === ConnectionStatus.OFFLINE
) { ) {
@ -118,12 +114,7 @@ export class ApplicationShell extends TheiaApplicationShell {
); );
return; // Theia does not reject on failed save: https://github.com/eclipse-theia/theia/pull/8803 return; // Theia does not reject on failed save: https://github.com/eclipse-theia/theia/pull/8803
} }
await super.saveAll(); return super.saveAll(options);
const options = { execOnlyIfTemp: true, openAfterMove: true };
await this.commandService.executeCommand(
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
options
);
} }
} }

View File

@ -5,6 +5,7 @@ import {
CommonCommands, CommonCommands,
} from '@theia/core/lib/browser/common-frontend-contribution'; } from '@theia/core/lib/browser/common-frontend-contribution';
import { CommandRegistry } from '@theia/core/lib/common/command'; import { CommandRegistry } from '@theia/core/lib/common/command';
import type { OnWillStopAction } from '@theia/core/lib/browser/frontend-application';
@injectable() @injectable()
export class CommonFrontendContribution extends TheiaCommonFrontendContribution { export class CommonFrontendContribution extends TheiaCommonFrontendContribution {
@ -48,4 +49,9 @@ export class CommonFrontendContribution extends TheiaCommonFrontendContribution
registry.unregisterMenuAction(command); registry.unregisterMenuAction(command);
} }
} }
override onWillStop(): OnWillStopAction | undefined {
// This is NOOP here. All window close and app quit requests are handled in the `Close` contribution.
return undefined;
}
} }

View File

@ -4,31 +4,6 @@ import { ElectronMenuContribution as TheiaElectronMenuContribution } from '@thei
import { MainMenuManager } from '../../../common/main-menu-manager'; import { MainMenuManager } from '../../../common/main-menu-manager';
import { ElectronMainMenuFactory } from './electron-main-menu-factory'; import { ElectronMainMenuFactory } from './electron-main-menu-factory';
import { ElectronMenuContribution } from './electron-menu-contribution'; import { ElectronMenuContribution } from './electron-menu-contribution';
import { nls } from '@theia/core/lib/common/nls';
import * as remote from '@theia/core/electron-shared/@electron/remote';
import * as dialogs from '@theia/core/lib/browser/dialogs';
Object.assign(dialogs, {
confirmExit: async () => {
const messageBoxResult = await remote.dialog.showMessageBox(
remote.getCurrentWindow(),
{
message: nls.localize(
'theia/core/quitMessage',
'Any unsaved changes will not be saved.'
),
title: nls.localize(
'theia/core/quitTitle',
'Are you sure you want to quit?'
),
type: 'question',
buttons: [dialogs.Dialog.CANCEL, dialogs.Dialog.YES],
}
);
return messageBoxResult.response === 1;
},
});
export default new ContainerModule((bind, unbind, isBound, rebind) => { export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ElectronMenuContribution).toSelf().inSingletonScope(); bind(ElectronMenuContribution).toSelf().inSingletonScope();