Skip to content

Commit bee7830

Browse files
msujewAkos Kitta
authored and
Alberto Iannaccone
committed
Save dialog for closing temporary sketch and unsaved files (#893)
* Use normal `OnWillStop` event * Align `CLOSE` command to rest of app * Fixed FS path vs encoded URL comparision when handling stop request. Ref: eclipse-theia/theia#11226 Signed-off-by: Akos Kitta <[email protected]> * Fixed the translations. Signed-off-by: Akos Kitta <[email protected]> * Fixed the translations again. Removed `electron` from the `nls-extract`. It does not contain app code. Signed-off-by: Akos Kitta <[email protected]> * Aligned the stop handler code to Theia. Signed-off-by: Akos Kitta <[email protected]> Co-authored-by: Akos Kitta <[email protected]>
1 parent 3f472f0 commit bee7830

File tree

7 files changed

+119
-76
lines changed

7 files changed

+119
-76
lines changed

Diff for: arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx

+49
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ import {
1717
DisposableCollection,
1818
} from '@theia/core';
1919
import {
20+
Dialog,
2021
FrontendApplication,
2122
FrontendApplicationContribution,
2223
LocalStorageService,
24+
OnWillStopAction,
2325
SaveableWidget,
2426
StatusBar,
2527
StatusBarAlignment,
@@ -667,4 +669,51 @@ export class ArduinoFrontendContribution
667669
}
668670
);
669671
}
672+
673+
onWillStop(): OnWillStopAction {
674+
return {
675+
reason: 'temp-sketch',
676+
action: () => {
677+
return this.showTempSketchDialog();
678+
}
679+
}
680+
}
681+
682+
private async showTempSketchDialog(): Promise<boolean> {
683+
const sketch = await this.sketchServiceClient.currentSketch();
684+
if (!sketch) {
685+
return true;
686+
}
687+
const isTemp = await this.sketchService.isTemp(sketch);
688+
if (!isTemp) {
689+
return true;
690+
}
691+
const messageBoxResult = await remote.dialog.showMessageBox(
692+
remote.getCurrentWindow(),
693+
{
694+
message: nls.localize('arduino/sketch/saveTempSketch', 'Save your sketch to open it again later.'),
695+
title: nls.localize('theia/core/quitTitle', 'Are you sure you want to quit?'),
696+
type: 'question',
697+
buttons: [
698+
Dialog.CANCEL,
699+
nls.localizeByDefault('Save As...'),
700+
nls.localizeByDefault("Don't Save"),
701+
],
702+
}
703+
)
704+
const result = messageBoxResult.response;
705+
if (result === 2) {
706+
return true;
707+
} else if (result === 1) {
708+
return !!(await this.commandRegistry.executeCommand(
709+
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
710+
{
711+
execOnlyIfTemp: false,
712+
openAfterMove: false,
713+
wipeOriginal: true
714+
}
715+
));
716+
}
717+
return false
718+
}
670719
}

Diff for: arduino-ide-extension/src/browser/contributions/close.ts

+1-72
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { inject, injectable } from '@theia/core/shared/inversify';
2-
import { toArray } from '@theia/core/shared/@phosphor/algorithm';
32
import * as remote from '@theia/core/electron-shared/@electron/remote';
43
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
54
import { EditorManager } from '@theia/editor/lib/browser/editor-manager';
65
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
76
import { FrontendApplication } from '@theia/core/lib/browser/frontend-application';
87
import { ArduinoMenus } from '../menu/arduino-menus';
9-
import { SaveAsSketch } from './save-as-sketch';
108
import {
119
SketchContribution,
1210
Command,
@@ -33,76 +31,7 @@ export class Close extends SketchContribution {
3331

3432
registerCommands(registry: CommandRegistry): void {
3533
registry.registerCommand(Close.Commands.CLOSE, {
36-
execute: async () => {
37-
// Close current editor if closeable.
38-
const { currentEditor } = this.editorManager;
39-
if (currentEditor && currentEditor.title.closable) {
40-
currentEditor.close();
41-
return;
42-
}
43-
44-
// Close current widget from the main area if possible.
45-
const { currentWidget } = this.shell;
46-
if (currentWidget) {
47-
const currentWidgetInMain = toArray(
48-
this.shell.mainPanel.widgets()
49-
).find((widget) => widget === currentWidget);
50-
if (currentWidgetInMain && currentWidgetInMain.title.closable) {
51-
return currentWidgetInMain.close();
52-
}
53-
}
54-
55-
// Close the sketch (window).
56-
const sketch = await this.sketchServiceClient.currentSketch();
57-
if (!sketch) {
58-
return;
59-
}
60-
const isTemp = await this.sketchService.isTemp(sketch);
61-
const uri = await this.sketchServiceClient.currentSketchFile();
62-
if (!uri) {
63-
return;
64-
}
65-
if (isTemp && (await this.wasTouched(uri))) {
66-
const { response } = await remote.dialog.showMessageBox({
67-
type: 'question',
68-
buttons: [
69-
nls.localize(
70-
'vscode/abstractTaskService/saveBeforeRun.dontSave',
71-
"Don't Save"
72-
),
73-
nls.localize('vscode/issueMainService/cancel', 'Cancel'),
74-
nls.localize(
75-
'vscode/abstractTaskService/saveBeforeRun.save',
76-
'Save'
77-
),
78-
],
79-
message: nls.localize(
80-
'arduino/common/saveChangesToSketch',
81-
'Do you want to save changes to this sketch before closing?'
82-
),
83-
detail: nls.localize(
84-
'arduino/common/loseChanges',
85-
"If you don't save, your changes will be lost."
86-
),
87-
});
88-
if (response === 1) {
89-
// Cancel
90-
return;
91-
}
92-
if (response === 2) {
93-
// Save
94-
const saved = await this.commandService.executeCommand(
95-
SaveAsSketch.Commands.SAVE_AS_SKETCH.id,
96-
{ openAfterMove: false, execOnlyIfTemp: true }
97-
);
98-
if (!saved) {
99-
// If it was not saved, do bail the close.
100-
return;
101-
}
102-
}
103-
}
104-
window.close();
105-
},
34+
execute: () => remote.getCurrentWindow().close()
10635
});
10736
}
10837

Diff for: arduino-ide-extension/src/electron-browser/theia/core/electron-menu-module.ts

+23
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,29 @@ import { MainMenuManager } from '../../../common/main-menu-manager';
1111
import { ElectronWindowService } from '../../electron-window-service';
1212
import { ElectronMainMenuFactory } from './electron-main-menu-factory';
1313
import { ElectronMenuContribution } from './electron-menu-contribution';
14+
import { nls } from '@theia/core/lib/common/nls';
15+
16+
import * as remote from '@theia/core/electron-shared/@electron/remote';
17+
import * as dialogs from '@theia/core/lib/browser/dialogs';
18+
19+
20+
Object.assign(dialogs, {
21+
confirmExit: async () => {
22+
const messageBoxResult = await remote.dialog.showMessageBox(
23+
remote.getCurrentWindow(),
24+
{
25+
message: nls.localize('theia/core/quitMessage', 'Any unsaved changes will not be saved.'),
26+
title: nls.localize('theia/core/quitTitle', 'Are you sure you want to quit?'),
27+
type: 'question',
28+
buttons: [
29+
dialogs.Dialog.CANCEL,
30+
dialogs.Dialog.YES,
31+
],
32+
}
33+
)
34+
return messageBoxResult.response === 1;
35+
}
36+
});
1437

1538
export default new ContainerModule((bind, unbind, isBound, rebind) => {
1639
bind(ElectronMenuContribution).toSelf().inSingletonScope();

Diff for: arduino-ide-extension/src/electron-main/arduino-electron-main-module.ts

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
IDEUpdaterPath,
2020
} from '../common/protocol/ide-updater';
2121
import { IDEUpdaterImpl } from './ide-updater/ide-updater-impl';
22+
import { TheiaElectronWindow } from './theia/theia-electron-window';
23+
import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window';
2224

2325
export default new ContainerModule((bind, unbind, isBound, rebind) => {
2426
bind(ElectronMainApplication).toSelf().inSingletonScope();
@@ -56,4 +58,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
5658
)
5759
)
5860
.inSingletonScope();
61+
62+
bind(TheiaElectronWindow).toSelf();
63+
rebind(DefaultTheiaElectronWindow).toService(TheiaElectronWindow);
5964
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { injectable } from '@theia/core/shared/inversify';
2+
import { StopReason } from '@theia/core/lib/electron-common/messaging/electron-messages';
3+
import { TheiaElectronWindow as DefaultTheiaElectronWindow } from '@theia/core/lib/electron-main/theia-electron-window';
4+
import { FileUri } from '@theia/core/lib/node';
5+
import URI from '@theia/core/lib/common/uri';
6+
7+
@injectable()
8+
export class TheiaElectronWindow extends DefaultTheiaElectronWindow {
9+
protected async handleStopRequest(
10+
onSafeCallback: () => unknown,
11+
reason: StopReason
12+
): Promise<boolean> {
13+
// Only confirm close to windows that have loaded our frontend.
14+
// Both the windows's URL and the FS path of the `index.html` should be converted to the "same" format to be able to compare them. (#11226)
15+
// Notes:
16+
// - Windows: file:///C:/path/to/somewhere vs file:///c%3A/path/to/somewhere
17+
// - macOS: file:///Applications/App%20Name.app/Contents vs /Applications/App Name.app/Contents
18+
// This URL string comes from electron, we can expect that this is properly encoded URL. For example, a space is `%20`
19+
const currentUrl = new URI(this.window.webContents.getURL()).toString();
20+
// THEIA_FRONTEND_HTML_PATH is an FS path, we have to covert to an encoded URI string.
21+
const frontendUri = FileUri.create(
22+
this.globals.THEIA_FRONTEND_HTML_PATH
23+
).toString();
24+
const safeToClose =
25+
!currentUrl.includes(frontendUri) || (await this.checkSafeToStop(reason));
26+
if (safeToClose) {
27+
try {
28+
await onSafeCallback();
29+
return true;
30+
} catch (e) {
31+
console.warn(`Request ${StopReason[reason]} failed.`, e);
32+
}
33+
}
34+
return false;
35+
}
36+
}

Diff for: i18n/en.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,11 @@
9393
},
9494
"common": {
9595
"later": "Later",
96-
"loseChanges": "If you don't save, your changes will be lost.",
9796
"noBoardSelected": "No board selected",
9897
"notConnected": "[not connected]",
9998
"offlineIndicator": "You appear to be offline. Without an Internet connection, the Arduino CLI might not be able to download the required resources and could cause malfunction. Please connect to the Internet and restart the application.",
10099
"oldFormat": "The '{0}' still uses the old `.pde` format. Do you want to switch to the new `.ino` extension?",
101100
"processing": "Processing",
102-
"saveChangesToSketch": "Do you want to save changes to this sketch before closing?",
103101
"selectBoard": "Select Board",
104102
"selectedOn": "on {0}",
105103
"serialMonitor": "Serial Monitor",
@@ -292,6 +290,7 @@
292290
"openSketchInNewWindow": "Open Sketch in New Window",
293291
"saveFolderAs": "Save sketch folder as...",
294292
"saveSketchAs": "Save sketch folder as...",
293+
"saveTempSketch": "Save your sketch to open it again later.",
295294
"showFolder": "Show Sketch Folder",
296295
"sketch": "Sketch",
297296
"sketchbook": "Sketchbook",
@@ -320,7 +319,9 @@
320319
"cannotConnectDaemon": "Cannot connect to the CLI daemon.",
321320
"couldNotSave": "Could not save the sketch. Please copy your unsaved work into your favorite text editor, and restart the IDE.",
322321
"daemonOffline": "CLI Daemon Offline",
323-
"offline": "Offline"
322+
"offline": "Offline",
323+
"quitMessage": "Any unsaved changes will not be saved.",
324+
"quitTitle": "Are you sure you want to quit?"
324325
},
325326
"debug": {
326327
"start": "Start...",

Diff for: package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"test": "lerna run test",
5050
"download:plugins": "theia download:plugins",
5151
"update:version": "node ./scripts/update-version.js",
52-
"i18n:generate": "theia nls-extract -e vscode -f \"+(arduino-ide-extension|browser-app|electron|electron-app|plugins)/**/*.ts?(x)\" -o ./i18n/en.json",
52+
"i18n:generate": "theia nls-extract -e vscode -f \"+(arduino-ide-extension|browser-app|electron-app|plugins)/**/*.ts?(x)\" -o ./i18n/en.json",
5353
"i18n:check": "yarn i18n:generate && git add -N ./i18n && git diff --exit-code ./i18n",
5454
"i18n:push": "node ./scripts/i18n/transifex-push.js ./i18n/en.json",
5555
"i18n:pull": "node ./scripts/i18n/transifex-pull.js ./i18n/",

0 commit comments

Comments
 (0)